about summary refs log tree commit diff
path: root/app/assets/javascripts
diff options
context:
space:
mode:
authorEugen Rochko <eugen@zeonfederated.com>2017-05-03 02:04:16 +0200
committerGitHub <noreply@github.com>2017-05-03 02:04:16 +0200
commitf5bf5ebb82e3af420dcd23d602b1be6cc86838e1 (patch)
tree92eef08642a038cf44ccbc6d16a884293e7a0814 /app/assets/javascripts
parent26bc5915727e0a0173c03cb49f5193dd612fb888 (diff)
Replace sprockets/browserify with Webpack (#2617)
* Replace browserify with webpack

* Add react-intl-translations-manager

* Do not minify in development, add offline-plugin for ServiceWorker background cache updates

* Adjust tests and dependencies

* Fix production deployments

* Fix tests

* More optimizations

* Improve travis cache for npm stuff

* Re-run travis

* Add back support for custom.scss as before

* Remove offline-plugin and babili

* Fix issue with Immutable.List().unshift(...values) not working as expected

* Make travis load schema instead of running all migrations in sequence

* Fix missing React import in WarningContainer. Optimize rendering performance by using ImmutablePureComponent instead of
React.PureComponent. ImmutablePureComponent uses Immutable.is() to compare props. Replace dynamic callback bindings in
<UI />

* Add react definitions to places that use JSX

* Add Procfile.dev for running rails, webpack and streaming API at the same time
Diffstat (limited to 'app/assets/javascripts')
-rw-r--r--app/assets/javascripts/application.js15
-rw-r--r--app/assets/javascripts/application_public.js9
-rw-r--r--app/assets/javascripts/components.js15
-rw-r--r--app/assets/javascripts/components/.gitkeep0
-rw-r--r--app/assets/javascripts/components/actions/accounts.jsx762
-rw-r--r--app/assets/javascripts/components/actions/alerts.jsx24
-rw-r--r--app/assets/javascripts/components/actions/blocks.jsx82
-rw-r--r--app/assets/javascripts/components/actions/cards.jsx52
-rw-r--r--app/assets/javascripts/components/actions/compose.jsx279
-rw-r--r--app/assets/javascripts/components/actions/favourites.jsx83
-rw-r--r--app/assets/javascripts/components/actions/interactions.jsx235
-rw-r--r--app/assets/javascripts/components/actions/modal.jsx16
-rw-r--r--app/assets/javascripts/components/actions/mutes.jsx82
-rw-r--r--app/assets/javascripts/components/actions/notifications.jsx165
-rw-r--r--app/assets/javascripts/components/actions/onboarding.jsx14
-rw-r--r--app/assets/javascripts/components/actions/reports.jsx72
-rw-r--r--app/assets/javascripts/components/actions/search.jsx73
-rw-r--r--app/assets/javascripts/components/actions/settings.jsx19
-rw-r--r--app/assets/javascripts/components/actions/statuses.jsx141
-rw-r--r--app/assets/javascripts/components/actions/store.jsx17
-rw-r--r--app/assets/javascripts/components/actions/timelines.jsx186
-rw-r--r--app/assets/javascripts/components/api.jsx26
-rw-r--r--app/assets/javascripts/components/components/account.jsx91
-rw-r--r--app/assets/javascripts/components/components/attachment_list.jsx32
-rw-r--r--app/assets/javascripts/components/components/autosuggest_textarea.jsx211
-rw-r--r--app/assets/javascripts/components/components/avatar.jsx63
-rw-r--r--app/assets/javascripts/components/components/button.jsx49
-rw-r--r--app/assets/javascripts/components/components/collapsable.jsx20
-rw-r--r--app/assets/javascripts/components/components/column_back_button.jsx31
-rw-r--r--app/assets/javascripts/components/components/column_back_button_slim.jsx31
-rw-r--r--app/assets/javascripts/components/components/column_collapsable.jsx56
-rw-r--r--app/assets/javascripts/components/components/display_name.jsx24
-rw-r--r--app/assets/javascripts/components/components/dropdown_menu.jsx78
-rw-r--r--app/assets/javascripts/components/components/extended_video_player.jsx53
-rw-r--r--app/assets/javascripts/components/components/icon_button.jsx95
-rw-r--r--app/assets/javascripts/components/components/load_more.jsx14
-rw-r--r--app/assets/javascripts/components/components/loading_indicator.jsx9
-rw-r--r--app/assets/javascripts/components/components/media_gallery.jsx195
-rw-r--r--app/assets/javascripts/components/components/missing_indicator.jsx9
-rw-r--r--app/assets/javascripts/components/components/permalink.jsx36
-rw-r--r--app/assets/javascripts/components/components/relative_timestamp.jsx19
-rw-r--r--app/assets/javascripts/components/components/status.jsx121
-rw-r--r--app/assets/javascripts/components/components/status_action_bar.jsx137
-rw-r--r--app/assets/javascripts/components/components/status_content.jsx157
-rw-r--r--app/assets/javascripts/components/components/status_list.jsx128
-rw-r--r--app/assets/javascripts/components/components/video_player.jsx198
-rw-r--r--app/assets/javascripts/components/containers/account_container.jsx50
-rw-r--r--app/assets/javascripts/components/containers/mastodon.jsx320
-rw-r--r--app/assets/javascripts/components/containers/status_container.jsx117
-rw-r--r--app/assets/javascripts/components/emoji.jsx35
-rw-r--r--app/assets/javascripts/components/features/account/components/action_bar.jsx92
-rw-r--r--app/assets/javascripts/components/features/account/components/header.jsx148
-rw-r--r--app/assets/javascripts/components/features/account_timeline/components/header.jsx81
-rw-r--r--app/assets/javascripts/components/features/account_timeline/containers/header_container.jsx75
-rw-r--r--app/assets/javascripts/components/features/account_timeline/index.jsx87
-rw-r--r--app/assets/javascripts/components/features/blocks/index.jsx72
-rw-r--r--app/assets/javascripts/components/features/community_timeline/index.jsx95
-rw-r--r--app/assets/javascripts/components/features/compose/components/autosuggest_account.jsx16
-rw-r--r--app/assets/javascripts/components/features/compose/components/autosuggest_status.jsx15
-rw-r--r--app/assets/javascripts/components/features/compose/components/character_counter.jsx26
-rw-r--r--app/assets/javascripts/components/features/compose/components/compose_form.jsx209
-rw-r--r--app/assets/javascripts/components/features/compose/components/emoji_picker_dropdown.jsx114
-rw-r--r--app/assets/javascripts/components/features/compose/components/navigation_bar.jsx32
-rw-r--r--app/assets/javascripts/components/features/compose/components/privacy_dropdown.jsx104
-rw-r--r--app/assets/javascripts/components/features/compose/components/reply_indicator.jsx69
-rw-r--r--app/assets/javascripts/components/features/compose/components/search.jsx82
-rw-r--r--app/assets/javascripts/components/features/compose/components/search_results.jsx65
-rw-r--r--app/assets/javascripts/components/features/compose/components/text_icon_button.jsx35
-rw-r--r--app/assets/javascripts/components/features/compose/components/upload_button.jsx60
-rw-r--r--app/assets/javascripts/components/features/compose/components/upload_form.jsx45
-rw-r--r--app/assets/javascripts/components/features/compose/components/upload_progress.jsx42
-rw-r--r--app/assets/javascripts/components/features/compose/components/warning.jsx25
-rw-r--r--app/assets/javascripts/components/features/compose/containers/autosuggest_account_container.jsx15
-rw-r--r--app/assets/javascripts/components/features/compose/containers/autosuggest_status_container.jsx15
-rw-r--r--app/assets/javascripts/components/features/compose/containers/compose_form_container.jsx64
-rw-r--r--app/assets/javascripts/components/features/compose/containers/navigation_container.jsx10
-rw-r--r--app/assets/javascripts/components/features/compose/containers/privacy_dropdown_container.jsx17
-rw-r--r--app/assets/javascripts/components/features/compose/containers/reply_indicator_container.jsx24
-rw-r--r--app/assets/javascripts/components/features/compose/containers/search_container.jsx35
-rw-r--r--app/assets/javascripts/components/features/compose/containers/search_results_container.jsx8
-rw-r--r--app/assets/javascripts/components/features/compose/containers/sensitive_button_container.jsx50
-rw-r--r--app/assets/javascripts/components/features/compose/containers/spoiler_button_container.jsx25
-rw-r--r--app/assets/javascripts/components/features/compose/containers/upload_button_container.jsx18
-rw-r--r--app/assets/javascripts/components/features/compose/containers/upload_form_container.jsx17
-rw-r--r--app/assets/javascripts/components/features/compose/containers/upload_progress_container.jsx9
-rw-r--r--app/assets/javascripts/components/features/compose/containers/warning_container.jsx48
-rw-r--r--app/assets/javascripts/components/features/compose/index.jsx85
-rw-r--r--app/assets/javascripts/components/features/favourited_statuses/index.jsx66
-rw-r--r--app/assets/javascripts/components/features/favourites/index.jsx59
-rw-r--r--app/assets/javascripts/components/features/follow_requests/components/account_authorize.jsx44
-rw-r--r--app/assets/javascripts/components/features/follow_requests/containers/account_authorize_container.jsx26
-rw-r--r--app/assets/javascripts/components/features/follow_requests/index.jsx72
-rw-r--r--app/assets/javascripts/components/features/followers/index.jsx90
-rw-r--r--app/assets/javascripts/components/features/following/index.jsx90
-rw-r--r--app/assets/javascripts/components/features/generic_not_found/index.jsx10
-rw-r--r--app/assets/javascripts/components/features/getting_started/index.jsx66
-rw-r--r--app/assets/javascripts/components/features/hashtag_timeline/index.jsx89
-rw-r--r--app/assets/javascripts/components/features/home_timeline/components/column_settings.jsx50
-rw-r--r--app/assets/javascripts/components/features/home_timeline/components/setting_text.jsx37
-rw-r--r--app/assets/javascripts/components/features/home_timeline/containers/column_settings_container.jsx21
-rw-r--r--app/assets/javascripts/components/features/home_timeline/index.jsx37
-rw-r--r--app/assets/javascripts/components/features/mutes/index.jsx73
-rw-r--r--app/assets/javascripts/components/features/notifications/components/clear_column_button.jsx26
-rw-r--r--app/assets/javascripts/components/features/notifications/components/column_settings.jsx70
-rw-r--r--app/assets/javascripts/components/features/notifications/components/notification.jsx88
-rw-r--r--app/assets/javascripts/components/features/notifications/components/setting_toggle.jsx20
-rw-r--r--app/assets/javascripts/components/features/notifications/containers/column_settings_container.jsx21
-rw-r--r--app/assets/javascripts/components/features/notifications/containers/notification_container.jsx15
-rw-r--r--app/assets/javascripts/components/features/notifications/index.jsx142
-rw-r--r--app/assets/javascripts/components/features/public_timeline/index.jsx95
-rw-r--r--app/assets/javascripts/components/features/reblogs/index.jsx59
-rw-r--r--app/assets/javascripts/components/features/report/components/status_check_box.jsx39
-rw-r--r--app/assets/javascripts/components/features/report/containers/status_check_box_container.jsx19
-rw-r--r--app/assets/javascripts/components/features/report/index.jsx130
-rw-r--r--app/assets/javascripts/components/features/status/components/action_bar.jsx101
-rw-r--r--app/assets/javascripts/components/features/status/components/card.jsx95
-rw-r--r--app/assets/javascripts/components/features/status/components/detailed_status.jsx94
-rw-r--r--app/assets/javascripts/components/features/status/containers/card_container.jsx8
-rw-r--r--app/assets/javascripts/components/features/status/index.jsx197
-rw-r--r--app/assets/javascripts/components/features/ui/components/boost_modal.jsx82
-rw-r--r--app/assets/javascripts/components/features/ui/components/column.jsx82
-rw-r--r--app/assets/javascripts/components/features/ui/components/column_header.jsx42
-rw-r--r--app/assets/javascripts/components/features/ui/components/column_link.jsx31
-rw-r--r--app/assets/javascripts/components/features/ui/components/column_subheading.jsx15
-rw-r--r--app/assets/javascripts/components/features/ui/components/columns_area.jsx19
-rw-r--r--app/assets/javascripts/components/features/ui/components/confirmation_modal.jsx50
-rw-r--r--app/assets/javascripts/components/features/ui/components/media_modal.jsx101
-rw-r--r--app/assets/javascripts/components/features/ui/components/modal_root.jsx92
-rw-r--r--app/assets/javascripts/components/features/ui/components/onboarding_modal.jsx263
-rw-r--r--app/assets/javascripts/components/features/ui/components/tabs_bar.jsx23
-rw-r--r--app/assets/javascripts/components/features/ui/components/upload_area.jsx59
-rw-r--r--app/assets/javascripts/components/features/ui/components/video_modal.jsx38
-rw-r--r--app/assets/javascripts/components/features/ui/containers/loading_bar_container.jsx8
-rw-r--r--app/assets/javascripts/components/features/ui/containers/modal_container.jsx16
-rw-r--r--app/assets/javascripts/components/features/ui/containers/notifications_container.jsx21
-rw-r--r--app/assets/javascripts/components/features/ui/containers/status_list_container.jsx74
-rw-r--r--app/assets/javascripts/components/features/ui/index.jsx166
-rw-r--r--app/assets/javascripts/components/is_mobile.jsx11
-rw-r--r--app/assets/javascripts/components/link_header.jsx33
-rw-r--r--app/assets/javascripts/components/locales/ar.jsx131
-rw-r--r--app/assets/javascripts/components/locales/bg.jsx68
-rw-r--r--app/assets/javascripts/components/locales/de.jsx126
-rw-r--r--app/assets/javascripts/components/locales/en.jsx177
-rw-r--r--app/assets/javascripts/components/locales/eo.jsx68
-rw-r--r--app/assets/javascripts/components/locales/es.jsx93
-rw-r--r--app/assets/javascripts/components/locales/fa.jsx136
-rw-r--r--app/assets/javascripts/components/locales/fi.jsx68
-rw-r--r--app/assets/javascripts/components/locales/fr.jsx155
-rw-r--r--app/assets/javascripts/components/locales/he.jsx177
-rw-r--r--app/assets/javascripts/components/locales/hr.jsx121
-rw-r--r--app/assets/javascripts/components/locales/hu.jsx57
-rw-r--r--app/assets/javascripts/components/locales/id.jsx167
-rw-r--r--app/assets/javascripts/components/locales/index.jsx57
-rw-r--r--app/assets/javascripts/components/locales/io.jsx126
-rw-r--r--app/assets/javascripts/components/locales/it.jsx125
-rw-r--r--app/assets/javascripts/components/locales/ja.jsx167
-rw-r--r--app/assets/javascripts/components/locales/nl.jsx130
-rw-r--r--app/assets/javascripts/components/locales/no.jsx130
-rw-r--r--app/assets/javascripts/components/locales/oc.jsx128
-rw-r--r--app/assets/javascripts/components/locales/pt-br.jsx125
-rw-r--r--app/assets/javascripts/components/locales/pt.jsx125
-rw-r--r--app/assets/javascripts/components/locales/ru.jsx138
-rw-r--r--app/assets/javascripts/components/locales/uk.jsx57
-rw-r--r--app/assets/javascripts/components/locales/zh-cn.jsx157
-rw-r--r--app/assets/javascripts/components/locales/zh-hk.jsx150
-rw-r--r--app/assets/javascripts/components/middleware/errors.jsx33
-rw-r--r--app/assets/javascripts/components/middleware/loading_bar.jsx25
-rw-r--r--app/assets/javascripts/components/middleware/sounds.jsx22
-rw-r--r--app/assets/javascripts/components/reducers/accounts.jsx131
-rw-r--r--app/assets/javascripts/components/reducers/alerts.jsx25
-rw-r--r--app/assets/javascripts/components/reducers/cards.jsx14
-rw-r--r--app/assets/javascripts/components/reducers/compose.jsx232
-rw-r--r--app/assets/javascripts/components/reducers/index.jsx36
-rw-r--r--app/assets/javascripts/components/reducers/meta.jsx17
-rw-r--r--app/assets/javascripts/components/reducers/modal.jsx18
-rw-r--r--app/assets/javascripts/components/reducers/notifications.jsx104
-rw-r--r--app/assets/javascripts/components/reducers/relationships.jsx38
-rw-r--r--app/assets/javascripts/components/reducers/reports.jsx60
-rw-r--r--app/assets/javascripts/components/reducers/search.jsx96
-rw-r--r--app/assets/javascripts/components/reducers/settings.jsx48
-rw-r--r--app/assets/javascripts/components/reducers/status_lists.jsx39
-rw-r--r--app/assets/javascripts/components/reducers/statuses.jsx124
-rw-r--r--app/assets/javascripts/components/reducers/timelines.jsx317
-rw-r--r--app/assets/javascripts/components/reducers/user_lists.jsx80
-rw-r--r--app/assets/javascripts/components/rtl.jsx27
-rw-r--r--app/assets/javascripts/components/selectors/index.jsx72
-rw-r--r--app/assets/javascripts/components/store/configureStore.jsx16
-rw-r--r--app/assets/javascripts/components/stream.jsx22
-rw-r--r--app/assets/javascripts/components/uuid.jsx3
-rw-r--r--app/assets/javascripts/extras.jsx49
190 files changed, 0 insertions, 14857 deletions
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
deleted file mode 100644
index 441282825..000000000
--- a/app/assets/javascripts/application.js
+++ /dev/null
@@ -1,15 +0,0 @@
-// This is a manifest file that'll be compiled into application.js, which will include all the files
-// listed below.
-//
-// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
-// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
-//
-// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
-// compiled file.
-//
-// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
-// about supported directives.
-//
-//= require jquery2
-//= require jquery_ujs
-//= require components
diff --git a/app/assets/javascripts/application_public.js b/app/assets/javascripts/application_public.js
deleted file mode 100644
index 7df4891e7..000000000
--- a/app/assets/javascripts/application_public.js
+++ /dev/null
@@ -1,9 +0,0 @@
-//= require jquery2
-//= require jquery_ujs
-//= require extras
-//= require best_in_place
-//= require local_time
-
-$(function () {
-  $(".best_in_place").best_in_place();
-});
diff --git a/app/assets/javascripts/components.js b/app/assets/javascripts/components.js
deleted file mode 100644
index 1604d5198..000000000
--- a/app/assets/javascripts/components.js
+++ /dev/null
@@ -1,15 +0,0 @@
-//= require_self
-//= require react_ujs
-
-window.React    = require('react');
-window.ReactDOM = require('react-dom');
-window.Perf     = require('react-addons-perf');
-
-if (!window.Intl) {
-  require('intl');
-  require('intl/locale-data/jsonp/en.js');
-}
-
-//= require_tree ./components
-
-window.Mastodon = require('./components/containers/mastodon');
diff --git a/app/assets/javascripts/components/.gitkeep b/app/assets/javascripts/components/.gitkeep
deleted file mode 100644
index e69de29bb..000000000
--- a/app/assets/javascripts/components/.gitkeep
+++ /dev/null
diff --git a/app/assets/javascripts/components/actions/accounts.jsx b/app/assets/javascripts/components/actions/accounts.jsx
deleted file mode 100644
index eac5c78bb..000000000
--- a/app/assets/javascripts/components/actions/accounts.jsx
+++ /dev/null
@@ -1,762 +0,0 @@
-import api, { getLinks } from '../api'
-import Immutable from 'immutable';
-
-export const ACCOUNT_FETCH_REQUEST = 'ACCOUNT_FETCH_REQUEST';
-export const ACCOUNT_FETCH_SUCCESS = 'ACCOUNT_FETCH_SUCCESS';
-export const ACCOUNT_FETCH_FAIL    = 'ACCOUNT_FETCH_FAIL';
-
-export const ACCOUNT_FOLLOW_REQUEST = 'ACCOUNT_FOLLOW_REQUEST';
-export const ACCOUNT_FOLLOW_SUCCESS = 'ACCOUNT_FOLLOW_SUCCESS';
-export const ACCOUNT_FOLLOW_FAIL    = 'ACCOUNT_FOLLOW_FAIL';
-
-export const ACCOUNT_UNFOLLOW_REQUEST = 'ACCOUNT_UNFOLLOW_REQUEST';
-export const ACCOUNT_UNFOLLOW_SUCCESS = 'ACCOUNT_UNFOLLOW_SUCCESS';
-export const ACCOUNT_UNFOLLOW_FAIL    = 'ACCOUNT_UNFOLLOW_FAIL';
-
-export const ACCOUNT_BLOCK_REQUEST = 'ACCOUNT_BLOCK_REQUEST';
-export const ACCOUNT_BLOCK_SUCCESS = 'ACCOUNT_BLOCK_SUCCESS';
-export const ACCOUNT_BLOCK_FAIL    = 'ACCOUNT_BLOCK_FAIL';
-
-export const ACCOUNT_UNBLOCK_REQUEST = 'ACCOUNT_UNBLOCK_REQUEST';
-export const ACCOUNT_UNBLOCK_SUCCESS = 'ACCOUNT_UNBLOCK_SUCCESS';
-export const ACCOUNT_UNBLOCK_FAIL    = 'ACCOUNT_UNBLOCK_FAIL';
-
-export const ACCOUNT_MUTE_REQUEST = 'ACCOUNT_MUTE_REQUEST';
-export const ACCOUNT_MUTE_SUCCESS = 'ACCOUNT_MUTE_SUCCESS';
-export const ACCOUNT_MUTE_FAIL    = 'ACCOUNT_MUTE_FAIL';
-
-export const ACCOUNT_UNMUTE_REQUEST = 'ACCOUNT_UNMUTE_REQUEST';
-export const ACCOUNT_UNMUTE_SUCCESS = 'ACCOUNT_UNMUTE_SUCCESS';
-export const ACCOUNT_UNMUTE_FAIL    = 'ACCOUNT_UNMUTE_FAIL';
-
-export const ACCOUNT_TIMELINE_FETCH_REQUEST = 'ACCOUNT_TIMELINE_FETCH_REQUEST';
-export const ACCOUNT_TIMELINE_FETCH_SUCCESS = 'ACCOUNT_TIMELINE_FETCH_SUCCESS';
-export const ACCOUNT_TIMELINE_FETCH_FAIL    = 'ACCOUNT_TIMELINE_FETCH_FAIL';
-
-export const ACCOUNT_TIMELINE_EXPAND_REQUEST = 'ACCOUNT_TIMELINE_EXPAND_REQUEST';
-export const ACCOUNT_TIMELINE_EXPAND_SUCCESS = 'ACCOUNT_TIMELINE_EXPAND_SUCCESS';
-export const ACCOUNT_TIMELINE_EXPAND_FAIL    = 'ACCOUNT_TIMELINE_EXPAND_FAIL';
-
-export const FOLLOWERS_FETCH_REQUEST = 'FOLLOWERS_FETCH_REQUEST';
-export const FOLLOWERS_FETCH_SUCCESS = 'FOLLOWERS_FETCH_SUCCESS';
-export const FOLLOWERS_FETCH_FAIL    = 'FOLLOWERS_FETCH_FAIL';
-
-export const FOLLOWERS_EXPAND_REQUEST = 'FOLLOWERS_EXPAND_REQUEST';
-export const FOLLOWERS_EXPAND_SUCCESS = 'FOLLOWERS_EXPAND_SUCCESS';
-export const FOLLOWERS_EXPAND_FAIL    = 'FOLLOWERS_EXPAND_FAIL';
-
-export const FOLLOWING_FETCH_REQUEST = 'FOLLOWING_FETCH_REQUEST';
-export const FOLLOWING_FETCH_SUCCESS = 'FOLLOWING_FETCH_SUCCESS';
-export const FOLLOWING_FETCH_FAIL    = 'FOLLOWING_FETCH_FAIL';
-
-export const FOLLOWING_EXPAND_REQUEST = 'FOLLOWING_EXPAND_REQUEST';
-export const FOLLOWING_EXPAND_SUCCESS = 'FOLLOWING_EXPAND_SUCCESS';
-export const FOLLOWING_EXPAND_FAIL    = 'FOLLOWING_EXPAND_FAIL';
-
-export const RELATIONSHIPS_FETCH_REQUEST = 'RELATIONSHIPS_FETCH_REQUEST';
-export const RELATIONSHIPS_FETCH_SUCCESS = 'RELATIONSHIPS_FETCH_SUCCESS';
-export const RELATIONSHIPS_FETCH_FAIL    = 'RELATIONSHIPS_FETCH_FAIL';
-
-export const FOLLOW_REQUESTS_FETCH_REQUEST = 'FOLLOW_REQUESTS_FETCH_REQUEST';
-export const FOLLOW_REQUESTS_FETCH_SUCCESS = 'FOLLOW_REQUESTS_FETCH_SUCCESS';
-export const FOLLOW_REQUESTS_FETCH_FAIL    = 'FOLLOW_REQUESTS_FETCH_FAIL';
-
-export const FOLLOW_REQUESTS_EXPAND_REQUEST = 'FOLLOW_REQUESTS_EXPAND_REQUEST';
-export const FOLLOW_REQUESTS_EXPAND_SUCCESS = 'FOLLOW_REQUESTS_EXPAND_SUCCESS';
-export const FOLLOW_REQUESTS_EXPAND_FAIL    = 'FOLLOW_REQUESTS_EXPAND_FAIL';
-
-export const FOLLOW_REQUEST_AUTHORIZE_REQUEST = 'FOLLOW_REQUEST_AUTHORIZE_REQUEST';
-export const FOLLOW_REQUEST_AUTHORIZE_SUCCESS = 'FOLLOW_REQUEST_AUTHORIZE_SUCCESS';
-export const FOLLOW_REQUEST_AUTHORIZE_FAIL    = 'FOLLOW_REQUEST_AUTHORIZE_FAIL';
-
-export const FOLLOW_REQUEST_REJECT_REQUEST = 'FOLLOW_REQUEST_REJECT_REQUEST';
-export const FOLLOW_REQUEST_REJECT_SUCCESS = 'FOLLOW_REQUEST_REJECT_SUCCESS';
-export const FOLLOW_REQUEST_REJECT_FAIL    = 'FOLLOW_REQUEST_REJECT_FAIL';
-
-export function fetchAccount(id) {
-  return (dispatch, getState) => {
-    dispatch(fetchRelationships([id]));
-
-    if (getState().getIn(['accounts', id], null) !== null) {
-      return;
-    }
-
-    dispatch(fetchAccountRequest(id));
-
-    api(getState).get(`/api/v1/accounts/${id}`).then(response => {
-      dispatch(fetchAccountSuccess(response.data));
-    }).catch(error => {
-      dispatch(fetchAccountFail(id, error));
-    });
-  };
-};
-
-export function fetchAccountTimeline(id, replace = false) {
-  return (dispatch, getState) => {
-    const ids      = getState().getIn(['timelines', 'accounts_timelines', id, 'items'], Immutable.List());
-    const newestId = ids.size > 0 ? ids.first() : null;
-
-    let params = '';
-    let skipLoading = false;
-
-    if (newestId !== null && !replace) {
-      params      = `?since_id=${newestId}`;
-      skipLoading = true;
-    }
-
-    dispatch(fetchAccountTimelineRequest(id, skipLoading));
-
-    api(getState).get(`/api/v1/accounts/${id}/statuses${params}`).then(response => {
-      dispatch(fetchAccountTimelineSuccess(id, response.data, replace, skipLoading));
-    }).catch(error => {
-      dispatch(fetchAccountTimelineFail(id, error, skipLoading));
-    });
-  };
-};
-
-export function expandAccountTimeline(id) {
-  return (dispatch, getState) => {
-    const lastId = getState().getIn(['timelines', 'accounts_timelines', id, 'items'], Immutable.List()).last();
-
-    dispatch(expandAccountTimelineRequest(id));
-
-    api(getState).get(`/api/v1/accounts/${id}/statuses`, {
-      params: {
-        limit: 10,
-        max_id: lastId
-      }
-    }).then(response => {
-      const next = getLinks(response).refs.find(link => link.rel === 'next');
-      dispatch(expandAccountTimelineSuccess(id, response.data, next));
-    }).catch(error => {
-      dispatch(expandAccountTimelineFail(id, error));
-    });
-  };
-};
-
-export function fetchAccountRequest(id) {
-  return {
-    type: ACCOUNT_FETCH_REQUEST,
-    id
-  };
-};
-
-export function fetchAccountSuccess(account) {
-  return {
-    type: ACCOUNT_FETCH_SUCCESS,
-    account
-  };
-};
-
-export function fetchAccountFail(id, error) {
-  return {
-    type: ACCOUNT_FETCH_FAIL,
-    id,
-    error,
-    skipAlert: true
-  };
-};
-
-export function followAccount(id) {
-  return (dispatch, getState) => {
-    dispatch(followAccountRequest(id));
-
-    api(getState).post(`/api/v1/accounts/${id}/follow`).then(response => {
-      dispatch(followAccountSuccess(response.data));
-    }).catch(error => {
-      dispatch(followAccountFail(error));
-    });
-  };
-};
-
-export function unfollowAccount(id) {
-  return (dispatch, getState) => {
-    dispatch(unfollowAccountRequest(id));
-
-    api(getState).post(`/api/v1/accounts/${id}/unfollow`).then(response => {
-      dispatch(unfollowAccountSuccess(response.data));
-    }).catch(error => {
-      dispatch(unfollowAccountFail(error));
-    });
-  }
-};
-
-export function followAccountRequest(id) {
-  return {
-    type: ACCOUNT_FOLLOW_REQUEST,
-    id
-  };
-};
-
-export function followAccountSuccess(relationship) {
-  return {
-    type: ACCOUNT_FOLLOW_SUCCESS,
-    relationship
-  };
-};
-
-export function followAccountFail(error) {
-  return {
-    type: ACCOUNT_FOLLOW_FAIL,
-    error
-  };
-};
-
-export function unfollowAccountRequest(id) {
-  return {
-    type: ACCOUNT_UNFOLLOW_REQUEST,
-    id
-  };
-};
-
-export function unfollowAccountSuccess(relationship) {
-  return {
-    type: ACCOUNT_UNFOLLOW_SUCCESS,
-    relationship
-  };
-};
-
-export function unfollowAccountFail(error) {
-  return {
-    type: ACCOUNT_UNFOLLOW_FAIL,
-    error
-  };
-};
-
-export function fetchAccountTimelineRequest(id, skipLoading) {
-  return {
-    type: ACCOUNT_TIMELINE_FETCH_REQUEST,
-    id,
-    skipLoading
-  };
-};
-
-export function fetchAccountTimelineSuccess(id, statuses, replace, skipLoading) {
-  return {
-    type: ACCOUNT_TIMELINE_FETCH_SUCCESS,
-    id,
-    statuses,
-    replace,
-    skipLoading
-  };
-};
-
-export function fetchAccountTimelineFail(id, error, skipLoading) {
-  return {
-    type: ACCOUNT_TIMELINE_FETCH_FAIL,
-    id,
-    error,
-    skipLoading,
-    skipAlert: error.response.status === 404
-  };
-};
-
-export function expandAccountTimelineRequest(id) {
-  return {
-    type: ACCOUNT_TIMELINE_EXPAND_REQUEST,
-    id
-  };
-};
-
-export function expandAccountTimelineSuccess(id, statuses, next) {
-  return {
-    type: ACCOUNT_TIMELINE_EXPAND_SUCCESS,
-    id,
-    statuses,
-    next
-  };
-};
-
-export function expandAccountTimelineFail(id, error) {
-  return {
-    type: ACCOUNT_TIMELINE_EXPAND_FAIL,
-    id,
-    error
-  };
-};
-
-export function blockAccount(id) {
-  return (dispatch, getState) => {
-    dispatch(blockAccountRequest(id));
-
-    api(getState).post(`/api/v1/accounts/${id}/block`).then(response => {
-      // Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers
-      dispatch(blockAccountSuccess(response.data, getState().get('statuses')));
-    }).catch(error => {
-      dispatch(blockAccountFail(id, error));
-    });
-  };
-};
-
-export function unblockAccount(id) {
-  return (dispatch, getState) => {
-    dispatch(unblockAccountRequest(id));
-
-    api(getState).post(`/api/v1/accounts/${id}/unblock`).then(response => {
-      dispatch(unblockAccountSuccess(response.data));
-    }).catch(error => {
-      dispatch(unblockAccountFail(id, error));
-    });
-  };
-};
-
-export function blockAccountRequest(id) {
-  return {
-    type: ACCOUNT_BLOCK_REQUEST,
-    id
-  };
-};
-
-export function blockAccountSuccess(relationship, statuses) {
-  return {
-    type: ACCOUNT_BLOCK_SUCCESS,
-    relationship,
-    statuses
-  };
-};
-
-export function blockAccountFail(error) {
-  return {
-    type: ACCOUNT_BLOCK_FAIL,
-    error
-  };
-};
-
-export function unblockAccountRequest(id) {
-  return {
-    type: ACCOUNT_UNBLOCK_REQUEST,
-    id
-  };
-};
-
-export function unblockAccountSuccess(relationship) {
-  return {
-    type: ACCOUNT_UNBLOCK_SUCCESS,
-    relationship
-  };
-};
-
-export function unblockAccountFail(error) {
-  return {
-    type: ACCOUNT_UNBLOCK_FAIL,
-    error
-  };
-};
-
-
-export function muteAccount(id) {
-  return (dispatch, getState) => {
-    dispatch(muteAccountRequest(id));
-
-    api(getState).post(`/api/v1/accounts/${id}/mute`).then(response => {
-      // Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers
-      dispatch(muteAccountSuccess(response.data, getState().get('statuses')));
-    }).catch(error => {
-      dispatch(muteAccountFail(id, error));
-    });
-  };
-};
-
-export function unmuteAccount(id) {
-  return (dispatch, getState) => {
-    dispatch(unmuteAccountRequest(id));
-
-    api(getState).post(`/api/v1/accounts/${id}/unmute`).then(response => {
-      dispatch(unmuteAccountSuccess(response.data));
-    }).catch(error => {
-      dispatch(unmuteAccountFail(id, error));
-    });
-  };
-};
-
-export function muteAccountRequest(id) {
-  return {
-    type: ACCOUNT_MUTE_REQUEST,
-    id
-  };
-};
-
-export function muteAccountSuccess(relationship, statuses) {
-  return {
-    type: ACCOUNT_MUTE_SUCCESS,
-    relationship,
-    statuses
-  };
-};
-
-export function muteAccountFail(error) {
-  return {
-    type: ACCOUNT_MUTE_FAIL,
-    error
-  };
-};
-
-export function unmuteAccountRequest(id) {
-  return {
-    type: ACCOUNT_UNMUTE_REQUEST,
-    id
-  };
-};
-
-export function unmuteAccountSuccess(relationship) {
-  return {
-    type: ACCOUNT_UNMUTE_SUCCESS,
-    relationship
-  };
-};
-
-export function unmuteAccountFail(error) {
-  return {
-    type: ACCOUNT_UNMUTE_FAIL,
-    error
-  };
-};
-
-
-export function fetchFollowers(id) {
-  return (dispatch, getState) => {
-    dispatch(fetchFollowersRequest(id));
-
-    api(getState).get(`/api/v1/accounts/${id}/followers`).then(response => {
-      const next = getLinks(response).refs.find(link => link.rel === 'next');
-
-      dispatch(fetchFollowersSuccess(id, response.data, next ? next.uri : null));
-      dispatch(fetchRelationships(response.data.map(item => item.id)));
-    }).catch(error => {
-      dispatch(fetchFollowersFail(id, error));
-    });
-  };
-};
-
-export function fetchFollowersRequest(id) {
-  return {
-    type: FOLLOWERS_FETCH_REQUEST,
-    id
-  };
-};
-
-export function fetchFollowersSuccess(id, accounts, next) {
-  return {
-    type: FOLLOWERS_FETCH_SUCCESS,
-    id,
-    accounts,
-    next
-  };
-};
-
-export function fetchFollowersFail(id, error) {
-  return {
-    type: FOLLOWERS_FETCH_FAIL,
-    id,
-    error
-  };
-};
-
-export function expandFollowers(id) {
-  return (dispatch, getState) => {
-    const url = getState().getIn(['user_lists', 'followers', id, 'next']);
-
-    if (url === null) {
-      return;
-    }
-
-    dispatch(expandFollowersRequest(id));
-
-    api(getState).get(url).then(response => {
-      const next = getLinks(response).refs.find(link => link.rel === 'next');
-
-      dispatch(expandFollowersSuccess(id, response.data, next ? next.uri : null));
-      dispatch(fetchRelationships(response.data.map(item => item.id)));
-    }).catch(error => {
-      dispatch(expandFollowersFail(id, error));
-    });
-  };
-};
-
-export function expandFollowersRequest(id) {
-  return {
-    type: FOLLOWERS_EXPAND_REQUEST,
-    id
-  };
-};
-
-export function expandFollowersSuccess(id, accounts, next) {
-  return {
-    type: FOLLOWERS_EXPAND_SUCCESS,
-    id,
-    accounts,
-    next
-  };
-};
-
-export function expandFollowersFail(id, error) {
-  return {
-    type: FOLLOWERS_EXPAND_FAIL,
-    id,
-    error
-  };
-};
-
-export function fetchFollowing(id) {
-  return (dispatch, getState) => {
-    dispatch(fetchFollowingRequest(id));
-
-    api(getState).get(`/api/v1/accounts/${id}/following`).then(response => {
-      const next = getLinks(response).refs.find(link => link.rel === 'next');
-
-      dispatch(fetchFollowingSuccess(id, response.data, next ? next.uri : null));
-      dispatch(fetchRelationships(response.data.map(item => item.id)));
-    }).catch(error => {
-      dispatch(fetchFollowingFail(id, error));
-    });
-  };
-};
-
-export function fetchFollowingRequest(id) {
-  return {
-    type: FOLLOWING_FETCH_REQUEST,
-    id
-  };
-};
-
-export function fetchFollowingSuccess(id, accounts, next) {
-  return {
-    type: FOLLOWING_FETCH_SUCCESS,
-    id,
-    accounts,
-    next
-  };
-};
-
-export function fetchFollowingFail(id, error) {
-  return {
-    type: FOLLOWING_FETCH_FAIL,
-    id,
-    error
-  };
-};
-
-export function expandFollowing(id) {
-  return (dispatch, getState) => {
-    const url = getState().getIn(['user_lists', 'following', id, 'next']);
-
-    if (url === null) {
-      return;
-    }
-
-    dispatch(expandFollowingRequest(id));
-
-    api(getState).get(url).then(response => {
-      const next = getLinks(response).refs.find(link => link.rel === 'next');
-
-      dispatch(expandFollowingSuccess(id, response.data, next ? next.uri : null));
-      dispatch(fetchRelationships(response.data.map(item => item.id)));
-    }).catch(error => {
-      dispatch(expandFollowingFail(id, error));
-    });
-  };
-};
-
-export function expandFollowingRequest(id) {
-  return {
-    type: FOLLOWING_EXPAND_REQUEST,
-    id
-  };
-};
-
-export function expandFollowingSuccess(id, accounts, next) {
-  return {
-    type: FOLLOWING_EXPAND_SUCCESS,
-    id,
-    accounts,
-    next
-  };
-};
-
-export function expandFollowingFail(id, error) {
-  return {
-    type: FOLLOWING_EXPAND_FAIL,
-    id,
-    error
-  };
-};
-
-export function fetchRelationships(accountIds) {
-  return (dispatch, getState) => {
-    const loadedRelationships = getState().get('relationships');
-    const newAccountIds = accountIds.filter(id => loadedRelationships.get(id, null) === null);
-
-    if (newAccountIds.length === 0) {
-      return;
-    }
-
-    dispatch(fetchRelationshipsRequest(newAccountIds));
-
-    api(getState).get(`/api/v1/accounts/relationships?${newAccountIds.map(id => `id[]=${id}`).join('&')}`).then(response => {
-      dispatch(fetchRelationshipsSuccess(response.data));
-    }).catch(error => {
-      dispatch(fetchRelationshipsFail(error));
-    });
-  };
-};
-
-export function fetchRelationshipsRequest(ids) {
-  return {
-    type: RELATIONSHIPS_FETCH_REQUEST,
-    ids,
-    skipLoading: true
-  };
-};
-
-export function fetchRelationshipsSuccess(relationships) {
-  return {
-    type: RELATIONSHIPS_FETCH_SUCCESS,
-    relationships,
-    skipLoading: true
-  };
-};
-
-export function fetchRelationshipsFail(error) {
-  return {
-    type: RELATIONSHIPS_FETCH_FAIL,
-    error,
-    skipLoading: true
-  };
-};
-
-export function fetchFollowRequests() {
-  return (dispatch, getState) => {
-    dispatch(fetchFollowRequestsRequest());
-
-    api(getState).get('/api/v1/follow_requests').then(response => {
-      const next = getLinks(response).refs.find(link => link.rel === 'next');
-      dispatch(fetchFollowRequestsSuccess(response.data, next ? next.uri : null))
-    }).catch(error => dispatch(fetchFollowRequestsFail(error)));
-  };
-};
-
-export function fetchFollowRequestsRequest() {
-  return {
-    type: FOLLOW_REQUESTS_FETCH_REQUEST
-  };
-};
-
-export function fetchFollowRequestsSuccess(accounts, next) {
-  return {
-    type: FOLLOW_REQUESTS_FETCH_SUCCESS,
-    accounts,
-    next
-  };
-};
-
-export function fetchFollowRequestsFail(error) {
-  return {
-    type: FOLLOW_REQUESTS_FETCH_FAIL,
-    error
-  };
-};
-
-export function expandFollowRequests() {
-  return (dispatch, getState) => {
-    const url = getState().getIn(['user_lists', 'follow_requests', 'next']);
-
-    if (url === null) {
-      return;
-    }
-
-    dispatch(expandFollowRequestsRequest());
-
-    api(getState).get(url).then(response => {
-      const next = getLinks(response).refs.find(link => link.rel === 'next');
-      dispatch(expandFollowRequestsSuccess(response.data, next ? next.uri : null))
-    }).catch(error => dispatch(expandFollowRequestsFail(error)));
-  };
-};
-
-export function expandFollowRequestsRequest() {
-  return {
-    type: FOLLOW_REQUESTS_EXPAND_REQUEST
-  };
-};
-
-export function expandFollowRequestsSuccess(accounts, next) {
-  return {
-    type: FOLLOW_REQUESTS_EXPAND_SUCCESS,
-    accounts,
-    next
-  };
-};
-
-export function expandFollowRequestsFail(error) {
-  return {
-    type: FOLLOW_REQUESTS_EXPAND_FAIL,
-    error
-  };
-};
-
-export function authorizeFollowRequest(id) {
-  return (dispatch, getState) => {
-    dispatch(authorizeFollowRequestRequest(id));
-
-    api(getState)
-      .post(`/api/v1/follow_requests/${id}/authorize`)
-      .then(response => dispatch(authorizeFollowRequestSuccess(id)))
-      .catch(error => dispatch(authorizeFollowRequestFail(id, error)));
-  };
-};
-
-export function authorizeFollowRequestRequest(id) {
-  return {
-    type: FOLLOW_REQUEST_AUTHORIZE_REQUEST,
-    id
-  };
-};
-
-export function authorizeFollowRequestSuccess(id) {
-  return {
-    type: FOLLOW_REQUEST_AUTHORIZE_SUCCESS,
-    id
-  };
-};
-
-export function authorizeFollowRequestFail(id, error) {
-  return {
-    type: FOLLOW_REQUEST_AUTHORIZE_FAIL,
-    id,
-    error
-  };
-};
-
-
-export function rejectFollowRequest(id) {
-  return (dispatch, getState) => {
-    dispatch(rejectFollowRequestRequest(id));
-
-    api(getState)
-      .post(`/api/v1/follow_requests/${id}/reject`)
-      .then(response => dispatch(rejectFollowRequestSuccess(id)))
-      .catch(error => dispatch(rejectFollowRequestFail(id, error)));
-  };
-};
-
-export function rejectFollowRequestRequest(id) {
-  return {
-    type: FOLLOW_REQUEST_REJECT_REQUEST,
-    id
-  };
-};
-
-export function rejectFollowRequestSuccess(id) {
-  return {
-    type: FOLLOW_REQUEST_REJECT_SUCCESS,
-    id
-  };
-};
-
-export function rejectFollowRequestFail(id, error) {
-  return {
-    type: FOLLOW_REQUEST_REJECT_FAIL,
-    id,
-    error
-  };
-};
diff --git a/app/assets/javascripts/components/actions/alerts.jsx b/app/assets/javascripts/components/actions/alerts.jsx
deleted file mode 100644
index 086e0727e..000000000
--- a/app/assets/javascripts/components/actions/alerts.jsx
+++ /dev/null
@@ -1,24 +0,0 @@
-export const ALERT_SHOW    = 'ALERT_SHOW';
-export const ALERT_DISMISS = 'ALERT_DISMISS';
-export const ALERT_CLEAR   = 'ALERT_CLEAR';
-
-export function dismissAlert(alert) {
-  return {
-    type: ALERT_DISMISS,
-    alert
-  };
-};
-
-export function clearAlert() {
-  return {
-    type: ALERT_CLEAR
-  };
-};
-
-export function showAlert(title, message) {
-  return {
-    type: ALERT_SHOW,
-    title,
-    message
-  };
-};
diff --git a/app/assets/javascripts/components/actions/blocks.jsx b/app/assets/javascripts/components/actions/blocks.jsx
deleted file mode 100644
index 79e316497..000000000
--- a/app/assets/javascripts/components/actions/blocks.jsx
+++ /dev/null
@@ -1,82 +0,0 @@
-import api, { getLinks } from '../api'
-import { fetchRelationships } from './accounts';
-
-export const BLOCKS_FETCH_REQUEST = 'BLOCKS_FETCH_REQUEST';
-export const BLOCKS_FETCH_SUCCESS = 'BLOCKS_FETCH_SUCCESS';
-export const BLOCKS_FETCH_FAIL    = 'BLOCKS_FETCH_FAIL';
-
-export const BLOCKS_EXPAND_REQUEST = 'BLOCKS_EXPAND_REQUEST';
-export const BLOCKS_EXPAND_SUCCESS = 'BLOCKS_EXPAND_SUCCESS';
-export const BLOCKS_EXPAND_FAIL    = 'BLOCKS_EXPAND_FAIL';
-
-export function fetchBlocks() {
-  return (dispatch, getState) => {
-    dispatch(fetchBlocksRequest());
-
-    api(getState).get('/api/v1/blocks').then(response => {
-      const next = getLinks(response).refs.find(link => link.rel === 'next');
-      dispatch(fetchBlocksSuccess(response.data, next ? next.uri : null));
-      dispatch(fetchRelationships(response.data.map(item => item.id)));
-    }).catch(error => dispatch(fetchBlocksFail(error)));
-  };
-};
-
-export function fetchBlocksRequest() {
-  return {
-    type: BLOCKS_FETCH_REQUEST
-  };
-};
-
-export function fetchBlocksSuccess(accounts, next) {
-  return {
-    type: BLOCKS_FETCH_SUCCESS,
-    accounts,
-    next
-  };
-};
-
-export function fetchBlocksFail(error) {
-  return {
-    type: BLOCKS_FETCH_FAIL,
-    error
-  };
-};
-
-export function expandBlocks() {
-  return (dispatch, getState) => {
-    const url = getState().getIn(['user_lists', 'blocks', 'next']);
-
-    if (url === null) {
-      return;
-    }
-
-    dispatch(expandBlocksRequest());
-
-    api(getState).get(url).then(response => {
-      const next = getLinks(response).refs.find(link => link.rel === 'next');
-      dispatch(expandBlocksSuccess(response.data, next ? next.uri : null));
-      dispatch(fetchRelationships(response.data.map(item => item.id)));
-    }).catch(error => dispatch(expandBlocksFail(error)));
-  };
-};
-
-export function expandBlocksRequest() {
-  return {
-    type: BLOCKS_EXPAND_REQUEST
-  };
-};
-
-export function expandBlocksSuccess(accounts, next) {
-  return {
-    type: BLOCKS_EXPAND_SUCCESS,
-    accounts,
-    next
-  };
-};
-
-export function expandBlocksFail(error) {
-  return {
-    type: BLOCKS_EXPAND_FAIL,
-    error
-  };
-};
diff --git a/app/assets/javascripts/components/actions/cards.jsx b/app/assets/javascripts/components/actions/cards.jsx
deleted file mode 100644
index 805be9709..000000000
--- a/app/assets/javascripts/components/actions/cards.jsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import api from '../api';
-
-export const STATUS_CARD_FETCH_REQUEST = 'STATUS_CARD_FETCH_REQUEST';
-export const STATUS_CARD_FETCH_SUCCESS = 'STATUS_CARD_FETCH_SUCCESS';
-export const STATUS_CARD_FETCH_FAIL    = 'STATUS_CARD_FETCH_FAIL';
-
-export function fetchStatusCard(id) {
-  return (dispatch, getState) => {
-    if (getState().getIn(['cards', id], null) !== null) {
-      return;
-    }
-
-    dispatch(fetchStatusCardRequest(id));
-
-    api(getState).get(`/api/v1/statuses/${id}/card`).then(response => {
-      if (!response.data.url) {
-        return;
-      }
-
-      dispatch(fetchStatusCardSuccess(id, response.data));
-    }).catch(error => {
-      dispatch(fetchStatusCardFail(id, error));
-    });
-  };
-};
-
-export function fetchStatusCardRequest(id) {
-  return {
-    type: STATUS_CARD_FETCH_REQUEST,
-    id,
-    skipLoading: true
-  };
-};
-
-export function fetchStatusCardSuccess(id, card) {
-  return {
-    type: STATUS_CARD_FETCH_SUCCESS,
-    id,
-    card,
-    skipLoading: true
-  };
-};
-
-export function fetchStatusCardFail(id, error) {
-  return {
-    type: STATUS_CARD_FETCH_FAIL,
-    id,
-    error,
-    skipLoading: true,
-    skipAlert: true
-  };
-};
diff --git a/app/assets/javascripts/components/actions/compose.jsx b/app/assets/javascripts/components/actions/compose.jsx
deleted file mode 100644
index d7ff6ea63..000000000
--- a/app/assets/javascripts/components/actions/compose.jsx
+++ /dev/null
@@ -1,279 +0,0 @@
-import api from '../api';
-
-import { updateTimeline } from './timelines';
-
-import * as emojione from 'emojione';
-
-export const COMPOSE_CHANGE          = 'COMPOSE_CHANGE';
-export const COMPOSE_SUBMIT_REQUEST  = 'COMPOSE_SUBMIT_REQUEST';
-export const COMPOSE_SUBMIT_SUCCESS  = 'COMPOSE_SUBMIT_SUCCESS';
-export const COMPOSE_SUBMIT_FAIL     = 'COMPOSE_SUBMIT_FAIL';
-export const COMPOSE_REPLY           = 'COMPOSE_REPLY';
-export const COMPOSE_REPLY_CANCEL    = 'COMPOSE_REPLY_CANCEL';
-export const COMPOSE_MENTION         = 'COMPOSE_MENTION';
-export const COMPOSE_UPLOAD_REQUEST  = 'COMPOSE_UPLOAD_REQUEST';
-export const COMPOSE_UPLOAD_SUCCESS  = 'COMPOSE_UPLOAD_SUCCESS';
-export const COMPOSE_UPLOAD_FAIL     = 'COMPOSE_UPLOAD_FAIL';
-export const COMPOSE_UPLOAD_PROGRESS = 'COMPOSE_UPLOAD_PROGRESS';
-export const COMPOSE_UPLOAD_UNDO     = 'COMPOSE_UPLOAD_UNDO';
-
-export const COMPOSE_SUGGESTIONS_CLEAR = 'COMPOSE_SUGGESTIONS_CLEAR';
-export const COMPOSE_SUGGESTIONS_READY = 'COMPOSE_SUGGESTIONS_READY';
-export const COMPOSE_SUGGESTION_SELECT = 'COMPOSE_SUGGESTION_SELECT';
-
-export const COMPOSE_MOUNT   = 'COMPOSE_MOUNT';
-export const COMPOSE_UNMOUNT = 'COMPOSE_UNMOUNT';
-
-export const COMPOSE_SENSITIVITY_CHANGE = 'COMPOSE_SENSITIVITY_CHANGE';
-export const COMPOSE_SPOILERNESS_CHANGE = 'COMPOSE_SPOILERNESS_CHANGE';
-export const COMPOSE_SPOILER_TEXT_CHANGE = 'COMPOSE_SPOILER_TEXT_CHANGE';
-export const COMPOSE_VISIBILITY_CHANGE  = 'COMPOSE_VISIBILITY_CHANGE';
-export const COMPOSE_LISTABILITY_CHANGE = 'COMPOSE_LISTABILITY_CHANGE';
-
-export const COMPOSE_EMOJI_INSERT = 'COMPOSE_EMOJI_INSERT';
-
-export function changeCompose(text) {
-  return {
-    type: COMPOSE_CHANGE,
-    text: text
-  };
-};
-
-export function replyCompose(status, router) {
-  return (dispatch, getState) => {
-    dispatch({
-      type: COMPOSE_REPLY,
-      status: status
-    });
-
-    if (!getState().getIn(['compose', 'mounted'])) {
-      router.push('/statuses/new');
-    }
-  };
-};
-
-export function cancelReplyCompose() {
-  return {
-    type: COMPOSE_REPLY_CANCEL
-  };
-};
-
-export function mentionCompose(account, router) {
-  return (dispatch, getState) => {
-    dispatch({
-      type: COMPOSE_MENTION,
-      account: account
-    });
-
-    if (!getState().getIn(['compose', 'mounted'])) {
-      router.push('/statuses/new');
-    }
-  };
-};
-
-export function submitCompose() {
-  return function (dispatch, getState) {
-    const status = emojione.shortnameToUnicode(getState().getIn(['compose', 'text'], ''));
-    if (!status || !status.length) {
-      return;
-    }
-    dispatch(submitComposeRequest());
-    api(getState).post('/api/v1/statuses', {
-      status,
-      in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null),
-      media_ids: getState().getIn(['compose', 'media_attachments']).map(item => item.get('id')),
-      sensitive: getState().getIn(['compose', 'sensitive']),
-      spoiler_text: getState().getIn(['compose', 'spoiler_text'], ''),
-      visibility: getState().getIn(['compose', 'privacy'])
-    }, {
-      headers: {
-        'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey'])
-      }
-    }).then(function (response) {
-      dispatch(submitComposeSuccess({ ...response.data }));
-
-      // To make the app more responsive, immediately get the status into the columns
-      dispatch(updateTimeline('home', { ...response.data }));
-
-      if (response.data.in_reply_to_id === null && response.data.visibility === 'public') {
-        if (getState().getIn(['timelines', 'community', 'loaded'])) {
-          dispatch(updateTimeline('community', { ...response.data }));
-        }
-
-        if (getState().getIn(['timelines', 'public', 'loaded'])) {
-          dispatch(updateTimeline('public', { ...response.data }));
-        }
-      }
-    }).catch(function (error) {
-      dispatch(submitComposeFail(error));
-    });
-  };
-};
-
-export function submitComposeRequest() {
-  return {
-    type: COMPOSE_SUBMIT_REQUEST
-  };
-};
-
-export function submitComposeSuccess(status) {
-  return {
-    type: COMPOSE_SUBMIT_SUCCESS,
-    status: status
-  };
-};
-
-export function submitComposeFail(error) {
-  return {
-    type: COMPOSE_SUBMIT_FAIL,
-    error: error
-  };
-};
-
-export function uploadCompose(files) {
-  return function (dispatch, getState) {
-    if (getState().getIn(['compose', 'media_attachments']).size > 3) {
-      return;
-    }
-
-    dispatch(uploadComposeRequest());
-
-    let data = new FormData();
-    data.append('file', files[0]);
-
-    api(getState).post('/api/v1/media', data, {
-      onUploadProgress: function (e) {
-        dispatch(uploadComposeProgress(e.loaded, e.total));
-      }
-    }).then(function (response) {
-      dispatch(uploadComposeSuccess(response.data));
-    }).catch(function (error) {
-      dispatch(uploadComposeFail(error));
-    });
-  };
-};
-
-export function uploadComposeRequest() {
-  return {
-    type: COMPOSE_UPLOAD_REQUEST,
-    skipLoading: true
-  };
-};
-
-export function uploadComposeProgress(loaded, total) {
-  return {
-    type: COMPOSE_UPLOAD_PROGRESS,
-    loaded: loaded,
-    total: total
-  };
-};
-
-export function uploadComposeSuccess(media) {
-  return {
-    type: COMPOSE_UPLOAD_SUCCESS,
-    media: media,
-    skipLoading: true
-  };
-};
-
-export function uploadComposeFail(error) {
-  return {
-    type: COMPOSE_UPLOAD_FAIL,
-    error: error,
-    skipLoading: true
-  };
-};
-
-export function undoUploadCompose(media_id) {
-  return {
-    type: COMPOSE_UPLOAD_UNDO,
-    media_id: media_id
-  };
-};
-
-export function clearComposeSuggestions() {
-  return {
-    type: COMPOSE_SUGGESTIONS_CLEAR
-  };
-};
-
-export function fetchComposeSuggestions(token) {
-  return (dispatch, getState) => {
-    api(getState).get('/api/v1/accounts/search', {
-      params: {
-        q: token,
-        resolve: false,
-        limit: 4
-      }
-    }).then(response => {
-      dispatch(readyComposeSuggestions(token, response.data));
-    });
-  };
-};
-
-export function readyComposeSuggestions(token, accounts) {
-  return {
-    type: COMPOSE_SUGGESTIONS_READY,
-    token,
-    accounts
-  };
-};
-
-export function selectComposeSuggestion(position, token, accountId) {
-  return (dispatch, getState) => {
-    const completion = getState().getIn(['accounts', accountId, 'acct']);
-
-    dispatch({
-      type: COMPOSE_SUGGESTION_SELECT,
-      position,
-      token,
-      completion
-    });
-  };
-};
-
-export function mountCompose() {
-  return {
-    type: COMPOSE_MOUNT
-  };
-};
-
-export function unmountCompose() {
-  return {
-    type: COMPOSE_UNMOUNT
-  };
-};
-
-export function changeComposeSensitivity() {
-  return {
-    type: COMPOSE_SENSITIVITY_CHANGE,
-  };
-};
-
-export function changeComposeSpoilerness() {
-  return {
-    type: COMPOSE_SPOILERNESS_CHANGE
-  };
-};
-
-export function changeComposeSpoilerText(text) {
-  return {
-    type: COMPOSE_SPOILER_TEXT_CHANGE,
-    text
-  };
-};
-
-export function changeComposeVisibility(value) {
-  return {
-    type: COMPOSE_VISIBILITY_CHANGE,
-    value
-  };
-};
-
-export function insertEmojiCompose(position, emoji) {
-  return {
-    type: COMPOSE_EMOJI_INSERT,
-    position,
-    emoji
-  };
-};
diff --git a/app/assets/javascripts/components/actions/favourites.jsx b/app/assets/javascripts/components/actions/favourites.jsx
deleted file mode 100644
index a25c1ae1c..000000000
--- a/app/assets/javascripts/components/actions/favourites.jsx
+++ /dev/null
@@ -1,83 +0,0 @@
-import api, { getLinks } from '../api'
-
-export const FAVOURITED_STATUSES_FETCH_REQUEST = 'FAVOURITED_STATUSES_FETCH_REQUEST';
-export const FAVOURITED_STATUSES_FETCH_SUCCESS = 'FAVOURITED_STATUSES_FETCH_SUCCESS';
-export const FAVOURITED_STATUSES_FETCH_FAIL    = 'FAVOURITED_STATUSES_FETCH_FAIL';
-
-export const FAVOURITED_STATUSES_EXPAND_REQUEST = 'FAVOURITED_STATUSES_EXPAND_REQUEST';
-export const FAVOURITED_STATUSES_EXPAND_SUCCESS = 'FAVOURITED_STATUSES_EXPAND_SUCCESS';
-export const FAVOURITED_STATUSES_EXPAND_FAIL    = 'FAVOURITED_STATUSES_EXPAND_FAIL';
-
-export function fetchFavouritedStatuses() {
-  return (dispatch, getState) => {
-    dispatch(fetchFavouritedStatusesRequest());
-
-    api(getState).get('/api/v1/favourites').then(response => {
-      const next = getLinks(response).refs.find(link => link.rel === 'next');
-      dispatch(fetchFavouritedStatusesSuccess(response.data, next ? next.uri : null));
-    }).catch(error => {
-      dispatch(fetchFavouritedStatusesFail(error));
-    });
-  };
-};
-
-export function fetchFavouritedStatusesRequest() {
-  return {
-    type: FAVOURITED_STATUSES_FETCH_REQUEST
-  };
-};
-
-export function fetchFavouritedStatusesSuccess(statuses, next) {
-  return {
-    type: FAVOURITED_STATUSES_FETCH_SUCCESS,
-    statuses,
-    next
-  };
-};
-
-export function fetchFavouritedStatusesFail(error) {
-  return {
-    type: FAVOURITED_STATUSES_FETCH_FAIL,
-    error
-  };
-};
-
-export function expandFavouritedStatuses() {
-  return (dispatch, getState) => {
-    const url = getState().getIn(['status_lists', 'favourites', 'next'], null);
-
-    if (url === null) {
-      return;
-    }
-
-    dispatch(expandFavouritedStatusesRequest());
-
-    api(getState).get(url).then(response => {
-      const next = getLinks(response).refs.find(link => link.rel === 'next');
-      dispatch(expandFavouritedStatusesSuccess(response.data, next ? next.uri : null));
-    }).catch(error => {
-      dispatch(expandFavouritedStatusesFail(error));
-    });
-  };
-};
-
-export function expandFavouritedStatusesRequest() {
-  return {
-    type: FAVOURITED_STATUSES_EXPAND_REQUEST
-  };
-};
-
-export function expandFavouritedStatusesSuccess(statuses, next) {
-  return {
-    type: FAVOURITED_STATUSES_EXPAND_SUCCESS,
-    statuses,
-    next
-  };
-};
-
-export function expandFavouritedStatusesFail(error) {
-  return {
-    type: FAVOURITED_STATUSES_EXPAND_FAIL,
-    error
-  };
-};
diff --git a/app/assets/javascripts/components/actions/interactions.jsx b/app/assets/javascripts/components/actions/interactions.jsx
deleted file mode 100644
index 45f4508f6..000000000
--- a/app/assets/javascripts/components/actions/interactions.jsx
+++ /dev/null
@@ -1,235 +0,0 @@
-import api from '../api'
-
-export const REBLOG_REQUEST = 'REBLOG_REQUEST';
-export const REBLOG_SUCCESS = 'REBLOG_SUCCESS';
-export const REBLOG_FAIL    = 'REBLOG_FAIL';
-
-export const FAVOURITE_REQUEST = 'FAVOURITE_REQUEST';
-export const FAVOURITE_SUCCESS = 'FAVOURITE_SUCCESS';
-export const FAVOURITE_FAIL    = 'FAVOURITE_FAIL';
-
-export const UNREBLOG_REQUEST = 'UNREBLOG_REQUEST';
-export const UNREBLOG_SUCCESS = 'UNREBLOG_SUCCESS';
-export const UNREBLOG_FAIL    = 'UNREBLOG_FAIL';
-
-export const UNFAVOURITE_REQUEST = 'UNFAVOURITE_REQUEST';
-export const UNFAVOURITE_SUCCESS = 'UNFAVOURITE_SUCCESS';
-export const UNFAVOURITE_FAIL    = 'UNFAVOURITE_FAIL';
-
-export const REBLOGS_FETCH_REQUEST = 'REBLOGS_FETCH_REQUEST';
-export const REBLOGS_FETCH_SUCCESS = 'REBLOGS_FETCH_SUCCESS';
-export const REBLOGS_FETCH_FAIL    = 'REBLOGS_FETCH_FAIL';
-
-export const FAVOURITES_FETCH_REQUEST = 'FAVOURITES_FETCH_REQUEST';
-export const FAVOURITES_FETCH_SUCCESS = 'FAVOURITES_FETCH_SUCCESS';
-export const FAVOURITES_FETCH_FAIL    = 'FAVOURITES_FETCH_FAIL';
-
-export function reblog(status) {
-  return function (dispatch, getState) {
-    dispatch(reblogRequest(status));
-
-    api(getState).post(`/api/v1/statuses/${status.get('id')}/reblog`).then(function (response) {
-      // The reblog API method returns a new status wrapped around the original. In this case we are only
-      // interested in how the original is modified, hence passing it skipping the wrapper
-      dispatch(reblogSuccess(status, response.data.reblog));
-    }).catch(function (error) {
-      dispatch(reblogFail(status, error));
-    });
-  };
-};
-
-export function unreblog(status) {
-  return (dispatch, getState) => {
-    dispatch(unreblogRequest(status));
-
-    api(getState).post(`/api/v1/statuses/${status.get('id')}/unreblog`).then(response => {
-      dispatch(unreblogSuccess(status, response.data));
-    }).catch(error => {
-      dispatch(unreblogFail(status, error));
-    });
-  };
-};
-
-export function reblogRequest(status) {
-  return {
-    type: REBLOG_REQUEST,
-    status: status
-  };
-};
-
-export function reblogSuccess(status, response) {
-  return {
-    type: REBLOG_SUCCESS,
-    status: status,
-    response: response
-  };
-};
-
-export function reblogFail(status, error) {
-  return {
-    type: REBLOG_FAIL,
-    status: status,
-    error: error
-  };
-};
-
-export function unreblogRequest(status) {
-  return {
-    type: UNREBLOG_REQUEST,
-    status: status
-  };
-};
-
-export function unreblogSuccess(status, response) {
-  return {
-    type: UNREBLOG_SUCCESS,
-    status: status,
-    response: response
-  };
-};
-
-export function unreblogFail(status, error) {
-  return {
-    type: UNREBLOG_FAIL,
-    status: status,
-    error: error
-  };
-};
-
-export function favourite(status) {
-  return function (dispatch, getState) {
-    dispatch(favouriteRequest(status));
-
-    api(getState).post(`/api/v1/statuses/${status.get('id')}/favourite`).then(function (response) {
-      dispatch(favouriteSuccess(status, response.data));
-    }).catch(function (error) {
-      dispatch(favouriteFail(status, error));
-    });
-  };
-};
-
-export function unfavourite(status) {
-  return (dispatch, getState) => {
-    dispatch(unfavouriteRequest(status));
-
-    api(getState).post(`/api/v1/statuses/${status.get('id')}/unfavourite`).then(response => {
-      dispatch(unfavouriteSuccess(status, response.data));
-    }).catch(error => {
-      dispatch(unfavouriteFail(status, error));
-    });
-  };
-};
-
-export function favouriteRequest(status) {
-  return {
-    type: FAVOURITE_REQUEST,
-    status: status
-  };
-};
-
-export function favouriteSuccess(status, response) {
-  return {
-    type: FAVOURITE_SUCCESS,
-    status: status,
-    response: response
-  };
-};
-
-export function favouriteFail(status, error) {
-  return {
-    type: FAVOURITE_FAIL,
-    status: status,
-    error: error
-  };
-};
-
-export function unfavouriteRequest(status) {
-  return {
-    type: UNFAVOURITE_REQUEST,
-    status: status
-  };
-};
-
-export function unfavouriteSuccess(status, response) {
-  return {
-    type: UNFAVOURITE_SUCCESS,
-    status: status,
-    response: response
-  };
-};
-
-export function unfavouriteFail(status, error) {
-  return {
-    type: UNFAVOURITE_FAIL,
-    status: status,
-    error: error
-  };
-};
-
-export function fetchReblogs(id) {
-  return (dispatch, getState) => {
-    dispatch(fetchReblogsRequest(id));
-
-    api(getState).get(`/api/v1/statuses/${id}/reblogged_by`).then(response => {
-      dispatch(fetchReblogsSuccess(id, response.data));
-    }).catch(error => {
-      dispatch(fetchReblogsFail(id, error));
-    });
-  };
-};
-
-export function fetchReblogsRequest(id) {
-  return {
-    type: REBLOGS_FETCH_REQUEST,
-    id
-  };
-};
-
-export function fetchReblogsSuccess(id, accounts) {
-  return {
-    type: REBLOGS_FETCH_SUCCESS,
-    id,
-    accounts
-  };
-};
-
-export function fetchReblogsFail(id, error) {
-  return {
-    type: REBLOGS_FETCH_FAIL,
-    error
-  };
-};
-
-export function fetchFavourites(id) {
-  return (dispatch, getState) => {
-    dispatch(fetchFavouritesRequest(id));
-
-    api(getState).get(`/api/v1/statuses/${id}/favourited_by`).then(response => {
-      dispatch(fetchFavouritesSuccess(id, response.data));
-    }).catch(error => {
-      dispatch(fetchFavouritesFail(id, error));
-    });
-  };
-};
-
-export function fetchFavouritesRequest(id) {
-  return {
-    type: FAVOURITES_FETCH_REQUEST,
-    id
-  };
-};
-
-export function fetchFavouritesSuccess(id, accounts) {
-  return {
-    type: FAVOURITES_FETCH_SUCCESS,
-    id,
-    accounts
-  };
-};
-
-export function fetchFavouritesFail(id, error) {
-  return {
-    type: FAVOURITES_FETCH_FAIL,
-    error
-  };
-};
diff --git a/app/assets/javascripts/components/actions/modal.jsx b/app/assets/javascripts/components/actions/modal.jsx
deleted file mode 100644
index 615cd6bfe..000000000
--- a/app/assets/javascripts/components/actions/modal.jsx
+++ /dev/null
@@ -1,16 +0,0 @@
-export const MODAL_OPEN  = 'MODAL_OPEN';
-export const MODAL_CLOSE = 'MODAL_CLOSE';
-
-export function openModal(type, props) {
-  return {
-    type: MODAL_OPEN,
-    modalType: type,
-    modalProps: props
-  };
-};
-
-export function closeModal() {
-  return {
-    type: MODAL_CLOSE
-  };
-};
diff --git a/app/assets/javascripts/components/actions/mutes.jsx b/app/assets/javascripts/components/actions/mutes.jsx
deleted file mode 100644
index 824821594..000000000
--- a/app/assets/javascripts/components/actions/mutes.jsx
+++ /dev/null
@@ -1,82 +0,0 @@
-import api, { getLinks } from '../api'
-import { fetchRelationships } from './accounts';
-
-export const MUTES_FETCH_REQUEST = 'MUTES_FETCH_REQUEST';
-export const MUTES_FETCH_SUCCESS = 'MUTES_FETCH_SUCCESS';
-export const MUTES_FETCH_FAIL    = 'MUTES_FETCH_FAIL';
-
-export const MUTES_EXPAND_REQUEST = 'MUTES_EXPAND_REQUEST';
-export const MUTES_EXPAND_SUCCESS = 'MUTES_EXPAND_SUCCESS';
-export const MUTES_EXPAND_FAIL    = 'MUTES_EXPAND_FAIL';
-
-export function fetchMutes() {
-  return (dispatch, getState) => {
-    dispatch(fetchMutesRequest());
-
-    api(getState).get('/api/v1/mutes').then(response => {
-      const next = getLinks(response).refs.find(link => link.rel === 'next');
-      dispatch(fetchMutesSuccess(response.data, next ? next.uri : null));
-      dispatch(fetchRelationships(response.data.map(item => item.id)));
-    }).catch(error => dispatch(fetchMutesFail(error)));
-  };
-};
-
-export function fetchMutesRequest() {
-  return {
-    type: MUTES_FETCH_REQUEST
-  };
-};
-
-export function fetchMutesSuccess(accounts, next) {
-  return {
-    type: MUTES_FETCH_SUCCESS,
-    accounts,
-    next
-  };
-};
-
-export function fetchMutesFail(error) {
-  return {
-    type: MUTES_FETCH_FAIL,
-    error
-  };
-};
-
-export function expandMutes() {
-  return (dispatch, getState) => {
-    const url = getState().getIn(['user_lists', 'mutes', 'next']);
-
-    if (url === null) {
-      return;
-    }
-
-    dispatch(expandMutesRequest());
-
-    api(getState).get(url).then(response => {
-      const next = getLinks(response).refs.find(link => link.rel === 'next');
-      dispatch(expandMutesSuccess(response.data, next ? next.uri : null));
-      dispatch(fetchRelationships(response.data.map(item => item.id)));
-    }).catch(error => dispatch(expandMutesFail(error)));
-  };
-};
-
-export function expandMutesRequest() {
-  return {
-    type: MUTES_EXPAND_REQUEST
-  };
-};
-
-export function expandMutesSuccess(accounts, next) {
-  return {
-    type: MUTES_EXPAND_SUCCESS,
-    accounts,
-    next
-  };
-};
-
-export function expandMutesFail(error) {
-  return {
-    type: MUTES_EXPAND_FAIL,
-    error
-  };
-};
diff --git a/app/assets/javascripts/components/actions/notifications.jsx b/app/assets/javascripts/components/actions/notifications.jsx
deleted file mode 100644
index b09ca0854..000000000
--- a/app/assets/javascripts/components/actions/notifications.jsx
+++ /dev/null
@@ -1,165 +0,0 @@
-import api, { getLinks } from '../api'
-import Immutable from 'immutable';
-import IntlMessageFormat from 'intl-messageformat';
-
-import { fetchRelationships } from './accounts';
-
-export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE';
-
-export const NOTIFICATIONS_REFRESH_REQUEST = 'NOTIFICATIONS_REFRESH_REQUEST';
-export const NOTIFICATIONS_REFRESH_SUCCESS = 'NOTIFICATIONS_REFRESH_SUCCESS';
-export const NOTIFICATIONS_REFRESH_FAIL    = 'NOTIFICATIONS_REFRESH_FAIL';
-
-export const NOTIFICATIONS_EXPAND_REQUEST = 'NOTIFICATIONS_EXPAND_REQUEST';
-export const NOTIFICATIONS_EXPAND_SUCCESS = 'NOTIFICATIONS_EXPAND_SUCCESS';
-export const NOTIFICATIONS_EXPAND_FAIL    = 'NOTIFICATIONS_EXPAND_FAIL';
-
-export const NOTIFICATIONS_CLEAR      = 'NOTIFICATIONS_CLEAR';
-export const NOTIFICATIONS_SCROLL_TOP = 'NOTIFICATIONS_SCROLL_TOP';
-
-const fetchRelatedRelationships = (dispatch, notifications) => {
-  const accountIds = notifications.filter(item => item.type === 'follow').map(item => item.account.id);
-
-  if (accountIds > 0) {
-    dispatch(fetchRelationships(accountIds));
-  }
-};
-
-export function updateNotifications(notification, intlMessages, intlLocale) {
-  return (dispatch, getState) => {
-    const showAlert = getState().getIn(['settings', 'notifications', 'alerts', notification.type], true);
-    const playSound = getState().getIn(['settings', 'notifications', 'sounds', notification.type], true);
-
-    dispatch({
-      type: NOTIFICATIONS_UPDATE,
-      notification,
-      account: notification.account,
-      status: notification.status,
-      meta: playSound ? { sound: 'boop' } : undefined
-    });
-
-    fetchRelatedRelationships(dispatch, [notification]);
-
-    // Desktop notifications
-    if (typeof window.Notification !== 'undefined' && showAlert) {
-      const title = new IntlMessageFormat(intlMessages[`notification.${notification.type}`], intlLocale).format({ name: notification.account.display_name.length > 0 ? notification.account.display_name : notification.account.username });
-      const body  = (notification.status && notification.status.spoiler_text.length > 0) ? notification.status.spoiler_text : $('<p>').html(notification.status ? notification.status.content : '').text();
-
-      new Notification(title, { body, icon: notification.account.avatar, tag: notification.id });
-    }
-  };
-};
-
-const excludeTypesFromSettings = state => state.getIn(['settings', 'notifications', 'shows']).filter(enabled => !enabled).keySeq().toJS();
-
-export function refreshNotifications() {
-  return (dispatch, getState) => {
-    dispatch(refreshNotificationsRequest());
-
-    const params = {};
-    const ids    = getState().getIn(['notifications', 'items']);
-
-    if (ids.size > 0) {
-      params.since_id = ids.first().get('id');
-    }
-
-    params.exclude_types = excludeTypesFromSettings(getState());
-
-    api(getState).get('/api/v1/notifications', { params }).then(response => {
-      const next = getLinks(response).refs.find(link => link.rel === 'next');
-
-      dispatch(refreshNotificationsSuccess(response.data, next ? next.uri : null));
-      fetchRelatedRelationships(dispatch, response.data);
-    }).catch(error => {
-      dispatch(refreshNotificationsFail(error));
-    });
-  };
-};
-
-export function refreshNotificationsRequest() {
-  return {
-    type: NOTIFICATIONS_REFRESH_REQUEST
-  };
-};
-
-export function refreshNotificationsSuccess(notifications, next) {
-  return {
-    type: NOTIFICATIONS_REFRESH_SUCCESS,
-    notifications,
-    accounts: notifications.map(item => item.account),
-    statuses: notifications.map(item => item.status).filter(status => !!status),
-    next
-  };
-};
-
-export function refreshNotificationsFail(error) {
-  return {
-    type: NOTIFICATIONS_REFRESH_FAIL,
-    error
-  };
-};
-
-export function expandNotifications() {
-  return (dispatch, getState) => {
-    const url = getState().getIn(['notifications', 'next'], null);
-
-    if (url === null || getState().getIn(['notifications', 'isLoading'])) {
-      return;
-    }
-
-    dispatch(expandNotificationsRequest());
-
-    const params = {};
-
-    params.exclude_types = excludeTypesFromSettings(getState());
-
-    api(getState).get(url, params).then(response => {
-      const next = getLinks(response).refs.find(link => link.rel === 'next');
-
-      dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null));
-      fetchRelatedRelationships(dispatch, response.data);
-    }).catch(error => {
-      dispatch(expandNotificationsFail(error));
-    });
-  };
-};
-
-export function expandNotificationsRequest() {
-  return {
-    type: NOTIFICATIONS_EXPAND_REQUEST
-  };
-};
-
-export function expandNotificationsSuccess(notifications, next) {
-  return {
-    type: NOTIFICATIONS_EXPAND_SUCCESS,
-    notifications,
-    accounts: notifications.map(item => item.account),
-    statuses: notifications.map(item => item.status).filter(status => !!status),
-    next
-  };
-};
-
-export function expandNotificationsFail(error) {
-  return {
-    type: NOTIFICATIONS_EXPAND_FAIL,
-    error
-  };
-};
-
-export function clearNotifications() {
-  return (dispatch, getState) => {
-    dispatch({
-      type: NOTIFICATIONS_CLEAR
-    });
-
-    api(getState).post('/api/v1/notifications/clear');
-  };
-};
-
-export function scrollTopNotifications(top) {
-  return {
-    type: NOTIFICATIONS_SCROLL_TOP,
-    top
-  };
-};
diff --git a/app/assets/javascripts/components/actions/onboarding.jsx b/app/assets/javascripts/components/actions/onboarding.jsx
deleted file mode 100644
index a161c50ef..000000000
--- a/app/assets/javascripts/components/actions/onboarding.jsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import { openModal } from './modal';
-import { changeSetting, saveSettings } from './settings';
-
-export function showOnboardingOnce() {
-  return (dispatch, getState) => {
-    const alreadySeen = getState().getIn(['settings', 'onboarded']);
-
-    if (!alreadySeen) {
-      dispatch(openModal('ONBOARDING'));
-      dispatch(changeSetting(['onboarded'], true));
-      dispatch(saveSettings());
-    }
-  };
-};
diff --git a/app/assets/javascripts/components/actions/reports.jsx b/app/assets/javascripts/components/actions/reports.jsx
deleted file mode 100644
index 094670d62..000000000
--- a/app/assets/javascripts/components/actions/reports.jsx
+++ /dev/null
@@ -1,72 +0,0 @@
-import api from '../api';
-
-export const REPORT_INIT   = 'REPORT_INIT';
-export const REPORT_CANCEL = 'REPORT_CANCEL';
-
-export const REPORT_SUBMIT_REQUEST = 'REPORT_SUBMIT_REQUEST';
-export const REPORT_SUBMIT_SUCCESS = 'REPORT_SUBMIT_SUCCESS';
-export const REPORT_SUBMIT_FAIL    = 'REPORT_SUBMIT_FAIL';
-
-export const REPORT_STATUS_TOGGLE  = 'REPORT_STATUS_TOGGLE';
-export const REPORT_COMMENT_CHANGE = 'REPORT_COMMENT_CHANGE';
-
-export function initReport(account, status) {
-  return {
-    type: REPORT_INIT,
-    account,
-    status
-  };
-};
-
-export function cancelReport() {
-  return {
-    type: REPORT_CANCEL
-  };
-};
-
-export function toggleStatusReport(statusId, checked) {
-  return {
-    type: REPORT_STATUS_TOGGLE,
-    statusId,
-    checked,
-  };
-};
-
-export function submitReport() {
-  return (dispatch, getState) => {
-    dispatch(submitReportRequest());
-
-    api(getState).post('/api/v1/reports', {
-      account_id: getState().getIn(['reports', 'new', 'account_id']),
-      status_ids: getState().getIn(['reports', 'new', 'status_ids']),
-      comment: getState().getIn(['reports', 'new', 'comment'])
-    }).then(response => dispatch(submitReportSuccess(response.data))).catch(error => dispatch(submitReportFail(error)));
-  };
-};
-
-export function submitReportRequest() {
-  return {
-    type: REPORT_SUBMIT_REQUEST
-  };
-};
-
-export function submitReportSuccess(report) {
-  return {
-    type: REPORT_SUBMIT_SUCCESS,
-    report
-  };
-};
-
-export function submitReportFail(error) {
-  return {
-    type: REPORT_SUBMIT_FAIL,
-    error
-  };
-};
-
-export function changeReportComment(comment) {
-  return {
-    type: REPORT_COMMENT_CHANGE,
-    comment
-  };
-};
diff --git a/app/assets/javascripts/components/actions/search.jsx b/app/assets/javascripts/components/actions/search.jsx
deleted file mode 100644
index df3ae0db1..000000000
--- a/app/assets/javascripts/components/actions/search.jsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import api from '../api'
-
-export const SEARCH_CHANGE = 'SEARCH_CHANGE';
-export const SEARCH_CLEAR  = 'SEARCH_CLEAR';
-export const SEARCH_SHOW   = 'SEARCH_SHOW';
-
-export const SEARCH_FETCH_REQUEST = 'SEARCH_FETCH_REQUEST';
-export const SEARCH_FETCH_SUCCESS = 'SEARCH_FETCH_SUCCESS';
-export const SEARCH_FETCH_FAIL    = 'SEARCH_FETCH_FAIL';
-
-export function changeSearch(value) {
-  return {
-    type: SEARCH_CHANGE,
-    value
-  };
-};
-
-export function clearSearch() {
-  return {
-    type: SEARCH_CLEAR
-  };
-};
-
-export function submitSearch() {
-  return (dispatch, getState) => {
-    const value = getState().getIn(['search', 'value']);
-
-    if (value.length === 0) {
-      return;
-    }
-
-    dispatch(fetchSearchRequest());
-
-    api(getState).get('/api/v1/search', {
-      params: {
-        q: value,
-        resolve: true
-      }
-    }).then(response => {
-      dispatch(fetchSearchSuccess(response.data));
-    }).catch(error => {
-      dispatch(fetchSearchFail(error));
-    });
-  };
-};
-
-export function fetchSearchRequest() {
-  return {
-    type: SEARCH_FETCH_REQUEST
-  };
-};
-
-export function fetchSearchSuccess(results) {
-  return {
-    type: SEARCH_FETCH_SUCCESS,
-    results,
-    accounts: results.accounts,
-    statuses: results.statuses
-  };
-};
-
-export function fetchSearchFail(error) {
-  return {
-    type: SEARCH_FETCH_FAIL,
-    error
-  };
-};
-
-export function showSearch() {
-  return {
-    type: SEARCH_SHOW
-  };
-};
diff --git a/app/assets/javascripts/components/actions/settings.jsx b/app/assets/javascripts/components/actions/settings.jsx
deleted file mode 100644
index c754b30ca..000000000
--- a/app/assets/javascripts/components/actions/settings.jsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import axios from 'axios';
-
-export const SETTING_CHANGE = 'SETTING_CHANGE';
-
-export function changeSetting(key, value) {
-  return {
-    type: SETTING_CHANGE,
-    key,
-    value
-  };
-};
-
-export function saveSettings() {
-  return (_, getState) => {
-    axios.put('/api/web/settings', {
-      data: getState().get('settings').toJS()
-    });
-  };
-};
diff --git a/app/assets/javascripts/components/actions/statuses.jsx b/app/assets/javascripts/components/actions/statuses.jsx
deleted file mode 100644
index 19df2c36c..000000000
--- a/app/assets/javascripts/components/actions/statuses.jsx
+++ /dev/null
@@ -1,141 +0,0 @@
-import api from '../api';
-
-import { deleteFromTimelines } from './timelines';
-import { fetchStatusCard } from './cards';
-
-export const STATUS_FETCH_REQUEST = 'STATUS_FETCH_REQUEST';
-export const STATUS_FETCH_SUCCESS = 'STATUS_FETCH_SUCCESS';
-export const STATUS_FETCH_FAIL    = 'STATUS_FETCH_FAIL';
-
-export const STATUS_DELETE_REQUEST = 'STATUS_DELETE_REQUEST';
-export const STATUS_DELETE_SUCCESS = 'STATUS_DELETE_SUCCESS';
-export const STATUS_DELETE_FAIL    = 'STATUS_DELETE_FAIL';
-
-export const CONTEXT_FETCH_REQUEST = 'CONTEXT_FETCH_REQUEST';
-export const CONTEXT_FETCH_SUCCESS = 'CONTEXT_FETCH_SUCCESS';
-export const CONTEXT_FETCH_FAIL    = 'CONTEXT_FETCH_FAIL';
-
-export function fetchStatusRequest(id, skipLoading) {
-  return {
-    type: STATUS_FETCH_REQUEST,
-    id,
-    skipLoading
-  };
-};
-
-export function fetchStatus(id) {
-  return (dispatch, getState) => {
-    const skipLoading = getState().getIn(['statuses', id], null) !== null;
-
-    dispatch(fetchContext(id));
-    dispatch(fetchStatusCard(id));
-
-    if (skipLoading) {
-      return;
-    }
-
-    dispatch(fetchStatusRequest(id, skipLoading));
-
-    api(getState).get(`/api/v1/statuses/${id}`).then(response => {
-      dispatch(fetchStatusSuccess(response.data, skipLoading));
-    }).catch(error => {
-      dispatch(fetchStatusFail(id, error, skipLoading));
-    });
-  };
-};
-
-export function fetchStatusSuccess(status, skipLoading) {
-  return {
-    type: STATUS_FETCH_SUCCESS,
-    status,
-    skipLoading
-  };
-};
-
-export function fetchStatusFail(id, error, skipLoading) {
-  return {
-    type: STATUS_FETCH_FAIL,
-    id,
-    error,
-    skipLoading,
-    skipAlert: true
-  };
-};
-
-export function deleteStatus(id) {
-  return (dispatch, getState) => {
-    dispatch(deleteStatusRequest(id));
-
-    api(getState).delete(`/api/v1/statuses/${id}`).then(response => {
-      dispatch(deleteStatusSuccess(id));
-      dispatch(deleteFromTimelines(id));
-    }).catch(error => {
-      dispatch(deleteStatusFail(id, error));
-    });
-  };
-};
-
-export function deleteStatusRequest(id) {
-  return {
-    type: STATUS_DELETE_REQUEST,
-    id: id
-  };
-};
-
-export function deleteStatusSuccess(id) {
-  return {
-    type: STATUS_DELETE_SUCCESS,
-    id: id
-  };
-};
-
-export function deleteStatusFail(id, error) {
-  return {
-    type: STATUS_DELETE_FAIL,
-    id: id,
-    error: error
-  };
-};
-
-export function fetchContext(id) {
-  return (dispatch, getState) => {
-    dispatch(fetchContextRequest(id));
-
-    api(getState).get(`/api/v1/statuses/${id}/context`).then(response => {
-      dispatch(fetchContextSuccess(id, response.data.ancestors, response.data.descendants));
-
-    }).catch(error => {
-      if (error.response.status === 404) {
-        dispatch(deleteFromTimelines(id));
-      }
-
-      dispatch(fetchContextFail(id, error));
-    });
-  };
-};
-
-export function fetchContextRequest(id) {
-  return {
-    type: CONTEXT_FETCH_REQUEST,
-    id
-  };
-};
-
-export function fetchContextSuccess(id, ancestors, descendants) {
-  return {
-    type: CONTEXT_FETCH_SUCCESS,
-    id,
-    ancestors,
-    descendants,
-    statuses: ancestors.concat(descendants)
-  };
-};
-
-export function fetchContextFail(id, error) {
-  return {
-    type: CONTEXT_FETCH_FAIL,
-    id,
-    error,
-    skipAlert: true
-  };
-};
diff --git a/app/assets/javascripts/components/actions/store.jsx b/app/assets/javascripts/components/actions/store.jsx
deleted file mode 100644
index 3bba99549..000000000
--- a/app/assets/javascripts/components/actions/store.jsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import Immutable from 'immutable';
-
-export const STORE_HYDRATE = 'STORE_HYDRATE';
-
-const convertState = rawState =>
-  Immutable.fromJS(rawState, (k, v) =>
-    Immutable.Iterable.isIndexed(v) ? v.toList() : v.toMap().mapKeys(x =>
-      Number.isNaN(x * 1) ? x : x * 1));
-
-export function hydrateStore(rawState) {
-  const state = convertState(rawState);
-
-  return {
-    type: STORE_HYDRATE,
-    state
-  };
-};
diff --git a/app/assets/javascripts/components/actions/timelines.jsx b/app/assets/javascripts/components/actions/timelines.jsx
deleted file mode 100644
index 6cd1f04b3..000000000
--- a/app/assets/javascripts/components/actions/timelines.jsx
+++ /dev/null
@@ -1,186 +0,0 @@
-import api, { getLinks } from '../api'
-import Immutable from 'immutable';
-
-export const TIMELINE_UPDATE  = 'TIMELINE_UPDATE';
-export const TIMELINE_DELETE  = 'TIMELINE_DELETE';
-
-export const TIMELINE_REFRESH_REQUEST = 'TIMELINE_REFRESH_REQUEST';
-export const TIMELINE_REFRESH_SUCCESS = 'TIMELINE_REFRESH_SUCCESS';
-export const TIMELINE_REFRESH_FAIL    = 'TIMELINE_REFRESH_FAIL';
-
-export const TIMELINE_EXPAND_REQUEST = 'TIMELINE_EXPAND_REQUEST';
-export const TIMELINE_EXPAND_SUCCESS = 'TIMELINE_EXPAND_SUCCESS';
-export const TIMELINE_EXPAND_FAIL    = 'TIMELINE_EXPAND_FAIL';
-
-export const TIMELINE_SCROLL_TOP = 'TIMELINE_SCROLL_TOP';
-
-export const TIMELINE_CONNECT    = 'TIMELINE_CONNECT';
-export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT';
-
-export function refreshTimelineSuccess(timeline, statuses, skipLoading, next) {
-  return {
-    type: TIMELINE_REFRESH_SUCCESS,
-    timeline,
-    statuses,
-    skipLoading,
-    next
-  };
-};
-
-export function updateTimeline(timeline, status) {
-  return (dispatch, getState) => {
-    const references = status.reblog ? getState().get('statuses').filter((item, itemId) => (itemId === status.reblog.id || item.get('reblog') === status.reblog.id)).map((_, itemId) => itemId) : [];
-
-    dispatch({
-      type: TIMELINE_UPDATE,
-      timeline,
-      status,
-      references
-    });
-  };
-};
-
-export function deleteFromTimelines(id) {
-  return (dispatch, getState) => {
-    const accountId  = getState().getIn(['statuses', id, 'account']);
-    const references = getState().get('statuses').filter(status => status.get('reblog') === id).map(status => [status.get('id'), status.get('account')]);
-    const reblogOf   = getState().getIn(['statuses', id, 'reblog'], null);
-
-    dispatch({
-      type: TIMELINE_DELETE,
-      id,
-      accountId,
-      references,
-      reblogOf
-    });
-  };
-};
-
-export function refreshTimelineRequest(timeline, id, skipLoading) {
-  return {
-    type: TIMELINE_REFRESH_REQUEST,
-    timeline,
-    id,
-    skipLoading
-  };
-};
-
-export function refreshTimeline(timeline, id = null) {
-  return function (dispatch, getState) {
-    if (getState().getIn(['timelines', timeline, 'isLoading'])) {
-      return;
-    }
-
-    const ids      = getState().getIn(['timelines', timeline, 'items'], Immutable.List());
-    const newestId = ids.size > 0 ? ids.first() : null;
-    let params     = getState().getIn(['timelines', timeline, 'params'], {});
-    const path     = getState().getIn(['timelines', timeline, 'path'])(id);
-
-    let skipLoading = false;
-
-    if (newestId !== null && getState().getIn(['timelines', timeline, 'loaded']) && (id === null || getState().getIn(['timelines', timeline, 'id']) === id)) {
-      if (id === null && getState().getIn(['timelines', timeline, 'online'])) {
-        // Skip refreshing when timeline is live anyway
-        return;
-      }
-
-      params          = { ...params, since_id: newestId };
-      skipLoading     = true;
-    }
-
-    dispatch(refreshTimelineRequest(timeline, id, skipLoading));
-
-    api(getState).get(path, { params }).then(response => {
-      const next = getLinks(response).refs.find(link => link.rel === 'next');
-      dispatch(refreshTimelineSuccess(timeline, response.data, skipLoading, next ? next.uri : null));
-    }).catch(error => {
-      dispatch(refreshTimelineFail(timeline, error, skipLoading));
-    });
-  };
-};
-
-export function refreshTimelineFail(timeline, error, skipLoading) {
-  return {
-    type: TIMELINE_REFRESH_FAIL,
-    timeline,
-    error,
-    skipLoading
-  };
-};
-
-export function expandTimeline(timeline) {
-  return (dispatch, getState) => {
-    if (getState().getIn(['timelines', timeline, 'isLoading'])) {
-      return;
-    }
-
-    if (getState().getIn(['timelines', timeline, 'items']).size === 0) {
-      return;
-    }
-
-    const path   = getState().getIn(['timelines', timeline, 'path'])(getState().getIn(['timelines', timeline, 'id']));
-    const params = getState().getIn(['timelines', timeline, 'params'], {});
-    const lastId = getState().getIn(['timelines', timeline, 'items']).last();
-
-    dispatch(expandTimelineRequest(timeline));
-
-    api(getState).get(path, {
-      params: {
-        ...params,
-        max_id: lastId,
-        limit: 10
-      }
-    }).then(response => {
-      const next = getLinks(response).refs.find(link => link.rel === 'next');
-      dispatch(expandTimelineSuccess(timeline, response.data, next ? next.uri : null));
-    }).catch(error => {
-      dispatch(expandTimelineFail(timeline, error));
-    });
-  };
-};
-
-export function expandTimelineRequest(timeline) {
-  return {
-    type: TIMELINE_EXPAND_REQUEST,
-    timeline
-  };
-};
-
-export function expandTimelineSuccess(timeline, statuses, next) {
-  return {
-    type: TIMELINE_EXPAND_SUCCESS,
-    timeline,
-    statuses,
-    next
-  };
-};
-
-export function expandTimelineFail(timeline, error) {
-  return {
-    type: TIMELINE_EXPAND_FAIL,
-    timeline,
-    error
-  };
-};
-
-export function scrollTopTimeline(timeline, top) {
-  return {
-    type: TIMELINE_SCROLL_TOP,
-    timeline,
-    top
-  };
-};
-
-export function connectTimeline(timeline) {
-  return {
-    type: TIMELINE_CONNECT,
-    timeline
-  };
-};
-
-export function disconnectTimeline(timeline) {
-  return {
-    type: TIMELINE_DISCONNECT,
-    timeline
-  };
-};
diff --git a/app/assets/javascripts/components/api.jsx b/app/assets/javascripts/components/api.jsx
deleted file mode 100644
index 185729ce0..000000000
--- a/app/assets/javascripts/components/api.jsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import axios from 'axios';
-import LinkHeader from './link_header';
-
-export const getLinks = response => {
-  const value = response.headers.link;
-
-  if (!value) {
-    return { refs: [] };
-  }
-
-  return LinkHeader.parse(value);
-};
-
-export default getState => axios.create({
-  headers: {
-    'Authorization': `Bearer ${getState().getIn(['meta', 'access_token'], '')}`
-  },
-
-  transformResponse: [function (data) {
-    try {
-      return JSON.parse(data);
-    } catch(Exception) {
-      return data;
-    }
-  }]
-});
diff --git a/app/assets/javascripts/components/components/account.jsx b/app/assets/javascripts/components/components/account.jsx
deleted file mode 100644
index 81439bd25..000000000
--- a/app/assets/javascripts/components/components/account.jsx
+++ /dev/null
@@ -1,91 +0,0 @@
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import Avatar from './avatar';
-import DisplayName from './display_name';
-import Permalink from './permalink';
-import IconButton from './icon_button';
-import { defineMessages, injectIntl } from 'react-intl';
-
-const messages = defineMessages({
-  follow: { id: 'account.follow', defaultMessage: 'Follow' },
-  unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
-  requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' },
-  unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
-  unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' }
-});
-
-class Account extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.handleFollow = this.handleFollow.bind(this);
-    this.handleBlock = this.handleBlock.bind(this);
-    this.handleMute = this.handleMute.bind(this);
-  }
-
-  handleFollow () {
-    this.props.onFollow(this.props.account);
-  }
-
-  handleBlock () {
-    this.props.onBlock(this.props.account);
-  }
-
-  handleMute () {
-    this.props.onMute(this.props.account);
-  }
-
-  render () {
-    const { account, me, intl } = this.props;
-
-    if (!account) {
-      return <div />;
-    }
-
-    let buttons;
-
-    if (account.get('id') !== me && account.get('relationship', null) !== null) {
-      const following = account.getIn(['relationship', 'following']);
-      const requested = account.getIn(['relationship', 'requested']);
-      const blocking  = account.getIn(['relationship', 'blocking']);
-      const muting  = account.getIn(['relationship', 'muting']);
-
-      if (requested) {
-        buttons = <IconButton disabled={true} icon='hourglass' title={intl.formatMessage(messages.requested)} />
-      } else if (blocking) {
-        buttons = <IconButton active={true} icon='unlock-alt' title={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.handleBlock} />;
-      } else if (muting) {
-        buttons = <IconButton active={true} icon='volume-up' title={intl.formatMessage(messages.unmute, { name: account.get('username') })} onClick={this.handleMute} />;
-      } else {
-        buttons = <IconButton icon={following ? 'user-times' : 'user-plus'} title={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={this.handleFollow} active={following} />;
-      }
-    }
-
-    return (
-      <div className='account'>
-        <div className='account__wrapper'>
-          <Permalink key={account.get('id')} className='account__display-name' href={account.get('url')} to={`/accounts/${account.get('id')}`}>
-            <div className='account__avatar-wrapper'><Avatar src={account.get('avatar')} staticSrc={account.get('avatar_static')} size={36} /></div>
-            <DisplayName account={account} />
-          </Permalink>
-
-          <div className='account__relationship'>
-            {buttons}
-          </div>
-        </div>
-      </div>
-    );
-  }
-
-}
-
-Account.propTypes = {
-  account: ImmutablePropTypes.map.isRequired,
-  me: PropTypes.number.isRequired,
-  onFollow: PropTypes.func.isRequired,
-  onBlock: PropTypes.func.isRequired,
-  onMute: PropTypes.func.isRequired,
-  intl: PropTypes.object.isRequired
-}
-
-export default injectIntl(Account);
diff --git a/app/assets/javascripts/components/components/attachment_list.jsx b/app/assets/javascripts/components/components/attachment_list.jsx
deleted file mode 100644
index 54841fa51..000000000
--- a/app/assets/javascripts/components/components/attachment_list.jsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import ImmutablePropTypes from 'react-immutable-proptypes';
-
-const filename = url => url.split('/').pop().split('#')[0].split('?')[0];
-
-class AttachmentList extends React.PureComponent {
-
-  render () {
-    const { media } = this.props;
-
-    return (
-      <div className='attachment-list'>
-        <div className='attachment-list__icon'>
-          <i className='fa fa-link' />
-        </div>
-
-        <ul className='attachment-list__list'>
-          {media.map(attachment =>
-            <li key={attachment.get('id')}>
-              <a href={attachment.get('remote_url')} target='_blank' rel='noopener'>{filename(attachment.get('remote_url'))}</a>
-            </li>
-          )}
-        </ul>
-      </div>
-    );
-  }
-}
-
-AttachmentList.propTypes = {
-  media: ImmutablePropTypes.list.isRequired
-};
-
-export default AttachmentList;
diff --git a/app/assets/javascripts/components/components/autosuggest_textarea.jsx b/app/assets/javascripts/components/components/autosuggest_textarea.jsx
deleted file mode 100644
index 9a4d5b7e3..000000000
--- a/app/assets/javascripts/components/components/autosuggest_textarea.jsx
+++ /dev/null
@@ -1,211 +0,0 @@
-import AutosuggestAccountContainer from '../features/compose/containers/autosuggest_account_container';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import { isRtl } from '../rtl';
-
-const textAtCursorMatchesToken = (str, caretPosition) => {
-  let word;
-
-  let left  = str.slice(0, caretPosition).search(/\S+$/);
-  let right = str.slice(caretPosition).search(/\s/);
-
-  if (right < 0) {
-    word = str.slice(left);
-  } else {
-    word = str.slice(left, right + caretPosition);
-  }
-
-  if (!word || word.trim().length < 2 || word[0] !== '@') {
-    return [null, null];
-  }
-
-  word = word.trim().toLowerCase().slice(1);
-
-  if (word.length > 0) {
-    return [left + 1, word];
-  } else {
-    return [null, null];
-  }
-};
-
-class AutosuggestTextarea extends React.Component {
-
-  constructor (props, context) {
-    super(props, context);
-    this.state = {
-      suggestionsHidden: false,
-      selectedSuggestion: 0,
-      lastToken: null,
-      tokenStart: 0
-    };
-    this.onChange = this.onChange.bind(this);
-    this.onKeyDown = this.onKeyDown.bind(this);
-    this.onBlur = this.onBlur.bind(this);
-    this.onSuggestionClick = this.onSuggestionClick.bind(this);
-    this.setTextarea = this.setTextarea.bind(this);
-    this.onPaste = this.onPaste.bind(this);
-  }
-
-  onChange (e) {
-    const [ tokenStart, token ] = textAtCursorMatchesToken(e.target.value, e.target.selectionStart);
-
-    if (token !== null && this.state.lastToken !== token) {
-      this.setState({ lastToken: token, selectedSuggestion: 0, tokenStart });
-      this.props.onSuggestionsFetchRequested(token);
-    } else if (token === null) {
-      this.setState({ lastToken: null });
-      this.props.onSuggestionsClearRequested();
-    }
-
-    // auto-resize textarea
-    e.target.style.height = `${e.target.scrollHeight}px`;
-
-    this.props.onChange(e);
-  }
-
-  onKeyDown (e) {
-    const { suggestions, disabled } = this.props;
-    const { selectedSuggestion, suggestionsHidden } = this.state;
-
-    if (disabled) {
-      e.preventDefault();
-      return;
-    }
-
-    switch(e.key) {
-    case 'Escape':
-      if (!suggestionsHidden) {
-        e.preventDefault();
-        this.setState({ suggestionsHidden: true });
-      }
-
-      break;
-    case 'ArrowDown':
-      if (suggestions.size > 0 && !suggestionsHidden) {
-        e.preventDefault();
-        this.setState({ selectedSuggestion: Math.min(selectedSuggestion + 1, suggestions.size - 1) });
-      }
-
-      break;
-    case 'ArrowUp':
-      if (suggestions.size > 0 && !suggestionsHidden) {
-        e.preventDefault();
-        this.setState({ selectedSuggestion: Math.max(selectedSuggestion - 1, 0) });
-      }
-
-      break;
-    case 'Enter':
-    case 'Tab':
-      // Select suggestion
-      if (this.state.lastToken !== null && suggestions.size > 0 && !suggestionsHidden) {
-        e.preventDefault();
-        e.stopPropagation();
-        this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestions.get(selectedSuggestion));
-      }
-
-      break;
-    }
-
-    if (e.defaultPrevented || !this.props.onKeyDown) {
-      return;
-    }
-
-    this.props.onKeyDown(e);
-  }
-
-  onBlur () {
-    // If we hide the suggestions immediately, then this will prevent the
-    // onClick for the suggestions themselves from firing.
-    // Setting a short window for that to take place before hiding the
-    // suggestions ensures that can't happen.
-    setTimeout(() => {
-      this.setState({ suggestionsHidden: true });
-    }, 100);
-  }
-
-  onSuggestionClick (suggestion, e) {
-    e.preventDefault();
-    this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestion);
-    this.textarea.focus();
-  }
-
-  componentWillReceiveProps (nextProps) {
-    if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden) {
-      this.setState({ suggestionsHidden: false });
-    }
-  }
-
-  setTextarea (c) {
-    this.textarea = c;
-  }
-
-  onPaste (e) {
-    if (e.clipboardData && e.clipboardData.files.length === 1) {
-      this.props.onPaste(e.clipboardData.files)
-      e.preventDefault();
-    }
-  }
-
-  reset () {
-    this.textarea.style.height = 'auto';
-  }
-
-  render () {
-    const { value, suggestions, disabled, placeholder, onKeyUp } = this.props;
-    const { suggestionsHidden, selectedSuggestion } = this.state;
-    const style = { direction: 'ltr' };
-
-    if (isRtl(value)) {
-      style.direction = 'rtl';
-    }
-
-    return (
-      <div className='autosuggest-textarea'>
-        <textarea
-          ref={this.setTextarea}
-          className='autosuggest-textarea__textarea'
-          disabled={disabled}
-          placeholder={placeholder}
-          autoFocus={true}
-          value={value}
-          onChange={this.onChange}
-          onKeyDown={this.onKeyDown}
-          onKeyUp={onKeyUp}
-          onBlur={this.onBlur}
-          onPaste={this.onPaste}
-          style={style}
-        />
-
-        <div style={{ display: (suggestions.size > 0 && !suggestionsHidden) ? 'block' : 'none' }} className='autosuggest-textarea__suggestions'>
-          {suggestions.map((suggestion, i) => (
-            <div
-              role='button'
-              tabIndex='0'
-              key={suggestion}
-              className={`autosuggest-textarea__suggestions__item ${i === selectedSuggestion ? 'selected' : ''}`}
-              onClick={this.onSuggestionClick.bind(this, suggestion)}>
-              <AutosuggestAccountContainer id={suggestion} />
-            </div>
-          ))}
-        </div>
-      </div>
-    );
-  }
-
-};
-
-AutosuggestTextarea.propTypes = {
-  value: PropTypes.string,
-  suggestions: ImmutablePropTypes.list,
-  disabled: PropTypes.bool,
-  placeholder: PropTypes.string,
-  onSuggestionSelected: PropTypes.func.isRequired,
-  onSuggestionsClearRequested: PropTypes.func.isRequired,
-  onSuggestionsFetchRequested: PropTypes.func.isRequired,
-  onChange: PropTypes.func.isRequired,
-  onKeyUp: PropTypes.func,
-  onKeyDown: PropTypes.func,
-  onPaste: PropTypes.func.isRequired,
-};
-
-export default AutosuggestTextarea;
diff --git a/app/assets/javascripts/components/components/avatar.jsx b/app/assets/javascripts/components/components/avatar.jsx
deleted file mode 100644
index d1a00ac39..000000000
--- a/app/assets/javascripts/components/components/avatar.jsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import PropTypes from 'prop-types';
-
-class Avatar extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.state = {
-      hovering: false
-    };
-    this.handleMouseEnter = this.handleMouseEnter.bind(this);
-    this.handleMouseLeave = this.handleMouseLeave.bind(this);
-  }
-
-  handleMouseEnter () {
-    this.setState({ hovering: true });
-  }
-
-  handleMouseLeave () {
-    this.setState({ hovering: false });
-  }
-
-  render () {
-    const { src, size, staticSrc, animate } = this.props;
-    const { hovering } = this.state;
-
-    const style = {
-      ...this.props.style,
-      width: `${size}px`,
-      height: `${size}px`,
-      backgroundSize: `${size}px ${size}px`
-    };
-
-    if (hovering || animate) {
-      style.backgroundImage = `url(${src})`;
-    } else {
-      style.backgroundImage = `url(${staticSrc})`;
-    }
-
-    return (
-      <div
-        className='account__avatar'
-        onMouseEnter={this.handleMouseEnter}
-        onMouseLeave={this.handleMouseLeave}
-        style={style}
-      />
-    );
-  }
-
-}
-
-Avatar.propTypes = {
-  src: PropTypes.string.isRequired,
-  staticSrc: PropTypes.string,
-  size: PropTypes.number.isRequired,
-  style: PropTypes.object,
-  animate: PropTypes.bool
-};
-
-Avatar.defaultProps = {
-  animate: false
-};
-
-export default Avatar;
diff --git a/app/assets/javascripts/components/components/button.jsx b/app/assets/javascripts/components/components/button.jsx
deleted file mode 100644
index 00d80b1fd..000000000
--- a/app/assets/javascripts/components/components/button.jsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import PropTypes from 'prop-types';
-
-class Button extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.handleClick = this.handleClick.bind(this);
-  }
-
-  handleClick (e) {
-    if (!this.props.disabled) {
-      this.props.onClick();
-    }
-  }
-
-  render () {
-    const style = {
-      display: this.props.block ? 'block' : 'inline-block',
-      width: this.props.block ? '100%' : 'auto',
-      padding: `0 ${this.props.size / 2.25}px`,
-      height: `${this.props.size}px`,
-      lineHeight: `${this.props.size}px`
-    };
-
-    return (
-      <button className={`button ${this.props.secondary ? 'button-secondary' : ''}`} disabled={this.props.disabled} onClick={this.handleClick} style={{ ...style, ...this.props.style }}>
-        {this.props.text || this.props.children}
-      </button>
-    );
-  }
-
-}
-
-Button.propTypes = {
-  text: PropTypes.node,
-  onClick: PropTypes.func,
-  disabled: PropTypes.bool,
-  block: PropTypes.bool,
-  secondary: PropTypes.bool,
-  size: PropTypes.number,
-  style: PropTypes.object,
-  children: PropTypes.node
-};
-
-Button.defaultProps = {
-  size: 36
-};
-
-export default Button;
diff --git a/app/assets/javascripts/components/components/collapsable.jsx b/app/assets/javascripts/components/components/collapsable.jsx
deleted file mode 100644
index 0810768f0..000000000
--- a/app/assets/javascripts/components/components/collapsable.jsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import { Motion, spring } from 'react-motion';
-import PropTypes from 'prop-types';
-
-const Collapsable = ({ fullHeight, isVisible, children }) => (
-  <Motion defaultStyle={{ opacity: !isVisible ? 0 : 100, height: isVisible ? fullHeight : 0 }} style={{ opacity: spring(!isVisible ? 0 : 100), height: spring(!isVisible ? 0 : fullHeight) }}>
-    {({ opacity, height }) =>
-      <div style={{ height: `${height}px`, overflow: 'hidden', opacity: opacity / 100, display: Math.floor(opacity) === 0 ? 'none' : 'block' }}>
-        {children}
-      </div>
-    }
-  </Motion>
-);
-
-Collapsable.propTypes = {
-  fullHeight: PropTypes.number.isRequired,
-  isVisible: PropTypes.bool.isRequired,
-  children: PropTypes.node.isRequired
-};
-
-export default Collapsable;
diff --git a/app/assets/javascripts/components/components/column_back_button.jsx b/app/assets/javascripts/components/components/column_back_button.jsx
deleted file mode 100644
index 70110f0aa..000000000
--- a/app/assets/javascripts/components/components/column_back_button.jsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import { FormattedMessage } from 'react-intl';
-import PropTypes from 'prop-types';
-
-class ColumnBackButton extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.handleClick = this.handleClick.bind(this);
-  }
-
-  handleClick () {
-    if (window.history && window.history.length === 1) this.context.router.push("/");
-    else this.context.router.goBack();
-  }
-
-  render () {
-    return (
-      <div role='button' tabIndex='0' 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>
-    );
-  }
-
-};
-
-ColumnBackButton.contextTypes = {
-  router: PropTypes.object
-};
-
-export default ColumnBackButton;
diff --git a/app/assets/javascripts/components/components/column_back_button_slim.jsx b/app/assets/javascripts/components/components/column_back_button_slim.jsx
deleted file mode 100644
index 719690097..000000000
--- a/app/assets/javascripts/components/components/column_back_button_slim.jsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import { FormattedMessage } from 'react-intl';
-import PropTypes from 'prop-types';
-
-class ColumnBackButtonSlim extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.handleClick = this.handleClick.bind(this);
-  }
-
-  handleClick () {
-    this.context.router.push('/');
-  }
-
-  render () {
-    return (
-      <div className='column-back-button--slim'>
-        <div role='button' tabIndex='0' onClick={this.handleClick} className='column-back-button column-back-button--slim-button'>
-          <i className='fa fa-fw fa-chevron-left column-back-button__icon' />
-          <FormattedMessage id='column_back_button.label' defaultMessage='Back' />
-        </div>
-      </div>
-    );
-  }
-}
-
-ColumnBackButtonSlim.contextTypes = {
-  router: PropTypes.object
-};
-
-export default ColumnBackButtonSlim;
diff --git a/app/assets/javascripts/components/components/column_collapsable.jsx b/app/assets/javascripts/components/components/column_collapsable.jsx
deleted file mode 100644
index 2cb440862..000000000
--- a/app/assets/javascripts/components/components/column_collapsable.jsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import { Motion, spring } from 'react-motion';
-import PropTypes from 'prop-types';
-
-class ColumnCollapsable extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.state = {
-      collapsed: true
-    };
-
-    this.handleToggleCollapsed = this.handleToggleCollapsed.bind(this);
-  }
-
-  handleToggleCollapsed () {
-    const currentState = this.state.collapsed;
-
-    this.setState({ collapsed: !currentState });
-
-    if (!currentState && this.props.onCollapse) {
-      this.props.onCollapse();
-    }
-  }
-
-  render () {
-    const { icon, title, fullHeight, children } = this.props;
-    const { collapsed } = this.state;
-    const collapsedClassName = collapsed ? 'collapsable-collapsed' : 'collapsable';
-
-    return (
-      <div className='column-collapsable'>
-        <div role='button' tabIndex='0' title={`${title}`} className={`column-icon ${collapsedClassName}`} onClick={this.handleToggleCollapsed}>
-          <i className={`fa fa-${icon}`} />
-        </div>
-
-        <Motion defaultStyle={{ opacity: 0, height: 0 }} style={{ opacity: spring(collapsed ? 0 : 100), height: spring(collapsed ? 0 : fullHeight, collapsed ? undefined : { stiffness: 150, damping: 9 }) }}>
-          {({ opacity, height }) =>
-            <div style={{ overflow: height === fullHeight ? 'auto' : 'hidden', height: `${height}px`, opacity: opacity / 100, maxHeight: '70vh' }}>
-              {children}
-            </div>
-          }
-        </Motion>
-      </div>
-    );
-  }
-}
-
-ColumnCollapsable.propTypes = {
-  icon: PropTypes.string.isRequired,
-  title: PropTypes.string,
-  fullHeight: PropTypes.number.isRequired,
-  children: PropTypes.node,
-  onCollapse: PropTypes.func
-};
-
-export default ColumnCollapsable;
diff --git a/app/assets/javascripts/components/components/display_name.jsx b/app/assets/javascripts/components/components/display_name.jsx
deleted file mode 100644
index d7257e092..000000000
--- a/app/assets/javascripts/components/components/display_name.jsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import escapeTextContentForBrowser from 'escape-html';
-import emojify from '../emoji';
-
-class DisplayName extends React.PureComponent {
-
-  render () {
-    const displayName     = this.props.account.get('display_name').length === 0 ? this.props.account.get('username') : this.props.account.get('display_name');
-    const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) };
-
-    return (
-      <span className='display-name'>
-        <strong className='display-name__html' dangerouslySetInnerHTML={displayNameHTML} /> <span className='display-name__account'>@{this.props.account.get('acct')}</span>
-      </span>
-    );
-  }
-
-};
-
-DisplayName.propTypes = {
-  account: ImmutablePropTypes.map.isRequired
-}
-
-export default DisplayName;
diff --git a/app/assets/javascripts/components/components/dropdown_menu.jsx b/app/assets/javascripts/components/components/dropdown_menu.jsx
deleted file mode 100644
index f5ee27a11..000000000
--- a/app/assets/javascripts/components/components/dropdown_menu.jsx
+++ /dev/null
@@ -1,78 +0,0 @@
-import Dropdown, { DropdownTrigger, DropdownContent } from 'react-simple-dropdown';
-import PropTypes from 'prop-types';
-
-class DropdownMenu extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.state = {
-      direction: 'left'
-    };
-    this.setRef = this.setRef.bind(this);
-    this.renderItem = this.renderItem.bind(this);
-  }
-
-  setRef (c) {
-    this.dropdown = c;
-  }
-
-  handleClick (i, e) {
-    const { action } = this.props.items[i];
-
-    if (typeof action === 'function') {
-      e.preventDefault();
-      action();
-      this.dropdown.hide();
-    }
-  }
-
-  renderItem (item, i) {
-    if (item === null) {
-      return <li key={ 'sep' + i } className='dropdown__sep' />;
-    }
-
-    const { text, action, href = '#' } = item;
-
-    return (
-      <li className='dropdown__content-list-item' key={ text + i }>
-        <a href={href} target='_blank' rel='noopener' onClick={this.handleClick.bind(this, i)} className='dropdown__content-list-link'>
-          {text}
-        </a>
-      </li>
-    );
-  }
-
-  render () {
-    const { icon, items, size, direction, ariaLabel } = this.props;
-    const directionClass = (direction === "left") ? "dropdown__left" : "dropdown__right";
-
-    return (
-      <Dropdown ref={this.setRef}>
-        <DropdownTrigger className='icon-button' style={{ fontSize: `${size}px`, width: `${size}px`, lineHeight: `${size}px` }} aria-label={ariaLabel}>
-          <i className={ `fa fa-fw fa-${icon} dropdown__icon` }  aria-hidden={true} />
-        </DropdownTrigger>
-
-        <DropdownContent className={directionClass}>
-          <ul className='dropdown__content-list'>
-            {items.map(this.renderItem)}
-          </ul>
-        </DropdownContent>
-      </Dropdown>
-    );
-  }
-
-}
-
-DropdownMenu.propTypes = {
-  icon: PropTypes.string.isRequired,
-  items: PropTypes.array.isRequired,
-  size: PropTypes.number.isRequired,
-  direction: PropTypes.string,
-  ariaLabel: PropTypes.string
-};
-
-DropdownMenu.defaultProps = {
-  ariaLabel: "Menu"
-};
-
-export default DropdownMenu;
diff --git a/app/assets/javascripts/components/components/extended_video_player.jsx b/app/assets/javascripts/components/components/extended_video_player.jsx
deleted file mode 100644
index bbbe09da8..000000000
--- a/app/assets/javascripts/components/components/extended_video_player.jsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import PropTypes from 'prop-types';
-
-class ExtendedVideoPlayer extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.handleLoadedData = this.handleLoadedData.bind(this);
-    this.setRef = this.setRef.bind(this);
-  }
-
-  handleLoadedData () {
-    if (this.props.time) {
-      this.video.currentTime = this.props.time;
-    }
-  }
-
-  componentDidMount () {
-    this.video.addEventListener('loadeddata', this.handleLoadedData);
-  }
-
-  componentWillUnmount () {
-    this.video.removeEventListener('loadeddata', this.handleLoadedData);
-  }
-
-  setRef (c) {
-    this.video = c;
-  }
-
-  render () {
-    return (
-      <div className='extended-video-player'>
-        <video
-          ref={this.setRef}
-          src={this.props.src}
-          autoPlay
-          muted={this.props.muted}
-          controls={this.props.controls}
-          loop={!this.props.controls}
-        />
-      </div>
-    );
-  }
-
-}
-
-ExtendedVideoPlayer.propTypes = {
-  src: PropTypes.string.isRequired,
-  time: PropTypes.number,
-  controls: PropTypes.bool.isRequired,
-  muted: PropTypes.bool.isRequired
-};
-
-export default ExtendedVideoPlayer;
diff --git a/app/assets/javascripts/components/components/icon_button.jsx b/app/assets/javascripts/components/components/icon_button.jsx
deleted file mode 100644
index 67c6513fd..000000000
--- a/app/assets/javascripts/components/components/icon_button.jsx
+++ /dev/null
@@ -1,95 +0,0 @@
-import { Motion, spring } from 'react-motion';
-import PropTypes from 'prop-types';
-
-class IconButton extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.handleClick = this.handleClick.bind(this);
-  }
-
-  handleClick (e) {
-    e.preventDefault();
-
-    if (!this.props.disabled) {
-      this.props.onClick(e);
-    }
-  }
-
-  render () {
-    let style = {
-      fontSize: `${this.props.size}px`,
-      width: `${this.props.size * 1.28571429}px`,
-      height: `${this.props.size * 1.28571429}px`,
-      lineHeight: `${this.props.size}px`,
-      ...this.props.style
-    };
-
-    if (this.props.active) {
-      style = { ...style, ...this.props.activeStyle };
-    }
-
-    const classes = ['icon-button'];
-
-    if (this.props.active) {
-      classes.push('active');
-    }
-
-    if (this.props.disabled) {
-      classes.push('disabled');
-    }
-
-    if (this.props.inverted) {
-      classes.push('inverted');
-    }
-
-    if (this.props.overlay) {
-      classes.push('overlayed');
-    }
-
-    if (this.props.className) {
-      classes.push(this.props.className)
-    }
-
-    return (
-      <Motion defaultStyle={{ rotate: this.props.active ? -360 : 0 }} style={{ rotate: this.props.animate ? spring(this.props.active ? -360 : 0, { stiffness: 120, damping: 7 }) : 0 }}>
-        {({ rotate }) =>
-          <button
-            aria-label={this.props.title}
-            title={this.props.title}
-            className={classes.join(' ')}
-            onClick={this.handleClick}
-            style={style}>
-            <i style={{ transform: `rotate(${rotate}deg)` }} className={`fa fa-fw fa-${this.props.icon}`} aria-hidden='true' />
-          </button>
-        }
-      </Motion>
-    );
-  }
-
-}
-
-IconButton.propTypes = {
-  className: PropTypes.string,
-  title: PropTypes.string.isRequired,
-  icon: PropTypes.string.isRequired,
-  onClick: PropTypes.func,
-  size: PropTypes.number,
-  active: PropTypes.bool,
-  style: PropTypes.object,
-  activeStyle: PropTypes.object,
-  disabled: PropTypes.bool,
-  inverted: PropTypes.bool,
-  animate: PropTypes.bool,
-  overlay: PropTypes.bool
-};
-
-IconButton.defaultProps = {
-  size: 18,
-  active: false,
-  disabled: false,
-  animate: false,
-  overlay: false
-};
-
-export default IconButton;
diff --git a/app/assets/javascripts/components/components/load_more.jsx b/app/assets/javascripts/components/components/load_more.jsx
deleted file mode 100644
index f59ff1103..000000000
--- a/app/assets/javascripts/components/components/load_more.jsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import { FormattedMessage } from 'react-intl';
-import PropTypes from 'prop-types';
-
-const LoadMore = ({ onClick }) => (
-  <a href="#" className='load-more' role='button' onClick={onClick}>
-    <FormattedMessage id='status.load_more' defaultMessage='Load more' />
-  </a>
-);
-
-LoadMore.propTypes = {
-  onClick: PropTypes.func
-};
-
-export default LoadMore;
diff --git a/app/assets/javascripts/components/components/loading_indicator.jsx b/app/assets/javascripts/components/components/loading_indicator.jsx
deleted file mode 100644
index 61e0a0f15..000000000
--- a/app/assets/javascripts/components/components/loading_indicator.jsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { FormattedMessage } from 'react-intl';
-
-const LoadingIndicator = () => (
-  <div className='loading-indicator'>
-    <FormattedMessage id='loading_indicator.label' defaultMessage='Loading...' />
-  </div>
-);
-
-export default LoadingIndicator;
diff --git a/app/assets/javascripts/components/components/media_gallery.jsx b/app/assets/javascripts/components/components/media_gallery.jsx
deleted file mode 100644
index 58e134f50..000000000
--- a/app/assets/javascripts/components/components/media_gallery.jsx
+++ /dev/null
@@ -1,195 +0,0 @@
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import IconButton from './icon_button';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import { isIOS } from '../is_mobile';
-
-const messages = defineMessages({
-  toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Toggle visibility' }
-});
-
-class Item extends React.PureComponent {
-  constructor (props, context) {
-    super(props, context);
-    this.handleClick = this.handleClick.bind(this);
-  }
-
-  handleClick (e) {
-    const { index, onClick } = this.props;
-
-    if (e.button === 0) {
-      e.preventDefault();
-      onClick(index);
-    }
-
-    e.stopPropagation();
-  }
-
-  render () {
-    const { attachment, index, size } = this.props;
-
-    let width  = 50;
-    let height = 100;
-    let top    = 'auto';
-    let left   = 'auto';
-    let bottom = 'auto';
-    let right  = 'auto';
-
-    if (size === 1) {
-      width = 100;
-    }
-
-    if (size === 4 || (size === 3 && index > 0)) {
-      height = 50;
-    }
-
-    if (size === 2) {
-      if (index === 0) {
-        right = '2px';
-      } else {
-        left = '2px';
-      }
-    } else if (size === 3) {
-      if (index === 0) {
-        right = '2px';
-      } else if (index > 0) {
-        left = '2px';
-      }
-
-      if (index === 1) {
-        bottom = '2px';
-      } else if (index > 1) {
-        top = '2px';
-      }
-    } else if (size === 4) {
-      if (index === 0 || index === 2) {
-        right = '2px';
-      }
-
-      if (index === 1 || index === 3) {
-        left = '2px';
-      }
-
-      if (index < 2) {
-        bottom = '2px';
-      } else {
-        top = '2px';
-      }
-    }
-
-    let thumbnail = '';
-
-    if (attachment.get('type') === 'image') {
-      thumbnail = (
-        <a
-          className='media-gallery__item-thumbnail'
-          href={attachment.get('remote_url') || attachment.get('url')}
-          onClick={this.handleClick}
-          target='_blank'
-          style={{ backgroundImage: `url(${attachment.get('preview_url')})` }}
-        />
-      );
-    } else if (attachment.get('type') === 'gifv') {
-      const autoPlay = !isIOS() && this.props.autoPlayGif;
-
-      thumbnail = (
-        <div className={`media-gallery__gifv ${autoPlay ? 'autoplay' : ''}`}>
-          <video
-            className='media-gallery__item-gifv-thumbnail'
-            role='application'
-            src={attachment.get('url')}
-            onClick={this.handleClick}
-            autoPlay={autoPlay}
-            loop={true}
-            muted={true}
-          />
-
-          <span className='media-gallery__gifv__label'>GIF</span>
-        </div>
-      );
-    }
-
-    return (
-      <div className='media-gallery__item' key={attachment.get('id')} style={{ left: left, top: top, right: right, bottom: bottom, width: `${width}%`, height: `${height}%` }}>
-        {thumbnail}
-      </div>
-    );
-  }
-
-}
-
-Item.propTypes = {
-  attachment: ImmutablePropTypes.map.isRequired,
-  index: PropTypes.number.isRequired,
-  size: PropTypes.number.isRequired,
-  onClick: PropTypes.func.isRequired,
-  autoPlayGif: PropTypes.bool.isRequired
-};
-
-class MediaGallery extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.state = {
-      visible: !props.sensitive
-    };
-    this.handleOpen = this.handleOpen.bind(this);
-    this.handleClick = this.handleClick.bind(this);
-  }
-
-  handleOpen (e) {
-    this.setState({ visible: !this.state.visible });
-  }
-
-  handleClick (index) {
-    this.props.onOpenMedia(this.props.media, index);
-  }
-
-  render () {
-    const { media, intl, sensitive } = this.props;
-
-    let children;
-
-    if (!this.state.visible) {
-      let warning;
-
-      if (sensitive) {
-        warning = <FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' />;
-      } else {
-        warning = <FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' />;
-      }
-
-      children = (
-        <div role='button' tabIndex='0' 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>
-      );
-    } else {
-      const size = media.take(4).size;
-      children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} autoPlayGif={this.props.autoPlayGif} index={i} size={size} />);
-    }
-
-    return (
-      <div className='media-gallery' style={{ height: `${this.props.height}px` }}>
-        <div className='spoiler-button' style={{ display: !this.state.visible ? 'none' : 'block' }}>
-          <IconButton title={intl.formatMessage(messages.toggle_visible)} icon={this.state.visible ? 'eye' : 'eye-slash'} overlay onClick={this.handleOpen} />
-        </div>
-
-        {children}
-      </div>
-    );
-  }
-
-}
-
-MediaGallery.propTypes = {
-  sensitive: PropTypes.bool,
-  media: ImmutablePropTypes.list.isRequired,
-  height: PropTypes.number.isRequired,
-  onOpenMedia: PropTypes.func.isRequired,
-  intl: PropTypes.object.isRequired,
-  autoPlayGif: PropTypes.bool.isRequired
-};
-
-export default injectIntl(MediaGallery);
diff --git a/app/assets/javascripts/components/components/missing_indicator.jsx b/app/assets/javascripts/components/components/missing_indicator.jsx
deleted file mode 100644
index 75129ae14..000000000
--- a/app/assets/javascripts/components/components/missing_indicator.jsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { FormattedMessage } from 'react-intl';
-
-const MissingIndicator = () => (
-  <div className='missing-indicator'>
-    <FormattedMessage id='missing_indicator.label' defaultMessage='Not found' />
-  </div>
-);
-
-export default MissingIndicator;
diff --git a/app/assets/javascripts/components/components/permalink.jsx b/app/assets/javascripts/components/components/permalink.jsx
deleted file mode 100644
index ccbe4944f..000000000
--- a/app/assets/javascripts/components/components/permalink.jsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import PropTypes from 'prop-types';
-
-class Permalink extends React.Component {
-
-  constructor (props, context) {
-    super(props, context);
-    this.handleClick = this.handleClick.bind(this);
-  }
-
-  handleClick (e) {
-    if (e.button === 0) {
-      e.preventDefault();
-      this.context.router.push(this.props.to);
-    }
-  }
-
-  render () {
-    const { href, children, className, ...other } = this.props;
-
-    return <a href={href} onClick={this.handleClick} {...other} className={'permalink ' + className}>{children}</a>;
-  }
-
-}
-
-Permalink.contextTypes = {
-  router: PropTypes.object
-};
-
-Permalink.propTypes = {
-  className: PropTypes.string,
-  href: PropTypes.string.isRequired,
-  to: PropTypes.string.isRequired,
-  children: PropTypes.node
-};
-
-export default Permalink;
diff --git a/app/assets/javascripts/components/components/relative_timestamp.jsx b/app/assets/javascripts/components/components/relative_timestamp.jsx
deleted file mode 100644
index 9ab472e2c..000000000
--- a/app/assets/javascripts/components/components/relative_timestamp.jsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { injectIntl, FormattedRelative } from 'react-intl';
-import PropTypes from 'prop-types';
-
-const RelativeTimestamp = ({ intl, timestamp }) => {
-  const date = new Date(timestamp);
-
-  return (
-    <time dateTime={timestamp} title={intl.formatDate(date, { hour12: false, year: 'numeric', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' })}>
-      <FormattedRelative value={date} />
-    </time>
-  );
-};
-
-RelativeTimestamp.propTypes = {
-  intl: PropTypes.object.isRequired,
-  timestamp: PropTypes.string.isRequired
-};
-
-export default injectIntl(RelativeTimestamp);
diff --git a/app/assets/javascripts/components/components/status.jsx b/app/assets/javascripts/components/components/status.jsx
deleted file mode 100644
index 193231837..000000000
--- a/app/assets/javascripts/components/components/status.jsx
+++ /dev/null
@@ -1,121 +0,0 @@
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import Avatar from './avatar';
-import RelativeTimestamp from './relative_timestamp';
-import DisplayName from './display_name';
-import MediaGallery from './media_gallery';
-import VideoPlayer from './video_player';
-import AttachmentList from './attachment_list';
-import StatusContent from './status_content';
-import StatusActionBar from './status_action_bar';
-import { FormattedMessage } from 'react-intl';
-import emojify from '../emoji';
-import escapeTextContentForBrowser from 'escape-html';
-
-class Status extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.handleClick = this.handleClick.bind(this);
-    this.handleAccountClick = this.handleAccountClick.bind(this);
-  }
-
-  handleClick () {
-    const { status } = this.props;
-    this.context.router.push(`/statuses/${status.getIn(['reblog', 'id'], status.get('id'))}`);
-  }
-
-  handleAccountClick (id, e) {
-    if (e.button === 0) {
-      e.preventDefault();
-      this.context.router.push(`/accounts/${id}`);
-    }
-  }
-
-  render () {
-    let media = '';
-    const { status, ...other } = this.props;
-
-    if (status === null) {
-      return <div />;
-    }
-
-    if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
-      let displayName = status.getIn(['account', 'display_name']);
-
-      if (displayName.length === 0) {
-        displayName = status.getIn(['account', 'username']);
-      }
-
-      const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) };
-
-      return (
-        <div className='status__wrapper'>
-          <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.bind(this, status.getIn(['account', 'id']))} href={status.getIn(['account', 'url'])} className='status__display-name muted'><strong dangerouslySetInnerHTML={displayNameHTML} /></a> }} />
-          </div>
-
-          <Status {...other} wrapped={true} status={status.get('reblog')} />
-        </div>
-      );
-    }
-
-    if (status.get('media_attachments').size > 0 && !this.props.muted) {
-      if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) {
-
-      } else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
-        media = <VideoPlayer media={status.getIn(['media_attachments', 0])} sensitive={status.get('sensitive')} onOpenVideo={this.props.onOpenVideo} />;
-      } else {
-        media = <MediaGallery media={status.get('media_attachments')} sensitive={status.get('sensitive')} height={110} onOpenMedia={this.props.onOpenMedia} autoPlayGif={this.props.autoPlayGif} />;
-      }
-    }
-
-    return (
-      <div className={this.props.muted ? 'status muted' : 'status'}>
-        <div className='status__info'>
-          <div className='status__info-time'>
-            <a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener'><RelativeTimestamp timestamp={status.get('created_at')} /></a>
-          </div>
-
-          <a onClick={this.handleAccountClick.bind(this, status.getIn(['account', 'id']))} href={status.getIn(['account', 'url'])} className='status__display-name'>
-            <div className='status__avatar'>
-              <Avatar src={status.getIn(['account', 'avatar'])} staticSrc={status.getIn(['account', 'avatar_static'])} size={48} />
-            </div>
-
-            <DisplayName account={status.get('account')} />
-          </a>
-        </div>
-
-        <StatusContent status={status} onClick={this.handleClick} />
-
-        {media}
-
-        <StatusActionBar {...this.props} />
-      </div>
-    );
-  }
-
-}
-
-Status.contextTypes = {
-  router: PropTypes.object
-};
-
-Status.propTypes = {
-  status: ImmutablePropTypes.map,
-  wrapped: PropTypes.bool,
-  onReply: PropTypes.func,
-  onFavourite: PropTypes.func,
-  onReblog: PropTypes.func,
-  onDelete: PropTypes.func,
-  onOpenMedia: PropTypes.func,
-  onOpenVideo: PropTypes.func,
-  onBlock: PropTypes.func,
-  me: PropTypes.number,
-  boostModal: PropTypes.bool,
-  autoPlayGif: PropTypes.bool,
-  muted: PropTypes.bool
-};
-
-export default Status;
diff --git a/app/assets/javascripts/components/components/status_action_bar.jsx b/app/assets/javascripts/components/components/status_action_bar.jsx
deleted file mode 100644
index 29938f23e..000000000
--- a/app/assets/javascripts/components/components/status_action_bar.jsx
+++ /dev/null
@@ -1,137 +0,0 @@
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import IconButton from './icon_button';
-import DropdownMenu from './dropdown_menu';
-import { defineMessages, injectIntl } from 'react-intl';
-
-const messages = defineMessages({
-  delete: { id: 'status.delete', defaultMessage: 'Delete' },
-  mention: { id: 'status.mention', defaultMessage: 'Mention @{name}' },
-  mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
-  block: { id: 'account.block', defaultMessage: 'Block @{name}' },
-  reply: { id: 'status.reply', defaultMessage: 'Reply' },
-  replyAll: { id: 'status.replyAll', defaultMessage: 'Reply to thread' },
-  reblog: { id: 'status.reblog', defaultMessage: 'Boost' },
-  cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' },
-  favourite: { id: 'status.favourite', defaultMessage: 'Favourite' },
-  open: { id: 'status.open', defaultMessage: 'Expand this status' },
-  report: { id: 'status.report', defaultMessage: 'Report @{name}' }
-});
-
-class StatusActionBar extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.handleReplyClick = this.handleReplyClick.bind(this);
-    this.handleFavouriteClick = this.handleFavouriteClick.bind(this);
-    this.handleReblogClick = this.handleReblogClick.bind(this);
-    this.handleDeleteClick = this.handleDeleteClick.bind(this);
-    this.handleMentionClick = this.handleMentionClick.bind(this);
-    this.handleMuteClick = this.handleMuteClick.bind(this);
-    this.handleBlockClick = this.handleBlockClick.bind(this);
-    this.handleOpen = this.handleOpen.bind(this);
-    this.handleReport = this.handleReport.bind(this);
-  }
-
-  handleReplyClick () {
-    this.props.onReply(this.props.status, this.context.router);
-  }
-
-  handleFavouriteClick () {
-    this.props.onFavourite(this.props.status);
-  }
-
-  handleReblogClick (e) {
-    this.props.onReblog(this.props.status, e);
-  }
-
-  handleDeleteClick () {
-    this.props.onDelete(this.props.status);
-  }
-
-  handleMentionClick () {
-    this.props.onMention(this.props.status.get('account'), this.context.router);
-  }
-
-  handleMuteClick () {
-    this.props.onMute(this.props.status.get('account'));
-  }
-
-  handleBlockClick () {
-    this.props.onBlock(this.props.status.get('account'));
-  }
-
-  handleOpen () {
-    this.context.router.push(`/statuses/${this.props.status.get('id')}`);
-  }
-
-  handleReport () {
-    this.props.onReport(this.props.status);
-    this.context.router.push('/report');
-  }
-
-  render () {
-    const { status, me, intl } = this.props;
-    const reblog_disabled = status.get('visibility') === 'private' || status.get('visibility') === 'direct';
-    let menu = [];
-
-    menu.push({ text: intl.formatMessage(messages.open), action: this.handleOpen });
-    menu.push(null);
-
-    if (status.getIn(['account', 'id']) === me) {
-      menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick });
-    } else {
-      menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick });
-      menu.push(null);
-      menu.push({ text: intl.formatMessage(messages.mute, { name: status.getIn(['account', 'username']) }), action: this.handleMuteClick });
-      menu.push({ text: intl.formatMessage(messages.block, { name: status.getIn(['account', 'username']) }), action: this.handleBlockClick });
-      menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport });
-    }
-
-    let reblogIcon = 'retweet';
-    if (status.get('visibility') === 'direct') reblogIcon = 'envelope';
-    else if (status.get('visibility') === 'private') reblogIcon = 'lock';
-    let reply_icon;
-    let reply_title;
-    if (status.get('in_reply_to_id', null) === null) {
-      reply_icon = "reply";
-      reply_title = intl.formatMessage(messages.reply);
-    } else {
-      reply_icon = "reply-all";
-      reply_title = intl.formatMessage(messages.replyAll);
-    }
-
-    return (
-      <div className='status__action-bar'>
-        <div className='status__action-bar-button-wrapper'><IconButton title={reply_title} icon={reply_icon} onClick={this.handleReplyClick} /></div>
-        <div className='status__action-bar-button-wrapper'><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='status__action-bar-button-wrapper'><IconButton animate={true} active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} className='star-icon' /></div>
-
-        <div className='status__action-bar-dropdown'>
-          <DropdownMenu items={menu} icon='ellipsis-h' size={18} direction="right" ariaLabel="More"/>
-        </div>
-      </div>
-    );
-  }
-
-}
-
-StatusActionBar.contextTypes = {
-  router: PropTypes.object
-};
-
-StatusActionBar.propTypes = {
-  status: ImmutablePropTypes.map.isRequired,
-  onReply: PropTypes.func,
-  onFavourite: PropTypes.func,
-  onReblog: PropTypes.func,
-  onDelete: PropTypes.func,
-  onMention: PropTypes.func,
-  onMute: PropTypes.func,
-  onBlock: PropTypes.func,
-  onReport: PropTypes.func,
-  me: PropTypes.number.isRequired,
-  intl: PropTypes.object.isRequired
-};
-
-export default injectIntl(StatusActionBar);
diff --git a/app/assets/javascripts/components/components/status_content.jsx b/app/assets/javascripts/components/components/status_content.jsx
deleted file mode 100644
index 714c00951..000000000
--- a/app/assets/javascripts/components/components/status_content.jsx
+++ /dev/null
@@ -1,157 +0,0 @@
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import escapeTextContentForBrowser from 'escape-html';
-import PropTypes from 'prop-types';
-import emojify from '../emoji';
-import { isRtl } from '../rtl';
-import { FormattedMessage } from 'react-intl';
-import Permalink from './permalink';
-
-class StatusContent extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.state = {
-      hidden: true
-    };
-    this.onMentionClick = this.onMentionClick.bind(this);
-    this.onHashtagClick = this.onHashtagClick.bind(this);
-    this.handleMouseDown = this.handleMouseDown.bind(this)
-    this.handleMouseUp = this.handleMouseUp.bind(this);
-    this.handleSpoilerClick = this.handleSpoilerClick.bind(this);
-  };
-
-  componentDidMount () {
-    const node  = ReactDOM.findDOMNode(this);
-    const links = node.querySelectorAll('a');
-
-    for (var i = 0; i < links.length; ++i) {
-      let link    = links[i];
-      let mention = this.props.status.get('mentions').find(item => link.href === item.get('url'));
-      let media   = this.props.status.get('media_attachments').find(item => link.href === item.get('text_url') || (item.get('remote_url').length > 0 && link.href === item.get('remote_url')));
-
-      if (mention) {
-        link.addEventListener('click', this.onMentionClick.bind(this, mention), false);
-        link.setAttribute('title', mention.get('acct'));
-      } else if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) {
-        link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false);
-      } else if (media) {
-        link.innerHTML = '<i class="fa fa-fw fa-photo"></i>';
-      } else {
-        link.setAttribute('target', '_blank');
-        link.setAttribute('rel', 'noopener');
-        link.setAttribute('title', link.href);
-      }
-    }
-  }
-
-  onMentionClick (mention, e) {
-    if (e.button === 0) {
-      e.preventDefault();
-      this.context.router.push(`/accounts/${mention.get('id')}`);
-    }
-  }
-
-  onHashtagClick (hashtag, e) {
-    hashtag = hashtag.replace(/^#/, '').toLowerCase();
-
-    if (e.button === 0) {
-      e.preventDefault();
-      this.context.router.push(`/timelines/tag/${hashtag}`);
-    }
-  }
-
-  handleMouseDown (e) {
-    this.startXY = [e.clientX, e.clientY];
-  }
-
-  handleMouseUp (e) {
-    const [ startX, startY ] = this.startXY;
-    const [ deltaX, deltaY ] = [Math.abs(e.clientX - startX), Math.abs(e.clientY - startY)];
-
-    if (e.target.localName === 'a' || (e.target.parentNode && e.target.parentNode.localName === 'a')) {
-      return;
-    }
-
-    if (deltaX + deltaY < 5 && e.button === 0) {
-      this.props.onClick();
-    }
-
-    this.startXY = null;
-  }
-
-  handleSpoilerClick (e) {
-    e.preventDefault();
-    this.setState({ hidden: !this.state.hidden });
-  }
-
-  render () {
-    const { status } = this.props;
-    const { hidden } = this.state;
-
-    const content = { __html: emojify(status.get('content')) };
-    const spoilerContent = { __html: emojify(escapeTextContentForBrowser(status.get('spoiler_text', ''))) };
-    const directionStyle = { direction: 'ltr' };
-
-    if (isRtl(status.get('content'))) {
-      directionStyle.direction = 'rtl';
-    }
-
-    if (status.get('spoiler_text').length > 0) {
-      let mentionsPlaceholder = '';
-
-      const mentionLinks = status.get('mentions').map(item => (
-        <Permalink to={`/accounts/${item.get('id')}`} href={item.get('url')} key={item.get('id')} className='mention'>
-          @<span>{item.get('username')}</span>
-        </Permalink>
-      )).reduce((aggregate, item) => [...aggregate, item, ' '], [])
-
-      const toggleText = hidden ? <FormattedMessage id='status.show_more' defaultMessage='Show more' /> : <FormattedMessage id='status.show_less' defaultMessage='Show less' />;
-
-      if (hidden) {
-        mentionsPlaceholder = <div>{mentionLinks}</div>;
-      }
-
-      return (
-        <div className='status__content' onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}>
-          <p style={{ marginBottom: hidden && status.get('mentions').size === 0 ? '0px' : '' }} >
-            <span dangerouslySetInnerHTML={spoilerContent} />  <a tabIndex='0' className='status__content__spoiler-link' role='button' onClick={this.handleSpoilerClick}>{toggleText}</a>
-          </p>
-
-          {mentionsPlaceholder}
-
-          <div style={{ display: hidden ? 'none' : 'block', ...directionStyle }} dangerouslySetInnerHTML={content} />
-        </div>
-      );
-    } else if (this.props.onClick) {
-      return (
-        <div
-          className='status__content'
-          style={{ ...directionStyle }}
-          onMouseDown={this.handleMouseDown}
-          onMouseUp={this.handleMouseUp}
-          dangerouslySetInnerHTML={content}
-        />
-      );
-    } else {
-      return (
-        <div
-          className='status__content status__content--no-action'
-          style={{ ...directionStyle }}
-          dangerouslySetInnerHTML={content}
-        />
-      );
-    }
-  }
-
-}
-
-StatusContent.contextTypes = {
-  router: PropTypes.object
-};
-
-StatusContent.propTypes = {
-  status: ImmutablePropTypes.map.isRequired,
-  onClick: PropTypes.func
-};
-
-export default StatusContent;
diff --git a/app/assets/javascripts/components/components/status_list.jsx b/app/assets/javascripts/components/components/status_list.jsx
deleted file mode 100644
index 517c8fe5d..000000000
--- a/app/assets/javascripts/components/components/status_list.jsx
+++ /dev/null
@@ -1,128 +0,0 @@
-import Status from './status';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import { ScrollContainer } from 'react-router-scroll';
-import PropTypes from 'prop-types';
-import StatusContainer from '../containers/status_container';
-import LoadMore from './load_more';
-
-class StatusList extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.handleScroll = this.handleScroll.bind(this);
-    this.setRef = this.setRef.bind(this);
-    this.handleLoadMore = this.handleLoadMore.bind(this);
-  }
-
-  handleScroll (e) {
-    const { scrollTop, scrollHeight, clientHeight } = e.target;
-    const offset = scrollHeight - scrollTop - clientHeight;
-    this._oldScrollPosition = scrollHeight - scrollTop;
-
-    if (250 > offset && this.props.onScrollToBottom && !this.props.isLoading) {
-      this.props.onScrollToBottom();
-    } else if (scrollTop < 100 && this.props.onScrollToTop) {
-      this.props.onScrollToTop();
-    } else if (this.props.onScroll) {
-      this.props.onScroll();
-    }
-  }
-
-  componentDidMount () {
-    this.attachScrollListener();
-  }
-
-  componentDidUpdate (prevProps) {
-    if (this.node.scrollTop > 0 && (prevProps.statusIds.size < this.props.statusIds.size && prevProps.statusIds.first() !== this.props.statusIds.first() && !!this._oldScrollPosition)) {
-      this.node.scrollTop = this.node.scrollHeight - this._oldScrollPosition;
-    }
-  }
-
-  componentWillUnmount () {
-    this.detachScrollListener();
-  }
-
-  attachScrollListener () {
-    this.node.addEventListener('scroll', this.handleScroll);
-  }
-
-  detachScrollListener () {
-    this.node.removeEventListener('scroll', this.handleScroll);
-  }
-
-  setRef (c) {
-    this.node = c;
-  }
-
-  handleLoadMore (e) {
-    e.preventDefault();
-    this.props.onScrollToBottom();
-  }
-
-  render () {
-    const { statusIds, onScrollToBottom, scrollKey, shouldUpdateScroll, isLoading, isUnread, hasMore, prepend, emptyMessage } = this.props;
-
-    let loadMore       = '';
-    let scrollableArea = '';
-    let unread         = '';
-
-    if (!isLoading && statusIds.size > 0 && hasMore) {
-      loadMore = <LoadMore onClick={this.handleLoadMore} />;
-    }
-
-    if (isUnread) {
-      unread = <div className='status-list__unread-indicator' />;
-    }
-
-    if (isLoading || statusIds.size > 0 || !emptyMessage) {
-      scrollableArea = (
-        <div className='scrollable' ref={this.setRef}>
-          {unread}
-
-          <div className='status-list'>
-            {prepend}
-
-            {statusIds.map((statusId) => {
-              return <StatusContainer key={statusId} id={statusId} />;
-            })}
-
-            {loadMore}
-          </div>
-        </div>
-      );
-    } else {
-      scrollableArea = (
-        <div className='empty-column-indicator' ref={this.setRef}>
-          {emptyMessage}
-        </div>
-      );
-    }
-
-    return (
-      <ScrollContainer scrollKey={scrollKey} shouldUpdateScroll={shouldUpdateScroll}>
-        {scrollableArea}
-      </ScrollContainer>
-    );
-  }
-
-}
-
-StatusList.propTypes = {
-  scrollKey: PropTypes.string.isRequired,
-  statusIds: ImmutablePropTypes.list.isRequired,
-  onScrollToBottom: PropTypes.func,
-  onScrollToTop: PropTypes.func,
-  onScroll: PropTypes.func,
-  shouldUpdateScroll: PropTypes.func,
-  isLoading: PropTypes.bool,
-  isUnread: PropTypes.bool,
-  hasMore: PropTypes.bool,
-  prepend: PropTypes.node,
-  emptyMessage: PropTypes.node
-};
-
-StatusList.defaultProps = {
-  trackScroll: true
-};
-
-export default StatusList;
diff --git a/app/assets/javascripts/components/components/video_player.jsx b/app/assets/javascripts/components/components/video_player.jsx
deleted file mode 100644
index 09c8ed875..000000000
--- a/app/assets/javascripts/components/components/video_player.jsx
+++ /dev/null
@@ -1,198 +0,0 @@
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import IconButton from './icon_button';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import { isIOS } from '../is_mobile';
-
-const messages = defineMessages({
-  toggle_sound: { id: 'video_player.toggle_sound', defaultMessage: 'Toggle sound' },
-  toggle_visible: { id: 'video_player.toggle_visible', defaultMessage: 'Toggle visibility' },
-  expand_video: { id: 'video_player.expand', defaultMessage: 'Expand video' },
-  expand_video: { id: 'video_player.video_error', defaultMessage: 'Video could not be played' }
-});
-
-class VideoPlayer extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.state = {
-      visible: !this.props.sensitive,
-      preview: true,
-      muted: true,
-      hasAudio: true,
-      videoError: false
-    };
-
-    this.handleClick = this.handleClick.bind(this);
-    this.handleVideoClick = this.handleVideoClick.bind(this);
-    this.handleOpen = this.handleOpen.bind(this);
-    this.handleVisibility = this.handleVisibility.bind(this);
-    this.handleExpand = this.handleExpand.bind(this);
-    this.setRef = this.setRef.bind(this);
-    this.handleLoadedData = this.handleLoadedData.bind(this);
-    this.handleVideoError = this.handleVideoError.bind(this);
-  }
-
-  handleClick () {
-    this.setState({ muted: !this.state.muted });
-  }
-
-  handleVideoClick (e) {
-    e.stopPropagation();
-
-    const node = ReactDOM.findDOMNode(this).querySelector('video');
-
-    if (node.paused) {
-      node.play();
-    } else {
-      node.pause();
-    }
-  }
-
-  handleOpen () {
-    this.setState({ preview: !this.state.preview });
-  }
-
-  handleVisibility () {
-    this.setState({
-      visible: !this.state.visible,
-      preview: true
-    });
-  }
-
-  handleExpand () {
-    this.video.pause();
-    this.props.onOpenVideo(this.props.media, this.video.currentTime);
-  }
-
-  setRef (c) {
-    this.video = c;
-  }
-
-  handleLoadedData () {
-    if (('WebkitAppearance' in document.documentElement.style && this.video.audioTracks.length === 0) || this.video.mozHasAudio === false) {
-      this.setState({ hasAudio: false });
-    }
-  }
-
-  handleVideoError () {
-    this.setState({ videoError: true });
-  }
-
-  componentDidMount () {
-    if (!this.video) {
-      return;
-    }
-
-    this.video.addEventListener('loadeddata', this.handleLoadedData);
-    this.video.addEventListener('error', this.handleVideoError);
-  }
-
-  componentDidUpdate () {
-    if (!this.video) {
-      return;
-    }
-
-    this.video.addEventListener('loadeddata', this.handleLoadedData);
-    this.video.addEventListener('error', this.handleVideoError);
-  }
-
-  componentWillUnmount () {
-    if (!this.video) {
-      return;
-    }
-
-    this.video.removeEventListener('loadeddata', this.handleLoadedData);
-    this.video.removeEventListener('error', this.handleVideoError);
-  }
-
-  render () {
-    const { media, intl, width, height, sensitive, autoplay } = this.props;
-
-    let spoilerButton = (
-      <div className='status__video-player-spoiler' style={{ display: !this.state.visible ? 'none' : 'block' }} >
-        <IconButton overlay title={intl.formatMessage(messages.toggle_visible)} icon={this.state.visible ? 'eye' : 'eye-slash'} onClick={this.handleVisibility} />
-      </div>
-    );
-
-    let expandButton = (
-      <div className='status__video-player-expand'>
-        <IconButton overlay title={intl.formatMessage(messages.expand_video)} icon='expand' onClick={this.handleExpand} />
-      </div>
-    );
-
-    let muteButton = '';
-
-    if (this.state.hasAudio) {
-      muteButton = (
-        <div className='status__video-player-mute'>
-          <IconButton overlay title={intl.formatMessage(messages.toggle_sound)} icon={this.state.muted ? 'volume-off' : 'volume-up'} onClick={this.handleClick} />
-        </div>
-      );
-    }
-
-    if (!this.state.visible) {
-      if (sensitive) {
-        return (
-          <div role='button' tabIndex='0' style={{ width: `${width}px`, height: `${height}px` }} className='media-spoiler' onClick={this.handleVisibility}>
-            {spoilerButton}
-            <span className='media-spoiler__warning'><FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' /></span>
-            <span className='media-spoiler__trigger'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
-          </div>
-        );
-      } else {
-        return (
-          <div role='button' tabIndex='0' style={{ width: `${width}px`, height: `${height}px` }} className='media-spoiler' onClick={this.handleVisibility}>
-            {spoilerButton}
-            <span className='media-spoiler__warning'><FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' /></span>
-            <span className='media-spoiler__trigger'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
-          </div>
-        );
-      }
-    }
-
-    if (this.state.preview && !autoplay) {
-      return (
-        <div role='button' tabIndex='0' className='media-spoiler-video' style={{ width: `${width}px`, height: `${height}px`, background: `url(${media.get('preview_url')}) no-repeat center` }} onClick={this.handleOpen}>
-          {spoilerButton}
-          <div className='media-spoiler-video-play-icon'><i className='fa fa-play' /></div>
-        </div>
-      );
-    }
-
-    if (this.state.videoError) {
-      return (
-        <div style={{ width: `${width}px`, height: `${height}px` }} className='video-error-cover' >
-          <span className='media-spoiler__warning'><FormattedMessage id='video_player.video_error' defaultMessage='Video could not be played' /></span>
-        </div>
-      );
-    }
-
-    return (
-      <div className='status__video-player' style={{ width: `${width}px`, height: `${height}px` }}>
-        {spoilerButton}
-        {muteButton}
-        {expandButton}
-        <video className='status__video-player-video' role='button' tabIndex='0' ref={this.setRef} src={media.get('url')} autoPlay={!isIOS()} loop={true} muted={this.state.muted} onClick={this.handleVideoClick} />
-      </div>
-    );
-  }
-
-}
-
-VideoPlayer.propTypes = {
-  media: ImmutablePropTypes.map.isRequired,
-  width: PropTypes.number,
-  height: PropTypes.number,
-  sensitive: PropTypes.bool,
-  intl: PropTypes.object.isRequired,
-  autoplay: PropTypes.bool,
-  onOpenVideo: PropTypes.func.isRequired
-};
-
-VideoPlayer.defaultProps = {
-  width: 239,
-  height: 110
-};
-
-export default injectIntl(VideoPlayer);
diff --git a/app/assets/javascripts/components/containers/account_container.jsx b/app/assets/javascripts/components/containers/account_container.jsx
deleted file mode 100644
index 3c30be715..000000000
--- a/app/assets/javascripts/components/containers/account_container.jsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import { connect } from 'react-redux';
-import { makeGetAccount } from '../selectors';
-import Account from '../components/account';
-import {
-  followAccount,
-  unfollowAccount,
-  blockAccount,
-  unblockAccount,
-  muteAccount,
-  unmuteAccount,
-} from '../actions/accounts';
-
-const makeMapStateToProps = () => {
-  const getAccount = makeGetAccount();
-
-  const mapStateToProps = (state, props) => ({
-    account: getAccount(state, props.id),
-    me: state.getIn(['meta', 'me'])
-  });
-
-  return mapStateToProps;
-};
-
-const mapDispatchToProps = (dispatch) => ({
-  onFollow (account) {
-    if (account.getIn(['relationship', 'following'])) {
-      dispatch(unfollowAccount(account.get('id')));
-    } else {
-      dispatch(followAccount(account.get('id')));
-    }
-  },
-
-  onBlock (account) {
-    if (account.getIn(['relationship', 'blocking'])) {
-      dispatch(unblockAccount(account.get('id')));
-    } else {
-      dispatch(blockAccount(account.get('id')));
-    }
-  },
-
-  onMute (account) {
-    if (account.getIn(['relationship', 'muting'])) {
-      dispatch(unmuteAccount(account.get('id')));
-    } else {
-      dispatch(muteAccount(account.get('id')));
-    }
-  }
-});
-
-export default connect(makeMapStateToProps, mapDispatchToProps)(Account);
diff --git a/app/assets/javascripts/components/containers/mastodon.jsx b/app/assets/javascripts/components/containers/mastodon.jsx
deleted file mode 100644
index 3f58b257a..000000000
--- a/app/assets/javascripts/components/containers/mastodon.jsx
+++ /dev/null
@@ -1,320 +0,0 @@
-import { Provider } from 'react-redux';
-import PropTypes from 'prop-types';
-import configureStore from '../store/configureStore';
-import {
-  refreshTimelineSuccess,
-  updateTimeline,
-  deleteFromTimelines,
-  refreshTimeline,
-  connectTimeline,
-  disconnectTimeline
-} from '../actions/timelines';
-import { showOnboardingOnce } from '../actions/onboarding';
-import { updateNotifications, refreshNotifications } from '../actions/notifications';
-import createBrowserHistory from 'history/lib/createBrowserHistory';
-import {
-  applyRouterMiddleware,
-  useRouterHistory,
-  Router,
-  Route,
-  IndexRedirect,
-  IndexRoute
-} from 'react-router';
-import { useScroll } from 'react-router-scroll';
-import UI from '../features/ui';
-import Status from '../features/status';
-import GettingStarted from '../features/getting_started';
-import PublicTimeline from '../features/public_timeline';
-import CommunityTimeline from '../features/community_timeline';
-import AccountTimeline from '../features/account_timeline';
-import HomeTimeline from '../features/home_timeline';
-import Compose from '../features/compose';
-import Followers from '../features/followers';
-import Following from '../features/following';
-import Reblogs from '../features/reblogs';
-import Favourites from '../features/favourites';
-import HashtagTimeline from '../features/hashtag_timeline';
-import Notifications from '../features/notifications';
-import FollowRequests from '../features/follow_requests';
-import GenericNotFound from '../features/generic_not_found';
-import FavouritedStatuses from '../features/favourited_statuses';
-import Blocks from '../features/blocks';
-import Mutes from '../features/mutes';
-import Report from '../features/report';
-import { IntlProvider, addLocaleData } from 'react-intl';
-import ar from 'react-intl/locale-data/ar';
-import en from 'react-intl/locale-data/en';
-import de from 'react-intl/locale-data/de';
-import eo from 'react-intl/locale-data/eo';
-import es from 'react-intl/locale-data/es';
-import fa from 'react-intl/locale-data/fa';
-import fi from 'react-intl/locale-data/fi';
-import fr from 'react-intl/locale-data/fr';
-import he from 'react-intl/locale-data/he';
-import hu from 'react-intl/locale-data/hu';
-import it from 'react-intl/locale-data/it';
-import ja from 'react-intl/locale-data/ja';
-import pt from 'react-intl/locale-data/pt';
-import nl from 'react-intl/locale-data/nl';
-import no from 'react-intl/locale-data/no';
-import ru from 'react-intl/locale-data/ru';
-import uk from 'react-intl/locale-data/uk';
-import zh from 'react-intl/locale-data/zh';
-import bg from 'react-intl/locale-data/bg';
-import id from 'react-intl/locale-data/id';
-import { localeData as zh_hk } from '../locales/zh-hk';
-import { localeData as zh_cn } from '../locales/zh-cn';
-import pt_br from '../locales/pt-br';
-import getMessagesForLocale from '../locales';
-import { hydrateStore } from '../actions/store';
-import createStream from '../stream';
-
-const store = configureStore();
-const initialState = JSON.parse(document.getElementById("initial-state").textContent);
-store.dispatch(hydrateStore(initialState));
-
-const browserHistory = useRouterHistory(createBrowserHistory)({
-  basename: '/web'
-});
-
-addLocaleData([
-  ...en,
-  ...ar,
-  ...de,
-  ...eo,
-  ...es,
-  ...fa,
-  ...fi,
-  ...fr,
-  ...he,
-  ...hu,
-  ...it,
-  ...ja,
-  ...pt,
-  ...pt_br,
-  ...nl,
-  ...no,
-  ...ru,
-  ...uk,
-  ...zh,
-  ...zh_hk,
-  ...zh_cn,
-  ...bg,
-  ...id,
-]);
-
-const getTopWhenReplacing = (previous, { location }) => location && location.action === 'REPLACE' && [0, 0];
-
-const hiddenColumnContainerStyle = {
-  position: 'absolute',
-  left: '0',
-  top:  '0',
-  visibility: 'hidden'
-};
-
-class Container extends React.PureComponent {
-
-  constructor(props) {
-    super(props);
-
-    this.state = {
-      renderedPersistents: [],
-      unrenderedPersistents: [],
-    };
-  }
-
-  componentWillMount () {
-    this.unlistenHistory = null;
-
-    this.setState(() => {
-      return {
-        mountImpersistent: false,
-        renderedPersistents: [],
-        unrenderedPersistents: [
-          {pathname: '/timelines/home', component: HomeTimeline},
-          {pathname: '/timelines/public', component: PublicTimeline},
-          {pathname: '/timelines/public/local', component: CommunityTimeline},
-
-          {pathname: '/notifications', component: Notifications},
-          {pathname: '/favourites', component: FavouritedStatuses}
-        ],
-      };
-    }, () => {
-      if (this.unlistenHistory) {
-        return;
-      }
-
-      this.unlistenHistory = browserHistory.listen(location => {
-        const pathname = location.pathname.replace(/\/$/, '').toLowerCase();
-
-        this.setState(oldState => {
-          let persistentMatched = false;
-
-          const newState = {
-            renderedPersistents: oldState.renderedPersistents.map(persistent => {
-              const givenMatched = persistent.pathname === pathname;
-
-              if (givenMatched) {
-                persistentMatched = true;
-              }
-
-              return {
-                hidden: !givenMatched,
-                pathname: persistent.pathname,
-                component: persistent.component
-              };
-            }),
-          };
-
-          if (!persistentMatched) {
-            newState.unrenderedPersistents = [];
-
-            oldState.unrenderedPersistents.forEach(persistent => {
-              if (persistent.pathname === pathname) {
-                persistentMatched = true;
-
-                newState.renderedPersistents.push({
-                  hidden: false,
-                  pathname: persistent.pathname,
-                  component: persistent.component
-                });
-              } else {
-                newState.unrenderedPersistents.push(persistent);
-              }
-            });
-          }
-
-          newState.mountImpersistent = !persistentMatched;
-
-          return newState;
-        });
-      });
-    });
-  }
-
-  componentWillUnmount () {
-    if (this.unlistenHistory) {
-      this.unlistenHistory();
-    }
-
-    this.unlistenHistory = "done";
-  }
-
-  render () {
-    // Hide some components rather than unmounting them to allow to show again
-    // quickly and keep the view state such as the scrolled offset.
-    const persistentsView = this.state.renderedPersistents.map((persistent) =>
-      <div aria-hidden={persistent.hidden} key={persistent.pathname} className='mastodon-column-container' style={persistent.hidden ? hiddenColumnContainerStyle : null}>
-        <persistent.component shouldUpdateScroll={persistent.hidden ? Function.prototype : getTopWhenReplacing} />
-      </div>
-    );
-
-    return (
-      <UI>
-        {this.state.mountImpersistent && this.props.children}
-        {persistentsView}
-      </UI>
-    );
-  }
-}
-
-Container.propTypes = {
-  children: PropTypes.node,
-};
-
-class Mastodon extends React.Component {
-
-  componentDidMount() {
-    const { locale }  = this.props;
-    const streamingAPIBaseURL = store.getState().getIn(['meta', 'streaming_api_base_url']);
-    const accessToken = store.getState().getIn(['meta', 'access_token']);
-
-    this.subscription = createStream(streamingAPIBaseURL, accessToken, 'user', {
-
-      connected () {
-        store.dispatch(connectTimeline('home'));
-      },
-
-      disconnected () {
-        store.dispatch(disconnectTimeline('home'));
-      },
-
-      received (data) {
-        switch(data.event) {
-        case 'update':
-          store.dispatch(updateTimeline('home', JSON.parse(data.payload)));
-          break;
-        case 'delete':
-          store.dispatch(deleteFromTimelines(data.payload));
-          break;
-        case 'notification':
-          store.dispatch(updateNotifications(JSON.parse(data.payload), getMessagesForLocale(locale), locale));
-          break;
-        }
-      },
-
-      reconnected () {
-        store.dispatch(connectTimeline('home'));
-        store.dispatch(refreshTimeline('home'));
-        store.dispatch(refreshNotifications());
-      }
-
-    });
-
-    // Desktop notifications
-    if (typeof window.Notification !== 'undefined' && Notification.permission === 'default') {
-      Notification.requestPermission();
-    }
-
-    store.dispatch(showOnboardingOnce());
-  }
-
-  componentWillUnmount () {
-    if (typeof this.subscription !== 'undefined') {
-      this.subscription.close();
-      this.subscription = null;
-    }
-  }
-
-  render () {
-    const { locale } = this.props;
-
-    return (
-      <IntlProvider locale={locale} messages={getMessagesForLocale(locale)}>
-        <Provider store={store}>
-          <Router history={browserHistory} render={applyRouterMiddleware(useScroll())}>
-            <Route path='/' component={Container}>
-              <IndexRedirect to="/getting-started" />
-
-              <Route path='getting-started' component={GettingStarted} />
-              <Route path='timelines/tag/:id' component={HashtagTimeline} />
-
-              <Route path='statuses/new' component={Compose} />
-              <Route path='statuses/:statusId' component={Status} />
-              <Route path='statuses/:statusId/reblogs' component={Reblogs} />
-              <Route path='statuses/:statusId/favourites' component={Favourites} />
-
-              <Route path='accounts/:accountId' component={AccountTimeline} />
-              <Route path='accounts/:accountId/followers' component={Followers} />
-              <Route path='accounts/:accountId/following' component={Following} />
-
-              <Route path='follow_requests' component={FollowRequests} />
-              <Route path='blocks' component={Blocks} />
-              <Route path='mutes' component={Mutes} />
-              <Route path='report' component={Report} />
-
-              <Route path='*' component={GenericNotFound} />
-            </Route>
-          </Router>
-        </Provider>
-      </IntlProvider>
-    );
-  }
-
-}
-
-Mastodon.propTypes = {
-  locale: PropTypes.string.isRequired
-};
-
-export default Mastodon;
diff --git a/app/assets/javascripts/components/containers/status_container.jsx b/app/assets/javascripts/components/containers/status_container.jsx
deleted file mode 100644
index ae83d36c9..000000000
--- a/app/assets/javascripts/components/containers/status_container.jsx
+++ /dev/null
@@ -1,117 +0,0 @@
-import { connect } from 'react-redux';
-import Status from '../components/status';
-import { makeGetStatus } from '../selectors';
-import {
-  replyCompose,
-  mentionCompose
-} from '../actions/compose';
-import {
-  reblog,
-  favourite,
-  unreblog,
-  unfavourite
-} from '../actions/interactions';
-import {
-  blockAccount,
-  muteAccount
-} from '../actions/accounts';
-import { deleteStatus } from '../actions/statuses';
-import { initReport } from '../actions/reports';
-import { openModal } from '../actions/modal';
-import { createSelector } from 'reselect'
-import { isMobile } from '../is_mobile'
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-
-const messages = defineMessages({
-  deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
-  deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' },
-  blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
-  muteConfirm: { id: 'confirmations.mute.confirm', defaultMessage: 'Mute' },
-});
-
-const makeMapStateToProps = () => {
-  const getStatus = makeGetStatus();
-
-  const mapStateToProps = (state, props) => ({
-    status: getStatus(state, props.id),
-    me: state.getIn(['meta', 'me']),
-    boostModal: state.getIn(['meta', 'boost_modal']),
-    autoPlayGif: state.getIn(['meta', 'auto_play_gif'])
-  });
-
-  return mapStateToProps;
-};
-
-const mapDispatchToProps = (dispatch, { intl }) => ({
-
-  onReply (status, router) {
-    dispatch(replyCompose(status, router));
-  },
-
-  onModalReblog (status) {
-    dispatch(reblog(status));
-  },
-
-  onReblog (status, e) {
-    if (status.get('reblogged')) {
-      dispatch(unreblog(status));
-    } else {
-      if (e.shiftKey || !this.boostModal) {
-        this.onModalReblog(status);
-      } else {
-        dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog }));
-      }
-    }
-  },
-
-  onFavourite (status) {
-    if (status.get('favourited')) {
-      dispatch(unfavourite(status));
-    } else {
-      dispatch(favourite(status));
-    }
-  },
-
-  onDelete (status) {
-    dispatch(openModal('CONFIRM', {
-      message: intl.formatMessage(messages.deleteMessage),
-      confirm: intl.formatMessage(messages.deleteConfirm),
-      onConfirm: () => dispatch(deleteStatus(status.get('id')))
-    }));
-  },
-
-  onMention (account, router) {
-    dispatch(mentionCompose(account, router));
-  },
-
-  onOpenMedia (media, index) {
-    dispatch(openModal('MEDIA', { media, index }));
-  },
-
-  onOpenVideo (media, time) {
-    dispatch(openModal('VIDEO', { media, time }));
-  },
-
-  onBlock (account) {
-    dispatch(openModal('CONFIRM', {
-      message: <FormattedMessage id='confirmations.block.message' defaultMessage='Are you sure you want to block {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
-      confirm: intl.formatMessage(messages.blockConfirm),
-      onConfirm: () => dispatch(blockAccount(account.get('id')))
-    }));
-  },
-
-  onReport (status) {
-    dispatch(initReport(status.get('account'), status));
-  },
-
-  onMute (account) {
-    dispatch(openModal('CONFIRM', {
-      message: <FormattedMessage id='confirmations.mute.message' defaultMessage='Are you sure you want to mute {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
-      confirm: intl.formatMessage(messages.muteConfirm),
-      onConfirm: () => dispatch(muteAccount(account.get('id')))
-    }));
-  },
-
-});
-
-export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Status));
diff --git a/app/assets/javascripts/components/emoji.jsx b/app/assets/javascripts/components/emoji.jsx
deleted file mode 100644
index eee657b86..000000000
--- a/app/assets/javascripts/components/emoji.jsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import emojione from 'emojione';
-
-const toImage = str => shortnameToImage(unicodeToImage(str));
-
-const unicodeToImage = str => {
-  const mappedUnicode = emojione.mapUnicodeToShort();
-
-  return str.replace(emojione.regUnicode, unicodeChar => {
-    if (typeof unicodeChar === 'undefined' || unicodeChar === '' || !(unicodeChar in emojione.jsEscapeMap)) {
-      return unicodeChar;
-    }
-
-    const unicode  = emojione.jsEscapeMap[unicodeChar];
-    const short    = mappedUnicode[unicode];
-    const filename = emojione.emojioneList[short].fname;
-    const alt      = emojione.convert(unicode.toUpperCase());
-
-    return `<img draggable="false" class="emojione" alt="${alt}" src="/emoji/${filename}.svg" />`;
-  });
-};
-
-const shortnameToImage = str => str.replace(emojione.regShortNames, shortname => {
-  if (typeof shortname === 'undefined' || shortname === '' || !(shortname in emojione.emojioneList)) {
-    return shortname;
-  }
-
-  const unicode = emojione.emojioneList[shortname].unicode[emojione.emojioneList[shortname].unicode.length - 1];
-  const alt     = emojione.convert(unicode.toUpperCase());
-
-  return `<img draggable="false" class="emojione" alt="${alt}" src="/emoji/${unicode}.svg" />`;
-});
-
-export default function emojify(text) {
-  return toImage(text);
-};
diff --git a/app/assets/javascripts/components/features/account/components/action_bar.jsx b/app/assets/javascripts/components/features/account/components/action_bar.jsx
deleted file mode 100644
index 772ea3a38..000000000
--- a/app/assets/javascripts/components/features/account/components/action_bar.jsx
+++ /dev/null
@@ -1,92 +0,0 @@
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import DropdownMenu from '../../../components/dropdown_menu';
-import { Link } from 'react-router';
-import { defineMessages, injectIntl, FormattedMessage, FormattedNumber } from 'react-intl';
-
-const messages = defineMessages({
-  mention: { id: 'account.mention', defaultMessage: 'Mention @{name}' },
-  edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
-  unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
-  unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
-  unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
-  block: { id: 'account.block', defaultMessage: 'Block @{name}' },
-  mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
-  follow: { id: 'account.follow', defaultMessage: 'Follow' },
-  report: { id: 'account.report', defaultMessage: 'Report @{name}' },
-  disclaimer: { id: 'account.disclaimer', defaultMessage: 'This user is from another instance. This number may be larger.' }
-});
-
-class ActionBar extends React.PureComponent {
-
-  render () {
-    const { account, me, intl } = this.props;
-
-    let menu = [];
-    let extraInfo = '';
-
-    menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention });
-    menu.push(null);
-
-    if (account.get('id') === me) {
-      menu.push({ text: intl.formatMessage(messages.edit_profile), href: '/settings/profile' });
-    } else {
-      if (account.getIn(['relationship', 'muting'])) {
-        menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.props.onMute });
-      } else {
-        menu.push({ text: intl.formatMessage(messages.mute, { name: account.get('username') }), action: this.props.onMute });
-      }
-
-      if (account.getIn(['relationship', 'blocking'])) {
-        menu.push({ text: intl.formatMessage(messages.unblock, { name: account.get('username') }), action: this.props.onBlock });
-      } else {
-        menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.props.onBlock });
-      }
-
-      menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.props.onReport });
-    }
-
-    if (account.get('acct') !== account.get('username')) {
-      extraInfo = <abbr title={intl.formatMessage(messages.disclaimer)}>*</abbr>;
-    }
-
-    return (
-      <div className='account__action-bar'>
-        <div className='account__action-bar-dropdown'>
-          <DropdownMenu items={menu} icon='bars' size={24} direction="right" />
-        </div>
-
-        <div className='account__action-bar-links'>
-          <Link className='account__action-bar__tab' to={`/accounts/${account.get('id')}`}>
-            <span><FormattedMessage id='account.posts' defaultMessage='Posts' /></span>
-            <strong><FormattedNumber value={account.get('statuses_count')} /> {extraInfo}</strong>
-          </Link>
-
-          <Link className='account__action-bar__tab' to={`/accounts/${account.get('id')}/following`}>
-            <span><FormattedMessage id='account.follows' defaultMessage='Follows' /></span>
-            <strong><FormattedNumber value={account.get('following_count')} /> {extraInfo}</strong>
-          </Link>
-
-          <Link className='account__action-bar__tab' to={`/accounts/${account.get('id')}/followers`}>
-            <span><FormattedMessage id='account.followers' defaultMessage='Followers' /></span>
-            <strong><FormattedNumber value={account.get('followers_count')} /> {extraInfo}</strong>
-          </Link>
-        </div>
-      </div>
-    );
-  }
-
-}
-
-ActionBar.propTypes = {
-  account: ImmutablePropTypes.map.isRequired,
-  me: PropTypes.number.isRequired,
-  onFollow: PropTypes.func,
-  onBlock: PropTypes.func.isRequired,
-  onMention: PropTypes.func.isRequired,
-  onReport: PropTypes.func.isRequired,
-  onMute: PropTypes.func.isRequired,
-  intl: PropTypes.object.isRequired
-};
-
-export default injectIntl(ActionBar);
diff --git a/app/assets/javascripts/components/features/account/components/header.jsx b/app/assets/javascripts/components/features/account/components/header.jsx
deleted file mode 100644
index 958a5206b..000000000
--- a/app/assets/javascripts/components/features/account/components/header.jsx
+++ /dev/null
@@ -1,148 +0,0 @@
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import emojify from '../../../emoji';
-import escapeTextContentForBrowser from 'escape-html';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import IconButton from '../../../components/icon_button';
-import { Motion, spring } from 'react-motion';
-import { connect } from 'react-redux';
-
-const messages = defineMessages({
-  unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
-  follow: { id: 'account.follow', defaultMessage: 'Follow' },
-  requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' }
-});
-
-const makeMapStateToProps = () => {
-  const mapStateToProps = (state, props) => ({
-    autoPlayGif: state.getIn(['meta', 'auto_play_gif'])
-  });
-
-  return mapStateToProps;
-};
-
-class Avatar extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-
-    this.state = {
-      isHovered: false
-    };
-
-    this.handleMouseOver = this.handleMouseOver.bind(this);
-    this.handleMouseOut = this.handleMouseOut.bind(this);
-  }
-
-  handleMouseOver () {
-    if (this.state.isHovered) return;
-    this.setState({ isHovered: true });
-  }
-
-  handleMouseOut () {
-    if (!this.state.isHovered) return;
-    this.setState({ isHovered: false });
-  }
-
-  render () {
-    const { account, autoPlayGif }   = this.props;
-    const { isHovered } = this.state;
-
-    return (
-      <Motion defaultStyle={{ radius: 90 }} style={{ radius: spring(isHovered ? 30 : 90, { stiffness: 180, damping: 12 }) }}>
-        {({ radius }) =>
-          <a
-            href={account.get('url')}
-            className='account__header__avatar'
-            target='_blank'
-            rel='noopener'
-            style={{ borderRadius: `${radius}px`, backgroundImage: `url(${autoPlayGif || isHovered ? account.get('avatar') : account.get('avatar_static')})` }}
-            onMouseOver={this.handleMouseOver}
-            onMouseOut={this.handleMouseOut}
-            onFocus={this.handleMouseOver}
-            onBlur={this.handleMouseOut}
-          />
-        }
-      </Motion>
-    );
-  }
-
-}
-
-Avatar.propTypes = {
-  account: ImmutablePropTypes.map.isRequired,
-  autoPlayGif: PropTypes.bool.isRequired
-};
-
-class Header extends React.Component {
-
-  render () {
-    const { account, me, intl } = this.props;
-
-    if (!account) {
-      return null;
-    }
-
-    let displayName = account.get('display_name');
-    let info        = '';
-    let actionBtn   = '';
-    let lockedIcon  = '';
-
-    if (displayName.length === 0) {
-      displayName = account.get('username');
-    }
-
-    if (me !== account.get('id') && account.getIn(['relationship', 'followed_by'])) {
-      info = <span className='account--follows-info' style={{ position: 'absolute', top: '10px', right: '10px', opacity: '0.7', display: 'inline-block', verticalAlign: 'top', background: 'rgba(0, 0, 0, 0.4)', textTransform: 'uppercase', fontSize: '11px', fontWeight: '500', padding: '4px', borderRadius: '4px' }}><FormattedMessage id='account.follows_you' defaultMessage='Follows you' /></span>
-    }
-
-    if (me !== account.get('id')) {
-      if (account.getIn(['relationship', 'requested'])) {
-        actionBtn = (
-          <div style={{ position: 'absolute', top: '10px', left: '20px' }}>
-            <IconButton size={26} disabled={true} icon='hourglass' title={intl.formatMessage(messages.requested)} />
-          </div>
-        );
-      } else if (!account.getIn(['relationship', 'blocking'])) {
-        actionBtn = (
-          <div style={{ position: 'absolute', top: '10px', left: '20px' }}>
-            <IconButton size={26} icon={account.getIn(['relationship', 'following']) ? 'user-times' : 'user-plus'} active={account.getIn(['relationship', 'following'])} title={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.props.onFollow} />
-          </div>
-        );
-      }
-    }
-
-    if (account.get('locked')) {
-      lockedIcon = <i className='fa fa-lock' />;
-    }
-
-    const content         = { __html: emojify(account.get('note')) };
-    const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) };
-
-    return (
-      <div className='account__header' style={{ backgroundImage: `url(${account.get('header')})` }}>
-        <div style={{ padding: '20px 10px' }}>
-          <Avatar account={account} autoPlayGif={this.props.autoPlayGif} />
-
-          <span style={{ display: 'inline-block', fontSize: '20px', lineHeight: '27px', fontWeight: '500' }} className='account__header__display-name' dangerouslySetInnerHTML={displayNameHTML} />
-          <span className='account__header__username' style={{ fontSize: '14px', fontWeight: '400', display: 'block', marginBottom: '10px' }}>@{account.get('acct')} {lockedIcon}</span>
-          <div style={{ fontSize: '14px' }} className='account__header__content' dangerouslySetInnerHTML={content} />
-
-          {info}
-          {actionBtn}
-        </div>
-      </div>
-    );
-  }
-
-}
-
-Header.propTypes = {
-  account: ImmutablePropTypes.map,
-  me: PropTypes.number.isRequired,
-  onFollow: PropTypes.func.isRequired,
-  intl: PropTypes.object.isRequired,
-  autoPlayGif: PropTypes.bool.isRequired
-};
-
-export default connect(makeMapStateToProps)(injectIntl(Header));
diff --git a/app/assets/javascripts/components/features/account_timeline/components/header.jsx b/app/assets/javascripts/components/features/account_timeline/components/header.jsx
deleted file mode 100644
index fd66c13e0..000000000
--- a/app/assets/javascripts/components/features/account_timeline/components/header.jsx
+++ /dev/null
@@ -1,81 +0,0 @@
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import InnerHeader from '../../account/components/header';
-import ActionBar from '../../account/components/action_bar';
-import MissingIndicator from '../../../components/missing_indicator';
-
-class Header extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.handleFollow = this.handleFollow.bind(this);
-    this.handleBlock = this.handleBlock.bind(this);
-    this.handleMention = this.handleMention.bind(this);
-    this.handleReport = this.handleReport.bind(this);
-    this.handleMute = this.handleMute.bind(this);
-  }
-
-  handleFollow () {
-    this.props.onFollow(this.props.account);
-  }
-
-  handleBlock () {
-    this.props.onBlock(this.props.account);
-  }
-
-  handleMention () {
-    this.props.onMention(this.props.account, this.context.router);
-  }
-
-  handleReport () {
-    this.props.onReport(this.props.account);
-    this.context.router.push('/report');
-  }
-
-  handleMute() {
-    this.props.onMute(this.props.account);
-  }
-
-  render () {
-    const { account, me } = this.props;
-
-    if (account === null) {
-      return <MissingIndicator />;
-    }
-
-    return (
-      <div className='account-timeline__header'>
-        <InnerHeader
-          account={account}
-          me={me}
-          onFollow={this.handleFollow}
-        />
-
-        <ActionBar
-          account={account}
-          me={me}
-          onBlock={this.handleBlock}
-          onMention={this.handleMention}
-          onReport={this.handleReport}
-          onMute={this.handleMute}
-        />
-      </div>
-    );
-  }
-}
-
-Header.propTypes = {
-  account: ImmutablePropTypes.map,
-  me: PropTypes.number.isRequired,
-  onFollow: PropTypes.func.isRequired,
-  onBlock: PropTypes.func.isRequired,
-  onMention: PropTypes.func.isRequired,
-  onReport: PropTypes.func.isRequired,
-  onMute: PropTypes.func.isRequired
-};
-
-Header.contextTypes = {
-  router: PropTypes.object
-};
-
-export default Header;
diff --git a/app/assets/javascripts/components/features/account_timeline/containers/header_container.jsx b/app/assets/javascripts/components/features/account_timeline/containers/header_container.jsx
deleted file mode 100644
index f924e7f5e..000000000
--- a/app/assets/javascripts/components/features/account_timeline/containers/header_container.jsx
+++ /dev/null
@@ -1,75 +0,0 @@
-import { connect } from 'react-redux';
-import { makeGetAccount } from '../../../selectors';
-import Header from '../components/header';
-import {
-  followAccount,
-  unfollowAccount,
-  blockAccount,
-  unblockAccount,
-  muteAccount,
-  unmuteAccount
-} from '../../../actions/accounts';
-import { mentionCompose } from '../../../actions/compose';
-import { initReport } from '../../../actions/reports';
-import { openModal } from '../../../actions/modal';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-
-const messages = defineMessages({
-  blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
-  muteConfirm: { id: 'confirmations.mute.confirm', defaultMessage: 'Mute' }
-});
-
-const makeMapStateToProps = () => {
-  const getAccount = makeGetAccount();
-
-  const mapStateToProps = (state, { accountId }) => ({
-    account: getAccount(state, Number(accountId)),
-    me: state.getIn(['meta', 'me'])
-  });
-
-  return mapStateToProps;
-};
-
-const mapDispatchToProps = (dispatch, { intl }) => ({
-  onFollow (account) {
-    if (account.getIn(['relationship', 'following'])) {
-      dispatch(unfollowAccount(account.get('id')));
-    } else {
-      dispatch(followAccount(account.get('id')));
-    }
-  },
-
-  onBlock (account) {
-    if (account.getIn(['relationship', 'blocking'])) {
-      dispatch(unblockAccount(account.get('id')));
-    } else {
-      dispatch(openModal('CONFIRM', {
-        message: <FormattedMessage id='confirmations.block.message' defaultMessage='Are you sure you want to block {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
-        confirm: intl.formatMessage(messages.blockConfirm),
-        onConfirm: () => dispatch(blockAccount(account.get('id')))
-      }));
-    }
-  },
-
-  onMention (account, router) {
-    dispatch(mentionCompose(account, router));
-  },
-
-  onReport (account) {
-    dispatch(initReport(account));
-  },
-
-  onMute (account) {
-    if (account.getIn(['relationship', 'muting'])) {
-      dispatch(unmuteAccount(account.get('id')));
-    } else {
-      dispatch(openModal('CONFIRM', {
-        message: <FormattedMessage id='confirmations.mute.message' defaultMessage='Are you sure you want to mute {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
-        confirm: intl.formatMessage(messages.muteConfirm),
-        onConfirm: () => dispatch(muteAccount(account.get('id')))
-      }));
-    }
-  }
-});
-
-export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Header));
diff --git a/app/assets/javascripts/components/features/account_timeline/index.jsx b/app/assets/javascripts/components/features/account_timeline/index.jsx
deleted file mode 100644
index a06de3d21..000000000
--- a/app/assets/javascripts/components/features/account_timeline/index.jsx
+++ /dev/null
@@ -1,87 +0,0 @@
-import { connect } from 'react-redux';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import {
-  fetchAccount,
-  fetchAccountTimeline,
-  expandAccountTimeline
-} from '../../actions/accounts';
-import StatusList from '../../components/status_list';
-import LoadingIndicator from '../../components/loading_indicator';
-import Column from '../ui/components/column';
-import HeaderContainer from './containers/header_container';
-import ColumnBackButton from '../../components/column_back_button';
-import Immutable from 'immutable';
-
-const mapStateToProps = (state, props) => ({
-  statusIds: state.getIn(['timelines', 'accounts_timelines', Number(props.params.accountId), 'items'], Immutable.List()),
-  isLoading: state.getIn(['timelines', 'accounts_timelines', Number(props.params.accountId), 'isLoading']),
-  hasMore: !!state.getIn(['timelines', 'accounts_timelines', Number(props.params.accountId), 'next']),
-  me: state.getIn(['meta', 'me'])
-});
-
-class AccountTimeline extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.handleScrollToBottom = this.handleScrollToBottom.bind(this);
-  }
-
-  componentWillMount () {
-    this.props.dispatch(fetchAccount(Number(this.props.params.accountId)));
-    this.props.dispatch(fetchAccountTimeline(Number(this.props.params.accountId)));
-  }
-
-  componentWillReceiveProps(nextProps) {
-    if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) {
-      this.props.dispatch(fetchAccount(Number(nextProps.params.accountId)));
-      this.props.dispatch(fetchAccountTimeline(Number(nextProps.params.accountId)));
-    }
-  }
-
-  handleScrollToBottom () {
-    if (!this.props.isLoading && this.props.hasMore) {
-      this.props.dispatch(expandAccountTimeline(Number(this.props.params.accountId)));
-    }
-  }
-
-  render () {
-    const { statusIds, isLoading, hasMore, me } = this.props;
-
-    if (!statusIds && isLoading) {
-      return (
-        <Column>
-          <LoadingIndicator />
-        </Column>
-      );
-    }
-
-    return (
-      <Column>
-        <ColumnBackButton />
-
-        <StatusList
-          prepend={<HeaderContainer accountId={this.props.params.accountId} />}
-          scrollKey='account_timeline'
-          statusIds={statusIds}
-          isLoading={isLoading}
-          hasMore={hasMore}
-          me={me}
-          onScrollToBottom={this.handleScrollToBottom}
-        />
-      </Column>
-    );
-  }
-
-}
-
-AccountTimeline.propTypes = {
-  params: PropTypes.object.isRequired,
-  dispatch: PropTypes.func.isRequired,
-  statusIds: ImmutablePropTypes.list,
-  isLoading: PropTypes.bool,
-  hasMore: PropTypes.bool,
-  me: PropTypes.number.isRequired
-};
-
-export default connect(mapStateToProps)(AccountTimeline);
diff --git a/app/assets/javascripts/components/features/blocks/index.jsx b/app/assets/javascripts/components/features/blocks/index.jsx
deleted file mode 100644
index 8b973ebb1..000000000
--- a/app/assets/javascripts/components/features/blocks/index.jsx
+++ /dev/null
@@ -1,72 +0,0 @@
-import { connect } from 'react-redux';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import LoadingIndicator from '../../components/loading_indicator';
-import { ScrollContainer } from 'react-router-scroll';
-import Column from '../ui/components/column';
-import ColumnBackButtonSlim from '../../components/column_back_button_slim';
-import AccountContainer from '../../containers/account_container';
-import { fetchBlocks, expandBlocks } from '../../actions/blocks';
-import { defineMessages, injectIntl } from 'react-intl';
-
-const messages = defineMessages({
-  heading: { id: 'column.blocks', defaultMessage: 'Blocked users' }
-});
-
-const mapStateToProps = state => ({
-  accountIds: state.getIn(['user_lists', 'blocks', 'items'])
-});
-
-class Blocks extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.handleScroll = this.handleScroll.bind(this);
-  }
-
-  componentWillMount () {
-    this.props.dispatch(fetchBlocks());
-  }
-
-  handleScroll (e) {
-    const { scrollTop, scrollHeight, clientHeight } = e.target;
-
-    if (scrollTop === scrollHeight - clientHeight) {
-      this.props.dispatch(expandBlocks());
-    }
-  }
-
-  render () {
-    const { intl, accountIds } = this.props;
-
-    if (!accountIds) {
-      return (
-        <Column>
-          <LoadingIndicator />
-        </Column>
-      );
-    }
-
-    return (
-      <Column icon='ban' heading={intl.formatMessage(messages.heading)}>
-        <ColumnBackButtonSlim />
-        <ScrollContainer scrollKey='blocks'>
-          <div className='scrollable' onScroll={this.handleScroll}>
-            {accountIds.map(id =>
-              <AccountContainer key={id} id={id} />
-            )}
-          </div>
-        </ScrollContainer>
-      </Column>
-    );
-  }
-}
-
-Blocks.propTypes = {
-  params: PropTypes.object.isRequired,
-  dispatch: PropTypes.func.isRequired,
-  accountIds: ImmutablePropTypes.list,
-  intl: PropTypes.object.isRequired
-};
-
-export default connect(mapStateToProps)(injectIntl(Blocks));
diff --git a/app/assets/javascripts/components/features/community_timeline/index.jsx b/app/assets/javascripts/components/features/community_timeline/index.jsx
deleted file mode 100644
index 3877888ba..000000000
--- a/app/assets/javascripts/components/features/community_timeline/index.jsx
+++ /dev/null
@@ -1,95 +0,0 @@
-import { connect } from 'react-redux';
-import PropTypes from 'prop-types';
-import StatusListContainer from '../ui/containers/status_list_container';
-import Column from '../ui/components/column';
-import {
-  refreshTimeline,
-  updateTimeline,
-  deleteFromTimelines,
-  connectTimeline,
-  disconnectTimeline
-} from '../../actions/timelines';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import ColumnBackButtonSlim from '../../components/column_back_button_slim';
-import createStream from '../../stream';
-
-const messages = defineMessages({
-  title: { id: 'column.community', defaultMessage: 'Local timeline' }
-});
-
-const mapStateToProps = state => ({
-  hasUnread: state.getIn(['timelines', 'community', 'unread']) > 0,
-  streamingAPIBaseURL: state.getIn(['meta', 'streaming_api_base_url']),
-  accessToken: state.getIn(['meta', 'access_token'])
-});
-
-let subscription;
-
-class CommunityTimeline extends React.PureComponent {
-
-  componentDidMount () {
-    const { dispatch, streamingAPIBaseURL, accessToken } = this.props;
-
-    dispatch(refreshTimeline('community'));
-
-    if (typeof subscription !== 'undefined') {
-      return;
-    }
-
-    subscription = createStream(streamingAPIBaseURL, accessToken, 'public:local', {
-
-      connected () {
-        dispatch(connectTimeline('community'));
-      },
-
-      reconnected () {
-        dispatch(connectTimeline('community'));
-      },
-
-      disconnected () {
-        dispatch(disconnectTimeline('community'));
-      },
-
-      received (data) {
-        switch(data.event) {
-        case 'update':
-          dispatch(updateTimeline('community', JSON.parse(data.payload)));
-          break;
-        case 'delete':
-          dispatch(deleteFromTimelines(data.payload));
-          break;
-        }
-      }
-
-    });
-  }
-
-  componentWillUnmount () {
-    // if (typeof subscription !== 'undefined') {
-    //   subscription.close();
-    //   subscription = null;
-    // }
-  }
-
-  render () {
-    const { intl, hasUnread } = this.props;
-
-    return (
-      <Column icon='users' active={hasUnread} heading={intl.formatMessage(messages.title)}>
-        <ColumnBackButtonSlim />
-        <StatusListContainer {...this.props} scrollKey='community_timeline' type='community' emptyMessage={<FormattedMessage id='empty_column.community' defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!' />} />
-      </Column>
-    );
-  }
-
-}
-
-CommunityTimeline.propTypes = {
-  dispatch: PropTypes.func.isRequired,
-  intl: PropTypes.object.isRequired,
-  streamingAPIBaseURL: PropTypes.string.isRequired,
-  accessToken: PropTypes.string.isRequired,
-  hasUnread: PropTypes.bool
-};
-
-export default connect(mapStateToProps)(injectIntl(CommunityTimeline));
diff --git a/app/assets/javascripts/components/features/compose/components/autosuggest_account.jsx b/app/assets/javascripts/components/features/compose/components/autosuggest_account.jsx
deleted file mode 100644
index bf6a15e5d..000000000
--- a/app/assets/javascripts/components/features/compose/components/autosuggest_account.jsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import Avatar from '../../../components/avatar';
-import DisplayName from '../../../components/display_name';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-
-const AutosuggestAccount = ({ account }) => (
-  <div className='autosuggest-account'>
-    <div className='autosuggest-account-icon'><Avatar src={account.get('avatar')} staticSrc={account.get('avatar_static')} size={18} /></div>
-    <DisplayName account={account} />
-  </div>
-);
-
-AutosuggestAccount.propTypes = {
-  account: ImmutablePropTypes.map.isRequired
-};
-
-export default AutosuggestAccount;
diff --git a/app/assets/javascripts/components/features/compose/components/autosuggest_status.jsx b/app/assets/javascripts/components/features/compose/components/autosuggest_status.jsx
deleted file mode 100644
index 275b3d5a6..000000000
--- a/app/assets/javascripts/components/features/compose/components/autosuggest_status.jsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import { FormattedMessage } from 'react-intl';
-import DisplayName from '../../../components/display_name';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-
-const AutosuggestStatus = ({ status }) => (
-  <div className='autosuggest-status'>
-    <FormattedMessage id='search.status_by' defaultMessage='Status by {name}' values={{ name: <strong>@{status.getIn(['account', 'acct'])}</strong> }} />
-  </div>
-);
-
-AutosuggestStatus.propTypes = {
-  status: ImmutablePropTypes.map.isRequired
-};
-
-export default AutosuggestStatus;
diff --git a/app/assets/javascripts/components/features/compose/components/character_counter.jsx b/app/assets/javascripts/components/features/compose/components/character_counter.jsx
deleted file mode 100644
index 08d2ac4d1..000000000
--- a/app/assets/javascripts/components/features/compose/components/character_counter.jsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import PropTypes from 'prop-types';
-import { length } from 'stringz';
-
-class CharacterCounter extends React.PureComponent {
-
-  checkRemainingText (diff) {
-    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);
-  }
-
-}
-
-CharacterCounter.propTypes = {
-  text: PropTypes.string.isRequired,
-  max: PropTypes.number.isRequired
-}
-
-export default CharacterCounter;
diff --git a/app/assets/javascripts/components/features/compose/components/compose_form.jsx b/app/assets/javascripts/components/features/compose/components/compose_form.jsx
deleted file mode 100644
index 6bc811160..000000000
--- a/app/assets/javascripts/components/features/compose/components/compose_form.jsx
+++ /dev/null
@@ -1,209 +0,0 @@
-import CharacterCounter from './character_counter';
-import Button from '../../../components/button';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import ReplyIndicatorContainer from '../containers/reply_indicator_container';
-import AutosuggestTextarea from '../../../components/autosuggest_textarea';
-import { debounce } from 'react-decoration';
-import UploadButtonContainer from '../containers/upload_button_container';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import Toggle from 'react-toggle';
-import Collapsable from '../../../components/collapsable';
-import SpoilerButtonContainer from '../containers/spoiler_button_container';
-import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
-import SensitiveButtonContainer from '../containers/sensitive_button_container';
-import EmojiPickerDropdown from './emoji_picker_dropdown';
-import UploadFormContainer from '../containers/upload_form_container';
-import TextIconButton from './text_icon_button';
-import WarningContainer from '../containers/warning_container';
-
-const messages = defineMessages({
-  placeholder: { id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' },
-  spoiler_placeholder: { id: 'compose_form.spoiler_placeholder', defaultMessage: 'Content warning' },
-  publish: { id: 'compose_form.publish', defaultMessage: 'Toot' }
-});
-
-class ComposeForm extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.handleChange = this.handleChange.bind(this);
-    this.handleKeyDown = this.handleKeyDown.bind(this);
-    this.handleSubmit = this.handleSubmit.bind(this);
-    this.onSuggestionsClearRequested = this.onSuggestionsClearRequested.bind(this);
-    this.onSuggestionsFetchRequested = this.onSuggestionsFetchRequested.bind(this);
-    this.onSuggestionSelected = this.onSuggestionSelected.bind(this);
-    this.handleChangeSpoilerText = this.handleChangeSpoilerText.bind(this);
-    this.setAutosuggestTextarea = this.setAutosuggestTextarea.bind(this);
-    this.handleEmojiPick = this.handleEmojiPick.bind(this);
-  }
-
-  handleChange (e) {
-    this.props.onChange(e.target.value);
-  }
-
-  handleKeyDown (e) {
-    if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
-      this.handleSubmit();
-    }
-  }
-
-  handleSubmit () {
-    this.autosuggestTextarea.reset();
-    this.props.onSubmit();
-  }
-
-  onSuggestionsClearRequested () {
-    this.props.onClearSuggestions();
-  }
-
-  @debounce(500)
-  onSuggestionsFetchRequested (token) {
-    this.props.onFetchSuggestions(token);
-  }
-
-  onSuggestionSelected (tokenStart, token, value) {
-    this._restoreCaret = null;
-    this.props.onSuggestionSelected(tokenStart, token, value);
-  }
-
-  handleChangeSpoilerText (e) {
-    this.props.onChangeSpoilerText(e.target.value);
-  }
-
-  componentWillReceiveProps (nextProps) {
-    // If this is the update where we've finished uploading,
-    // save the last caret position so we can restore it below!
-    if (!nextProps.is_uploading && this.props.is_uploading) {
-      this._restoreCaret = this.autosuggestTextarea.textarea.selectionStart;
-    }
-  }
-
-  componentDidUpdate (prevProps) {
-    // This statement does several things:
-    // - If we're beginning a reply, and,
-    //     - Replying to zero or one users, places the cursor at the end of the textbox.
-    //     - Replying to more than one user, selects any usernames past the first;
-    //       this provides a convenient shortcut to drop everyone else from the conversation.
-    // - If we've just finished uploading an image, and have a saved caret position,
-    //   restores the cursor to that position after the text changes!
-    if (this.props.focusDate !== prevProps.focusDate || (prevProps.is_uploading && !this.props.is_uploading && typeof this._restoreCaret === 'number')) {
-      let selectionEnd, selectionStart;
-
-      if (this.props.preselectDate !== prevProps.preselectDate) {
-        selectionEnd   = this.props.text.length;
-        selectionStart = this.props.text.search(/\s/) + 1;
-      } else if (typeof this._restoreCaret === 'number') {
-        selectionStart = this._restoreCaret;
-        selectionEnd   = this._restoreCaret;
-      } else {
-        selectionEnd   = this.props.text.length;
-        selectionStart = selectionEnd;
-      }
-
-      this.autosuggestTextarea.textarea.setSelectionRange(selectionStart, selectionEnd);
-      this.autosuggestTextarea.textarea.focus();
-    }
-  }
-
-  setAutosuggestTextarea (c) {
-    this.autosuggestTextarea = c;
-  }
-
-  handleEmojiPick (data) {
-    const position     = this.autosuggestTextarea.textarea.selectionStart;
-    this._restoreCaret = position + data.shortname.length + 1;
-    this.props.onPickEmoji(position, data);
-  }
-
-  render () {
-    const { intl, onPaste } = this.props;
-    const disabled = this.props.is_submitting;
-    const text = [this.props.spoiler_text, this.props.text].join('');
-
-    let publishText    = '';
-    let reply_to_other = false;
-
-    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>;
-    } else {
-      publishText = intl.formatMessage(messages.publish) + (this.props.privacy !== 'unlisted' ? '!' : '');
-    }
-
-    return (
-      <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'/>
-          </div>
-        </Collapsable>
-
-        <WarningContainer />
-
-        <ReplyIndicatorContainer />
-
-        <div className='compose-form__autosuggest-wrapper'>
-          <AutosuggestTextarea
-            ref={this.setAutosuggestTextarea}
-            placeholder={intl.formatMessage(messages.placeholder)}
-            disabled={disabled}
-            value={this.props.text}
-            onChange={this.handleChange}
-            suggestions={this.props.suggestions}
-            onKeyDown={this.handleKeyDown}
-            onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
-            onSuggestionsClearRequested={this.onSuggestionsClearRequested}
-            onSuggestionSelected={this.onSuggestionSelected}
-            onPaste={onPaste}
-          />
-
-          <EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} />
-        </div>
-
-        <div className='compose-form__modifiers'>
-          <UploadFormContainer />
-        </div>
-
-        <div className='compose-form__buttons-wrapper'>
-          <div className='compose-form__buttons'>
-            <UploadButtonContainer />
-            <PrivacyDropdownContainer />
-            <SensitiveButtonContainer />
-            <SpoilerButtonContainer />
-          </div>
-
-          <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 || text.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, "_").length > 500 || (text.length !==0 && text.trim().length === 0)} block /></div>
-          </div>
-        </div>
-      </div>
-    );
-  }
-
-}
-
-ComposeForm.propTypes = {
-  intl: PropTypes.object.isRequired,
-  text: PropTypes.string.isRequired,
-  suggestion_token: PropTypes.string,
-  suggestions: ImmutablePropTypes.list,
-  spoiler: PropTypes.bool,
-  privacy: PropTypes.string,
-  spoiler_text: PropTypes.string,
-  focusDate: PropTypes.instanceOf(Date),
-  preselectDate: PropTypes.instanceOf(Date),
-  is_submitting: PropTypes.bool,
-  is_uploading: PropTypes.bool,
-  me: PropTypes.number,
-  onChange: PropTypes.func.isRequired,
-  onSubmit: PropTypes.func.isRequired,
-  onClearSuggestions: PropTypes.func.isRequired,
-  onFetchSuggestions: PropTypes.func.isRequired,
-  onSuggestionSelected: PropTypes.func.isRequired,
-  onChangeSpoilerText: PropTypes.func.isRequired,
-  onPaste: PropTypes.func.isRequired,
-  onPickEmoji: PropTypes.func.isRequired
-};
-
-export default injectIntl(ComposeForm);
diff --git a/app/assets/javascripts/components/features/compose/components/emoji_picker_dropdown.jsx b/app/assets/javascripts/components/features/compose/components/emoji_picker_dropdown.jsx
deleted file mode 100644
index bc22b074d..000000000
--- a/app/assets/javascripts/components/features/compose/components/emoji_picker_dropdown.jsx
+++ /dev/null
@@ -1,114 +0,0 @@
-import Dropdown, { DropdownTrigger, DropdownContent } from 'react-simple-dropdown';
-import EmojiPicker from 'emojione-picker';
-import PropTypes from 'prop-types';
-import { defineMessages, injectIntl } from 'react-intl';
-
-const messages = defineMessages({
-  emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' },
-  emoji_search: { id: 'emoji_button.search', defaultMessage: 'Search...' },
-  people: { id: 'emoji_button.people', defaultMessage: 'People' },
-  nature: { id: 'emoji_button.nature', defaultMessage: 'Nature' },
-  food: { id: 'emoji_button.food', defaultMessage: 'Food & Drink' },
-  activity: { id: 'emoji_button.activity', defaultMessage: 'Activity' },
-  travel: { id: 'emoji_button.travel', defaultMessage: 'Travel & Places' },
-  objects: { id: 'emoji_button.objects', defaultMessage: 'Objects' },
-  symbols: { id: 'emoji_button.symbols', defaultMessage: 'Symbols' },
-  flags: { id: 'emoji_button.flags', defaultMessage: 'Flags' }
-});
-
-const settings = {
-  imageType: 'png',
-  sprites: false,
-  imagePathPNG: '/emoji/'
-};
-
-const dropdownStyle = {
-  position: 'absolute',
-  right: '5px',
-  top: '5px'
-};
-
-const dropdownTriggerStyle = {
-  display: 'block',
-  fontSize: '24px',
-  lineHeight: '24px',
-  marginLeft: '2px',
-  width: '24px'
-}
-
-class EmojiPickerDropdown extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.setRef = this.setRef.bind(this);
-    this.handleChange = this.handleChange.bind(this);
-  }
-
-  setRef (c) {
-    this.dropdown = c;
-  }
-
-  handleChange (data) {
-    this.dropdown.hide();
-    this.props.onPickEmoji(data);
-  }
-
-  render () {
-    const { intl } = this.props;
-
-    const categories = {
-      people: {
-        title: intl.formatMessage(messages.people),
-        emoji: 'smile',
-      },
-      nature: {
-        title: intl.formatMessage(messages.nature),
-        emoji: 'hamster',
-      },
-      food: {
-        title: intl.formatMessage(messages.food),
-        emoji: 'pizza',
-      },
-      activity: {
-        title: intl.formatMessage(messages.activity),
-        emoji: 'soccer',
-      },
-      travel: {
-        title: intl.formatMessage(messages.travel),
-        emoji: 'earth_americas',
-      },
-      objects: {
-        title: intl.formatMessage(messages.objects),
-        emoji: 'bulb',
-      },
-      symbols: {
-        title: intl.formatMessage(messages.symbols),
-        emoji: 'clock9',
-      },
-      flags: {
-        title: intl.formatMessage(messages.flags),
-        emoji: 'flag_gb',
-      }
-    }
-
-    return (
-      <Dropdown ref={this.setRef} style={dropdownStyle}>
-        <DropdownTrigger className='emoji-button' title={intl.formatMessage(messages.emoji)} style={dropdownTriggerStyle}>
-          <img draggable="false" className="emojione" alt="🙂" src="/emoji/1f602.svg" />
-        </DropdownTrigger>
-
-        <DropdownContent className='dropdown__left'>
-          <EmojiPicker emojione={settings} onChange={this.handleChange} searchPlaceholder={intl.formatMessage(messages.emoji_search)} categories={categories} search={true} />
-        </DropdownContent>
-      </Dropdown>
-    );
-  }
-
-}
-
-EmojiPickerDropdown.propTypes = {
-  intl: PropTypes.object.isRequired,
-  onPickEmoji: PropTypes.func.isRequired
-};
-
-export default injectIntl(EmojiPickerDropdown);
diff --git a/app/assets/javascripts/components/features/compose/components/navigation_bar.jsx b/app/assets/javascripts/components/features/compose/components/navigation_bar.jsx
deleted file mode 100644
index aae0592c6..000000000
--- a/app/assets/javascripts/components/features/compose/components/navigation_bar.jsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import Avatar from '../../../components/avatar';
-import IconButton from '../../../components/icon_button';
-import DisplayName from '../../../components/display_name';
-import Permalink from '../../../components/permalink';
-import { FormattedMessage } from 'react-intl';
-import { Link } from 'react-router';
-
-class NavigationBar extends React.PureComponent {
-
-  render () {
-    return (
-      <div className='navigation-bar'>
-        <Permalink href={this.props.account.get('url')} to={`/accounts/${this.props.account.get('id')}`}><Avatar src={this.props.account.get('avatar')} animate size={40} /></Permalink>
-
-        <div className='navigation-bar__profile'>
-          <Permalink href={this.props.account.get('url')} to={`/accounts/${this.props.account.get('id')}`}>
-            <strong className='navigation-bar__profile-account'>@{this.props.account.get('acct')}</strong>
-          </Permalink>
-          <a href='/settings/profile' className='navigation-bar__profile-edit'><FormattedMessage id='navigation_bar.edit_profile' defaultMessage='Edit profile' /></a>
-        </div>
-      </div>
-    );
-  }
-
-}
-
-NavigationBar.propTypes = {
-  account: ImmutablePropTypes.map.isRequired
-};
-
-export default NavigationBar;
diff --git a/app/assets/javascripts/components/features/compose/components/privacy_dropdown.jsx b/app/assets/javascripts/components/features/compose/components/privacy_dropdown.jsx
deleted file mode 100644
index 82b3454c6..000000000
--- a/app/assets/javascripts/components/features/compose/components/privacy_dropdown.jsx
+++ /dev/null
@@ -1,104 +0,0 @@
-import PropTypes from 'prop-types';
-import { injectIntl, defineMessages } from 'react-intl';
-import IconButton from '../../../components/icon_button';
-
-const messages = defineMessages({
-  public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
-  public_long: { id: 'privacy.public.long', defaultMessage: 'Post to public timelines' },
-  unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
-  unlisted_long: { id: 'privacy.unlisted.long', defaultMessage: 'Do not show in public timelines' },
-  private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' },
-  private_long: { id: 'privacy.private.long', defaultMessage: 'Post to followers only' },
-  direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' },
-  direct_long: { id: 'privacy.direct.long', defaultMessage: 'Post to mentioned users only' },
-  change_privacy: { id: 'privacy.change', defaultMessage: 'Adjust status privacy' }
-});
-
-const iconStyle = {
-  height: null,
-  lineHeight: '27px'
-}
-
-class PrivacyDropdown extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.state = {
-      open: false
-    };
-    this.handleToggle = this.handleToggle.bind(this);
-    this.handleClick = this.handleClick.bind(this);
-    this.onGlobalClick = this.onGlobalClick.bind(this);
-    this.setRef = this.setRef.bind(this);
-  }
-
-  handleToggle () {
-    this.setState({ open: !this.state.open });
-  }
-
-  handleClick (value, e) {
-    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 });
-    }
-  }
-
-  componentDidMount () {
-    window.addEventListener('click', this.onGlobalClick);
-    window.addEventListener('touchstart', this.onGlobalClick);
-  }
-
-  componentWillUnmount () {
-    window.removeEventListener('click', this.onGlobalClick);
-    window.removeEventListener('touchstart', this.onGlobalClick);
-  }
-
-  setRef (c) {
-    this.node = c;
-  }
-
-  render () {
-    const { value, onChange, 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);
-
-    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__dropdown'>
-          {options.map(item =>
-            <div role='button' tabIndex='0' key={item.value} onClick={this.handleClick.bind(this, item.value)} 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}
-              </div>
-            </div>
-          )}
-        </div>
-      </div>
-    );
-  }
-
-}
-
-PrivacyDropdown.propTypes = {
-  value: PropTypes.string.isRequired,
-  onChange: PropTypes.func.isRequired,
-  intl: PropTypes.object.isRequired
-};
-
-export default injectIntl(PrivacyDropdown);
diff --git a/app/assets/javascripts/components/features/compose/components/reply_indicator.jsx b/app/assets/javascripts/components/features/compose/components/reply_indicator.jsx
deleted file mode 100644
index 442ed5a35..000000000
--- a/app/assets/javascripts/components/features/compose/components/reply_indicator.jsx
+++ /dev/null
@@ -1,69 +0,0 @@
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import Avatar from '../../../components/avatar';
-import IconButton from '../../../components/icon_button';
-import DisplayName from '../../../components/display_name';
-import emojify from '../../../emoji';
-import { defineMessages, injectIntl } from 'react-intl';
-
-const messages = defineMessages({
-  cancel: { id: 'reply_indicator.cancel', defaultMessage: 'Cancel' }
-});
-
-class ReplyIndicator extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.handleClick = this.handleClick.bind(this);
-    this.handleAccountClick = this.handleAccountClick.bind(this);
-  }
-
-  handleClick () {
-    this.props.onCancel();
-  }
-
-  handleAccountClick (e) {
-    if (e.button === 0) {
-      e.preventDefault();
-      this.context.router.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
-    }
-  }
-
-  render () {
-    const { status, intl } = this.props;
-
-    if (!status) {
-      return null;
-    }
-
-    const content  = { __html: emojify(status.get('content')) };
-
-    return (
-      <div className='reply-indicator'>
-        <div className='reply-indicator__header'>
-          <div className='reply-indicator__cancel'><IconButton title={intl.formatMessage(messages.cancel)} icon='times' onClick={this.handleClick} /></div>
-
-          <a href={status.getIn(['account', 'url'])} onClick={this.handleAccountClick} className='reply-indicator__display-name'>
-            <div className='reply-indicator__display-avatar'><Avatar size={24} src={status.getIn(['account', 'avatar'])} staticSrc={status.getIn(['account', 'avatar_static'])} /></div>
-            <DisplayName account={status.get('account')} />
-          </a>
-        </div>
-
-        <div className='reply-indicator__content' dangerouslySetInnerHTML={content} />
-      </div>
-    );
-  }
-
-}
-
-ReplyIndicator.contextTypes = {
-  router: PropTypes.object
-};
-
-ReplyIndicator.propTypes = {
-  status: ImmutablePropTypes.map,
-  onCancel: PropTypes.func.isRequired,
-  intl: PropTypes.object.isRequired
-};
-
-export default injectIntl(ReplyIndicator);
diff --git a/app/assets/javascripts/components/features/compose/components/search.jsx b/app/assets/javascripts/components/features/compose/components/search.jsx
deleted file mode 100644
index f62248a33..000000000
--- a/app/assets/javascripts/components/features/compose/components/search.jsx
+++ /dev/null
@@ -1,82 +0,0 @@
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-
-const messages = defineMessages({
-  placeholder: { id: 'search.placeholder', defaultMessage: 'Search' }
-});
-
-class Search extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.handleChange = this.handleChange.bind(this);
-    this.handleKeyDown = this.handleKeyDown.bind(this);
-    this.handleFocus = this.handleFocus.bind(this);
-    this.handleClear = this.handleClear.bind(this);
-  }
-
-  handleChange (e) {
-    this.props.onChange(e.target.value);
-  }
-
-  handleClear (e) {
-    e.preventDefault();
-
-    if (this.props.value.length > 0 || this.props.submitted) {
-      this.props.onClear();
-    }
-  }
-
-  handleKeyDown (e) {
-    if (e.key === 'Enter') {
-      e.preventDefault();
-      this.props.onSubmit();
-    }
-  }
-
-  noop () {
-
-  }
-
-  handleFocus () {
-    this.props.onShow();
-  }
-
-  render () {
-    const { intl, value, submitted } = this.props;
-    const hasValue = value.length > 0 || submitted;
-
-    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}
-        />
-
-        <div role='button' tabIndex='0' className='search__icon' onClick={this.handleClear}>
-          <i className={`fa fa-search ${hasValue ? '' : 'active'}`} />
-          <i aria-label={intl.formatMessage(messages.placeholder)} className={`fa fa-times-circle ${hasValue ? 'active' : ''}`} />
-        </div>
-      </div>
-    );
-  }
-
-}
-
-Search.propTypes = {
-  value: PropTypes.string.isRequired,
-  submitted: PropTypes.bool,
-  onChange: PropTypes.func.isRequired,
-  onSubmit: PropTypes.func.isRequired,
-  onClear: PropTypes.func.isRequired,
-  onShow: PropTypes.func.isRequired,
-  intl: PropTypes.object.isRequired
-};
-
-export default injectIntl(Search);
diff --git a/app/assets/javascripts/components/features/compose/components/search_results.jsx b/app/assets/javascripts/components/features/compose/components/search_results.jsx
deleted file mode 100644
index 00bfd1786..000000000
--- a/app/assets/javascripts/components/features/compose/components/search_results.jsx
+++ /dev/null
@@ -1,65 +0,0 @@
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import AccountContainer from '../../../containers/account_container';
-import StatusContainer from '../../../containers/status_container';
-import { Link } from 'react-router';
-
-class SearchResults extends React.PureComponent {
-
-  render () {
-    const { results } = this.props;
-
-    let accounts, statuses, hashtags;
-    let count = 0;
-
-    if (results.get('accounts') && results.get('accounts').size > 0) {
-      count   += results.get('accounts').size;
-      accounts = (
-        <div className='search-results__section'>
-          {results.get('accounts').map(accountId => <AccountContainer key={accountId} id={accountId} />)}
-        </div>
-      );
-    }
-
-    if (results.get('statuses') && results.get('statuses').size > 0) {
-      count   += results.get('statuses').size;
-      statuses = (
-        <div className='search-results__section'>
-          {results.get('statuses').map(statusId => <StatusContainer key={statusId} id={statusId} />)}
-        </div>
-      );
-    }
-
-    if (results.get('hashtags') && results.get('hashtags').size > 0) {
-      count += results.get('hashtags').size;
-      hashtags = (
-        <div className='search-results__section'>
-          {results.get('hashtags').map(hashtag =>
-            <Link className='search-results__hashtag' to={`/timelines/tag/${hashtag}`}>
-              #{hashtag}
-            </Link>
-          )}
-        </div>
-      );
-    }
-
-    return (
-      <div className='search-results'>
-        <div className='search-results__header'>
-          <FormattedMessage id='search_results.total' defaultMessage='{count, number} {count, plural, one {result} other {results}}' values={{ count }} />
-        </div>
-
-        {accounts}
-        {statuses}
-        {hashtags}
-      </div>
-    );
-  }
-
-}
-
-SearchResults.propTypes = {
-  results: ImmutablePropTypes.map.isRequired
-};
-
-export default SearchResults;
diff --git a/app/assets/javascripts/components/features/compose/components/text_icon_button.jsx b/app/assets/javascripts/components/features/compose/components/text_icon_button.jsx
deleted file mode 100644
index 4252596c2..000000000
--- a/app/assets/javascripts/components/features/compose/components/text_icon_button.jsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import PropTypes from 'prop-types';
-
-class TextIconButton extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.handleClick = this.handleClick.bind(this);
-  }
-
-  handleClick (e) {
-    e.preventDefault();
-    this.props.onClick();
-  }
-
-  render () {
-    const { label, title, active, ariaControls } = this.props;
-
-    return (
-      <button title={title} aria-label={title} className={`text-icon-button ${active ? 'active' : ''}`} aria-expanded={active} onClick={this.handleClick} aria-controls={ariaControls}>
-        {label}
-      </button>
-    );
-  }
-
-}
-
-TextIconButton.propTypes = {
-  label: PropTypes.string.isRequired,
-  title: PropTypes.string,
-  active: PropTypes.bool,
-  onClick: PropTypes.func.isRequired,
-  ariaControls: PropTypes.string
-};
-
-export default TextIconButton;
diff --git a/app/assets/javascripts/components/features/compose/components/upload_button.jsx b/app/assets/javascripts/components/features/compose/components/upload_button.jsx
deleted file mode 100644
index 9b2de0332..000000000
--- a/app/assets/javascripts/components/features/compose/components/upload_button.jsx
+++ /dev/null
@@ -1,60 +0,0 @@
-import IconButton from '../../../components/icon_button';
-import PropTypes from 'prop-types';
-import { defineMessages, injectIntl } from 'react-intl';
-
-const messages = defineMessages({
-  upload: { id: 'upload_button.label', defaultMessage: 'Add media' }
-});
-
-
-const iconStyle = {
-  height: null,
-  lineHeight: '27px'
-}
-
-class UploadButton extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.handleChange = this.handleChange.bind(this);
-    this.handleClick = this.handleClick.bind(this);
-    this.setRef = this.setRef.bind(this);
-  }
-
-  handleChange (e) {
-    if (e.target.files.length > 0) {
-      this.props.onSelectFile(e.target.files);
-    }
-  }
-
-  handleClick () {
-    this.fileElement.click();
-  }
-
-  setRef (c) {
-    this.fileElement = c;
-  }
-
-  render () {
-
-    const { intl, resetFileKey, disabled } = this.props;
-
-    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} onChange={this.handleChange} disabled={disabled} style={{ display: 'none' }} />
-      </div>
-    );
-  }
-
-}
-
-UploadButton.propTypes = {
-  disabled: PropTypes.bool,
-  onSelectFile: PropTypes.func.isRequired,
-  style: PropTypes.object,
-  resetFileKey: PropTypes.number,
-  intl: PropTypes.object.isRequired
-};
-
-export default injectIntl(UploadButton);
diff --git a/app/assets/javascripts/components/features/compose/components/upload_form.jsx b/app/assets/javascripts/components/features/compose/components/upload_form.jsx
deleted file mode 100644
index a2fb7cfe0..000000000
--- a/app/assets/javascripts/components/features/compose/components/upload_form.jsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import IconButton from '../../../components/icon_button';
-import { defineMessages, injectIntl } from 'react-intl';
-import UploadProgressContainer from '../containers/upload_progress_container';
-import { Motion, spring } from 'react-motion';
-
-const messages = defineMessages({
-  undo: { id: 'upload_form.undo', defaultMessage: 'Undo' }
-});
-
-class UploadForm extends React.PureComponent {
-
-  render () {
-    const { intl, media } = this.props;
-
-    const uploads = media.map(attachment =>
-      <div className='compose-form__upload' key={attachment.get('id')}>
-        <Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12 }) }}>
-          {({ scale }) =>
-            <div className='compose-form__upload-thumbnail' style={{ transform: `translateZ(0) scale(${scale})`, backgroundImage: `url(${attachment.get('preview_url')})` }}>
-              <IconButton icon='times' title={intl.formatMessage(messages.undo)} size={36} onClick={this.props.onRemoveFile.bind(this, attachment.get('id'))} />
-            </div>
-          }
-        </Motion>
-      </div>
-    );
-
-    return (
-      <div className='compose-form__upload-wrapper'>
-        <UploadProgressContainer />
-        <div className='compose-form__uploads-wrapper'>{uploads}</div>
-      </div>
-    );
-  }
-
-}
-
-UploadForm.propTypes = {
-  media: ImmutablePropTypes.list.isRequired,
-  onRemoveFile: PropTypes.func.isRequired,
-  intl: PropTypes.object.isRequired
-};
-
-export default injectIntl(UploadForm);
diff --git a/app/assets/javascripts/components/features/compose/components/upload_progress.jsx b/app/assets/javascripts/components/features/compose/components/upload_progress.jsx
deleted file mode 100644
index 8f03bb76a..000000000
--- a/app/assets/javascripts/components/features/compose/components/upload_progress.jsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import PropTypes from 'prop-types';
-import { Motion, spring } from 'react-motion';
-import { FormattedMessage } from 'react-intl';
-
-class UploadProgress extends React.PureComponent {
-
-  render () {
-    const { active, progress } = this.props;
-
-    if (!active) {
-      return null;
-    }
-
-    return (
-      <div className='upload-progress'>
-        <div className='upload-progress__icon'>
-          <i className='fa fa-upload' />
-        </div>
-
-        <div className='upload-progress__message'>
-          <FormattedMessage id='upload_progress.label' defaultMessage='Uploading...' />
-
-          <div className='upload-progress__backdrop'>
-            <Motion defaultStyle={{ width: 0 }} style={{ width: spring(progress) }}>
-              {({ width }) =>
-                <div className='upload-progress__tracker' style={{ width: `${width}%` }} />
-              }
-            </Motion>
-          </div>
-        </div>
-      </div>
-    );
-  }
-
-}
-
-UploadProgress.propTypes = {
-  active: PropTypes.bool,
-  progress: PropTypes.number
-};
-
-export default UploadProgress;
diff --git a/app/assets/javascripts/components/features/compose/components/warning.jsx b/app/assets/javascripts/components/features/compose/components/warning.jsx
deleted file mode 100644
index ff1989755..000000000
--- a/app/assets/javascripts/components/features/compose/components/warning.jsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import PropTypes from 'prop-types';
-
-class Warning extends React.PureComponent {
-
-  constructor (props) {
-    super(props);
-  }
-
-  render () {
-    const { message } = this.props;
-
-    return (
-      <div className='compose-form__warning'>
-        {message}
-      </div>
-    );
-  }
-
-}
-
-Warning.propTypes = {
-  message: PropTypes.node.isRequired
-};
-
-export default Warning;
diff --git a/app/assets/javascripts/components/features/compose/containers/autosuggest_account_container.jsx b/app/assets/javascripts/components/features/compose/containers/autosuggest_account_container.jsx
deleted file mode 100644
index de76a364d..000000000
--- a/app/assets/javascripts/components/features/compose/containers/autosuggest_account_container.jsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import { connect } from 'react-redux';
-import AutosuggestAccount from '../components/autosuggest_account';
-import { makeGetAccount } from '../../../selectors';
-
-const makeMapStateToProps = () => {
-  const getAccount = makeGetAccount();
-
-  const mapStateToProps = (state, { id }) => ({
-    account: getAccount(state, id)
-  });
-
-  return mapStateToProps;
-};
-
-export default connect(makeMapStateToProps)(AutosuggestAccount);
diff --git a/app/assets/javascripts/components/features/compose/containers/autosuggest_status_container.jsx b/app/assets/javascripts/components/features/compose/containers/autosuggest_status_container.jsx
deleted file mode 100644
index ef46eb09c..000000000
--- a/app/assets/javascripts/components/features/compose/containers/autosuggest_status_container.jsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import { connect } from 'react-redux';
-import AutosuggestStatus from '../components/autosuggest_status';
-import { makeGetStatus } from '../../../selectors';
-
-const makeMapStateToProps = () => {
-  const getStatus = makeGetStatus();
-
-  const mapStateToProps = (state, { id }) => ({
-    status: getStatus(state, id)
-  });
-
-  return mapStateToProps;
-};
-
-export default connect(makeMapStateToProps)(AutosuggestStatus);
diff --git a/app/assets/javascripts/components/features/compose/containers/compose_form_container.jsx b/app/assets/javascripts/components/features/compose/containers/compose_form_container.jsx
deleted file mode 100644
index 892183b83..000000000
--- a/app/assets/javascripts/components/features/compose/containers/compose_form_container.jsx
+++ /dev/null
@@ -1,64 +0,0 @@
-import { connect } from 'react-redux';
-import ComposeForm from '../components/compose_form';
-import { uploadCompose } from '../../../actions/compose';
-import {
-  changeCompose,
-  submitCompose,
-  clearComposeSuggestions,
-  fetchComposeSuggestions,
-  selectComposeSuggestion,
-  changeComposeSpoilerText,
-  insertEmojiCompose
-} from '../../../actions/compose';
-
-const mapStateToProps = state => ({
-  text: state.getIn(['compose', 'text']),
-  suggestion_token: state.getIn(['compose', 'suggestion_token']),
-  suggestions: state.getIn(['compose', 'suggestions']),
-  spoiler: state.getIn(['compose', 'spoiler']),
-  spoiler_text: state.getIn(['compose', 'spoiler_text']),
-  privacy: state.getIn(['compose', 'privacy']),
-  focusDate: state.getIn(['compose', 'focusDate']),
-  preselectDate: state.getIn(['compose', 'preselectDate']),
-  is_submitting: state.getIn(['compose', 'is_submitting']),
-  is_uploading: state.getIn(['compose', 'is_uploading']),
-  me: state.getIn(['compose', 'me'])
-});
-
-const mapDispatchToProps = (dispatch) => ({
-
-  onChange (text) {
-    dispatch(changeCompose(text));
-  },
-
-  onSubmit () {
-    dispatch(submitCompose());
-  },
-
-  onClearSuggestions () {
-    dispatch(clearComposeSuggestions());
-  },
-
-  onFetchSuggestions (token) {
-    dispatch(fetchComposeSuggestions(token));
-  },
-
-  onSuggestionSelected (position, token, accountId) {
-    dispatch(selectComposeSuggestion(position, token, accountId));
-  },
-
-  onChangeSpoilerText (checked) {
-    dispatch(changeComposeSpoilerText(checked));
-  },
-
-  onPaste (files) {
-    dispatch(uploadCompose(files));
-  },
-
-  onPickEmoji (position, data) {
-    dispatch(insertEmojiCompose(position, data));
-  },
-
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(ComposeForm);
diff --git a/app/assets/javascripts/components/features/compose/containers/navigation_container.jsx b/app/assets/javascripts/components/features/compose/containers/navigation_container.jsx
deleted file mode 100644
index 0006608da..000000000
--- a/app/assets/javascripts/components/features/compose/containers/navigation_container.jsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import { connect }   from 'react-redux';
-import NavigationBar from '../components/navigation_bar';
-
-const mapStateToProps = (state, props) => {
-  return {
-    account: state.getIn(['accounts', state.getIn(['meta', 'me'])])
-  };
-};
-
-export default connect(mapStateToProps)(NavigationBar);
diff --git a/app/assets/javascripts/components/features/compose/containers/privacy_dropdown_container.jsx b/app/assets/javascripts/components/features/compose/containers/privacy_dropdown_container.jsx
deleted file mode 100644
index 1eee8f84c..000000000
--- a/app/assets/javascripts/components/features/compose/containers/privacy_dropdown_container.jsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import { connect } from 'react-redux';
-import PrivacyDropdown from '../components/privacy_dropdown';
-import { changeComposeVisibility } from '../../../actions/compose';
-
-const mapStateToProps = state => ({
-  value: state.getIn(['compose', 'privacy'])
-});
-
-const mapDispatchToProps = dispatch => ({
-
-  onChange (value) {
-    dispatch(changeComposeVisibility(value));
-  }
-
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(PrivacyDropdown);
diff --git a/app/assets/javascripts/components/features/compose/containers/reply_indicator_container.jsx b/app/assets/javascripts/components/features/compose/containers/reply_indicator_container.jsx
deleted file mode 100644
index 39b48f3b6..000000000
--- a/app/assets/javascripts/components/features/compose/containers/reply_indicator_container.jsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import { connect } from 'react-redux';
-import { cancelReplyCompose } from '../../../actions/compose';
-import { makeGetStatus } from '../../../selectors';
-import ReplyIndicator from '../components/reply_indicator';
-
-const makeMapStateToProps = () => {
-  const getStatus = makeGetStatus();
-
-  const mapStateToProps = (state, props) => ({
-    status: getStatus(state, state.getIn(['compose', 'in_reply_to'])),
-  });
-
-  return mapStateToProps;
-};
-
-const mapDispatchToProps = dispatch => ({
-
-  onCancel () {
-    dispatch(cancelReplyCompose());
-  }
-
-});
-
-export default connect(makeMapStateToProps, mapDispatchToProps)(ReplyIndicator);
diff --git a/app/assets/javascripts/components/features/compose/containers/search_container.jsx b/app/assets/javascripts/components/features/compose/containers/search_container.jsx
deleted file mode 100644
index 906c0c28c..000000000
--- a/app/assets/javascripts/components/features/compose/containers/search_container.jsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import { connect } from 'react-redux';
-import {
-  changeSearch,
-  clearSearch,
-  submitSearch,
-  showSearch
-} from '../../../actions/search';
-import Search from '../components/search';
-
-const mapStateToProps = state => ({
-  value: state.getIn(['search', 'value']),
-  submitted: state.getIn(['search', 'submitted'])
-});
-
-const mapDispatchToProps = dispatch => ({
-
-  onChange (value) {
-    dispatch(changeSearch(value));
-  },
-
-  onClear () {
-    dispatch(clearSearch());
-  },
-
-  onSubmit () {
-    dispatch(submitSearch());
-  },
-
-  onShow () {
-    dispatch(showSearch());
-  }
-
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(Search);
diff --git a/app/assets/javascripts/components/features/compose/containers/search_results_container.jsx b/app/assets/javascripts/components/features/compose/containers/search_results_container.jsx
deleted file mode 100644
index e5911fd38..000000000
--- a/app/assets/javascripts/components/features/compose/containers/search_results_container.jsx
+++ /dev/null
@@ -1,8 +0,0 @@
-import { connect } from 'react-redux';
-import SearchResults from '../components/search_results';
-
-const mapStateToProps = state => ({
-  results: state.getIn(['search', 'results'])
-});
-
-export default connect(mapStateToProps)(SearchResults);
diff --git a/app/assets/javascripts/components/features/compose/containers/sensitive_button_container.jsx b/app/assets/javascripts/components/features/compose/containers/sensitive_button_container.jsx
deleted file mode 100644
index c83598a7d..000000000
--- a/app/assets/javascripts/components/features/compose/containers/sensitive_button_container.jsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import { connect } from 'react-redux';
-import PropTypes from 'prop-types';
-import TextIconButton from '../components/text_icon_button';
-import { changeComposeSensitivity } from '../../../actions/compose';
-import { Motion, spring } from 'react-motion';
-import { injectIntl, defineMessages } from 'react-intl';
-
-const messages = defineMessages({
-  title: { id: 'compose_form.sensitive', defaultMessage: 'Mark media as sensitive' }
-});
-
-const mapStateToProps = state => ({
-  visible: state.getIn(['compose', 'media_attachments']).size > 0,
-  active: state.getIn(['compose', 'sensitive'])
-});
-
-const mapDispatchToProps = dispatch => ({
-
-  onClick () {
-    dispatch(changeComposeSensitivity());
-  }
-
-});
-
-class SensitiveButton extends React.PureComponent {
-
-  render () {
-    const { visible, active, onClick, intl } = this.props;
-
-    return (
-      <Motion defaultStyle={{ scale: 0.87 }} style={{ scale: spring(visible ? 1 : 0.87, { stiffness: 200, damping: 3 }) }}>
-        {({ scale }) =>
-          <div style={{ display: visible ? 'block' : 'none', transform: `translateZ(0) scale(${scale})` }}>
-            <TextIconButton onClick={onClick} label='NSFW' title={intl.formatMessage(messages.title)} active={active} />
-          </div>
-        }
-      </Motion>
-    );
-  }
-
-}
-
-SensitiveButton.propTypes = {
-  visible: PropTypes.bool,
-  active: PropTypes.bool,
-  onClick: PropTypes.func.isRequired,
-  intl: PropTypes.object.isRequired
-};
-
-export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(SensitiveButton));
diff --git a/app/assets/javascripts/components/features/compose/containers/spoiler_button_container.jsx b/app/assets/javascripts/components/features/compose/containers/spoiler_button_container.jsx
deleted file mode 100644
index b1c80fe19..000000000
--- a/app/assets/javascripts/components/features/compose/containers/spoiler_button_container.jsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { connect } from 'react-redux';
-import TextIconButton from '../components/text_icon_button';
-import { changeComposeSpoilerness } from '../../../actions/compose';
-import { injectIntl, defineMessages } from 'react-intl';
-
-const messages = defineMessages({
-  title: { id: 'compose_form.spoiler', defaultMessage: 'Hide text behind warning' }
-});
-
-const mapStateToProps = (state, { intl }) => ({
-  label: 'CW',
-  title: intl.formatMessage(messages.title),
-  active: state.getIn(['compose', 'spoiler']),
-  ariaControls: 'cw-spoiler-input'
-});
-
-const mapDispatchToProps = dispatch => ({
-
-  onClick () {
-    dispatch(changeComposeSpoilerness());
-  }
-
-});
-
-export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(TextIconButton));
diff --git a/app/assets/javascripts/components/features/compose/containers/upload_button_container.jsx b/app/assets/javascripts/components/features/compose/containers/upload_button_container.jsx
deleted file mode 100644
index 78e5312f5..000000000
--- a/app/assets/javascripts/components/features/compose/containers/upload_button_container.jsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { connect } from 'react-redux';
-import UploadButton from '../components/upload_button';
-import { uploadCompose } from '../../../actions/compose';
-
-const mapStateToProps = state => ({
-  disabled: state.getIn(['compose', 'is_uploading']) || (state.getIn(['compose', 'media_attachments']).size > 3 || state.getIn(['compose', 'media_attachments']).some(m => m.get('type') === 'video')),
-  resetFileKey: state.getIn(['compose', 'resetFileKey'])
-});
-
-const mapDispatchToProps = dispatch => ({
-
-  onSelectFile (files) {
-    dispatch(uploadCompose(files));
-  }
-
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(UploadButton);
diff --git a/app/assets/javascripts/components/features/compose/containers/upload_form_container.jsx b/app/assets/javascripts/components/features/compose/containers/upload_form_container.jsx
deleted file mode 100644
index a6a202e17..000000000
--- a/app/assets/javascripts/components/features/compose/containers/upload_form_container.jsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import { connect } from 'react-redux';
-import UploadForm from '../components/upload_form';
-import { undoUploadCompose } from '../../../actions/compose';
-
-const mapStateToProps = (state, props) => ({
-  media: state.getIn(['compose', 'media_attachments']),
-});
-
-const mapDispatchToProps = dispatch => ({
-
-  onRemoveFile (media_id) {
-    dispatch(undoUploadCompose(media_id));
-  }
-
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(UploadForm);
diff --git a/app/assets/javascripts/components/features/compose/containers/upload_progress_container.jsx b/app/assets/javascripts/components/features/compose/containers/upload_progress_container.jsx
deleted file mode 100644
index b0f1d4d19..000000000
--- a/app/assets/javascripts/components/features/compose/containers/upload_progress_container.jsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { connect } from 'react-redux';
-import UploadProgress from '../components/upload_progress';
-
-const mapStateToProps = (state, props) => ({
-  active: state.getIn(['compose', 'is_uploading']),
-  progress: state.getIn(['compose', 'progress'])
-});
-
-export default connect(mapStateToProps)(UploadProgress);
diff --git a/app/assets/javascripts/components/features/compose/containers/warning_container.jsx b/app/assets/javascripts/components/features/compose/containers/warning_container.jsx
deleted file mode 100644
index cd744ed82..000000000
--- a/app/assets/javascripts/components/features/compose/containers/warning_container.jsx
+++ /dev/null
@@ -1,48 +0,0 @@
-import { connect } from 'react-redux';
-import Warning from '../components/warning';
-import { createSelector } from 'reselect';
-import PropTypes from 'prop-types';
-import { FormattedMessage } from 'react-intl';
-
-const getMentionedUsernames = createSelector(state => state.getIn(['compose', 'text']), text => text.match(/(?:^|[^\/\w])@([a-z0-9_]+@[a-z0-9\.\-]+)/ig));
-
-const getMentionedDomains = createSelector(getMentionedUsernames, mentionedUsernamesWithDomains => {
-  return mentionedUsernamesWithDomains !== null ? [...new Set(mentionedUsernamesWithDomains.map(item => item.split('@')[2]))] : [];
-});
-
-const mapStateToProps = state => {
-  const mentionedUsernames = getMentionedUsernames(state);
-  const mentionedUsernamesWithDomains = getMentionedDomains(state);
-
-  return {
-    needsLeakWarning: (state.getIn(['compose', 'privacy']) === 'private' || state.getIn(['compose', 'privacy']) === 'direct') && mentionedUsernames !== null,
-    mentionedDomains: mentionedUsernamesWithDomains,
-    needsLockWarning: state.getIn(['compose', 'privacy']) === 'private' && !state.getIn(['accounts', state.getIn(['meta', 'me']), 'locked'])
-  };
-};
-
-const WarningWrapper = ({ needsLeakWarning, needsLockWarning, mentionedDomains }) => {
-  if (needsLockWarning) {
-    return <Warning message={<FormattedMessage id='compose_form.lock_disclaimer' defaultMessage='Your account is not {locked}. Anyone can follow you to view your follower-only posts.' values={{ locked: <a href='/settings/profile'><FormattedMessage id='compose_form.lock_disclaimer.lock' defaultMessage='locked' /></a> }} />} />;
-  } else if (needsLeakWarning) {
-    return (
-      <Warning
-        message={<FormattedMessage
-          id='compose_form.privacy_disclaimer'
-          defaultMessage='Your private status will be delivered to mentioned users on {domains}. Do you trust {domainsCount, plural, one {that server} other {those servers}}? Post privacy only works on Mastodon instances. If {domains} {domainsCount, plural, one {is not a Mastodon instance} other {are not Mastodon instances}}, there will be no indication that your post is private, and it may be boosted or otherwise made visible to unintended recipients.'
-          values={{ domains: <strong>{mentionedDomains.join(', ')}</strong>, domainsCount: mentionedDomains.length }}
-        />}
-      />
-    );
-  }
-
-  return null;
-};
-
-WarningWrapper.propTypes = {
-  needsLeakWarning: PropTypes.bool,
-  needsLockWarning: PropTypes.bool,
-  mentionedDomains: PropTypes.array.isRequired,
-};
-
-export default connect(mapStateToProps)(WarningWrapper);
diff --git a/app/assets/javascripts/components/features/compose/index.jsx b/app/assets/javascripts/components/features/compose/index.jsx
deleted file mode 100644
index ae1b52ca0..000000000
--- a/app/assets/javascripts/components/features/compose/index.jsx
+++ /dev/null
@@ -1,85 +0,0 @@
-import ComposeFormContainer from './containers/compose_form_container';
-import UploadFormContainer from './containers/upload_form_container';
-import NavigationContainer from './containers/navigation_container';
-import PropTypes from 'prop-types';
-import { connect } from 'react-redux';
-import { mountCompose, unmountCompose } from '../../actions/compose';
-import { Link } from 'react-router';
-import { injectIntl, defineMessages } from 'react-intl';
-import SearchContainer from './containers/search_container';
-import { Motion, spring } from 'react-motion';
-import SearchResultsContainer from './containers/search_results_container';
-
-const messages = defineMessages({
-  start: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
-  public: { id: 'navigation_bar.public_timeline', defaultMessage: 'Federated timeline' },
-  community: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' },
-  preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
-  logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' }
-});
-
-const mapStateToProps = state => ({
-  showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden'])
-});
-
-class Compose extends React.PureComponent {
-
-  componentDidMount () {
-    this.props.dispatch(mountCompose());
-  }
-
-  componentWillUnmount () {
-    this.props.dispatch(unmountCompose());
-  }
-
-  render () {
-    const { withHeader, showSearch, intl } = this.props;
-
-    let header = '';
-
-    if (withHeader) {
-      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>
-          <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' className='drawer__tab' title={intl.formatMessage(messages.public)}><i role="img" aria-label={intl.formatMessage(messages.public)} className='fa fa-fw fa-globe' /></Link>
-          <a href='/settings/preferences' className='drawer__tab' title={intl.formatMessage(messages.preferences)}><i role="img" aria-label={intl.formatMessage(messages.preferences)} className='fa fa-fw fa-cog' /></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>
-      );
-    }
-
-    return (
-      <div className='drawer'>
-        {header}
-
-        <SearchContainer />
-
-        <div className='drawer__pager'>
-          <div className='drawer__inner'>
-            <NavigationContainer />
-            <ComposeFormContainer />
-          </div>
-
-          <Motion defaultStyle={{ x: -100 }} style={{ x: spring(showSearch ? 0 : -100, { stiffness: 210, damping: 20 }) }}>
-            {({ x }) =>
-              <div className='drawer__inner darker' style={{ transform: `translateX(${x}%)`, visibility: x === -100 ? 'hidden' : 'visible' }}>
-                <SearchResultsContainer />
-              </div>
-            }
-          </Motion>
-        </div>
-      </div>
-    );
-  }
-
-}
-
-Compose.propTypes = {
-  dispatch: PropTypes.func.isRequired,
-  withHeader: PropTypes.bool,
-  showSearch: PropTypes.bool,
-  intl: PropTypes.object.isRequired
-};
-
-export default connect(mapStateToProps)(injectIntl(Compose));
diff --git a/app/assets/javascripts/components/features/favourited_statuses/index.jsx b/app/assets/javascripts/components/features/favourited_statuses/index.jsx
deleted file mode 100644
index bc45ace51..000000000
--- a/app/assets/javascripts/components/features/favourited_statuses/index.jsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import { connect } from 'react-redux';
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import LoadingIndicator from '../../components/loading_indicator';
-import { fetchFavouritedStatuses, expandFavouritedStatuses } from '../../actions/favourites';
-import Column from '../ui/components/column';
-import StatusList from '../../components/status_list';
-import ColumnBackButtonSlim from '../../components/column_back_button_slim';
-import { defineMessages, injectIntl } from 'react-intl';
-
-const messages = defineMessages({
-  heading: { id: 'column.favourites', defaultMessage: 'Favourites' }
-});
-
-const mapStateToProps = state => ({
-  statusIds: state.getIn(['status_lists', 'favourites', 'items']),
-  loaded: state.getIn(['status_lists', 'favourites', 'loaded']),
-  me: state.getIn(['meta', 'me'])
-});
-
-class Favourites extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.handleScrollToBottom = this.handleScrollToBottom.bind(this);
-  }
-
-  componentWillMount () {
-    this.props.dispatch(fetchFavouritedStatuses());
-  }
-
-  handleScrollToBottom () {
-    this.props.dispatch(expandFavouritedStatuses());
-  }
-
-  render () {
-    const { statusIds, loaded, intl, me } = this.props;
-
-    if (!loaded) {
-      return (
-        <Column>
-          <LoadingIndicator />
-        </Column>
-      );
-    }
-
-    return (
-      <Column icon='star' heading={intl.formatMessage(messages.heading)}>
-        <ColumnBackButtonSlim />
-        <StatusList {...this.props} onScrollToBottom={this.handleScrollToBottom} />
-      </Column>
-    );
-  }
-
-}
-
-Favourites.propTypes = {
-  params: PropTypes.object.isRequired,
-  dispatch: PropTypes.func.isRequired,
-  statusIds: ImmutablePropTypes.list.isRequired,
-  loaded: PropTypes.bool,
-  intl: PropTypes.object.isRequired,
-  me: PropTypes.number.isRequired
-};
-
-export default connect(mapStateToProps)(injectIntl(Favourites));
diff --git a/app/assets/javascripts/components/features/favourites/index.jsx b/app/assets/javascripts/components/features/favourites/index.jsx
deleted file mode 100644
index bd6cf8a90..000000000
--- a/app/assets/javascripts/components/features/favourites/index.jsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import { connect } from 'react-redux';
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import LoadingIndicator from '../../components/loading_indicator';
-import { fetchFavourites } from '../../actions/interactions';
-import { ScrollContainer } from 'react-router-scroll';
-import AccountContainer from '../../containers/account_container';
-import Column from '../ui/components/column';
-import ColumnBackButton from '../../components/column_back_button';
-
-const mapStateToProps = (state, props) => ({
-  accountIds: state.getIn(['user_lists', 'favourited_by', Number(props.params.statusId)])
-});
-
-class Favourites extends React.PureComponent {
-
-  componentWillMount () {
-    this.props.dispatch(fetchFavourites(Number(this.props.params.statusId)));
-  }
-
-  componentWillReceiveProps(nextProps) {
-    if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) {
-      this.props.dispatch(fetchFavourites(Number(nextProps.params.statusId)));
-    }
-  }
-
-  render () {
-    const { accountIds } = this.props;
-
-    if (!accountIds) {
-      return (
-        <Column>
-          <LoadingIndicator />
-        </Column>
-      );
-    }
-
-    return (
-      <Column>
-        <ColumnBackButton />
-
-        <ScrollContainer scrollKey='favourites'>
-          <div className='scrollable'>
-            {accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)}
-          </div>
-        </ScrollContainer>
-      </Column>
-    );
-  }
-
-}
-
-Favourites.propTypes = {
-  params: PropTypes.object.isRequired,
-  dispatch: PropTypes.func.isRequired,
-  accountIds: ImmutablePropTypes.list
-};
-
-export default connect(mapStateToProps)(Favourites);
diff --git a/app/assets/javascripts/components/features/follow_requests/components/account_authorize.jsx b/app/assets/javascripts/components/features/follow_requests/components/account_authorize.jsx
deleted file mode 100644
index d35a54c12..000000000
--- a/app/assets/javascripts/components/features/follow_requests/components/account_authorize.jsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import Permalink from '../../../components/permalink';
-import Avatar from '../../../components/avatar';
-import DisplayName from '../../../components/display_name';
-import emojify from '../../../emoji';
-import IconButton from '../../../components/icon_button';
-import { defineMessages, injectIntl } from 'react-intl';
-
-const messages = defineMessages({
-  authorize: { id: 'follow_request.authorize', defaultMessage: 'Authorize' },
-  reject: { id: 'follow_request.reject', defaultMessage: 'Reject' }
-});
-
-const AccountAuthorize = ({ intl, account, onAuthorize, onReject }) => {
-  const content = { __html: emojify(account.get('note')) };
-
-  return (
-    <div className='account-authorize__wrapper'>
-      <div className='account-authorize'>
-        <Permalink href={account.get('url')} to={`/accounts/${account.get('id')}`} className='detailed-status__display-name'>
-          <div className='account-authorize__avatar'><Avatar src={account.get('avatar')} staticSrc={account.get('avatar_static')} size={48} /></div>
-          <DisplayName account={account} />
-        </Permalink>
-
-        <div className='account__header__content' dangerouslySetInnerHTML={content} />
-      </div>
-
-      <div className='account--panel'>
-        <div className='account--panel__button'><IconButton title={intl.formatMessage(messages.authorize)} icon='check' onClick={onAuthorize} /></div>
-        <div className='account--panel__button'><IconButton title={intl.formatMessage(messages.reject)} icon='times' onClick={onReject} /></div>
-      </div>
-    </div>
-  )
-};
-
-AccountAuthorize.propTypes = {
-  account: ImmutablePropTypes.map.isRequired,
-  onAuthorize: PropTypes.func.isRequired,
-  onReject: PropTypes.func.isRequired,
-  intl: PropTypes.object.isRequired
-};
-
-export default injectIntl(AccountAuthorize);
diff --git a/app/assets/javascripts/components/features/follow_requests/containers/account_authorize_container.jsx b/app/assets/javascripts/components/features/follow_requests/containers/account_authorize_container.jsx
deleted file mode 100644
index da1e5eaa1..000000000
--- a/app/assets/javascripts/components/features/follow_requests/containers/account_authorize_container.jsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import { connect } from 'react-redux';
-import { makeGetAccount } from '../../../selectors';
-import AccountAuthorize from '../components/account_authorize';
-import { authorizeFollowRequest, rejectFollowRequest } from '../../../actions/accounts';
-
-const makeMapStateToProps = () => {
-  const getAccount = makeGetAccount();
-
-  const mapStateToProps = (state, props) => ({
-    account: getAccount(state, props.id)
-  });
-
-  return mapStateToProps;
-};
-
-const mapDispatchToProps = (dispatch, { id }) => ({
-  onAuthorize (account) {
-    dispatch(authorizeFollowRequest(id));
-  },
-
-  onReject (account) {
-    dispatch(rejectFollowRequest(id));
-  }
-});
-
-export default connect(makeMapStateToProps, mapDispatchToProps)(AccountAuthorize);
diff --git a/app/assets/javascripts/components/features/follow_requests/index.jsx b/app/assets/javascripts/components/features/follow_requests/index.jsx
deleted file mode 100644
index 3dc709654..000000000
--- a/app/assets/javascripts/components/features/follow_requests/index.jsx
+++ /dev/null
@@ -1,72 +0,0 @@
-import { connect } from 'react-redux';
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import LoadingIndicator from '../../components/loading_indicator';
-import { ScrollContainer } from 'react-router-scroll';
-import Column from '../ui/components/column';
-import ColumnBackButtonSlim from '../../components/column_back_button_slim';
-import AccountAuthorizeContainer from './containers/account_authorize_container';
-import { fetchFollowRequests, expandFollowRequests } from '../../actions/accounts';
-import { defineMessages, injectIntl } from 'react-intl';
-
-const messages = defineMessages({
-  heading: { id: 'column.follow_requests', defaultMessage: 'Follow requests' }
-});
-
-const mapStateToProps = state => ({
-  accountIds: state.getIn(['user_lists', 'follow_requests', 'items'])
-});
-
-class FollowRequests extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.handleScroll = this.handleScroll.bind(this);
-  }
-
-  componentWillMount () {
-    this.props.dispatch(fetchFollowRequests());
-  }
-
-  handleScroll (e) {
-    const { scrollTop, scrollHeight, clientHeight } = e.target;
-
-    if (scrollTop === scrollHeight - clientHeight) {
-      this.props.dispatch(expandFollowRequests());
-    }
-  }
-
-  render () {
-    const { intl, accountIds } = this.props;
-
-    if (!accountIds) {
-      return (
-        <Column>
-          <LoadingIndicator />
-        </Column>
-      );
-    }
-
-    return (
-      <Column icon='users' heading={intl.formatMessage(messages.heading)}>
-        <ColumnBackButtonSlim />
-        <ScrollContainer scrollKey='follow_requests'>
-          <div className='scrollable' onScroll={this.handleScroll}>
-            {accountIds.map(id =>
-              <AccountAuthorizeContainer key={id} id={id} />
-            )}
-          </div>
-        </ScrollContainer>
-      </Column>
-    );
-  }
-}
-
-FollowRequests.propTypes = {
-  params: PropTypes.object.isRequired,
-  dispatch: PropTypes.func.isRequired,
-  accountIds: ImmutablePropTypes.list,
-  intl: PropTypes.object.isRequired
-};
-
-export default connect(mapStateToProps)(injectIntl(FollowRequests));
diff --git a/app/assets/javascripts/components/features/followers/index.jsx b/app/assets/javascripts/components/features/followers/index.jsx
deleted file mode 100644
index 2b1e3719e..000000000
--- a/app/assets/javascripts/components/features/followers/index.jsx
+++ /dev/null
@@ -1,90 +0,0 @@
-import { connect } from 'react-redux';
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import LoadingIndicator from '../../components/loading_indicator';
-import {
-  fetchAccount,
-  fetchFollowers,
-  expandFollowers
-} from '../../actions/accounts';
-import { ScrollContainer } from 'react-router-scroll';
-import AccountContainer from '../../containers/account_container';
-import Column from '../ui/components/column';
-import HeaderContainer from '../account_timeline/containers/header_container';
-import LoadMore from '../../components/load_more';
-import ColumnBackButton from '../../components/column_back_button';
-
-const mapStateToProps = (state, props) => ({
-  accountIds: state.getIn(['user_lists', 'followers', Number(props.params.accountId), 'items'])
-});
-
-class Followers extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.handleScroll = this.handleScroll.bind(this);
-    this.handleLoadMore = this.handleLoadMore.bind(this);
-  }
-
-  componentWillMount () {
-    this.props.dispatch(fetchAccount(Number(this.props.params.accountId)));
-    this.props.dispatch(fetchFollowers(Number(this.props.params.accountId)));
-  }
-
-  componentWillReceiveProps(nextProps) {
-    if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) {
-      this.props.dispatch(fetchAccount(Number(nextProps.params.accountId)));
-      this.props.dispatch(fetchFollowers(Number(nextProps.params.accountId)));
-    }
-  }
-
-  handleScroll (e) {
-    const { scrollTop, scrollHeight, clientHeight } = e.target;
-
-    if (scrollTop === scrollHeight - clientHeight) {
-      this.props.dispatch(expandFollowers(Number(this.props.params.accountId)));
-    }
-  }
-
-  handleLoadMore (e) {
-    e.preventDefault();
-    this.props.dispatch(expandFollowers(Number(this.props.params.accountId)));
-  }
-
-  render () {
-    const { accountIds } = this.props;
-
-    if (!accountIds) {
-      return (
-        <Column>
-          <LoadingIndicator />
-        </Column>
-      );
-    }
-
-    return (
-      <Column>
-        <ColumnBackButton />
-
-        <ScrollContainer scrollKey='followers'>
-          <div className='scrollable' onScroll={this.handleScroll}>
-            <div className='followers'>
-              <HeaderContainer accountId={this.props.params.accountId} />
-              {accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)}
-              <LoadMore onClick={this.handleLoadMore} />
-            </div>
-          </div>
-        </ScrollContainer>
-      </Column>
-    );
-  }
-
-}
-
-Followers.propTypes = {
-  params: PropTypes.object.isRequired,
-  dispatch: PropTypes.func.isRequired,
-  accountIds: ImmutablePropTypes.list
-};
-
-export default connect(mapStateToProps)(Followers);
diff --git a/app/assets/javascripts/components/features/following/index.jsx b/app/assets/javascripts/components/features/following/index.jsx
deleted file mode 100644
index 30b320917..000000000
--- a/app/assets/javascripts/components/features/following/index.jsx
+++ /dev/null
@@ -1,90 +0,0 @@
-import { connect } from 'react-redux';
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import LoadingIndicator from '../../components/loading_indicator';
-import {
-  fetchAccount,
-  fetchFollowing,
-  expandFollowing
-} from '../../actions/accounts';
-import { ScrollContainer } from 'react-router-scroll';
-import AccountContainer from '../../containers/account_container';
-import Column from '../ui/components/column';
-import HeaderContainer from '../account_timeline/containers/header_container';
-import LoadMore from '../../components/load_more';
-import ColumnBackButton from '../../components/column_back_button';
-
-const mapStateToProps = (state, props) => ({
-  accountIds: state.getIn(['user_lists', 'following', Number(props.params.accountId), 'items'])
-});
-
-class Following extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.handleScroll = this.handleScroll.bind(this);
-    this.handleLoadMore = this.handleLoadMore.bind(this);
-  }
-
-  componentWillMount () {
-    this.props.dispatch(fetchAccount(Number(this.props.params.accountId)));
-    this.props.dispatch(fetchFollowing(Number(this.props.params.accountId)));
-  }
-
-  componentWillReceiveProps(nextProps) {
-    if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) {
-      this.props.dispatch(fetchAccount(Number(nextProps.params.accountId)));
-      this.props.dispatch(fetchFollowing(Number(nextProps.params.accountId)));
-    }
-  }
-
-  handleScroll (e) {
-    const { scrollTop, scrollHeight, clientHeight } = e.target;
-
-    if (scrollTop === scrollHeight - clientHeight) {
-      this.props.dispatch(expandFollowing(Number(this.props.params.accountId)));
-    }
-  }
-
-  handleLoadMore (e) {
-    e.preventDefault();
-    this.props.dispatch(expandFollowing(Number(this.props.params.accountId)));
-  }
-
-  render () {
-    const { accountIds } = this.props;
-
-    if (!accountIds) {
-      return (
-        <Column>
-          <LoadingIndicator />
-        </Column>
-      );
-    }
-
-    return (
-      <Column>
-        <ColumnBackButton />
-
-        <ScrollContainer scrollKey='following'>
-          <div className='scrollable' onScroll={this.handleScroll}>
-            <div className='following'>
-              <HeaderContainer accountId={this.props.params.accountId} />
-              {accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)}
-              <LoadMore onClick={this.handleLoadMore} />
-            </div>
-          </div>
-        </ScrollContainer>
-      </Column>
-    );
-  }
-
-}
-
-Following.propTypes = {
-  params: PropTypes.object.isRequired,
-  dispatch: PropTypes.func.isRequired,
-  accountIds: ImmutablePropTypes.list
-};
-
-export default connect(mapStateToProps)(Following);
diff --git a/app/assets/javascripts/components/features/generic_not_found/index.jsx b/app/assets/javascripts/components/features/generic_not_found/index.jsx
deleted file mode 100644
index a7afe29b0..000000000
--- a/app/assets/javascripts/components/features/generic_not_found/index.jsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import Column from '../ui/components/column';
-import MissingIndicator from '../../components/missing_indicator';
-
-const GenericNotFound = () => (
-  <Column>
-    <MissingIndicator />
-  </Column>
-);
-
-export default GenericNotFound;
diff --git a/app/assets/javascripts/components/features/getting_started/index.jsx b/app/assets/javascripts/components/features/getting_started/index.jsx
deleted file mode 100644
index bd4920c94..000000000
--- a/app/assets/javascripts/components/features/getting_started/index.jsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import Column from '../ui/components/column';
-import ColumnLink from '../ui/components/column_link';
-import ColumnSubheading from '../ui/components/column_subheading';
-import { Link } from 'react-router';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import { connect } from 'react-redux';
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-
-const messages = defineMessages({
-  heading: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
-  public_timeline: { id: 'navigation_bar.public_timeline', defaultMessage: 'Federated timeline' },
-  navigation_subheading: { id: 'column_subheading.navigation', defaultMessage: 'Navigation'},
-  settings_subheading: { id: 'column_subheading.settings', defaultMessage: 'Settings'},
-  community_timeline: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' },
-  preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
-  follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
-  sign_out: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
-  favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },
-  blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
-  mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
-  info: { id: 'navigation_bar.info', defaultMessage: 'Extended information' }
-});
-
-const mapStateToProps = state => ({
-  me: state.getIn(['accounts', state.getIn(['meta', 'me'])])
-});
-
-const GettingStarted = ({ intl, me }) => {
-  let followRequests = '';
-
-  if (me.get('locked')) {
-    followRequests = <ColumnLink icon='users' text={intl.formatMessage(messages.follow_requests)} to='/follow_requests' />;
-  }
-
-  return (
-    <Column icon='asterisk' heading={intl.formatMessage(messages.heading)} hideHeadingOnMobile={true}>
-      <div className='getting-started__wrapper'>
-        <ColumnSubheading text={intl.formatMessage(messages.navigation_subheading)}/>
-        <ColumnLink icon='users' hideOnMobile={true} text={intl.formatMessage(messages.community_timeline)} to='/timelines/public/local' />
-        <ColumnLink icon='globe' hideOnMobile={true} text={intl.formatMessage(messages.public_timeline)} to='/timelines/public' />
-        <ColumnLink icon='star' text={intl.formatMessage(messages.favourites)} to='/favourites' />
-        {followRequests}
-        <ColumnLink icon='volume-off' text={intl.formatMessage(messages.mutes)} to='/mutes' />
-        <ColumnLink icon='ban' text={intl.formatMessage(messages.blocks)} to='/blocks' />
-        <ColumnSubheading text={intl.formatMessage(messages.settings_subheading)}/>
-        <ColumnLink icon='book' text={intl.formatMessage(messages.info)} href='/about/more' />
-        <ColumnLink icon='cog' text={intl.formatMessage(messages.preferences)} href='/settings/preferences' />
-        <ColumnLink icon='sign-out' text={intl.formatMessage(messages.sign_out)} href='/auth/sign_out' method='delete' />
-      </div>
-
-      <div className='scrollable optionally-scrollable' style={{ display: 'flex', flexDirection: 'column' }}>
-        <div className='static-content getting-started'>
-          <p><FormattedMessage id='getting_started.open_source_notice' defaultMessage='Mastodon is open source software. You can contribute or report issues on GitHub at {github}. {apps}.' values={{ github: <a href="https://github.com/tootsuite/mastodon" target="_blank">tootsuite/mastodon</a>, apps: <a href="https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md" target="_blank"><FormattedMessage id='getting_started.apps' defaultMessage='Various apps are available' /></a> }} /></p>
-        </div>
-      </div>
-    </Column>
-  );
-};
-
-GettingStarted.propTypes = {
-  intl: PropTypes.object.isRequired,
-  me: ImmutablePropTypes.map.isRequired
-};
-
-export default connect(mapStateToProps)(injectIntl(GettingStarted));
diff --git a/app/assets/javascripts/components/features/hashtag_timeline/index.jsx b/app/assets/javascripts/components/features/hashtag_timeline/index.jsx
deleted file mode 100644
index 0575e9214..000000000
--- a/app/assets/javascripts/components/features/hashtag_timeline/index.jsx
+++ /dev/null
@@ -1,89 +0,0 @@
-import { connect } from 'react-redux';
-import PropTypes from 'prop-types';
-import StatusListContainer from '../ui/containers/status_list_container';
-import Column from '../ui/components/column';
-import {
-  refreshTimeline,
-  updateTimeline,
-  deleteFromTimelines
-} from '../../actions/timelines';
-import ColumnBackButtonSlim from '../../components/column_back_button_slim';
-import { FormattedMessage } from 'react-intl';
-import createStream from '../../stream';
-
-const mapStateToProps = state => ({
-  hasUnread: state.getIn(['timelines', 'tag', 'unread']) > 0,
-  streamingAPIBaseURL: state.getIn(['meta', 'streaming_api_base_url']),
-  accessToken: state.getIn(['meta', 'access_token'])
-});
-
-class HashtagTimeline extends React.PureComponent {
-
-  _subscribe (dispatch, id) {
-    const { streamingAPIBaseURL, accessToken } = this.props;
-
-    this.subscription = createStream(streamingAPIBaseURL, accessToken, `hashtag&tag=${id}`, {
-
-      received (data) {
-        switch(data.event) {
-        case 'update':
-          dispatch(updateTimeline('tag', JSON.parse(data.payload)));
-          break;
-        case 'delete':
-          dispatch(deleteFromTimelines(data.payload));
-          break;
-        }
-      }
-
-    });
-  }
-
-  _unsubscribe () {
-    if (typeof this.subscription !== 'undefined') {
-      this.subscription.close();
-      this.subscription = null;
-    }
-  }
-
-  componentDidMount () {
-    const { dispatch } = this.props;
-    const { id } = this.props.params;
-
-    dispatch(refreshTimeline('tag', id));
-    this._subscribe(dispatch, id);
-  }
-
-  componentWillReceiveProps (nextProps) {
-    if (nextProps.params.id !== this.props.params.id) {
-      this.props.dispatch(refreshTimeline('tag', nextProps.params.id));
-      this._unsubscribe();
-      this._subscribe(this.props.dispatch, nextProps.params.id);
-    }
-  }
-
-  componentWillUnmount () {
-    this._unsubscribe();
-  }
-
-  render () {
-    const { id, hasUnread } = this.props.params;
-
-    return (
-      <Column icon='hashtag' active={hasUnread} heading={id}>
-        <ColumnBackButtonSlim />
-        <StatusListContainer scrollKey='hashtag_timeline' type='tag' id={id} emptyMessage={<FormattedMessage id='empty_column.hashtag' defaultMessage='There is nothing in this hashtag yet.' />} />
-      </Column>
-    );
-  }
-
-}
-
-HashtagTimeline.propTypes = {
-  params: PropTypes.object.isRequired,
-  dispatch: PropTypes.func.isRequired,
-  streamingAPIBaseURL: PropTypes.string.isRequired,
-  accessToken: PropTypes.string.isRequired,
-  hasUnread: PropTypes.bool
-};
-
-export default connect(mapStateToProps)(HashtagTimeline);
diff --git a/app/assets/javascripts/components/features/home_timeline/components/column_settings.jsx b/app/assets/javascripts/components/features/home_timeline/components/column_settings.jsx
deleted file mode 100644
index 81a1a0e5b..000000000
--- a/app/assets/javascripts/components/features/home_timeline/components/column_settings.jsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import ColumnCollapsable from '../../../components/column_collapsable';
-import SettingToggle from '../../notifications/components/setting_toggle';
-import SettingText from './setting_text';
-
-const messages = defineMessages({
-  filter_regex: { id: 'home.column_settings.filter_regex', defaultMessage: 'Filter out by regular expressions' },
-  settings: { id: 'home.settings', defaultMessage: 'Column settings' }
-});
-
-class ColumnSettings extends React.PureComponent {
-
-  render () {
-    const { settings, onChange, onSave, intl } = this.props;
-
-    return (
-      <ColumnCollapsable icon='sliders' title={intl.formatMessage(messages.settings)} fullHeight={209} onCollapse={onSave}>
-        <div className='column-settings__outer'>
-          <span className='column-settings__section'><FormattedMessage id='home.column_settings.basic' defaultMessage='Basic' /></span>
-
-          <div className='column-settings__row'>
-            <SettingToggle settings={settings} settingKey={['shows', 'reblog']} onChange={onChange} label={<FormattedMessage id='home.column_settings.show_reblogs' defaultMessage='Show boosts' />} />
-          </div>
-
-          <div className='column-settings__row'>
-            <SettingToggle settings={settings} settingKey={['shows', 'reply']} onChange={onChange} label={<FormattedMessage id='home.column_settings.show_replies' defaultMessage='Show replies' />} />
-          </div>
-
-          <span className='column-settings__section'><FormattedMessage id='home.column_settings.advanced' defaultMessage='Advanced' /></span>
-
-          <div className='column-settings__row'>
-            <SettingText settings={settings} settingKey={['regex', 'body']} onChange={onChange} label={intl.formatMessage(messages.filter_regex)} />
-          </div>
-        </div>
-      </ColumnCollapsable>
-    );
-  }
-
-}
-
-ColumnSettings.propTypes = {
-  settings: ImmutablePropTypes.map.isRequired,
-  onChange: PropTypes.func.isRequired,
-  onSave: PropTypes.func.isRequired,
-  intl: PropTypes.object.isRequired
-}
-
-export default injectIntl(ColumnSettings);
diff --git a/app/assets/javascripts/components/features/home_timeline/components/setting_text.jsx b/app/assets/javascripts/components/features/home_timeline/components/setting_text.jsx
deleted file mode 100644
index 90b4aeb94..000000000
--- a/app/assets/javascripts/components/features/home_timeline/components/setting_text.jsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-
-class SettingText extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.handleChange = this.handleChange.bind(this);
-  }
-
-  handleChange (e) {
-    this.props.onChange(this.props.settingKey, e.target.value)
-  }
-
-  render () {
-    const { settings, settingKey, label } = this.props;
-
-    return (
-      <input
-        className='setting-text'
-        value={settings.getIn(settingKey)}
-        onChange={this.handleChange}
-        placeholder={label}
-      />
-    );
-  }
-
-}
-
-SettingText.propTypes = {
-  settings: ImmutablePropTypes.map.isRequired,
-  settingKey: PropTypes.array.isRequired,
-  label: PropTypes.string.isRequired,
-  onChange: PropTypes.func.isRequired
-};
-
-export default SettingText;
diff --git a/app/assets/javascripts/components/features/home_timeline/containers/column_settings_container.jsx b/app/assets/javascripts/components/features/home_timeline/containers/column_settings_container.jsx
deleted file mode 100644
index 3b3ce19bc..000000000
--- a/app/assets/javascripts/components/features/home_timeline/containers/column_settings_container.jsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import { connect } from 'react-redux';
-import ColumnSettings from '../components/column_settings';
-import { changeSetting, saveSettings } from '../../../actions/settings';
-
-const mapStateToProps = state => ({
-  settings: state.getIn(['settings', 'home'])
-});
-
-const mapDispatchToProps = dispatch => ({
-
-  onChange (key, checked) {
-    dispatch(changeSetting(['home', ...key], checked));
-  },
-
-  onSave () {
-    dispatch(saveSettings());
-  }
-
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(ColumnSettings);
diff --git a/app/assets/javascripts/components/features/home_timeline/index.jsx b/app/assets/javascripts/components/features/home_timeline/index.jsx
deleted file mode 100644
index 52b94690d..000000000
--- a/app/assets/javascripts/components/features/home_timeline/index.jsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import { connect } from 'react-redux';
-import PropTypes from 'prop-types';
-import StatusListContainer from '../ui/containers/status_list_container';
-import Column from '../ui/components/column';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import ColumnSettingsContainer from './containers/column_settings_container';
-import { Link } from 'react-router';
-
-const messages = defineMessages({
-  title: { id: 'column.home', defaultMessage: 'Home' }
-});
-
-const mapStateToProps = state => ({
-  hasUnread: state.getIn(['timelines', 'home', 'unread']) > 0
-});
-
-class HomeTimeline extends React.PureComponent {
-
-  render () {
-    const { intl, hasUnread } = this.props;
-
-    return (
-      <Column icon='home' active={hasUnread} heading={intl.formatMessage(messages.title)}>
-        <ColumnSettingsContainer />
-        <StatusListContainer {...this.props} scrollKey='home_timeline' type='home' emptyMessage={<FormattedMessage id='empty_column.home' defaultMessage="You aren't following anyone yet. Visit {public} or use search to get started and meet other users." values={{ public: <Link to='/timelines/public'><FormattedMessage id='empty_column.home.public_timeline' defaultMessage='the public timeline' /></Link> }} />} />
-      </Column>
-    );
-  }
-
-}
-
-HomeTimeline.propTypes = {
-  intl: PropTypes.object.isRequired,
-  hasUnread: PropTypes.bool
-};
-
-export default connect(mapStateToProps)(injectIntl(HomeTimeline));
diff --git a/app/assets/javascripts/components/features/mutes/index.jsx b/app/assets/javascripts/components/features/mutes/index.jsx
deleted file mode 100644
index 0310fa7f2..000000000
--- a/app/assets/javascripts/components/features/mutes/index.jsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import { connect } from 'react-redux';
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import LoadingIndicator from '../../components/loading_indicator';
-import { ScrollContainer } from 'react-router-scroll';
-import Column from '../ui/components/column';
-import ColumnBackButtonSlim from '../../components/column_back_button_slim';
-import AccountContainer from '../../containers/account_container';
-import { fetchMutes, expandMutes } from '../../actions/mutes';
-import { defineMessages, injectIntl } from 'react-intl';
-
-const messages = defineMessages({
-  heading: { id: 'column.mutes', defaultMessage: 'Muted users' }
-});
-
-const mapStateToProps = state => ({
-  accountIds: state.getIn(['user_lists', 'mutes', 'items'])
-});
-
-class Mutes extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.handleScroll = this.handleScroll.bind(this);
-  }
-
-  componentWillMount () {
-    this.props.dispatch(fetchMutes());
-  }
-
-  handleScroll (e) {
-    const { scrollTop, scrollHeight, clientHeight } = e.target;
-
-    if (scrollTop === scrollHeight - clientHeight) {
-      this.props.dispatch(expandMutes());
-    }
-  }
-
-  render () {
-    const { intl, accountIds } = this.props;
-
-    if (!accountIds) {
-      return (
-        <Column>
-          <LoadingIndicator />
-        </Column>
-      );
-    }
-
-    return (
-      <Column icon='volume-off' heading={intl.formatMessage(messages.heading)}>
-        <ColumnBackButtonSlim />
-        <ScrollContainer scrollKey='mutes'>
-          <div className='scrollable mutes' onScroll={this.handleScroll}>
-            {accountIds.map(id =>
-              <AccountContainer key={id} id={id} />
-            )}
-          </div>
-        </ScrollContainer>
-      </Column>
-    );
-  }
-
-}
-
-Mutes.propTypes = {
-  params: PropTypes.object.isRequired,
-  dispatch: PropTypes.func.isRequired,
-  accountIds: ImmutablePropTypes.list,
-  intl: PropTypes.object.isRequired
-};
-
-export default connect(mapStateToProps)(injectIntl(Mutes));
diff --git a/app/assets/javascripts/components/features/notifications/components/clear_column_button.jsx b/app/assets/javascripts/components/features/notifications/components/clear_column_button.jsx
deleted file mode 100644
index 206b05f91..000000000
--- a/app/assets/javascripts/components/features/notifications/components/clear_column_button.jsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import PropTypes from 'prop-types';
-import { defineMessages, injectIntl } from 'react-intl';
-
-const messages = defineMessages({
-  clear: { id: 'notifications.clear', defaultMessage: 'Clear notifications' }
-});
-
-class ClearColumnButton extends React.Component {
-
-  render () {
-    const { intl } = this.props;
-
-    return (
-      <div role='button' title={intl.formatMessage(messages.clear)} className='column-icon column-icon-clear' tabIndex='0' onClick={this.props.onClick}>
-        <i className='fa fa-eraser' />
-      </div>
-    );
-  }
-}
-
-ClearColumnButton.propTypes = {
-  onClick: PropTypes.func.isRequired,
-  intl: PropTypes.object.isRequired
-};
-
-export default injectIntl(ClearColumnButton);
diff --git a/app/assets/javascripts/components/features/notifications/components/column_settings.jsx b/app/assets/javascripts/components/features/notifications/components/column_settings.jsx
deleted file mode 100644
index 30063010c..000000000
--- a/app/assets/javascripts/components/features/notifications/components/column_settings.jsx
+++ /dev/null
@@ -1,70 +0,0 @@
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import ColumnCollapsable from '../../../components/column_collapsable';
-import SettingToggle from './setting_toggle';
-
-const messages = defineMessages({
-  settings: { id: 'notifications.settings', defaultMessage: 'Column settings' }
-});
-
-class ColumnSettings extends React.PureComponent {
-
-  render () {
-    const { settings, intl, onChange, onSave } = this.props;
-
-    const alertStr = <FormattedMessage id='notifications.column_settings.alert' defaultMessage='Desktop notifications' />;
-    const showStr  = <FormattedMessage id='notifications.column_settings.show' defaultMessage='Show in column' />;
-    const soundStr = <FormattedMessage id='notifications.column_settings.sound' defaultMessage='Play sound' />;
-
-    return (
-      <ColumnCollapsable icon='sliders' title={intl.formatMessage(messages.settings)} fullHeight={616} onCollapse={onSave}>
-        <div className='column-settings__outer'>
-          <span className='column-settings__section'><FormattedMessage id='notifications.column_settings.follow' defaultMessage='New followers:' /></span>
-
-          <div className='column-settings__row'>
-            <SettingToggle settings={settings} settingKey={['alerts', 'follow']} onChange={onChange} label={alertStr} />
-            <SettingToggle settings={settings} settingKey={['shows', 'follow']} onChange={onChange} label={showStr} />
-            <SettingToggle settings={settings} settingKey={['sounds', 'follow']} onChange={onChange} label={soundStr} />
-          </div>
-
-          <span className='column-settings__section'><FormattedMessage id='notifications.column_settings.favourite' defaultMessage='Favourites:' /></span>
-
-          <div className='column-settings__row'>
-            <SettingToggle settings={settings} settingKey={['alerts', 'favourite']} onChange={onChange} label={alertStr} />
-            <SettingToggle settings={settings} settingKey={['shows', 'favourite']} onChange={onChange} label={showStr} />
-            <SettingToggle settings={settings} settingKey={['sounds', 'favourite']} onChange={onChange} label={soundStr} />
-          </div>
-
-          <span className='column-settings__section'><FormattedMessage id='notifications.column_settings.mention' defaultMessage='Mentions:' /></span>
-
-          <div className='column-settings__row'>
-            <SettingToggle settings={settings} settingKey={['alerts', 'mention']} onChange={onChange} label={alertStr} />
-            <SettingToggle settings={settings} settingKey={['shows', 'mention']} onChange={onChange} label={showStr} />
-            <SettingToggle settings={settings} settingKey={['sounds', 'mention']} onChange={onChange} label={soundStr} />
-          </div>
-
-          <span className='column-settings__section'><FormattedMessage id='notifications.column_settings.reblog' defaultMessage='Boosts:' /></span>
-
-          <div className='column-settings__row'>
-            <SettingToggle settings={settings} settingKey={['alerts', 'reblog']} onChange={onChange} label={alertStr} />
-            <SettingToggle settings={settings} settingKey={['shows', 'reblog']} onChange={onChange} label={showStr} />
-            <SettingToggle settings={settings} settingKey={['sounds', 'reblog']} onChange={onChange} label={soundStr} />
-          </div>
-        </div>
-      </ColumnCollapsable>
-    );
-  }
-
-}
-
-ColumnSettings.propTypes = {
-  settings: ImmutablePropTypes.map.isRequired,
-  onChange: PropTypes.func.isRequired,
-  onSave: PropTypes.func.isRequired,
-  intl: PropTypes.shape({
-    formatMessage: PropTypes.func.isRequired
-  }).isRequired
-};
-
-export default injectIntl(ColumnSettings);
diff --git a/app/assets/javascripts/components/features/notifications/components/notification.jsx b/app/assets/javascripts/components/features/notifications/components/notification.jsx
deleted file mode 100644
index 34dd76bb7..000000000
--- a/app/assets/javascripts/components/features/notifications/components/notification.jsx
+++ /dev/null
@@ -1,88 +0,0 @@
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import StatusContainer from '../../../containers/status_container';
-import AccountContainer from '../../../containers/account_container';
-import { FormattedMessage } from 'react-intl';
-import Permalink from '../../../components/permalink';
-import emojify from '../../../emoji';
-import escapeTextContentForBrowser from 'escape-html';
-
-class Notification extends React.PureComponent {
-
-  renderFollow (account, link) {
-    return (
-      <div className='notification notification-follow'>
-        <div className='notification__message'>
-          <div className='notification__favourite-icon-wrapper'>
-            <i className='fa fa-fw fa-user-plus' />
-          </div>
-
-          <FormattedMessage id='notification.follow' defaultMessage='{name} followed you' values={{ name: link }} />
-        </div>
-
-        <AccountContainer id={account.get('id')} withNote={false} />
-      </div>
-    );
-  }
-
-  renderMention (notification) {
-    return <StatusContainer id={notification.get('status')} />;
-  }
-
-  renderFavourite (notification, link) {
-    return (
-      <div className='notification notification-favourite'>
-        <div className='notification__message'>
-          <div className='notification__favourite-icon-wrapper'>
-            <i className='fa fa-fw fa-star star-icon'/>
-          </div>
-
-          <FormattedMessage id='notification.favourite' defaultMessage='{name} favourited your status' values={{ name: link }} />
-        </div>
-
-        <StatusContainer id={notification.get('status')} muted={true} />
-      </div>
-    );
-  }
-
-  renderReblog (notification, link) {
-    return (
-      <div className='notification notification-reblog'>
-        <div className='notification__message'>
-          <div className='notification__favourite-icon-wrapper'>
-            <i className='fa fa-fw fa-retweet' />
-          </div>
-
-          <FormattedMessage id='notification.reblog' defaultMessage='{name} boosted your status' values={{ name: link }} />
-        </div>
-
-        <StatusContainer id={notification.get('status')} muted={true} />
-      </div>
-    );
-  }
-
-  render () { // eslint-disable-line consistent-return
-    const { notification } = this.props;
-    const account          = notification.get('account');
-    const displayName      = account.get('display_name').length > 0 ? account.get('display_name') : account.get('username');
-    const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) };
-    const link             = <Permalink className='notification__display-name' href={account.get('url')} title={account.get('acct')} to={`/accounts/${account.get('id')}`} dangerouslySetInnerHTML={displayNameHTML} />;
-
-    switch(notification.get('type')) {
-    case 'follow':
-      return this.renderFollow(account, link);
-    case 'mention':
-      return this.renderMention(notification);
-    case 'favourite':
-      return this.renderFavourite(notification, link);
-    case 'reblog':
-      return this.renderReblog(notification, link);
-    }
-  }
-
-}
-
-Notification.propTypes = {
-  notification: ImmutablePropTypes.map.isRequired
-};
-
-export default Notification;
diff --git a/app/assets/javascripts/components/features/notifications/components/setting_toggle.jsx b/app/assets/javascripts/components/features/notifications/components/setting_toggle.jsx
deleted file mode 100644
index e9bca5928..000000000
--- a/app/assets/javascripts/components/features/notifications/components/setting_toggle.jsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import Toggle from 'react-toggle';
-
-const SettingToggle = ({ settings, settingKey, label, onChange, htmlFor = '' }) => (
-  <label htmlFor={htmlFor} className='setting-toggle__label'>
-    <Toggle checked={settings.getIn(settingKey)} onChange={(e) => onChange(settingKey, e.target.checked)} />
-    <span className='setting-toggle'>{label}</span>
-  </label>
-);
-
-SettingToggle.propTypes = {
-  settings: ImmutablePropTypes.map.isRequired,
-  settingKey: PropTypes.array.isRequired,
-  label: PropTypes.node.isRequired,
-  onChange: PropTypes.func.isRequired,
-  htmlFor: PropTypes.string
-};
-
-export default SettingToggle;
diff --git a/app/assets/javascripts/components/features/notifications/containers/column_settings_container.jsx b/app/assets/javascripts/components/features/notifications/containers/column_settings_container.jsx
deleted file mode 100644
index bc24c75e0..000000000
--- a/app/assets/javascripts/components/features/notifications/containers/column_settings_container.jsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import { connect } from 'react-redux';
-import ColumnSettings from '../components/column_settings';
-import { changeSetting, saveSettings } from '../../../actions/settings';
-
-const mapStateToProps = state => ({
-  settings: state.getIn(['settings', 'notifications'])
-});
-
-const mapDispatchToProps = dispatch => ({
-
-  onChange (key, checked) {
-    dispatch(changeSetting(['notifications', ...key], checked));
-  },
-
-  onSave () {
-    dispatch(saveSettings());
-  }
-
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(ColumnSettings);
diff --git a/app/assets/javascripts/components/features/notifications/containers/notification_container.jsx b/app/assets/javascripts/components/features/notifications/containers/notification_container.jsx
deleted file mode 100644
index 4ca1b1b7b..000000000
--- a/app/assets/javascripts/components/features/notifications/containers/notification_container.jsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import { connect } from 'react-redux';
-import { makeGetNotification } from '../../../selectors';
-import Notification from '../components/notification';
-
-const makeMapStateToProps = () => {
-  const getNotification = makeGetNotification();
-
-  const mapStateToProps = (state, props) => ({
-    notification: getNotification(state, props.notification, props.accountId)
-  });
-
-  return mapStateToProps;
-};
-
-export default connect(makeMapStateToProps)(Notification);
diff --git a/app/assets/javascripts/components/features/notifications/index.jsx b/app/assets/javascripts/components/features/notifications/index.jsx
deleted file mode 100644
index da3ce2f62..000000000
--- a/app/assets/javascripts/components/features/notifications/index.jsx
+++ /dev/null
@@ -1,142 +0,0 @@
-import { connect } from 'react-redux';
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import Column from '../ui/components/column';
-import { expandNotifications, clearNotifications, scrollTopNotifications } from '../../actions/notifications';
-import NotificationContainer from './containers/notification_container';
-import { ScrollContainer } from 'react-router-scroll';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import ColumnSettingsContainer from './containers/column_settings_container';
-import { createSelector } from 'reselect';
-import Immutable from 'immutable';
-import LoadMore from '../../components/load_more';
-import ClearColumnButton from './components/clear_column_button';
-import { openModal } from '../../actions/modal';
-
-const messages = defineMessages({
-  title: { id: 'column.notifications', defaultMessage: 'Notifications' },
-  clearMessage: { id: 'notifications.clear_confirmation', defaultMessage: 'Are you sure you want to permanently clear all your notifications?' },
-  clearConfirm: { id: 'notifications.clear', defaultMessage: 'Clear notifications' }
-});
-
-const getNotifications = createSelector([
-  state => Immutable.List(state.getIn(['settings', 'notifications', 'shows']).filter(item => !item).keys()),
-  state => state.getIn(['notifications', 'items'])
-], (excludedTypes, notifications) => notifications.filterNot(item => excludedTypes.includes(item.get('type'))));
-
-const mapStateToProps = state => ({
-  notifications: getNotifications(state),
-  isLoading: state.getIn(['notifications', 'isLoading'], true),
-  isUnread: state.getIn(['notifications', 'unread']) > 0
-});
-
-class Notifications extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.handleScroll = this.handleScroll.bind(this);
-    this.handleLoadMore = this.handleLoadMore.bind(this);
-    this.handleClear = this.handleClear.bind(this);
-    this.setRef = this.setRef.bind(this);
-  }
-
-  handleScroll (e) {
-    const { scrollTop, scrollHeight, clientHeight } = e.target;
-    const offset = scrollHeight - scrollTop - clientHeight;
-    this._oldScrollPosition = scrollHeight - scrollTop;
-
-    if (250 > offset && !this.props.isLoading) {
-      this.props.dispatch(expandNotifications());
-    } else if (scrollTop < 100) {
-      this.props.dispatch(scrollTopNotifications(true));
-    } else {
-      this.props.dispatch(scrollTopNotifications(false));
-    }
-  }
-
-  componentDidUpdate (prevProps) {
-    if (this.node.scrollTop > 0 && (prevProps.notifications.size < this.props.notifications.size && prevProps.notifications.first() !== this.props.notifications.first() && !!this._oldScrollPosition)) {
-      this.node.scrollTop = this.node.scrollHeight - this._oldScrollPosition;
-    }
-  }
-
-  handleLoadMore (e) {
-    e.preventDefault();
-    this.props.dispatch(expandNotifications());
-  }
-
-  handleClear () {
-    const { dispatch, intl } = this.props;
-
-    dispatch(openModal('CONFIRM', {
-      message: intl.formatMessage(messages.clearMessage),
-      confirm: intl.formatMessage(messages.clearConfirm),
-      onConfirm: () => dispatch(clearNotifications())
-    }));
-  }
-
-  setRef (c) {
-    this.node = c;
-  }
-
-  render () {
-    const { intl, notifications, shouldUpdateScroll, isLoading, isUnread } = this.props;
-
-    let loadMore       = '';
-    let scrollableArea = '';
-    let unread         = '';
-
-    if (!isLoading && notifications.size > 0) {
-      loadMore = <LoadMore onClick={this.handleLoadMore} />;
-    }
-
-    if (isUnread) {
-      unread = <div className='notifications__unread-indicator' />;
-    }
-
-    if (isLoading || notifications.size > 0) {
-      scrollableArea = (
-        <div className='scrollable' onScroll={this.handleScroll} ref={this.setRef}>
-          {unread}
-
-          <div>
-            {notifications.map(item => <NotificationContainer key={item.get('id')} notification={item} accountId={item.get('account')} />)}
-            {loadMore}
-          </div>
-        </div>
-      );
-    } else {
-      scrollableArea = (
-        <div className='empty-column-indicator' ref={this.setRef}>
-          <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. Interact with others to start the conversation." />
-        </div>
-      );
-    }
-
-    return (
-      <Column icon='bell' active={isUnread} heading={intl.formatMessage(messages.title)}>
-        <ColumnSettingsContainer />
-        <ClearColumnButton onClick={this.handleClear} />
-        <ScrollContainer scrollKey='notifications' shouldUpdateScroll={shouldUpdateScroll}>
-          {scrollableArea}
-        </ScrollContainer>
-      </Column>
-    );
-  }
-
-}
-
-Notifications.propTypes = {
-  notifications: ImmutablePropTypes.list.isRequired,
-  dispatch: PropTypes.func.isRequired,
-  shouldUpdateScroll: PropTypes.func,
-  intl: PropTypes.object.isRequired,
-  isLoading: PropTypes.bool,
-  isUnread: PropTypes.bool
-};
-
-Notifications.defaultProps = {
-  trackScroll: true
-};
-
-export default connect(mapStateToProps)(injectIntl(Notifications));
diff --git a/app/assets/javascripts/components/features/public_timeline/index.jsx b/app/assets/javascripts/components/features/public_timeline/index.jsx
deleted file mode 100644
index 53be13686..000000000
--- a/app/assets/javascripts/components/features/public_timeline/index.jsx
+++ /dev/null
@@ -1,95 +0,0 @@
-import { connect } from 'react-redux';
-import PropTypes from 'prop-types';
-import StatusListContainer from '../ui/containers/status_list_container';
-import Column from '../ui/components/column';
-import {
-  refreshTimeline,
-  updateTimeline,
-  deleteFromTimelines,
-  connectTimeline,
-  disconnectTimeline
-} from '../../actions/timelines';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import ColumnBackButtonSlim from '../../components/column_back_button_slim';
-import createStream from '../../stream';
-
-const messages = defineMessages({
-  title: { id: 'column.public', defaultMessage: 'Federated timeline' }
-});
-
-const mapStateToProps = state => ({
-  hasUnread: state.getIn(['timelines', 'public', 'unread']) > 0,
-  streamingAPIBaseURL: state.getIn(['meta', 'streaming_api_base_url']),
-  accessToken: state.getIn(['meta', 'access_token'])
-});
-
-let subscription;
-
-class PublicTimeline extends React.PureComponent {
-
-  componentDidMount () {
-    const { dispatch, streamingAPIBaseURL, accessToken } = this.props;
-
-    dispatch(refreshTimeline('public'));
-
-    if (typeof subscription !== 'undefined') {
-      return;
-    }
-
-    subscription = createStream(streamingAPIBaseURL, accessToken, 'public', {
-
-      connected () {
-        dispatch(connectTimeline('public'));
-      },
-
-      reconnected () {
-        dispatch(connectTimeline('public'));
-      },
-
-      disconnected () {
-        dispatch(disconnectTimeline('public'));
-      },
-
-      received (data) {
-        switch(data.event) {
-        case 'update':
-          dispatch(updateTimeline('public', JSON.parse(data.payload)));
-          break;
-        case 'delete':
-          dispatch(deleteFromTimelines(data.payload));
-          break;
-        }
-      }
-
-    });
-  }
-
-  componentWillUnmount () {
-    // if (typeof subscription !== 'undefined') {
-    //   subscription.close();
-    //   subscription = null;
-    // }
-  }
-
-  render () {
-    const { intl, hasUnread } = this.props;
-
-    return (
-      <Column icon='globe' active={hasUnread} heading={intl.formatMessage(messages.title)}>
-        <ColumnBackButtonSlim />
-        <StatusListContainer {...this.props} type='public' scrollKey='public_timeline' emptyMessage={<FormattedMessage id='empty_column.public' defaultMessage='There is nothing here! Write something publicly, or manually follow users from other instances to fill it up' />} />
-      </Column>
-    );
-  }
-
-}
-
-PublicTimeline.propTypes = {
-  dispatch: PropTypes.func.isRequired,
-  intl: PropTypes.object.isRequired,
-  streamingAPIBaseURL: PropTypes.string.isRequired,
-  accessToken: PropTypes.string.isRequired,
-  hasUnread: PropTypes.bool
-};
-
-export default connect(mapStateToProps)(injectIntl(PublicTimeline));
diff --git a/app/assets/javascripts/components/features/reblogs/index.jsx b/app/assets/javascripts/components/features/reblogs/index.jsx
deleted file mode 100644
index 5e5671422..000000000
--- a/app/assets/javascripts/components/features/reblogs/index.jsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import { connect } from 'react-redux';
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import LoadingIndicator from '../../components/loading_indicator';
-import { fetchReblogs } from '../../actions/interactions';
-import { ScrollContainer } from 'react-router-scroll';
-import AccountContainer from '../../containers/account_container';
-import Column from '../ui/components/column';
-import ColumnBackButton from '../../components/column_back_button';
-
-const mapStateToProps = (state, props) => ({
-  accountIds: state.getIn(['user_lists', 'reblogged_by', Number(props.params.statusId)])
-});
-
-class Reblogs extends React.PureComponent {
-
-  componentWillMount () {
-    this.props.dispatch(fetchReblogs(Number(this.props.params.statusId)));
-  }
-
-  componentWillReceiveProps(nextProps) {
-    if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) {
-      this.props.dispatch(fetchReblogs(Number(nextProps.params.statusId)));
-    }
-  }
-
-  render () {
-    const { accountIds } = this.props;
-
-    if (!accountIds) {
-      return (
-        <Column>
-          <LoadingIndicator />
-        </Column>
-      );
-    }
-
-    return (
-      <Column>
-        <ColumnBackButton />
-
-        <ScrollContainer scrollKey='reblogs'>
-          <div className='scrollable reblogs'>
-            {accountIds.map(id => <AccountContainer key={id} id={id} withNote={false} />)}
-          </div>
-        </ScrollContainer>
-      </Column>
-    );
-  }
-
-}
-
-Reblogs.propTypes = {
-  params: PropTypes.object.isRequired,
-  dispatch: PropTypes.func.isRequired,
-  accountIds: ImmutablePropTypes.list
-};
-
-export default connect(mapStateToProps)(Reblogs);
diff --git a/app/assets/javascripts/components/features/report/components/status_check_box.jsx b/app/assets/javascripts/components/features/report/components/status_check_box.jsx
deleted file mode 100644
index bc866616a..000000000
--- a/app/assets/javascripts/components/features/report/components/status_check_box.jsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import emojify from '../../../emoji';
-import Toggle from 'react-toggle';
-
-class StatusCheckBox extends React.PureComponent {
-
-  render () {
-    const { status, checked, onToggle, disabled } = this.props;
-    const content = { __html: emojify(status.get('content')) };
-
-    if (status.get('reblog')) {
-      return null;
-    }
-
-    return (
-      <div className='status-check-box'>
-        <div
-          className='status__content'
-          dangerouslySetInnerHTML={content}
-        />
-
-        <div className='status-check-box-toggle'>
-          <Toggle checked={checked} onChange={onToggle} disabled={disabled} />
-        </div>
-      </div>
-    );
-  }
-
-}
-
-StatusCheckBox.propTypes = {
-  status: ImmutablePropTypes.map.isRequired,
-  checked: PropTypes.bool,
-  onToggle: PropTypes.func.isRequired,
-  disabled: PropTypes.bool
-};
-
-export default StatusCheckBox;
diff --git a/app/assets/javascripts/components/features/report/containers/status_check_box_container.jsx b/app/assets/javascripts/components/features/report/containers/status_check_box_container.jsx
deleted file mode 100644
index 67ce9d9f3..000000000
--- a/app/assets/javascripts/components/features/report/containers/status_check_box_container.jsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { connect } from 'react-redux';
-import StatusCheckBox from '../components/status_check_box';
-import { toggleStatusReport } from '../../../actions/reports';
-import Immutable from 'immutable';
-
-const mapStateToProps = (state, { id }) => ({
-  status: state.getIn(['statuses', id]),
-  checked: state.getIn(['reports', 'new', 'status_ids'], Immutable.Set()).includes(id)
-});
-
-const mapDispatchToProps = (dispatch, { id }) => ({
-
-  onToggle (e) {
-    dispatch(toggleStatusReport(id, e.target.checked));
-  }
-
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(StatusCheckBox);
diff --git a/app/assets/javascripts/components/features/report/index.jsx b/app/assets/javascripts/components/features/report/index.jsx
deleted file mode 100644
index 6e3cfcb2a..000000000
--- a/app/assets/javascripts/components/features/report/index.jsx
+++ /dev/null
@@ -1,130 +0,0 @@
-import { connect } from 'react-redux';
-import { cancelReport, changeReportComment, submitReport } from '../../actions/reports';
-import { fetchAccountTimeline } from '../../actions/accounts';
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import Column from '../ui/components/column';
-import Button from '../../components/button';
-import { makeGetAccount } from '../../selectors';
-import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
-import StatusCheckBox from './containers/status_check_box_container';
-import Immutable from 'immutable';
-import ColumnBackButtonSlim from '../../components/column_back_button_slim';
-
-const messages = defineMessages({
-  heading: { id: 'report.heading', defaultMessage: 'New report' },
-  placeholder: { id: 'report.placeholder', defaultMessage: 'Additional comments' },
-  submit: { id: 'report.submit', defaultMessage: 'Submit' }
-});
-
-const makeMapStateToProps = () => {
-  const getAccount = makeGetAccount();
-
-  const mapStateToProps = state => {
-    const accountId = state.getIn(['reports', 'new', 'account_id']);
-
-    return {
-      isSubmitting: state.getIn(['reports', 'new', 'isSubmitting']),
-      account: getAccount(state, accountId),
-      comment: state.getIn(['reports', 'new', 'comment']),
-      statusIds: Immutable.OrderedSet(state.getIn(['timelines', 'accounts_timelines', accountId, 'items'])).union(state.getIn(['reports', 'new', 'status_ids']))
-    };
-  };
-
-  return mapStateToProps;
-};
-
-class Report extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.handleCommentChange = this.handleCommentChange.bind(this);
-    this.handleSubmit = this.handleSubmit.bind(this);
-  }
-
-  componentWillMount () {
-    if (!this.props.account) {
-      this.context.router.replace('/');
-    }
-  }
-
-  componentDidMount () {
-    if (!this.props.account) {
-      return;
-    }
-
-    this.props.dispatch(fetchAccountTimeline(this.props.account.get('id')));
-  }
-
-  componentWillReceiveProps (nextProps) {
-    if (this.props.account !== nextProps.account && nextProps.account) {
-      this.props.dispatch(fetchAccountTimeline(nextProps.account.get('id')));
-    }
-  }
-
-  handleCommentChange (e) {
-    this.props.dispatch(changeReportComment(e.target.value));
-  }
-
-  handleSubmit () {
-    this.props.dispatch(submitReport());
-    this.context.router.replace('/');
-  }
-
-  render () {
-    const { account, comment, intl, statusIds, isSubmitting } = this.props;
-
-    if (!account) {
-      return null;
-    }
-
-    return (
-      <Column heading={intl.formatMessage(messages.heading)} icon='flag'>
-        <ColumnBackButtonSlim />
-
-        <div className='report scrollable'>
-          <div className='report__target'>
-            <FormattedMessage id='report.target' defaultMessage='Reporting' />
-            <strong>{account.get('acct')}</strong>
-          </div>
-
-          <div className='scrollable report__statuses'>
-            <div>
-              {statusIds.map(statusId => <StatusCheckBox id={statusId} key={statusId} disabled={isSubmitting} />)}
-            </div>
-          </div>
-
-          <div className='report__textarea-wrapper'>
-            <textarea
-              className='report__textarea'
-              placeholder={intl.formatMessage(messages.placeholder)}
-              value={comment}
-              onChange={this.handleCommentChange}
-              disabled={isSubmitting}
-            />
-
-            <div className='report__submit'>
-              <div className='report__submit-button'><Button disabled={isSubmitting} text={intl.formatMessage(messages.submit)} onClick={this.handleSubmit} /></div>
-            </div>
-          </div>
-        </div>
-      </Column>
-    );
-  }
-
-}
-
-Report.contextTypes = {
-  router: PropTypes.object
-};
-
-Report.propTypes = {
-  isSubmitting: PropTypes.bool,
-  account: ImmutablePropTypes.map,
-  statusIds: ImmutablePropTypes.orderedSet.isRequired,
-  comment: PropTypes.string.isRequired,
-  dispatch: PropTypes.func.isRequired,
-  intl: PropTypes.object.isRequired
-};
-
-export default connect(makeMapStateToProps)(injectIntl(Report));
diff --git a/app/assets/javascripts/components/features/status/components/action_bar.jsx b/app/assets/javascripts/components/features/status/components/action_bar.jsx
deleted file mode 100644
index 1e0b3f74d..000000000
--- a/app/assets/javascripts/components/features/status/components/action_bar.jsx
+++ /dev/null
@@ -1,101 +0,0 @@
-import PropTypes from 'prop-types';
-import IconButton from '../../../components/icon_button';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import DropdownMenu from '../../../components/dropdown_menu';
-import { defineMessages, injectIntl } from 'react-intl';
-
-const messages = defineMessages({
-  delete: { id: 'status.delete', defaultMessage: 'Delete' },
-  mention: { id: 'status.mention', defaultMessage: 'Mention @{name}' },
-  reply: { id: 'status.reply', defaultMessage: 'Reply' },
-  reblog: { id: 'status.reblog', defaultMessage: 'Boost' },
-  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}' }
-});
-
-class ActionBar extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.handleReplyClick = this.handleReplyClick.bind(this);
-    this.handleReblogClick = this.handleReblogClick.bind(this);
-    this.handleFavouriteClick = this.handleFavouriteClick.bind(this);
-    this.handleDeleteClick = this.handleDeleteClick.bind(this);
-    this.handleMentionClick = this.handleMentionClick.bind(this);
-    this.handleReport = this.handleReport.bind(this);
-  }
-
-  handleReplyClick () {
-    this.props.onReply(this.props.status);
-  }
-
-  handleReblogClick (e) {
-    this.props.onReblog(this.props.status, e);
-  }
-
-  handleFavouriteClick () {
-    this.props.onFavourite(this.props.status);
-  }
-
-  handleDeleteClick () {
-    this.props.onDelete(this.props.status);
-  }
-
-  handleMentionClick () {
-    this.props.onMention(this.props.status.get('account'), this.context.router);
-  }
-
-  handleReport () {
-    this.props.onReport(this.props.status);
-    this.context.router.push('/report');
-  }
-
-  render () {
-    const { status, me, intl } = this.props;
-
-    let menu = [];
-
-    if (me === status.getIn(['account', 'id'])) {
-      menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick });
-    } else {
-      menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick });
-      menu.push(null);
-      menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport });
-    }
-
-    let reblogIcon = 'retweet';
-    if (status.get('visibility') === 'direct') reblogIcon = 'envelope';
-    else if (status.get('visibility') === 'private') reblogIcon = 'lock';
-
-    let reblog_disabled = (status.get('visibility') === 'direct' || status.get('visibility') === 'private');
-
-    return (
-      <div className='detailed-status__action-bar'>
-        <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={true} active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} activeStyle={{ color: '#ca8f04' }} /></div>
-        <div className='detailed-status__button'><DropdownMenu size={18} icon='ellipsis-h' items={menu} direction="left" ariaLabel="More" /></div>
-      </div>
-    );
-  }
-
-}
-
-ActionBar.contextTypes = {
-  router: PropTypes.object
-};
-
-ActionBar.propTypes = {
-  status: ImmutablePropTypes.map.isRequired,
-  onReply: PropTypes.func.isRequired,
-  onReblog: PropTypes.func.isRequired,
-  onFavourite: PropTypes.func.isRequired,
-  onDelete: PropTypes.func.isRequired,
-  onMention: PropTypes.func.isRequired,
-  onReport: PropTypes.func,
-  me: PropTypes.number.isRequired,
-  intl: PropTypes.object.isRequired
-};
-
-export default injectIntl(ActionBar);
diff --git a/app/assets/javascripts/components/features/status/components/card.jsx b/app/assets/javascripts/components/features/status/components/card.jsx
deleted file mode 100644
index a5ce7f08a..000000000
--- a/app/assets/javascripts/components/features/status/components/card.jsx
+++ /dev/null
@@ -1,95 +0,0 @@
-import ImmutablePropTypes from 'react-immutable-proptypes';
-
-const hostStyle = {
-  display: 'block',
-  marginTop: '5px',
-  fontSize: '13px'
-};
-
-const getHostname = url => {
-  const parser = document.createElement('a');
-  parser.href = url;
-  return parser.hostname;
-};
-
-class Card extends React.PureComponent {
-
-  renderLink () {
-    const { card } = this.props;
-
-    let image    = '';
-    let provider = card.get('provider_name');
-
-    if (card.get('image')) {
-      image = (
-        <div className='status-card__image'>
-          <img src={card.get('image')} alt={card.get('title')} className='status-card__image-image' />
-        </div>
-      );
-    }
-
-    if (provider.length < 1) {
-      provider = getHostname(card.get('url'))
-    }
-
-    return (
-      <a href={card.get('url')} className='status-card' target='_blank' rel='noopener'>
-        {image}
-
-        <div className='status-card__content'>
-          <strong className='status-card__title' title={card.get('title')}>{card.get('title')}</strong>
-          <p className='status-card__description'>{(card.get('description') || '').substring(0, 50)}</p>
-          <span className='status-card__host' style={hostStyle}>{provider}</span>
-        </div>
-      </a>
-    );
-  }
-
-  renderPhoto () {
-    const { card } = this.props;
-
-    return (
-      <a href={card.get('url')} className='status-card-photo' target='_blank' rel='noopener'>
-        <img src={card.get('url')} alt={card.get('title')} width={card.get('width')} height={card.get('height')} />
-      </a>
-    );
-  }
-
-  renderVideo () {
-    const { card } = this.props;
-    const content  = { __html: card.get('html') };
-
-    return (
-      <div
-        className='status-card-video'
-        dangerouslySetInnerHTML={content}
-      />
-    );
-  }
-
-  render () {
-    const { card } = this.props;
-
-    if (card === null) {
-      return null;
-    }
-
-    switch(card.get('type')) {
-    case 'link':
-      return this.renderLink();
-    case 'photo':
-      return this.renderPhoto();
-    case 'video':
-      return this.renderVideo();
-    case 'rich':
-    default:
-      return null;
-    }
-  }
-}
-
-Card.propTypes = {
-  card: ImmutablePropTypes.map
-};
-
-export default Card;
diff --git a/app/assets/javascripts/components/features/status/components/detailed_status.jsx b/app/assets/javascripts/components/features/status/components/detailed_status.jsx
deleted file mode 100644
index 0e2a7c17e..000000000
--- a/app/assets/javascripts/components/features/status/components/detailed_status.jsx
+++ /dev/null
@@ -1,94 +0,0 @@
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import Avatar from '../../../components/avatar';
-import DisplayName from '../../../components/display_name';
-import StatusContent from '../../../components/status_content';
-import MediaGallery from '../../../components/media_gallery';
-import VideoPlayer from '../../../components/video_player';
-import AttachmentList from '../../../components/attachment_list';
-import { Link } from 'react-router';
-import { FormattedDate, FormattedNumber } from 'react-intl';
-import CardContainer from '../containers/card_container';
-
-class DetailedStatus extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.handleAccountClick = this.handleAccountClick.bind(this);
-  }
-
-  handleAccountClick (e) {
-    if (e.button === 0) {
-      e.preventDefault();
-      this.context.router.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
-    }
-
-    e.stopPropagation();
-  }
-
-  render () {
-    const status = this.props.status.get('reblog') ? this.props.status.get('reblog') : this.props.status;
-
-    let media           = '';
-    let applicationLink = '';
-
-    if (status.get('media_attachments').size > 0) {
-      if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) {
-        media = <AttachmentList media={status.get('media_attachments')} />;
-      } else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
-        media = <VideoPlayer sensitive={status.get('sensitive')} media={status.getIn(['media_attachments', 0])} width={300} height={150} onOpenVideo={this.props.onOpenVideo} autoplay />;
-      } else {
-        media = <MediaGallery sensitive={status.get('sensitive')} media={status.get('media_attachments')} height={300} onOpenMedia={this.props.onOpenMedia} autoPlayGif={this.props.autoPlayGif} />;
-      }
-    } else if (status.get('spoiler_text').length === 0) {
-      media = <CardContainer statusId={status.get('id')} />;
-    }
-
-    if (status.get('application')) {
-      applicationLink = <span> · <a className='detailed-status__application' href={status.getIn(['application', 'website'])} target='_blank' rel='noopener'>{status.getIn(['application', 'name'])}</a></span>;
-    }
-
-    return (
-      <div className='detailed-status'>
-        <a href={status.getIn(['account', 'url'])} onClick={this.handleAccountClick} className='detailed-status__display-name'>
-          <div className='detailed-status__display-avatar'><Avatar src={status.getIn(['account', 'avatar'])} staticSrc={status.getIn(['account', 'avatar_static'])} size={48} /></div>
-          <DisplayName account={status.get('account')} />
-        </a>
-
-        <StatusContent status={status} />
-
-        {media}
-
-        <div className='detailed-status__meta'>
-          <a className='detailed-status__datetime' href={status.get('url')} target='_blank' rel='noopener'>
-            <FormattedDate value={new Date(status.get('created_at'))} hour12={false} year='numeric' month='short' day='2-digit' hour='2-digit' minute='2-digit' />
-          </a>{applicationLink} · <Link to={`/statuses/${status.get('id')}/reblogs`} className='detailed-status__link'>
-            <i className='fa fa-retweet' />
-            <span className='detailed-status__reblogs'>
-              <FormattedNumber value={status.get('reblogs_count')} />
-            </span>
-          </Link> · <Link to={`/statuses/${status.get('id')}/favourites`} className='detailed-status__link'>
-            <i className='fa fa-star' />
-            <span className='detailed-status__favorites'>
-              <FormattedNumber value={status.get('favourites_count')} />
-            </span>
-          </Link>
-        </div>
-      </div>
-    );
-  }
-
-}
-
-DetailedStatus.contextTypes = {
-  router: PropTypes.object
-};
-
-DetailedStatus.propTypes = {
-  status: ImmutablePropTypes.map.isRequired,
-  onOpenMedia: PropTypes.func.isRequired,
-  onOpenVideo: PropTypes.func.isRequired,
-  autoPlayGif: PropTypes.bool,
-};
-
-export default DetailedStatus;
diff --git a/app/assets/javascripts/components/features/status/containers/card_container.jsx b/app/assets/javascripts/components/features/status/containers/card_container.jsx
deleted file mode 100644
index 5c8bfeec2..000000000
--- a/app/assets/javascripts/components/features/status/containers/card_container.jsx
+++ /dev/null
@@ -1,8 +0,0 @@
-import { connect } from 'react-redux';
-import Card from '../components/card';
-
-const mapStateToProps = (state, { statusId }) => ({
-  card: state.getIn(['cards', statusId], null)
-});
-
-export default connect(mapStateToProps)(Card);
diff --git a/app/assets/javascripts/components/features/status/index.jsx b/app/assets/javascripts/components/features/status/index.jsx
deleted file mode 100644
index 595df251c..000000000
--- a/app/assets/javascripts/components/features/status/index.jsx
+++ /dev/null
@@ -1,197 +0,0 @@
-import { connect } from 'react-redux';
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import { fetchStatus } from '../../actions/statuses';
-import Immutable from 'immutable';
-import EmbeddedStatus from '../../components/status';
-import MissingIndicator from '../../components/missing_indicator';
-import DetailedStatus from './components/detailed_status';
-import ActionBar from './components/action_bar';
-import Column from '../ui/components/column';
-import {
-  favourite,
-  unfavourite,
-  reblog,
-  unreblog
-} from '../../actions/interactions';
-import {
-  replyCompose,
-  mentionCompose
-} from '../../actions/compose';
-import { deleteStatus } from '../../actions/statuses';
-import { initReport } from '../../actions/reports';
-import {
-  makeGetStatus,
-  getStatusAncestors,
-  getStatusDescendants
-} from '../../selectors';
-import { ScrollContainer } from 'react-router-scroll';
-import ColumnBackButton from '../../components/column_back_button';
-import StatusContainer from '../../containers/status_container';
-import { openModal } from '../../actions/modal';
-import { isMobile } from '../../is_mobile'
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-
-const messages = defineMessages({
-  deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
-  deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' }
-});
-
-const makeMapStateToProps = () => {
-  const getStatus = makeGetStatus();
-
-  const mapStateToProps = (state, props) => ({
-    status: getStatus(state, Number(props.params.statusId)),
-    ancestorsIds: state.getIn(['timelines', 'ancestors', Number(props.params.statusId)]),
-    descendantsIds: state.getIn(['timelines', 'descendants', Number(props.params.statusId)]),
-    me: state.getIn(['meta', 'me']),
-    boostModal: state.getIn(['meta', 'boost_modal']),
-    autoPlayGif: state.getIn(['meta', 'auto_play_gif'])
-  });
-
-  return mapStateToProps;
-};
-
-class Status extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.handleFavouriteClick = this.handleFavouriteClick.bind(this);
-    this.handleReplyClick = this.handleReplyClick.bind(this);
-    this.handleModalReblog = this.handleModalReblog.bind(this);
-    this.handleReblogClick = this.handleReblogClick.bind(this);
-    this.handleDeleteClick = this.handleDeleteClick.bind(this);
-    this.handleMentionClick = this.handleMentionClick.bind(this);
-    this.handleOpenMedia = this.handleOpenMedia.bind(this);
-    this.handleOpenVideo = this.handleOpenVideo.bind(this);
-    this.handleReport = this.handleReport.bind(this);
-  }
-
-  componentWillMount () {
-    this.props.dispatch(fetchStatus(Number(this.props.params.statusId)));
-  }
-
-  componentWillReceiveProps (nextProps) {
-    if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) {
-      this.props.dispatch(fetchStatus(Number(nextProps.params.statusId)));
-    }
-  }
-
-  handleFavouriteClick (status) {
-    if (status.get('favourited')) {
-      this.props.dispatch(unfavourite(status));
-    } else {
-      this.props.dispatch(favourite(status));
-    }
-  }
-
-  handleReplyClick (status) {
-    this.props.dispatch(replyCompose(status, this.context.router));
-  }
-
-  handleModalReblog (status) {
-    this.props.dispatch(reblog(status));
-  }
-
-  handleReblogClick (status, e) {
-    if (status.get('reblogged')) {
-      this.props.dispatch(unreblog(status));
-    } else {
-      if (e.shiftKey || !this.props.boostModal) {
-        this.handleModalReblog(status);
-      } else {
-        this.props.dispatch(openModal('BOOST', { status, onReblog: this.handleModalReblog }));
-      }
-    }
-  }
-
-  handleDeleteClick (status) {
-    const { dispatch, intl } = this.props;
-
-    dispatch(openModal('CONFIRM', {
-      message: intl.formatMessage(messages.deleteMessage),
-      confirm: intl.formatMessage(messages.deleteConfirm),
-      onConfirm: () => dispatch(deleteStatus(status.get('id')))
-    }));
-  }
-
-  handleMentionClick (account, router) {
-    this.props.dispatch(mentionCompose(account, router));
-  }
-
-  handleOpenMedia (media, index) {
-    this.props.dispatch(openModal('MEDIA', { media, index }));
-  }
-
-  handleOpenVideo (media, time) {
-    this.props.dispatch(openModal('VIDEO', { media, time }));
-  }
-
-  handleReport (status) {
-    this.props.dispatch(initReport(status.get('account'), status));
-  }
-
-  renderChildren (list) {
-    return list.map(id => <StatusContainer key={id} id={id} />);
-  }
-
-  render () {
-    let ancestors, descendants;
-    const { status, ancestorsIds, descendantsIds, me, autoPlayGif } = this.props;
-
-    if (status === null) {
-      return (
-        <Column>
-          <ColumnBackButton />
-          <MissingIndicator />
-        </Column>
-      );
-    }
-
-    const account = status.get('account');
-
-    if (ancestorsIds && ancestorsIds.size > 0) {
-      ancestors = <div>{this.renderChildren(ancestorsIds)}</div>;
-    }
-
-    if (descendantsIds && descendantsIds.size > 0) {
-      descendants = <div>{this.renderChildren(descendantsIds)}</div>;
-    }
-
-    return (
-      <Column>
-        <ColumnBackButton />
-
-        <ScrollContainer scrollKey='thread'>
-          <div className='scrollable detailed-status__wrapper'>
-            {ancestors}
-
-            <DetailedStatus status={status} autoPlayGif={autoPlayGif} me={me} onOpenVideo={this.handleOpenVideo} onOpenMedia={this.handleOpenMedia} />
-            <ActionBar status={status} me={me} onReply={this.handleReplyClick} onFavourite={this.handleFavouriteClick} onReblog={this.handleReblogClick} onDelete={this.handleDeleteClick} onMention={this.handleMentionClick} onReport={this.handleReport} />
-
-            {descendants}
-          </div>
-        </ScrollContainer>
-      </Column>
-    );
-  }
-
-}
-
-Status.contextTypes = {
-  router: PropTypes.object
-};
-
-Status.propTypes = {
-  params: PropTypes.object.isRequired,
-  dispatch: PropTypes.func.isRequired,
-  status: ImmutablePropTypes.map,
-  ancestorsIds: ImmutablePropTypes.list,
-  descendantsIds: ImmutablePropTypes.list,
-  me: PropTypes.number,
-  boostModal: PropTypes.bool,
-  autoPlayGif: PropTypes.bool,
-  intl: PropTypes.object.isRequired
-};
-
-export default injectIntl(connect(makeMapStateToProps)(Status));
diff --git a/app/assets/javascripts/components/features/ui/components/boost_modal.jsx b/app/assets/javascripts/components/features/ui/components/boost_modal.jsx
deleted file mode 100644
index 3bd82ceee..000000000
--- a/app/assets/javascripts/components/features/ui/components/boost_modal.jsx
+++ /dev/null
@@ -1,82 +0,0 @@
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import IconButton from '../../../components/icon_button';
-import Button from '../../../components/button';
-import StatusContent from '../../../components/status_content';
-import Avatar from '../../../components/avatar';
-import RelativeTimestamp from '../../../components/relative_timestamp';
-import DisplayName from '../../../components/display_name';
-
-const messages = defineMessages({
-  reblog: { id: 'status.reblog', defaultMessage: 'Boost' }
-});
-
-class BoostModal extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.handleReblog = this.handleReblog.bind(this);
-    this.handleAccountClick = this.handleAccountClick.bind(this);
-  }
-
-  handleReblog() {
-    this.props.onReblog(this.props.status);
-    this.props.onClose();
-  }
-
-  handleAccountClick (e) {
-    if (e.button === 0) {
-      e.preventDefault();
-      this.props.onClose();
-      this.context.router.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
-    }
-  }
-
-  render () {
-    const { status, intl, onClose } = this.props;
-
-    return (
-      <div className='modal-root__modal boost-modal'>
-        <div className='boost-modal__container'>
-          <div className='status light'>
-            <div className='boost-modal__status-header'>
-              <div className='boost-modal__status-time'>
-                <a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener'><RelativeTimestamp timestamp={status.get('created_at')} /></a>
-              </div>
-
-              <a onClick={this.handleAccountClick} href={status.getIn(['account', 'url'])} className='status__display-name'>
-                <div className='status__avatar'>
-                  <Avatar src={status.getIn(['account', 'avatar'])} staticSrc={status.getIn(['account', 'avatar_static'])} size={48} />
-                </div>
-
-                <DisplayName account={status.get('account')} />
-              </a>
-            </div>
-
-            <StatusContent status={status} />
-          </div>
-        </div>
-
-        <div className='boost-modal__action-bar'>
-          <div><FormattedMessage id='boost_modal.combo' defaultMessage='You can press {combo} to skip this next time' values={{ combo: <span>Shift + <i className='fa fa-retweet' /></span> }} /></div>
-          <Button text={intl.formatMessage(messages.reblog)} onClick={this.handleReblog} />
-        </div>
-      </div>
-    );
-  }
-
-}
-
-BoostModal.contextTypes = {
-  router: PropTypes.object
-};
-
-BoostModal.propTypes = {
-  status: ImmutablePropTypes.map.isRequired,
-  onReblog: PropTypes.func.isRequired,
-  onClose: PropTypes.func.isRequired,
-  intl: PropTypes.object.isRequired
-};
-
-export default injectIntl(BoostModal);
diff --git a/app/assets/javascripts/components/features/ui/components/column.jsx b/app/assets/javascripts/components/features/ui/components/column.jsx
deleted file mode 100644
index aa09d0fd2..000000000
--- a/app/assets/javascripts/components/features/ui/components/column.jsx
+++ /dev/null
@@ -1,82 +0,0 @@
-import ColumnHeader from './column_header';
-import PropTypes from 'prop-types';
-
-const easingOutQuint = (x, t, b, c, d) => c*((t=t/d-1)*t*t*t*t + 1) + b;
-
-const scrollTop = (node) => {
-  const startTime = Date.now();
-  const offset    = node.scrollTop;
-  const targetY   = -offset;
-  const duration  = 1000;
-  let interrupt   = false;
-
-  const step = () => {
-    const elapsed    = Date.now() - startTime;
-    const percentage = elapsed / duration;
-
-    if (percentage > 1 || interrupt) {
-      return;
-    }
-
-    node.scrollTop = easingOutQuint(0, elapsed, offset, targetY, duration);
-    requestAnimationFrame(step);
-  };
-
-  step();
-
-  return () => {
-    interrupt = true;
-  };
-};
-
-class Column extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.handleHeaderClick = this.handleHeaderClick.bind(this);
-    this.handleWheel = this.handleWheel.bind(this);
-  }
-
-  handleHeaderClick () {
-    const scrollable = ReactDOM.findDOMNode(this).querySelector('.scrollable');
-    if (!scrollable) {
-      return;
-    }
-    this._interruptScrollAnimation = scrollTop(scrollable);
-  }
-
-  handleWheel () {
-    if (typeof this._interruptScrollAnimation !== 'undefined') {
-      this._interruptScrollAnimation();
-    }
-  }
-
-  render () {
-    const { heading, icon, children, active, hideHeadingOnMobile } = this.props;
-
-    let columnHeaderId = null
-    let header = '';
-
-    if (heading) {
-      columnHeaderId = heading.replace(/ /g, '-')
-      header = <ColumnHeader icon={icon} active={active} type={heading} onClick={this.handleHeaderClick} hideOnMobile={hideHeadingOnMobile} columnHeaderId={columnHeaderId}/>;
-    }
-    return (
-      <div role='region' aria-labelledby={columnHeaderId} className='column' onWheel={this.handleWheel}>
-        {header}
-        {children}
-      </div>
-    );
-  }
-
-}
-
-Column.propTypes = {
-  heading: PropTypes.string,
-  icon: PropTypes.string,
-  children: PropTypes.node,
-  active: PropTypes.bool,
-  hideHeadingOnMobile: PropTypes.bool
-};
-
-export default Column;
diff --git a/app/assets/javascripts/components/features/ui/components/column_header.jsx b/app/assets/javascripts/components/features/ui/components/column_header.jsx
deleted file mode 100644
index 7ccd72e0b..000000000
--- a/app/assets/javascripts/components/features/ui/components/column_header.jsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import PropTypes from 'prop-types'
-
-class ColumnHeader extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.handleClick = this.handleClick.bind(this);
-  }
-
-  handleClick () {
-    this.props.onClick();
-  }
-
-  render () {
-    const { type, active, hideOnMobile, columnHeaderId } = this.props;
-
-    let icon = '';
-
-    if (this.props.icon) {
-      icon = <i className={`fa fa-fw fa-${this.props.icon} column-header__icon`} />;
-    }
-
-    return (
-      <div role='button heading' tabIndex='0' className={`column-header ${active ? 'active' : ''} ${hideOnMobile ? 'hidden-on-mobile' : ''}`} onClick={this.handleClick} id={columnHeaderId || null}>
-        {icon}
-        {type}
-      </div>
-    );
-  }
-
-}
-
-ColumnHeader.propTypes = {
-  icon: PropTypes.string,
-  type: PropTypes.string,
-  active: PropTypes.bool,
-  onClick: PropTypes.func,
-  hideOnMobile: PropTypes.bool,
-  columnHeaderId: PropTypes.string
-};
-
-export default ColumnHeader;
diff --git a/app/assets/javascripts/components/features/ui/components/column_link.jsx b/app/assets/javascripts/components/features/ui/components/column_link.jsx
deleted file mode 100644
index 820e4246a..000000000
--- a/app/assets/javascripts/components/features/ui/components/column_link.jsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import PropTypes from 'prop-types';
-import { Link } from 'react-router';
-
-const ColumnLink = ({ icon, text, to, href, method, hideOnMobile }) => {
-  if (href) {
-    return (
-      <a href={href} className={`column-link ${hideOnMobile ? 'hidden-on-mobile' : ''}`} data-method={method}>
-        <i className={`fa fa-fw fa-${icon} column-link__icon`} />
-        {text}
-      </a>
-    );
-  } else {
-    return (
-      <Link to={to} className={`column-link ${hideOnMobile ? 'hidden-on-mobile' : ''}`}>
-        <i className={`fa fa-fw fa-${icon} column-link__icon`} />
-        {text}
-      </Link>
-    );
-  }
-};
-
-ColumnLink.propTypes = {
-  icon: PropTypes.string.isRequired,
-  text: PropTypes.string.isRequired,
-  to: PropTypes.string,
-  href: PropTypes.string,
-  method: PropTypes.string,
-  hideOnMobile: PropTypes.bool
-};
-
-export default ColumnLink;
diff --git a/app/assets/javascripts/components/features/ui/components/column_subheading.jsx b/app/assets/javascripts/components/features/ui/components/column_subheading.jsx
deleted file mode 100644
index 061c8be6c..000000000
--- a/app/assets/javascripts/components/features/ui/components/column_subheading.jsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import PropTypes from 'prop-types';
-
-const ColumnSubheading = ({ text }) => {
-    return (
-      <div className='column-subheading'>
-        {text}
-      </div>
-    );
-  };
-
-ColumnSubheading.propTypes = {
-  text: PropTypes.string.isRequired,
-};
-
-export default ColumnSubheading;
diff --git a/app/assets/javascripts/components/features/ui/components/columns_area.jsx b/app/assets/javascripts/components/features/ui/components/columns_area.jsx
deleted file mode 100644
index 360a759ae..000000000
--- a/app/assets/javascripts/components/features/ui/components/columns_area.jsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import PropTypes from 'prop-types';
-
-class ColumnsArea extends React.PureComponent {
-
-  render () {
-    return (
-      <div className='columns-area'>
-        {this.props.children}
-      </div>
-    );
-  }
-
-}
-
-ColumnsArea.propTypes = {
-  children: PropTypes.node
-};
-
-export default ColumnsArea;
diff --git a/app/assets/javascripts/components/features/ui/components/confirmation_modal.jsx b/app/assets/javascripts/components/features/ui/components/confirmation_modal.jsx
deleted file mode 100644
index 914c12f82..000000000
--- a/app/assets/javascripts/components/features/ui/components/confirmation_modal.jsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import PropTypes from 'prop-types';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import Button from '../../../components/button';
-
-class ConfirmationModal extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.handleClick = this.handleClick.bind(this);
-    this.handleCancel = this.handleCancel.bind(this);
-  }
-
-  handleClick () {
-    this.props.onClose();
-    this.props.onConfirm();
-  }
-
-  handleCancel (e) {
-    e.preventDefault();
-    this.props.onClose();
-  }
-
-  render () {
-    const { intl, message, confirm, onConfirm, onClose } = this.props;
-
-    return (
-      <div className='modal-root__modal confirmation-modal'>
-        <div className='confirmation-modal__container'>
-          {message}
-        </div>
-
-        <div className='confirmation-modal__action-bar'>
-          <div><a href='#' onClick={this.handleCancel}><FormattedMessage id='confirmation_modal.cancel' defaultMessage='Cancel' /></a></div>
-          <Button text={confirm} onClick={this.handleClick} />
-        </div>
-      </div>
-    );
-  }
-
-}
-
-ConfirmationModal.propTypes = {
-  message: PropTypes.node.isRequired,
-  confirm: PropTypes.string.isRequired,
-  onClose: PropTypes.func.isRequired,
-  onConfirm: PropTypes.func.isRequired,
-  intl: PropTypes.object.isRequired
-};
-
-export default injectIntl(ConfirmationModal);
diff --git a/app/assets/javascripts/components/features/ui/components/media_modal.jsx b/app/assets/javascripts/components/features/ui/components/media_modal.jsx
deleted file mode 100644
index 02a577500..000000000
--- a/app/assets/javascripts/components/features/ui/components/media_modal.jsx
+++ /dev/null
@@ -1,101 +0,0 @@
-import LoadingIndicator from '../../../components/loading_indicator';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import ExtendedVideoPlayer from '../../../components/extended_video_player';
-import ImageLoader from 'react-imageloader';
-import { defineMessages, injectIntl } from 'react-intl';
-import IconButton from '../../../components/icon_button';
-
-const messages = defineMessages({
-  close: { id: 'lightbox.close', defaultMessage: 'Close' }
-});
-
-class MediaModal extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.state = {
-      index: null
-    };
-    this.handleNextClick = this.handleNextClick.bind(this);
-    this.handlePrevClick = this.handlePrevClick.bind(this);
-    this.handleKeyUp = this.handleKeyUp.bind(this);
-  }
-
-  handleNextClick () {
-    this.setState({ index: (this.getIndex() + 1) % this.props.media.size});
-  }
-
-  handlePrevClick () {
-    this.setState({ index: (this.getIndex() - 1) % this.props.media.size});
-  }
-
-  handleKeyUp (e) {
-    switch(e.key) {
-    case 'ArrowLeft':
-      this.handlePrevClick();
-      break;
-    case 'ArrowRight':
-      this.handleNextClick();
-      break;
-    }
-  }
-
-  componentDidMount () {
-    window.addEventListener('keyup', this.handleKeyUp, false);
-  }
-
-  componentWillUnmount () {
-    window.removeEventListener('keyup', this.handleKeyUp);
-  }
-
-  getIndex () {
-    return this.state.index !== null ? this.state.index : this.props.index;
-  }
-
-  render () {
-    const { media, intl, onClose } = this.props;
-
-    const index = this.getIndex();
-    const attachment = media.get(index);
-    const url = attachment.get('url');
-
-    let leftNav, rightNav, content;
-
-    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>;
-    }
-
-    if (attachment.get('type') === 'image') {
-      content = <ImageLoader src={url} imgProps={{ style: { display: 'block' } }} />;
-    } else if (attachment.get('type') === 'gifv') {
-      content = <ExtendedVideoPlayer src={url} muted={true} controls={false} />;
-    }
-
-    return (
-      <div className='modal-root__modal media-modal'>
-        {leftNav}
-
-        <div className='media-modal__content'>
-          <IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={16} />
-          {content}
-        </div>
-
-        {rightNav}
-      </div>
-    );
-  }
-
-}
-
-MediaModal.propTypes = {
-  media: ImmutablePropTypes.list.isRequired,
-  index: PropTypes.number.isRequired,
-  onClose: PropTypes.func.isRequired,
-  intl: PropTypes.object.isRequired
-};
-
-export default injectIntl(MediaModal);
diff --git a/app/assets/javascripts/components/features/ui/components/modal_root.jsx b/app/assets/javascripts/components/features/ui/components/modal_root.jsx
deleted file mode 100644
index 23057715c..000000000
--- a/app/assets/javascripts/components/features/ui/components/modal_root.jsx
+++ /dev/null
@@ -1,92 +0,0 @@
-import PropTypes from 'prop-types';
-import MediaModal from './media_modal';
-import OnboardingModal from './onboarding_modal';
-import VideoModal from './video_modal';
-import BoostModal from './boost_modal';
-import ConfirmationModal from './confirmation_modal';
-import { TransitionMotion, spring } from 'react-motion';
-
-const MODAL_COMPONENTS = {
-  'MEDIA': MediaModal,
-  'ONBOARDING': OnboardingModal,
-  'VIDEO': VideoModal,
-  'BOOST': BoostModal,
-  'CONFIRM': ConfirmationModal
-};
-
-class ModalRoot extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.handleKeyUp = this.handleKeyUp.bind(this);
-  }
-
-  handleKeyUp (e) {
-    if ((e.key === 'Escape' || e.key === 'Esc' || e.keyCode === 27)
-         && !!this.props.type) {
-      this.props.onClose();
-    }
-  }
-
-  componentDidMount () {
-    window.addEventListener('keyup', this.handleKeyUp, false);
-  }
-
-  componentWillUnmount () {
-    window.removeEventListener('keyup', this.handleKeyUp);
-  }
-
-  willEnter () {
-    return { opacity: 0, scale: 0.98 };
-  }
-
-  willLeave () {
-    return { opacity: spring(0), scale: spring(0.98) };
-  }
-
-  render () {
-    const { type, props, onClose } = this.props;
-    const items = [];
-
-    if (!!type) {
-      items.push({
-        key: type,
-        data: { type, props },
-        style: { opacity: spring(1), scale: spring(1, { stiffness: 120, damping: 14 }) }
-      });
-    }
-
-    return (
-      <TransitionMotion
-        styles={items}
-        willEnter={this.willEnter}
-        willLeave={this.willLeave}>
-        {interpolatedStyles =>
-          <div className='modal-root'>
-            {interpolatedStyles.map(({ key, data: { type, props }, style }) => {
-              const SpecificComponent = MODAL_COMPONENTS[type];
-
-              return (
-                <div key={key}>
-                  <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})` }}>
-                    <SpecificComponent {...props} onClose={onClose} />
-                  </div>
-                </div>
-              );
-            })}
-          </div>
-        }
-      </TransitionMotion>
-    );
-  }
-
-}
-
-ModalRoot.propTypes = {
-  type: PropTypes.string,
-  props: PropTypes.object,
-  onClose: PropTypes.func.isRequired
-};
-
-export default ModalRoot;
diff --git a/app/assets/javascripts/components/features/ui/components/onboarding_modal.jsx b/app/assets/javascripts/components/features/ui/components/onboarding_modal.jsx
deleted file mode 100644
index 4c2c55f93..000000000
--- a/app/assets/javascripts/components/features/ui/components/onboarding_modal.jsx
+++ /dev/null
@@ -1,263 +0,0 @@
-import { connect } from 'react-redux';
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import Permalink from '../../../components/permalink';
-import { TransitionMotion, spring } from 'react-motion';
-import ComposeForm from '../../compose/components/compose_form';
-import Search from '../../compose/components/search';
-import NavigationBar from '../../compose/components/navigation_bar';
-import ColumnHeader from './column_header';
-import Immutable from 'immutable';
-
-const messages = defineMessages({
-  home_title: { id: 'column.home', defaultMessage: 'Home' },
-  notifications_title: { id: 'column.notifications', defaultMessage: 'Notifications' },
-  local_title: { id: 'column.community', defaultMessage: 'Local timeline' },
-  federated_title: { id: 'column.public', defaultMessage: 'Federated timeline' }
-});
-
-const PageOne = ({ acct, domain }) => (
-  <div className='onboarding-modal__page onboarding-modal__page-one'>
-    <div style={{ flex: '0 0 auto' }}>
-      <div className='onboarding-modal__page-one__elephant-friend' />
-    </div>
-
-    <div>
-      <h1><FormattedMessage id='onboarding.page_one.welcome' defaultMessage='Welcome to Mastodon!' /></h1>
-      <p><FormattedMessage id='onboarding.page_one.federation' defaultMessage='Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.' /></p>
-      <p><FormattedMessage id='onboarding.page_one.handle' defaultMessage='You are on {domain}, so your full handle is {handle}' values={{ domain, handle: <strong>{acct}@{domain}</strong> }}/></p>
-    </div>
-  </div>
-);
-
-PageOne.propTypes = {
-  acct: PropTypes.string.isRequired,
-  domain: PropTypes.string.isRequired
-};
-
-const PageTwo = ({ me }) => (
-  <div className='onboarding-modal__page onboarding-modal__page-two'>
-    <div className='figure non-interactive'>
-      <div className='pseudo-drawer'>
-        <NavigationBar account={me} />
-      </div>
-      <ComposeForm
-        text='Awoo! #introductions'
-        suggestions={Immutable.List()}
-        mentionedDomains={[]}
-        spoiler={false}
-        onChange={() => {}}
-        onSubmit={() => {}}
-        onPaste={() => {}}
-        onPickEmoji={() => {}}
-        onChangeSpoilerText={() => {}}
-        onClearSuggestions={() => {}}
-        onFetchSuggestions={() => {}}
-        onSuggestionSelected={() => {}}
-      />
-    </div>
-
-    <p><FormattedMessage id='onboarding.page_two.compose' defaultMessage='Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.' /></p>
-  </div>
-);
-
-PageTwo.propTypes = {
-  me: ImmutablePropTypes.map.isRequired,
-};
-
-const PageThree = ({ me, domain }) => (
-  <div className='onboarding-modal__page onboarding-modal__page-three'>
-    <div className='figure non-interactive'>
-      <Search
-        value=''
-        onChange={() => {}}
-        onSubmit={() => {}}
-        onClear={() => {}}
-        onShow={() => {}}
-      />
-
-      <div className='pseudo-drawer'>
-        <NavigationBar account={me} />
-      </div>
-    </div>
-
-    <p><FormattedMessage id='onboarding.page_three.search' defaultMessage='Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.' values={{ illustration: <Permalink to='/timelines/tag/illustration' href='/tags/illustration'>#illustration</Permalink>, introductions: <Permalink to='/timelines/tag/introductions' href='/tags/introductions'>#introductions</Permalink> }}/></p>
-    <p><FormattedMessage id='onboarding.page_three.profile' defaultMessage='Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.' /></p>
-  </div>
-);
-
-PageThree.propTypes = {
-  me: ImmutablePropTypes.map.isRequired,
-  domain: PropTypes.string.isRequired
-};
-
-const PageFour = ({ domain, intl }) => (
-  <div className='onboarding-modal__page onboarding-modal__page-four'>
-    <div className='onboarding-modal__page-four__columns'>
-      <div className='row'>
-        <div>
-          <div className='figure non-interactive'><ColumnHeader icon='home' type={intl.formatMessage(messages.home_title)} /></div>
-          <p><FormattedMessage id='onboarding.page_four.home' defaultMessage='The home timeline shows posts from people you follow.'/></p>
-        </div>
-
-        <div>
-          <div className='figure non-interactive'><ColumnHeader icon='bell' type={intl.formatMessage(messages.notifications_title)} /></div>
-          <p><FormattedMessage id='onboarding.page_four.notifications' defaultMessage='The notifications column shows when someone interacts with you.' /></p>
-        </div>
-      </div>
-
-      <div className='row'>
-        <div>
-          <div className='figure non-interactive' style={{ marginBottom: 0 }}><ColumnHeader icon='users' type={intl.formatMessage(messages.local_title)} /></div>
-        </div>
-
-        <div>
-          <div className='figure non-interactive' style={{ marginBottom: 0 }}><ColumnHeader icon='globe' type={intl.formatMessage(messages.federated_title)} /></div>
-        </div>
-      </div>
-
-      <p><FormattedMessage id='onboarding.page_five.public_timelines' defaultMessage='The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.' values={{ domain }} /></p>
-    </div>
-  </div>
-);
-
-PageFour.propTypes = {
-  domain: PropTypes.string.isRequired,
-  intl: PropTypes.object.isRequired
-};
-
-const PageSix = ({ admin, domain }) => {
-  let adminSection = '';
-
-  if (admin) {
-    adminSection = (
-      <p>
-        <FormattedMessage id='onboarding.page_six.admin' defaultMessage="Your instance's admin is {admin}." values={{ admin: <Permalink href={admin.get('url')} to={`/accounts/${admin.get('id')}`}>@{admin.get('acct')}</Permalink> }} />
-        <br />
-        <FormattedMessage id='onboarding.page_six.read_guidelines' defaultMessage="Please read {domain}'s {guidelines}!" values={{domain, guidelines: <a href='/about/more' target='_blank'><FormattedMessage id='onboarding.page_six.guidelines' defaultMessage='community guidelines' /></a> }}/>
-      </p>
-    );
-  }
-
-  return (
-    <div className='onboarding-modal__page onboarding-modal__page-six'>
-      <h1><FormattedMessage id='onboarding.page_six.almost_done' defaultMessage='Almost done...' /></h1>
-      {adminSection}
-      <p><FormattedMessage id='onboarding.page_six.github' defaultMessage='Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.' values={{ github: <a href='https://github.com/tootsuite/mastodon' target='_blank' rel='noopener'>GitHub</a> }} /></p>
-      <p><FormattedMessage id='onboarding.page_six.apps_available' defaultMessage='There are {apps} available for iOS, Android and other platforms.' values={{ apps: <a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md' target='_blank' rel='noopener'><FormattedMessage id='onboarding.page_six.various_app' defaultMessage='mobile apps' /></a> }} /></p>
-      <p><em><FormattedMessage id='onboarding.page_six.appetoot' defaultMessage='Bon Appetoot!' /></em></p>
-    </div>
-  );
-};
-
-PageSix.propTypes = {
-  admin: ImmutablePropTypes.map,
-  domain: PropTypes.string.isRequired
-};
-
-const mapStateToProps = state => ({
-  me: state.getIn(['accounts', state.getIn(['meta', 'me'])]),
-  admin: state.getIn(['accounts', state.getIn(['meta', 'admin'])]),
-  domain: state.getIn(['meta', 'domain'])
-});
-
-class OnboardingModal extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.state = {
-      currentIndex: 0
-    };
-    this.handleSkip = this.handleSkip.bind(this);
-    this.handleDot = this.handleDot.bind(this);
-    this.handleNext = this.handleNext.bind(this);
-  }
-
-  handleSkip (e) {
-    e.preventDefault();
-    this.props.onClose();
-  }
-
-  handleDot (i, e) {
-    e.preventDefault();
-    this.setState({ currentIndex: i });
-  }
-
-  handleNext (maxNum, e) {
-    e.preventDefault();
-
-    if (this.state.currentIndex < maxNum - 1) {
-      this.setState({ currentIndex: this.state.currentIndex + 1 });
-    } else {
-      this.props.onClose();
-    }
-  }
-
-  render () {
-    const { me, admin, domain, intl } = this.props;
-
-    const pages = [
-      <PageOne acct={me.get('acct')} domain={domain} />,
-      <PageTwo me={me} />,
-      <PageThree me={me} domain={domain} />,
-      <PageFour domain={domain} intl={intl} />,
-      <PageSix admin={admin} domain={domain} />
-    ];
-
-    const { currentIndex } = this.state;
-    const hasMore = currentIndex < pages.length - 1;
-
-    let nextOrDoneBtn;
-
-    if(hasMore) {
-      nextOrDoneBtn = <a href='#' onClick={this.handleNext.bind(null, pages.length)} className='onboarding-modal__nav onboarding-modal__next'><FormattedMessage id='onboarding.next' defaultMessage='Next' /></a>;
-    } else {
-      nextOrDoneBtn = <a href='#' onClick={this.handleNext.bind(null, pages.length)} className='onboarding-modal__nav onboarding-modal__done'><FormattedMessage id='onboarding.done' defaultMessage='Done' /></a>;
-    }
-
-    const styles = pages.map((page, i) => ({
-      key: `page-${i}`,
-      style: { opacity: spring(i === currentIndex ? 1 : 0) }
-    }));
-
-    return (
-      <div className='modal-root__modal onboarding-modal'>
-        <TransitionMotion styles={styles}>
-          {interpolatedStyles =>
-            <div className='onboarding-modal__pager'>
-              {pages.map((page, i) =>
-                <div key={`page-${i}`} style={{ opacity: interpolatedStyles[i].style.opacity, pointerEvents: i === currentIndex ? 'auto' : 'none' }}>{page}</div>
-              )}
-            </div>
-          }
-        </TransitionMotion>
-
-        <div className='onboarding-modal__paginator'>
-          <div>
-            <a href='#' className='onboarding-modal__skip' onClick={this.handleSkip}><FormattedMessage id='onboarding.skip' defaultMessage='Skip' /></a>
-          </div>
-
-          <div className='onboarding-modal__dots'>
-            {pages.map((_, i) => <div key={i} onClick={this.handleDot.bind(null, i)} className={`onboarding-modal__dot ${i === currentIndex ? 'active' : ''}`} />)}
-          </div>
-
-          <div>
-            {nextOrDoneBtn}
-          </div>
-        </div>
-      </div>
-    );
-  }
-
-}
-
-OnboardingModal.propTypes = {
-  onClose: PropTypes.func.isRequired,
-  intl: PropTypes.object.isRequired,
-  me: ImmutablePropTypes.map.isRequired,
-  domain: PropTypes.string.isRequired,
-  admin: ImmutablePropTypes.map
-}
-
-export default connect(mapStateToProps)(injectIntl(OnboardingModal));
diff --git a/app/assets/javascripts/components/features/ui/components/tabs_bar.jsx b/app/assets/javascripts/components/features/ui/components/tabs_bar.jsx
deleted file mode 100644
index 316b4bf7d..000000000
--- a/app/assets/javascripts/components/features/ui/components/tabs_bar.jsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import { Link } from 'react-router';
-import { FormattedMessage } from 'react-intl';
-
-class TabsBar extends React.Component {
-
-  render () {
-    return (
-      <div className='tabs-bar'>
-        <Link className='tabs-bar__link primary' activeClassName='active' to='/statuses/new'><i className='fa fa-fw fa-pencil' /><FormattedMessage id='tabs_bar.compose' defaultMessage='Compose' /></Link>
-        <Link className='tabs-bar__link primary' activeClassName='active' to='/timelines/home'><i className='fa fa-fw fa-home' /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></Link>
-        <Link className='tabs-bar__link primary' activeClassName='active' to='/notifications'><i className='fa fa-fw fa-bell' /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></Link>
-
-        <Link className='tabs-bar__link secondary' activeClassName='active' to='/timelines/public/local'><i className='fa fa-fw fa-users' /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></Link>
-        <Link className='tabs-bar__link secondary' activeClassName='active' to='/timelines/public'><i className='fa fa-fw fa-globe' /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></Link>
-
-        <Link className='tabs-bar__link primary' activeClassName='active' style={{ flexGrow: '0', flexBasis: '30px' }} to='/getting-started'><i className='fa fa-fw fa-asterisk' /></Link>
-      </div>
-    );
-  }
-
-}
-
-export default TabsBar;
diff --git a/app/assets/javascripts/components/features/ui/components/upload_area.jsx b/app/assets/javascripts/components/features/ui/components/upload_area.jsx
deleted file mode 100644
index 3a933398b..000000000
--- a/app/assets/javascripts/components/features/ui/components/upload_area.jsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import PropTypes from 'prop-types';
-import { Motion, spring } from 'react-motion';
-import { FormattedMessage } from 'react-intl';
-
-class UploadArea extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-
-    this.handleKeyUp = this.handleKeyUp.bind(this);
-  }
-
-  handleKeyUp (e) {
-    e.preventDefault();
-    e.stopPropagation();
-
-    const keyCode = e.keyCode
-    if (this.props.active) {
-      switch(keyCode) {
-      case 27:
-        this.props.onClose();
-        break;
-      }
-    }
-  }
-
-  componentDidMount () {
-    window.addEventListener('keyup', this.handleKeyUp, false);
-  }
-
-  componentWillUnmount () {
-    window.removeEventListener('keyup', this.handleKeyUp);
-  }
-
-  render () {
-    const { active } = this.props;
-
-    return (
-      <Motion defaultStyle={{ backgroundOpacity: 0, backgroundScale: 0.95 }} style={{ backgroundOpacity: spring(active ? 1 : 0, { stiffness: 150, damping: 15 }), backgroundScale: spring(active ? 1 : 0.95, { stiffness: 200, damping: 3 }) }}>
-        {({ backgroundOpacity, backgroundScale }) =>
-          <div className='upload-area' style={{ visibility: active ? 'visible' : 'hidden', opacity: backgroundOpacity }}>
-            <div className='upload-area__drop'>
-              <div className='upload-area__background' style={{ transform: `translateZ(0) scale(${backgroundScale})` }} />
-              <div className='upload-area__content'><FormattedMessage id='upload_area.title' defaultMessage='Drag & drop to upload' /></div>
-            </div>
-          </div>
-        }
-      </Motion>
-    );
-  }
-
-}
-
-UploadArea.propTypes = {
-  active: PropTypes.bool,
-  onClose: PropTypes.func
-};
-
-export default UploadArea;
diff --git a/app/assets/javascripts/components/features/ui/components/video_modal.jsx b/app/assets/javascripts/components/features/ui/components/video_modal.jsx
deleted file mode 100644
index d98b42882..000000000
--- a/app/assets/javascripts/components/features/ui/components/video_modal.jsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import LoadingIndicator from '../../../components/loading_indicator';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import ExtendedVideoPlayer from '../../../components/extended_video_player';
-import { defineMessages, injectIntl } from 'react-intl';
-import IconButton from '../../../components/icon_button';
-
-const messages = defineMessages({
-  close: { id: 'lightbox.close', defaultMessage: 'Close' }
-});
-
-class VideoModal extends React.PureComponent {
-
-  render () {
-    const { media, intl, time, onClose } = this.props;
-
-    const url = media.get('url');
-
-    return (
-      <div className='modal-root__modal media-modal'>
-        <div>
-          <div className='media-modal__close'><IconButton title={intl.formatMessage(messages.close)} icon='times' overlay onClick={onClose} /></div>
-          <ExtendedVideoPlayer src={url} muted={false} controls={true} time={time} />
-        </div>
-      </div>
-    );
-  }
-
-}
-
-VideoModal.propTypes = {
-  media: ImmutablePropTypes.map.isRequired,
-  time: PropTypes.number,
-  onClose: PropTypes.func.isRequired,
-  intl: PropTypes.object.isRequired
-};
-
-export default injectIntl(VideoModal);
diff --git a/app/assets/javascripts/components/features/ui/containers/loading_bar_container.jsx b/app/assets/javascripts/components/features/ui/containers/loading_bar_container.jsx
deleted file mode 100644
index 6c4e73e38..000000000
--- a/app/assets/javascripts/components/features/ui/containers/loading_bar_container.jsx
+++ /dev/null
@@ -1,8 +0,0 @@
-import { connect }    from 'react-redux';
-import LoadingBar from 'react-redux-loading-bar';
-
-const mapStateToProps = (state) => ({
-  loading: state.get('loadingBar')
-});
-
-export default connect(mapStateToProps)(LoadingBar.WrappedComponent);
diff --git a/app/assets/javascripts/components/features/ui/containers/modal_container.jsx b/app/assets/javascripts/components/features/ui/containers/modal_container.jsx
deleted file mode 100644
index 26d77818c..000000000
--- a/app/assets/javascripts/components/features/ui/containers/modal_container.jsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { connect } from 'react-redux';
-import { closeModal } from '../../../actions/modal';
-import ModalRoot from '../components/modal_root';
-
-const mapStateToProps = state => ({
-  type: state.get('modal').modalType,
-  props: state.get('modal').modalProps
-});
-
-const mapDispatchToProps = dispatch => ({
-  onClose () {
-    dispatch(closeModal());
-  },
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(ModalRoot);
diff --git a/app/assets/javascripts/components/features/ui/containers/notifications_container.jsx b/app/assets/javascripts/components/features/ui/containers/notifications_container.jsx
deleted file mode 100644
index 529ebf6c8..000000000
--- a/app/assets/javascripts/components/features/ui/containers/notifications_container.jsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import { connect } from 'react-redux';
-import { NotificationStack } from 'react-notification';
-import {
-  dismissAlert,
-  clearAlerts
-} from '../../../actions/alerts';
-import { getAlerts } from '../../../selectors';
-
-const mapStateToProps = (state, props) => ({
-  notifications: getAlerts(state)
-});
-
-const mapDispatchToProps = (dispatch) => {
-  return {
-    onDismiss: alert => {
-      dispatch(dismissAlert(alert));
-    }
-  };
-};
-
-export default connect(mapStateToProps, mapDispatchToProps)(NotificationStack);
diff --git a/app/assets/javascripts/components/features/ui/containers/status_list_container.jsx b/app/assets/javascripts/components/features/ui/containers/status_list_container.jsx
deleted file mode 100644
index 1599000b5..000000000
--- a/app/assets/javascripts/components/features/ui/containers/status_list_container.jsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import { connect } from 'react-redux';
-import StatusList from '../../../components/status_list';
-import { expandTimeline, scrollTopTimeline } from '../../../actions/timelines';
-import Immutable from 'immutable';
-import { createSelector } from 'reselect';
-import { debounce } from 'react-decoration';
-
-const makeGetStatusIds = () => createSelector([
-  (state, { type }) => state.getIn(['settings', type], Immutable.Map()),
-  (state, { type }) => state.getIn(['timelines', type, 'items'], Immutable.List()),
-  (state)           => state.get('statuses'),
-  (state)           => state.getIn(['meta', 'me'])
-], (columnSettings, statusIds, statuses, me) => statusIds.filter(id => {
-  const statusForId = statuses.get(id);
-  let showStatus    = true;
-
-  if (columnSettings.getIn(['shows', 'reblog']) === false) {
-    showStatus = showStatus && statusForId.get('reblog') === null;
-  }
-
-  if (columnSettings.getIn(['shows', 'reply']) === false) {
-    showStatus = showStatus && (statusForId.get('in_reply_to_id') === null || statusForId.get('in_reply_to_account_id') === me);
-  }
-
-  if (columnSettings.getIn(['regex', 'body'], '').trim().length > 0) {
-    try {
-      if (showStatus) {
-        const regex = new RegExp(columnSettings.getIn(['regex', 'body']).trim(), 'i');
-        showStatus = !regex.test(statusForId.get('reblog') ? statuses.getIn([statusForId.get('reblog'), 'unescaped_content']) : statusForId.get('unescaped_content'));
-      }
-    } catch(e) {
-      // Bad regex, don't affect filters
-    }
-  }
-
-  return showStatus;
-}));
-
-const makeMapStateToProps = () => {
-  const getStatusIds = makeGetStatusIds();
-
-  const mapStateToProps = (state, props) => ({
-    scrollKey: props.scrollKey,
-    shouldUpdateScroll: props.shouldUpdateScroll,
-    statusIds: getStatusIds(state, props),
-    isLoading: state.getIn(['timelines', props.type, 'isLoading'], true),
-    isUnread: state.getIn(['timelines', props.type, 'unread']) > 0,
-    hasMore: !!state.getIn(['timelines', props.type, 'next'])
-  });
-
-  return mapStateToProps;
-};
-
-const mapDispatchToProps = (dispatch, { type, id }) => ({
-
-  @debounce(300, true)
-  onScrollToBottom () {
-    dispatch(scrollTopTimeline(type, false));
-    dispatch(expandTimeline(type, id));
-  },
-
-  @debounce(100)
-  onScrollToTop () {
-    dispatch(scrollTopTimeline(type, true));
-  },
-
-  @debounce(100)
-  onScroll () {
-    dispatch(scrollTopTimeline(type, false));
-  }
-
-});
-
-export default connect(makeMapStateToProps, mapDispatchToProps)(StatusList);
diff --git a/app/assets/javascripts/components/features/ui/index.jsx b/app/assets/javascripts/components/features/ui/index.jsx
deleted file mode 100644
index b402639ce..000000000
--- a/app/assets/javascripts/components/features/ui/index.jsx
+++ /dev/null
@@ -1,166 +0,0 @@
-import ColumnsArea from './components/columns_area';
-import NotificationsContainer from './containers/notifications_container';
-import PropTypes from 'prop-types';
-import LoadingBarContainer from './containers/loading_bar_container';
-import HomeTimeline from '../home_timeline';
-import Compose from '../compose';
-import TabsBar from './components/tabs_bar';
-import ModalContainer from './containers/modal_container';
-import Notifications from '../notifications';
-import { connect } from 'react-redux';
-import { isMobile } from '../../is_mobile';
-import { debounce } from 'react-decoration';
-import { uploadCompose } from '../../actions/compose';
-import { refreshTimeline } from '../../actions/timelines';
-import { refreshNotifications } from '../../actions/notifications';
-import UploadArea from './components/upload_area';
-
-class UI extends React.PureComponent {
-
-  constructor (props, context) {
-    super(props, context);
-    this.state = {
-      width: window.innerWidth,
-      draggingOver: false
-    };
-    this.handleResize = this.handleResize.bind(this);
-    this.handleDragEnter = this.handleDragEnter.bind(this);
-    this.handleDragOver = this.handleDragOver.bind(this);
-    this.handleDrop = this.handleDrop.bind(this);
-    this.handleDragLeave = this.handleDragLeave.bind(this);
-    this.handleDragEnd = this.handleDragLeave.bind(this)
-    this.closeUploadModal = this.closeUploadModal.bind(this)
-    this.setRef = this.setRef.bind(this);
-  }
-
-  @debounce(500)
-  handleResize () {
-    this.setState({ width: window.innerWidth });
-  }
-
-  handleDragEnter (e) {
-    e.preventDefault();
-
-    if (!this.dragTargets) {
-      this.dragTargets = [];
-    }
-
-    if (this.dragTargets.indexOf(e.target) === -1) {
-      this.dragTargets.push(e.target);
-    }
-
-    if (e.dataTransfer && e.dataTransfer.types.includes('Files')) {
-      this.setState({ draggingOver: true });
-    }
-  }
-
-  handleDragOver (e) {
-    e.preventDefault();
-    e.stopPropagation();
-
-    try {
-      e.dataTransfer.dropEffect = 'copy';
-    } catch (err) {
-
-    }
-
-    return false;
-  }
-
-  handleDrop (e) {
-    e.preventDefault();
-
-    this.setState({ draggingOver: false });
-
-    if (e.dataTransfer && e.dataTransfer.files.length === 1) {
-      this.props.dispatch(uploadCompose(e.dataTransfer.files));
-    }
-  }
-
-  handleDragLeave (e) {
-    e.preventDefault();
-    e.stopPropagation();
-
-    this.dragTargets = this.dragTargets.filter(el => el !== e.target && this.node.contains(el));
-
-    if (this.dragTargets.length > 0) {
-      return;
-    }
-
-    this.setState({ draggingOver: false });
-  }
-
-  closeUploadModal() {
-    this.setState({ draggingOver: false });
-  }
-
-  componentWillMount () {
-    window.addEventListener('resize', this.handleResize, { passive: true });
-    document.addEventListener('dragenter', this.handleDragEnter, false);
-    document.addEventListener('dragover', this.handleDragOver, false);
-    document.addEventListener('drop', this.handleDrop, false);
-    document.addEventListener('dragleave', this.handleDragLeave, false);
-    document.addEventListener('dragend', this.handleDragEnd, false);
-
-    this.props.dispatch(refreshTimeline('home'));
-    this.props.dispatch(refreshNotifications());
-  }
-
-  componentWillUnmount () {
-    window.removeEventListener('resize', this.handleResize);
-    document.removeEventListener('dragenter', this.handleDragEnter);
-    document.removeEventListener('dragover', this.handleDragOver);
-    document.removeEventListener('drop', this.handleDrop);
-    document.removeEventListener('dragleave', this.handleDragLeave);
-    document.removeEventListener('dragend', this.handleDragEnd);
-  }
-
-  setRef (c) {
-    this.node = c;
-  }
-
-  render () {
-    const { width, draggingOver } = this.state;
-    const { children } = this.props;
-
-    let mountedColumns;
-
-    if (isMobile(width)) {
-      mountedColumns = (
-        <ColumnsArea>
-          {children}
-        </ColumnsArea>
-      );
-    } else {
-      mountedColumns = (
-        <ColumnsArea>
-          <Compose withHeader={true} />
-          <HomeTimeline shouldUpdateScroll={() => false} />
-          <Notifications shouldUpdateScroll={() => false} />
-          <div style={{display: 'flex', flex: '1 1 auto', position: 'relative'}}>{children}</div>
-        </ColumnsArea>
-      );
-    }
-
-    return (
-      <div className='ui' ref={this.setRef}>
-        <TabsBar />
-
-        {mountedColumns}
-
-        <NotificationsContainer />
-        <LoadingBarContainer className="loading-bar" />
-        <ModalContainer />
-        <UploadArea active={draggingOver} onClose={this.closeUploadModal} />
-      </div>
-    );
-  }
-
-}
-
-UI.propTypes = {
-  dispatch: PropTypes.func.isRequired,
-  children: PropTypes.node
-};
-
-export default connect()(UI);
diff --git a/app/assets/javascripts/components/is_mobile.jsx b/app/assets/javascripts/components/is_mobile.jsx
deleted file mode 100644
index 992e63727..000000000
--- a/app/assets/javascripts/components/is_mobile.jsx
+++ /dev/null
@@ -1,11 +0,0 @@
-const LAYOUT_BREAKPOINT = 1024;
-
-export function isMobile(width) {
-  return width <= LAYOUT_BREAKPOINT;
-};
-
-const iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
-
-export function isIOS() {
-  return iOS;
-};
diff --git a/app/assets/javascripts/components/link_header.jsx b/app/assets/javascripts/components/link_header.jsx
deleted file mode 100644
index b872dc24a..000000000
--- a/app/assets/javascripts/components/link_header.jsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import Link from 'http-link-header';
-import querystring from 'querystring';
-
-Link.parseAttrs = (link, parts) => {
-  let match = null
-  let attr  = ''
-  let value = ''
-  let attrs = ''
-
-  let uriAttrs = /<(.*)>;\s*(.*)/gi.exec(parts)
-
-  if(uriAttrs) {
-    attrs = uriAttrs[2]
-    link  = Link.parseParams(link, uriAttrs[1])
-  }
-
-  while(match = Link.attrPattern.exec(attrs)) { // eslint-disable-line no-cond-assign
-    attr  = match[1].toLowerCase()
-    value = match[4] || match[3] || match[2]
-
-    if( /\*$/.test(attr)) {
-      Link.setAttr(link, attr, Link.parseExtendedValue(value))
-    } else if(/%/.test(value)) {
-      Link.setAttr(link, attr, querystring.decode(value))
-    } else {
-      Link.setAttr(link, attr, value)
-    }
-  }
-
-  return link
-};
-
-export default Link;
diff --git a/app/assets/javascripts/components/locales/ar.jsx b/app/assets/javascripts/components/locales/ar.jsx
deleted file mode 100644
index 06a83b609..000000000
--- a/app/assets/javascripts/components/locales/ar.jsx
+++ /dev/null
@@ -1,131 +0,0 @@
-/**
-  * ملاحظة للمساهمين و المساهمات :
-  * لجعل مهمة المساهمين الآخرين أسهل، رجاءا تذكر :
-  * 1. إضافة سلسلة جديدة هنا؛ و
-  * 2. لإزالة السلاسل القديمة التي لم تعد هناك حاجة إليها. و
-  * 3. لفرز السلاسل تبعا للأبجدية
-  * شكر!
- */
-const ar = {
-  "account.block": "حظر @{name}",
-  "account.disclaimer": "هذا المستخدم من مثيل خادم آخر. قد يكون هذا الرقم أكبر.",
-  "account.edit_profile": "تعديل الملف الشخصي",
-  "account.follow": "إتبع",
-  "account.followers": "المتابعون",
-  "account.follows": "يتبع",
-  "account.follows_you": "يتابعك",
-  "account.mention": "أُذكُر @{name}",
-  "account.mute": "أكتم @{name}",
-  "account.posts": "المشاركات",
-  "account.report": "أبلغ عن @{name}",
-  "account.requested": "في انتظار الموافقة",
-  "account.unblock": "إلغاء الحظر عن @{name}",
-  "account.unfollow": "إلغاء المتابعة",
-  "account.unmute": "إلغاء الكتم عن @{name}",
-  "boost_modal.combo": "يمكنك ضغط {combo} لتخطّي هذه في المرّة القادمة",
-  "column.blocks": "الحسابات المحجوبة",
-  "column.community": "الخيط العام المحلي",
-  "column.favourites": "المفضلة",
-  "column.follow_requests": "طلبات المتابعة",
-  "column.home": "الرئيسية",
-  "column.mutes": "الحسابات المكتومة",
-  "column.notifications": "الإشعارات",
-  "column.public": "الخيط العام الموحد",
-  "column_back_button.label": "العودة",
-  "column_subheading.navigation": "التصفح",
-  "column_subheading.settings": "الإعدادات",
-  "compose_form.lock_disclaimer": "حسابك ليس {locked}. يمكن لأي شخص متابعتك و عرض المنشورات.",
-  "compose_form.lock_disclaimer.lock": "مقفل",
-  "compose_form.placeholder": "فيمَ تفكّر؟",
-  "compose_form.publish": "بوّق !",
-  "compose_form.sensitive": "ضع علامة على الوسيط باعتباره حسّاس",
-  "compose_form.spoiler": "أخفِ النص واعرض تحذيرا",
-  "compose_form.spoiler_placeholder": "تنبيه عن المحتوى",
-  "confirmation_modal.cancel": "إلغاء",
-  "confirmations.block.confirm": "حجب",
-  "confirmations.block.message": "هل أنت متأكد أنك تريد حجب {name} ؟",
-  "confirmations.delete.confirm": "حذف",
-  "confirmations.delete.message": "هل أنت متأكد أنك تريد حذف هذا المنشور ؟",
-  "confirmations.mute.confirm": "أكتم",
-  "confirmations.mute.message": "هل أنت متأكد أنك تريد كتم {name} ؟",
-  "emoji_button.activity": "الأنشطة",
-  "emoji_button.flags": "الأعلام",
-  "emoji_button.food": "الطعام والشراب",
-  "emoji_button.label": "أدرج إيموجي",
-  "emoji_button.nature": "الطبيعة",
-  "emoji_button.objects": "أشياء",
-  "emoji_button.people": "الناس",
-  "emoji_button.search": "ابحث...",
-  "emoji_button.symbols": "رموز",
-  "emoji_button.travel": "أماكن و أسفار",
-  "empty_column.community": "الخط الزمني المحلي فارغ. اكتب شيئا ما للعامة كبداية.",
-  "empty_column.hashtag": "ليس هناك بعدُ أي محتوى ذو علاقة بهذا الوسم.",
-  "empty_column.home.public_timeline": "الخيط العام",
-  "empty_column.home": "إنك لا تتبع بعد أي شخص إلى حد الآن. زر {public} أو استخدام حقل البحث لكي تبدأ على التعرف على مستخدمين آخرين.",
-  "empty_column.notifications": "لم تتلق أي إشعار بعدُ. تفاعل مع المستخدمين الآخرين لإنشاء محادثة.",
-  "empty_column.public": "لا يوجد شيء هنا ! قم بتحرير شيء ما بشكل عام، أو اتبع مستخدمين آخرين في الخوادم المثيلة الأخرى لملء خيط المحادثات العام.",
-  "follow_request.authorize": "ترخيص",
-  "follow_request.reject": "رفض",
-  "getting_started.apps": "عدة تطبيقات مختلفة متوفرة",
-  "getting_started.heading": "إستعدّ للبدء",
-  "getting_started.about_addressing": "يمكنك متابعة الأشخاص إذا كنت تعرف اسم المستخدم الخاص بهم والنطاق الذي هم عليه عن طريق إدخال عنوان شبيه بالبريد الإلكتروني في الحقل المخصص للبحث.",
-  "getting_started.about_shortcuts": "إذا كان المستخدم المستهدف في نفس النطاق الذي تستخدمه، فإسم المستخدم وحده يكفي. وتنطبق نفس القاعدة على ذكر الأشخاص في المنشورات و التبويقات.",
-  "getting_started.open_source_notice": "ماستدون برنامج مفتوح المصدر. يمكنك المساهمة، أو الإبلاغ عن تقارير الأخطاء، على GitHub {github}. {apps}.",
-  "home.column_settings.advanced": "متقدمة",
-  "home.column_settings.basic": "أساسية",
-  "home.column_settings.filter_regex": "تصفية حسب التعبيرات العادية",
-  "home.column_settings.show_reblogs": "عرض الترقيات",
-  "home.column_settings.show_replies": "عرض الردود",
-  "home.settings": "إعدادات العمود",
-  "lightbox.close": "إغلاق",
-  "loading_indicator.label": "تحميل ...",
-  "media_gallery.toggle_visible": "Toggle visibility",
-  "missing_indicator.label": "تعذر العثور عليه",
-  "navigation_bar.blocks": "الحسابات المحجوبة",
-  "navigation_bar.community_timeline": "الخيط العام المحلي",
-  "navigation_bar.edit_profile": "تعديل الملف الشخصي",
-  "navigation_bar.preferences": "التفضيلات",
-  "navigation_bar.community_timeline": "الخيط العام المحلي",
-  "navigation_bar.public_timeline": "الخيط العام الموحد",
-  "navigation_bar.logout": "خروج",
-  "reply_indicator.cancel": "إلغاء",
-  "search.placeholder": "ابحث",
-  "search.account": "حساب",
-  "search.hashtag": "وسم",
-  "status.mention": "أذكُر @{name}",
-  "status.delete": "إحذف",
-  "status.reply": "ردّ",
-  "status.reblog": "رَقِّي",
-  "status.favourite": "أضف إلى المفضلة",
-  "status.reblogged_by": "{name} رقى",
-  "status.sensitive_warning": "محتوى حساس",
-  "status.sensitive_toggle": "اضغط للعرض",
-  "status.show_more": "أظهر المزيد",
-  "status.show_less": "إعرض أقلّ",
-  "status.open": "وسع هذه المشاركة",
-  "status.report": "إبلِغ عن @{name}",
-  "tabs_bar.compose": "تحرير",
-  "tabs_bar.home": "الرئيسية",
-  "tabs_bar.mentions": "الإشارات",
-  "tabs_bar.public": "الخيط العام الموحد",
-  "tabs_bar.notifications": "الإخطارات",
-  "upload_button.label": "إضافة وسائط",
-  "upload_form.undo": "إلغاء",
-  "upload_progress.label": "يرفع...",
-  "notification.follow": "{name} يتبعك",
-  "notification.favourite": "{name} أعجب بمنشورك",
-  "notification.reblog": "{name} قام بترقية تبويقك",
-  "notification.mention": "{name} ذكرك",
-  "notifications.column_settings.alert": "إشعارات سطح المكتب",
-  "notifications.column_settings.show": "إعرِضها في عمود",
-  "notifications.column_settings.follow": "متابعُون جُدُد :",
-  "notifications.column_settings.favourite": "المُفَضَّلة :",
-  "notifications.column_settings.mention": "الإشارات :",
-  "notifications.column_settings.reblog": "الترقيّات:",
-  "video_player.toggle_sound": "تبديل الصوت",
-  "video_player.toggle_visible": "إظهار / إخفاء الفيديو",
-  "video_player.expand": "وسّع الفيديو",
-  "video_player.video_error": "تعذر تشغيل الفيديو",
-};
-
-export default ar;
diff --git a/app/assets/javascripts/components/locales/bg.jsx b/app/assets/javascripts/components/locales/bg.jsx
deleted file mode 100644
index a194cdbdd..000000000
--- a/app/assets/javascripts/components/locales/bg.jsx
+++ /dev/null
@@ -1,68 +0,0 @@
-const bg = {
-  "column_back_button.label": "Назад",
-  "lightbox.close": "Затвори",
-  "loading_indicator.label": "Зареждане...",
-  "status.mention": "Споменаване",
-  "status.delete": "Изтриване",
-  "status.reply": "Отговор",
-  "status.reblog": "Споделяне",
-  "status.favourite": "Предпочитани",
-  "status.reblogged_by": "{name} сподели",
-  "status.sensitive_warning": "Деликатно съдържание",
-  "status.sensitive_toggle": "Покажи",
-  "video_player.toggle_sound": "Звук",
-  "account.mention": "Споменаване",
-  "account.edit_profile": "Редактирай профила си",
-  "account.unblock": "Не блокирай",
-  "account.unfollow": "Не следвай",
-  "account.block": "Блокирай",
-  "account.follow": "Последвай",
-  "account.posts": "Публикации",
-  "account.follows": "Следвам",
-  "account.followers": "Последователи",
-  "account.follows_you": "Твой последовател",
-  "account.requested": "В очакване на одобрение",
-  "getting_started.heading": "Първи стъпки",
-  "getting_started.about_addressing": "Можеш да последваш потребител, ако знаеш потребителското му име и домейна, на който се намира, като в полето за търсене ги въведеш по този начин: име@домейн",
-  "getting_started.about_shortcuts": "Ако с търсения потребител се намирате на един и същ домейн, достатъчно е да въведеш само името. Същото важи и за споменаване на хора в публикации.",
-  "getting_started.about_developer": "Можеш да потърсиш разработчика на този проект като: Gargron@mastodon.social",
-  "getting_started.open_source_notice": "Mastodon е софтуер с отворен код. Можеш да помогнеш или да докладваш за проблеми в Github: {github}.",
-  "column.home": "Начало",
-  "column.mentions": "Споменавания",
-  "column.public": "Публичен канал",
-  "column.notifications": "Известия",
-  "tabs_bar.compose": "Съставяне",
-  "tabs_bar.home": "Начало",
-  "tabs_bar.mentions": "Споменавания",
-  "tabs_bar.public": "Публичен канал",
-  "tabs_bar.notifications": "Известия",
-  "compose_form.placeholder": "Какво си мислиш?",
-  "compose_form.publish": "Раздумай",
-  "compose_form.sensitive": "Отбележи съдържанието като деликатно",
-  "compose_form.spoiler": "Скрий текста зад предупреждение",
-  "compose_form.private": "Отбележи като поверително",
-  "compose_form.privacy_disclaimer": "Поверителни публикации ще бъдат изпратени до споменатите потребители на {domains}. Доверяваш ли се на {domainsCount, plural, one {that server} other {those servers}}, че няма да издаде твоята публикация?",
-  "compose_form.unlisted": "Не показвай в публичния канал",
-  "navigation_bar.edit_profile": "Редактирай профил",
-  "navigation_bar.preferences": "Предпочитания",
-  "navigation_bar.public_timeline": "Публичен канал",
-  "navigation_bar.logout": "Излизане",
-  "reply_indicator.cancel": "Отказ",
-  "search.placeholder": "Търсене",
-  "search.account": "Акаунт",
-  "search.hashtag": "Хаштаг",
-  "upload_button.label": "Добави медия",
-  "upload_form.undo": "Отмяна",
-  "notification.follow": "{name} те последва",
-  "notification.favourite": "{name} хареса твоята публикация",
-  "notification.reblog": "{name} сподели твоята публикация",
-  "notification.mention": "{name} те спомена",
-  "notifications.column_settings.alert": "Десктоп известия",
-  "notifications.column_settings.show": "Покажи в колона",
-  "notifications.column_settings.follow": "Нови последователи:",
-  "notifications.column_settings.favourite": "Предпочитани:",
-  "notifications.column_settings.mention": "Споменавания:",
-  "notifications.column_settings.reblog": "Споделяния:",
-};
-
-export default bg;
diff --git a/app/assets/javascripts/components/locales/de.jsx b/app/assets/javascripts/components/locales/de.jsx
deleted file mode 100644
index 8720a2d36..000000000
--- a/app/assets/javascripts/components/locales/de.jsx
+++ /dev/null
@@ -1,126 +0,0 @@
-const de = {
-  "account.block": "@{name} blocken",
-  "account.disclaimer": "Dieser Benutzer ist von einer anderen Instanz. Diese Zahl könnte größer sein.",
-  "account.edit_profile": "Profil bearbeiten",
-  "account.follow": "Folgen",
-  "account.followers": "Folgende",
-  "account.follows": "Folgt",
-  "account.follows_you": "Folgt dir",
-  "account.mention": "@{name} erwähnen",
-  "account.mute": "@{name} stummschalten",
-  "account.posts": "Beiträge",
-  "account.report": "@{name} melden",
-  "account.requested": "Warte auf Erlaubnis",
-  "account.unblock": "@{name} entblocken",
-  "account.unfollow": "Entfolgen",
-  "account.unmute": "@{name} nicht mehr stummschalten",
-  "boost_modal.combo": "Du kannst {combo} drücken, um dies beim nächsten Mal zu überspringen",
-  "column_back_button.label": "Zurück",
-  "column.blocks": "Blockierte Benutzer",
-  "column.community": "Lokale Zeitleiste",
-  "column.favourites": "Favoriten",
-  "column.follow_requests": "Folgeanfragen",
-  "column.home": "Startseite",
-  "column.mutes": "Stummgeschaltete Benutzer",
-  "column.notifications": "Mitteilungen",
-  "column.public": "Gesamtes bekanntes Netz",
-  "compose_form.placeholder": "Worüber möchtest du schreiben?",
-  "compose_form.privacy_disclaimer": "Dein privater Status wird an die genannten Benutzer auf den Domains {domains} zugestellt. Vertraust du {domainsCount, plural, one {diesem Server} other {diesen Servern}}? Private Beiträge funktionieren nur auf Mastodon-Instanzen. Wenn {domains} {domainsCount, plural, one {keine Mastodon-Instanz ist} other {keine Mastodon-Instanzen sind}}, wird es dort kein Anzeichen geben, dass dein Beitrag privat ist und er könnte geteilt oder anderweitig für unerwünschte Empfänger sichtbar gemacht werden.",
-  "compose_form.publish": "Tröt",
-  "compose_form.sensitive": "Medien als heikel markieren",
-  "compose_form.spoiler_placeholder": "Inhaltswarnung",
-  "compose_form.spoiler": "Text hinter Warnung verbergen",
-  "emoji_button.label": "Emoji einfügen",
-  "empty_column.community": "Die lokale Zeitleiste ist leer. Schreibe etwas öffentlich, um den Ball ins Rollen zu bringen!",
-  "empty_column.hashtag": "Es gibt noch nichts unter diesem Hashtag.",
-  "empty_column.home.public_timeline": "die öffentliche Zeitleiste",
-  "empty_column.home": "Du folgst noch niemandem. Besuche {public} oder benutze die Suche, um zu starten oder andere Benutzer anzutreffen.",
-  "empty_column.notifications": "Du hast noch keine Mitteilungen. Interagiere mit anderen, um die Konversation zu starten.",
-  "empty_column.public": "Hier ist nichts zu sehen! Schreibe etwas öffentlich oder folge Benutzern von anderen Instanzen, um es aufzufüllen.",
-  "follow_request.authorize": "Erlauben",
-  "follow_request.reject": "Ablehnen",
-  "getting_started.apps": "Es sind verschiedene Apps verfügbar",
-  "getting_started.heading": "Erste Schritte",
-  "getting_started.open_source_notice": "Mastodon ist quelloffene Software. Du kannst auf {github} dazu beitragen oder Probleme melden.",
-  "home.column_settings.advanced": "Fortgeschritten",
-  "home.column_settings.basic": "Einfach",
-  "home.column_settings.filter_regex": "Filter durch reguläre Ausdrücke",
-  "home.column_settings.show_reblogs": "Geteilte Beiträge anzeigen",
-  "home.column_settings.show_replies": "Antworten anzeigen",
-  "home.settings": "Spalteneinstellungen",
-  "lightbox.close": "Schließen",
-  "loading_indicator.label": "Lade…",
-  "media_gallery.toggle_visible": "Sichtbarkeit einstellen",
-  "missing_indicator.label": "Nicht gefunden",
-  "navigation_bar.blocks": "Blockierte Benutzer",
-  "navigation_bar.community_timeline": "Lokale Zeitleiste",
-  "navigation_bar.edit_profile": "Profil bearbeiten",
-  "navigation_bar.favourites": "Favoriten",
-  "navigation_bar.follow_requests": "Folgeanfragen",
-  "navigation_bar.info": "Erweiterte Informationen",
-  "navigation_bar.logout": "Abmelden",
-  "navigation_bar.mutes": "Stummgeschaltete Benutzer",
-  "navigation_bar.preferences": "Einstellungen",
-  "navigation_bar.public_timeline": "Föderierte Zeitleiste",
-  "notification.favourite": "{name} favorisierte deinen Status",
-  "notification.follow": "{name} folgt dir",
-  "notification.mention": "{name} erwähnte dich",
-  "notification.reblog": "{name} teilte deinen Status",
-  "notifications.clear_confirmation": "Bist du sicher, dass du alle Mitteilungen beseitigen willst?",
-  "notifications.clear": "Mitteilungen beseitigen",
-  "notifications.column_settings.alert": "Desktop-Benachrichtigungen",
-  "notifications.column_settings.favourite": "Favorisierungen:",
-  "notifications.column_settings.follow": "Neue Folgende:",
-  "notifications.column_settings.mention": "Erwähnungen:",
-  "notifications.column_settings.reblog": "Geteilte Beiträge:",
-  "notifications.column_settings.show": "In der Spalte anzeigen",
-  "notifications.column_settings.sound": "Ton abspielen",
-  "notifications.settings": "Spalteneinstellungen",
-  "privacy.change": "Privatsphäre des Status anpassen",
-  "privacy.direct.long": "Beitrag nur an erwähnte Benutzer",
-  "privacy.direct.short": "Direkt",
-  "privacy.private.long": "Beitrag nur an Folgende",
-  "privacy.private.short": "Privat",
-  "privacy.public.long": "Beitrag an öffentliche Zeitleisten",
-  "privacy.public.short": "Öffentlich",
-  "privacy.unlisted.long": "Nicht in öffentlichen Zeitleisten anzeigen",
-  "privacy.unlisted.short": "Nicht gelistet",
-  "reply_indicator.cancel": "Abbrechen",
-  "report.heading": "Neue Meldung",
-  "report.placeholder": "Zusätzliche Kommentare",
-  "report.submit": "Absenden",
-  "report.target": "Melden",
-  "search_results.total": "{count, number} {count, plural, one {Ergebnis} other {Ergebnisse}}",
-  "search.placeholder": "Suche",
-  "search.status_by": "Status von {name}",
-  "status.delete": "Löschen",
-  "status.favourite": "Favorisieren",
-  "status.load_more": "Weitere laden",
-  "status.media_hidden": "Medien versteckt",
-  "status.mention": "Erwähnen",
-  "status.open": "Öffnen",
-  "status.reblog": "Teilen",
-  "status.reblogged_by": "{name} teilte",
-  "status.reply": "Antworten",
-  "status.replyAll": "Auf Thread antworten",
-  "status.report": "@{name} melden",
-  "status.sensitive_toggle": "Klicke, um sie zu sehen",
-  "status.sensitive_warning": "Heikle Inhalte",
-  "status.show_less": "Weniger anzeigen",
-  "status.show_more": "Mehr anzeigen",
-  "tabs_bar.compose": "Schreiben",
-  "tabs_bar.federated_timeline": "Föderation",
-  "tabs_bar.home": "Home",
-  "tabs_bar.local_timeline": "Lokal",
-  "tabs_bar.notifications": "Mitteilungen",
-  "upload_area.title": "Hereinziehen zum Hochladen",
-  "upload_button.label": "Mediendatei hinzufügen",
-  "upload_form.undo": "Entfernen",
-  "upload_progress.label": "Lade hoch…",
-  "video_player.toggle_sound": "Ton umschalten",
-  "video_player.toggle_visible": "Sichtbarkeit umschalten",
-  "video_player.expand": "Videoanzeige vergrößern",
-  "video_player.video_error": "Video konnte nicht abgespielt werden",
-};
-
-export default de;
diff --git a/app/assets/javascripts/components/locales/en.jsx b/app/assets/javascripts/components/locales/en.jsx
deleted file mode 100644
index afe714cac..000000000
--- a/app/assets/javascripts/components/locales/en.jsx
+++ /dev/null
@@ -1,177 +0,0 @@
-/**
- * Note for Contributors:
- * This file (en.jsx) serve as a template for other languages.
- * To make other contributors' life easier, please REMEMBER:
- *   1. to add your new string here; and
- *   2. to remove old strings that are no longer needed; and
- *   3. to sort the strings by the key.
- *   4. To rename the `en` const name and export default name to match your locale.
- * Thanks!
- */
-const en = {
-  "account.block": "Block @{name}",
-  "account.disclaimer": "This user is from another instance. This number may be larger.",
-  "account.edit_profile": "Edit profile",
-  "account.follow": "Follow",
-  "account.followers": "Followers",
-  "account.follows": "Follows",
-  "account.follows_you": "Follows you",
-  "account.mention": "Mention @{name}",
-  "account.mute": "Mute @{name}",
-  "account.posts": "Posts",
-  "account.report": "Report @{name}",
-  "account.requested": "Awaiting approval",
-  "account.unblock": "Unblock @{name}",
-  "account.unfollow": "Unfollow",
-  "account.unmute": "Unmute @{name}",
-  "boost_modal.combo": "You can press {combo} to skip this next time",
-  "column.blocks": "Blocked users",
-  "column.community": "Local timeline",
-  "column.favourites": "Favourites",
-  "column.follow_requests": "Follow requests",
-  "column.home": "Home",
-  "column.mutes": "Muted users",
-  "column.notifications": "Notifications",
-  "column.public": "Federated timeline",
-  "column_back_button.label": "Back",
-  "column_subheading.navigation": "Navigation",
-  "column_subheading.settings": "Settings",
-  "compose_form.lock_disclaimer": "Your account is not {locked}. Anyone can follow you to view your follower-only posts.",
-  "compose_form.lock_disclaimer.lock": "locked",
-  "compose_form.placeholder": "What is on your mind?",
-  "compose_form.privacy_disclaimer": "Your post will be delivered to mentioned users on {domains}. Do you trust {domainsCount, plural, one {that server} other {those servers}}? Post privacy only works on Mastodon instances. If {domains} {domainsCount, plural, one {is not a Mastodon instance} other {are not Mastodon instances}}, there will be no indication that your post is not a public post, and it may be boosted or otherwise made visible to unintended recipients.",
-  "compose_form.publish": "Toot",
-  "compose_form.sensitive": "Mark media as sensitive",
-  "compose_form.spoiler": "Hide text behind warning",
-  "compose_form.spoiler_placeholder": "Content warning",
-  "confirmation_modal.cancel": "Cancel",
-  "confirmations.block.confirm": "Block",
-  "confirmations.block.message": "Are you sure you want to block {name}?",
-  "confirmations.delete.confirm": "Delete",
-  "confirmations.delete.message": "Are you sure you want to delete this status?",
-  "confirmations.mute.confirm": "Mute",
-  "confirmations.mute.message": "Are you sure you want to mute {name}?",
-  "emoji_button.activity": "Activity",
-  "emoji_button.flags": "Flags",
-  "emoji_button.food": "Food & Drink",
-  "emoji_button.label": "Insert emoji",
-  "emoji_button.nature": "Nature",
-  "emoji_button.objects": "Objects",
-  "emoji_button.people": "People",
-  "emoji_button.search": "Search...",
-  "emoji_button.symbols": "Symbols",
-  "emoji_button.travel": "Travel & Places",
-  "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
-  "empty_column.hashtag": "There is nothing in this hashtag yet.",
-  "empty_column.home.public_timeline": "the public timeline",
-  "empty_column.home": "You aren't following anyone yet. Visit {public} or use search to get started and meet other users.",
-  "empty_column.notifications": "You don't have any notifications yet. Interact with others to start the conversation.",
-  "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other instances to fill it up",
-  "follow_request.authorize": "Authorize",
-  "follow_request.reject": "Reject",
-  "getting_started.apps": "Various apps are available",
-  "getting_started.heading": "Getting started",
-  "getting_started.open_source_notice": "Mastodon is open source software. You can contribute or report issues on GitHub at {github}. {apps}.",
-  "home.column_settings.advanced": "Advanced",
-  "home.column_settings.basic": "Basic",
-  "home.column_settings.filter_regex": "Filter out by regular expressions",
-  "home.column_settings.show_reblogs": "Show boosts",
-  "home.column_settings.show_replies": "Show replies",
-  "home.settings": "Column settings",
-  "lightbox.close": "Close",
-  "loading_indicator.label": "Loading...",
-  "media_gallery.toggle_visible": "Toggle visibility",
-  "missing_indicator.label": "Not found",
-  "navigation_bar.blocks": "Blocked users",
-  "navigation_bar.community_timeline": "Local timeline",
-  "navigation_bar.edit_profile": "Edit profile",
-  "navigation_bar.favourites": "Favourites",
-  "navigation_bar.follow_requests": "Follow requests",
-  "navigation_bar.info": "Extended information",
-  "navigation_bar.logout": "Logout",
-  "navigation_bar.mutes": "Muted users",
-  "navigation_bar.preferences": "Preferences",
-  "navigation_bar.public_timeline": "Federated timeline",
-  "notification.favourite": "{name} favourited your status",
-  "notification.follow": "{name} followed you",
-  "notification.mention": "{name} mentioned you",
-  "notification.reblog": "{name} boosted your status",
-  "notifications.clear": "Clear notifications",
-  "notifications.clear_confirmation": "Are you sure you want to permanently clear all your notifications?",
-  "notifications.column_settings.alert": "Desktop notifications",
-  "notifications.column_settings.favourite": "Favourites:",
-  "notifications.column_settings.follow": "New followers:",
-  "notifications.column_settings.mention": "Mentions:",
-  "notifications.column_settings.reblog": "Boosts:",
-  "notifications.column_settings.show": "Show in column",
-  "notifications.column_settings.sound": "Play sound",
-  "notifications.settings": "Column settings",
-  "onboarding.done": "Done",
-  "onboarding.next": "Next",
-  "onboarding.page_five.public_timelines": "The local timeline shows public posts from everyone on {domain}. The federated timeline shows public posts from everyone who people on {domain} follow. These are the Public Timelines, a great way to discover new people.",
-  "onboarding.page_four.home": "The home timeline shows posts from people you follow.",
-  "onboarding.page_four.notifications": "The notifications column shows when someone interacts with you.",
-  "onboarding.page_one.federation": "Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
-  "onboarding.page_one.handle": "You are on {domain}, so your full handle is {handle}",
-  "onboarding.page_one.welcome": "Welcome to Mastodon!",
-  "onboarding.page_six.admin": "Your instance's admin is {admin}.",
-  "onboarding.page_six.almost_done": "Almost done...",
-  "onboarding.page_six.appetoot": "Bon Appetoot!",
-  "onboarding.page_six.apps_available": "There are {apps} available for iOS, Android and other platforms.",
-  "onboarding.page_six.github": "Mastodon is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
-  "onboarding.page_six.guidelines": "community guidelines",
-  "onboarding.page_six.read_guidelines": "Please read {domain}'s {guidelines}!",
-  "onboarding.page_six.various_app": "mobile apps",
-  "onboarding.page_three.profile": "Edit your profile to change your avatar, bio, and display name. There, you will also find other preferences.",
-  "onboarding.page_three.search": "Use the search bar to find people and look at hashtags, such as {illustration} and {introductions}. To look for a person who is not on this instance, use their full handle.",
-  "onboarding.page_two.compose": "Write posts from the compose column. You can upload images, change privacy settings, and add content warnings with the icons below.",
-  "onboarding.skip": "Skip",
-  "privacy.change": "Adjust status privacy",
-  "privacy.direct.long": "Post to mentioned users only",
-  "privacy.direct.short": "Direct",
-  "privacy.private.long": "Post to followers only",
-  "privacy.private.short": "Followers-only",
-  "privacy.public.long": "Post to public timelines",
-  "privacy.public.short": "Public",
-  "privacy.unlisted.long": "Do not post to public timelines",
-  "privacy.unlisted.short": "Unlisted",
-  "reply_indicator.cancel": "Cancel",
-  "report.heading": "New report",
-  "report.placeholder": "Additional comments",
-  "report.submit": "Submit",
-  "report.target": "Reporting",
-  "search.placeholder": "Search",
-  "search.status_by": "Status by {name}",
-  "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
-  "status.cannot_reblog": "This post cannot be boosted",
-  "status.delete": "Delete",
-  "status.favourite": "Favourite",
-  "status.load_more": "Load more",
-  "status.media_hidden": "Media hidden",
-  "status.mention": "Mention @{name}",
-  "status.open": "Expand this status",
-  "status.reblog": "Boost",
-  "status.reblogged_by": "{name} boosted",
-  "status.reply": "Reply",
-  "status.replyAll": "Reply to thread",
-  "status.report": "Report @{name}",
-  "status.sensitive_toggle": "Click to view",
-  "status.sensitive_warning": "Sensitive content",
-  "status.show_less": "Show less",
-  "status.show_more": "Show more",
-  "tabs_bar.compose": "Compose",
-  "tabs_bar.federated_timeline": "Federated",
-  "tabs_bar.home": "Home",
-  "tabs_bar.local_timeline": "Local",
-  "tabs_bar.notifications": "Notifications",
-  "upload_area.title": "Drag & drop to upload",
-  "upload_button.label": "Add media",
-  "upload_form.undo": "Undo",
-  "upload_progress.label": "Uploading...",
-  "video_player.expand": "Expand video",
-  "video_player.toggle_sound": "Toggle sound",
-  "video_player.toggle_visible": "Toggle visibility",
-  "video_player.video_error": "Video could not be played",
-};
-
-export default en;
diff --git a/app/assets/javascripts/components/locales/eo.jsx b/app/assets/javascripts/components/locales/eo.jsx
deleted file mode 100644
index 8c118b31f..000000000
--- a/app/assets/javascripts/components/locales/eo.jsx
+++ /dev/null
@@ -1,68 +0,0 @@
-const eo = {
-  "column_back_button.label": "Reveni",
-  "lightbox.close": "Fermi",
-  "loading_indicator.label": "Ŝarĝanta...",
-  "status.mention": "Mencii @{name}",
-  "status.delete": "Forigi",
-  "status.reply": "Respondi",
-  "status.reblog": "Diskonigi",
-  "status.favourite": "Favori",
-  "status.reblogged_by": "{name} diskonigita",
-  "status.sensitive_warning": "Tikla enhavo",
-  "status.sensitive_toggle": "Alklaki por vidi",
-  "video_player.toggle_sound": "Aktivigi sonojn",
-  "account.mention": "Mencii @{name}",
-  "account.edit_profile": "Redakti la profilon",
-  "account.unblock": "Malbloki @{name}",
-  "account.unfollow": "Malsekvi",
-  "account.block": "Bloki @{name}",
-  "account.follow": "Sekvi",
-  "account.posts": "Mesaĝoj",
-  "account.follows": "Sekvatoj",
-  "account.followers": "Sekvantoj",
-  "account.follows_you": "Sekvas vin",
-  "account.requested": "Atendas aprobon",
-  "getting_started.heading": "Por komenci",
-  "getting_started.about_addressing": "Vi povas sekvi homojn se vi konas la uzantnomon kaj domajnon tajpinte retpoŝtecan adreson en la serĉilon.",
-  "getting_started.about_shortcuts": "Se la celita uzanto troviĝas en la sama domajno de vi, uzi nur la uzantnomon sufiĉos. La sama regulo validas por mencii aliajn uzantojn en mesaĝo.",
-  "getting_started.open_source_notice": "Mastodon estas malfermitkoda programo. Vi povas kontribui aŭ raporti problemojn en github je {github}. {apps}.",
-  "column.home": "Hejmo",
-  "column.community": "Loka tempolinio",
-  "column.public": "Fratara tempolinio",
-  "column.notifications": "Sciigoj",
-  "tabs_bar.compose": "Ekskribi",
-  "tabs_bar.home": "Hejmo",
-  "tabs_bar.mentions": "Sciigoj",
-  "tabs_bar.public": "Fratara tempolinio",
-  "tabs_bar.notifications": "Sciigoj",
-  "compose_form.placeholder": "Pri kio vi pensas?",
-  "compose_form.publish": "Hup",
-  "compose_form.sensitive": "Marki ke la enhavo estas tikla",
-  "compose_form.spoiler": "Kaŝi la tekston malantaŭ averto",
-  "compose_form.private": "Marki ke la enhavo estas privata",
-  "compose_form.privacy_disclaimer": "Via privata mesaĝo estos sendita nur al menciitaj uzantoj en {domains}. Ĉu vi fidas {domainsCount, plural, one {tiun servilon} other {tiujn servilojn}}? Mesaĝa privateco funkcias nur en aperaĵoj de Mastodon. Se {domains} {domainsCount, plural, one {ne estas aperaĵo de Mastodon} other {ne estas aperaĵoj de Mastodon}}, estos neniu indiko ke via mesaĝo estas privata, kaj ĝi povus esti diskonigita aŭ videbligita al necelitaj ricevantoj.",
-  "compose_form.unlisted": "Ne afiŝi en publikaj tempolinioj",
-  "navigation_bar.edit_profile": "Redakti la profilon",
-  "navigation_bar.preferences": "Preferoj",
-  "navigation_bar.community_timeline": "Loka tempolinio",
-  "navigation_bar.public_timeline": "Fratara tempolinio",
-  "navigation_bar.logout": "Elsaluti",
-  "reply_indicator.cancel": "Rezigni",
-  "search.placeholder": "Serĉi",
-  "search.account": "Konto",
-  "search.hashtag": "Kradvorto",
-  "upload_button.label": "Aldoni enhavaĵon",
-  "upload_form.undo": "Malfari",
-  "notification.follow": "{name} sekvis vin",
-  "notification.favourite": "{name} favoris vian mesaĝon",
-  "notification.reblog": "{name} diskonigis vian mesaĝon",
-  "notification.mention": "{name} menciis vin",
-  "notifications.column_settings.alert": "Retumilaj atentigoj",
-  "notifications.column_settings.show": "Montri en kolono",
-  "notifications.column_settings.follow": "Novaj sekvantoj:",
-  "notifications.column_settings.favourite": "Favoroj:",
-  "notifications.column_settings.mention": "Mencioj:",
-  "notifications.column_settings.reblog": "Diskonigoj:",
-};
-
-export default eo;
diff --git a/app/assets/javascripts/components/locales/es.jsx b/app/assets/javascripts/components/locales/es.jsx
deleted file mode 100644
index bbd6e07c1..000000000
--- a/app/assets/javascripts/components/locales/es.jsx
+++ /dev/null
@@ -1,93 +0,0 @@
-const es = {
-  "column_back_button.label": "Atrás",
-  "lightbox.close": "Cerrar",
-  "loading_indicator.label": "Cargando...",
-  "status.mention": "Mencionar",
-  "status.delete": "Borrar",
-  "status.reply": "Responder",
-  "status.reblog": "Retoot",
-  "status.favourite": "Favorito",
-  "status.reblogged_by": "Retooteado por {name}",
-  "status.sensitive_warning": "Contenido sensible",
-  "status.sensitive_toggle": "Click para ver",
-  "status.show_more": "Mostrar más",
-  "status.show_less": "Mostrar menos",
-  "status.open": "Expandir estado",
-  "status.report": "Reportar",
-  "video_player.toggle_sound": "Act/Desac. sonido",
-  "account.mention": "Mencionar",
-  "account.edit_profile": "Editar perfil",
-  "account.unblock": "Desbloquear",
-  "account.unfollow": "Dejar de seguir",
-  "account.mute": "Silenciar",
-  "account.block": "Bloquear",
-  "account.follow": "Seguir",
-  "account.posts": "Publicaciones",
-  "account.follows": "Seguir",
-  "account.followers": "Seguidores",
-  "account.follows_you": "Te sigue",
-  "account.requested": "Esperando aprobación",
-  "getting_started.heading": "Primeros pasos",
-  "getting_started.about_addressing": "Puedes seguir a gente si conoces su nombre de usuario y el dominio en el que están registrados, introduciendo algo similar a una dirección de correo electrónico en el formulario en la parte superior de la barra lateral.",
-  "getting_started.about_shortcuts": "Si el usuario que buscas está en el mismo dominio que tú, simplemente funcionará introduciendo el nombre de usuario. La misma regla se aplica para mencionar a usuarios.",
-  "getting_started.open_source_notice": "Mastodon es software libre. Puedes contribuir o reportar errores en {github}. {apps}.",
-  "column.home": "Inicio",
-  "column.community": "Historia local",
-  "column.public": "Historia federada",
-  "column.notifications": "Notificaciones",
-  "column.blocks": "Usuarios bloqueados",
-  "column.favourites": "Favoritos",
-  "column.follow_requests": "Solicitudes para seguirte",
-  "column.mutes": "Usuarios silenciados",
-  "tabs_bar.compose": "Redactar",
-  "tabs_bar.home": "Inicio",
-  "tabs_bar.mentions": "Menciones",
-  "tabs_bar.public": "Público",
-  "tabs_bar.notifications": "Notificaciones",
-  "compose_form.placeholder": "¿En qué estás pensando?",
-  "compose_form.publish": "Tootear",
-  "compose_form.sensitive": "Marcar contenido como sensible",
-  "compose_form.spoiler": "Ocultar texto tras advertencia",
-  "compose_form.spoiler_placeholder": "Advertencia de contenido",
-  "composer_form.private": "Marcar como privado",
-  "composer_form.privacy_disclaimer": "Tu estado se mostrará a los usuarios mencionados en {domains}. Tu estado podrá ser visto en otras instancias, quizás no quieras que tu estado sea visto por otros usuarios.",
-  "compose_form.unlisted": "No mostrar en la historia federada",
-  "navigation_bar.edit_profile": "Editar perfil",
-  "navigation_bar.preferences": "Preferencias",
-  "navigation_bar.community_timeline": "Historia local",
-  "navigation_bar.public_timeline": "Historia federada",
-  "navigation_bar.favourites": "Favoritos",
-  "navigation_bar.blocks": "Usuarios bloqueados",
-  "navigation_bar.info": "Información adicional",
-  "navigation_bar.logout": "Cerrar sesión",
-  "navigation_bar.follow_requests": "Solicitudes para seguirte",
-  "navigation_bar.mutes": "Usuarios silenciados",
-  "reply_indicator.cancel": "Cancelar",
-  "search.placeholder": "Buscar",
-  "search.account": "Cuenta",
-  "search.hashtag": "Etiqueta",
-  "upload_button.label": "Subir multimedia",
-  "upload_form.undo": "Deshacer",
-  "notification.follow": "{name} te empezó a seguir",
-  "notification.favourite": "{name} marcó tu estado como favorito",
-  "notification.reblog": "{name} ha retooteado tu estado",
-  "notification.mention": "{name} te ha mencionado",
-  "notifications.column_settings.alert": "Notificaciones de escritorio",
-  "notifications.column_settings.show": "Mostrar en columna",
-  "notifications.column_settings.follow": "Nuevos seguidores:",
-  "notifications.column_settings.favourite": "Favoritos:",
-  "notifications.column_settings.mention": "Menciones:",
-  "notifications.column_settings.reblog": "Retoots:",
-  "emoji_button.label": "Insertar emoji",
-  "privacy.public.short": "Público",
-  "privacy.public.long": "Mostrar en la historia federada",
-  "privacy.unlisted.short": "Sin federar",
-  "privacy.unlisted.long": "No mostrar en la historia federada",
-  "privacy.private.short": "Privado",
-  "privacy.private.long": "Sólo mostrar a seguidores",
-  "privacy.direct.short": "Directo",
-  "privacy.direct.long": "Sólo mostrar a los usuarios mencionados",
-  "privacy.change": "Ajustar privacidad"
-};
-
-export default es;
diff --git a/app/assets/javascripts/components/locales/fa.jsx b/app/assets/javascripts/components/locales/fa.jsx
deleted file mode 100644
index 40a750618..000000000
--- a/app/assets/javascripts/components/locales/fa.jsx
+++ /dev/null
@@ -1,136 +0,0 @@
-const fa = {
-  "account.block": "@{name} را مسدود کن",
-  "account.disclaimer": "این کاربر عضو سرور متفاوتی است. شاید عدد واقعی بیشتر از این باشد.",
-  "account.edit_profile": "ویرایش نمایه",
-  "account.follow": "پی بگیرید",
-  "account.followers": "پیگیران",
-  "account.follows_you": "پیگیر شماست",
-  "account.follows": "پی می‌گیرد",
-  "account.mention": "نام‌بردن از @{name}",
-  "account.mute": "بی‌صدا کردن @{name}",
-  "account.posts": "نوشته‌ها",
-  "account.report": "گزارش @{name}",
-  "account.requested": "در انتظار پذیرش",
-  "account.unblock": "رفع انسداد @{name}",
-  "account.unfollow": "پایان پیگیری",
-  "account.unmute": "باصدا کردن @{name}",
-  "boost_modal.combo": "دکمهٔ {combo} را بزنید تا دیگر این را نبینید",
-  "column_back_button.label": "بازگشت",
-  "column.blocks": "کاربران مسدودشده",
-  "column.community": "نوشته‌های محلی",
-  "column.favourites": "پسندیده‌ها",
-  "column.follow_requests": "درخواست‌های پیگیری",
-  "column.home": "خانه",
-  "column.mutes": "کاربران بی‌صداشده",
-  "column.notifications": "اعلان‌ها",
-  "column.public": "نوشته‌های همه‌جا",
-  "compose_form.placeholder": "تازه چه خبر؟",
-  "compose_form.privacy_disclaimer": "نوشتهٔ خصوصی شما به کاربران نام‌برده‌شده در {domains} فرستاده می‌شود. آیا به {domainsCount, plural, one {آن سرور} other {آن سرورها}} اعتماد دارید؟ تنظیمات حریم خصوصی نوشته‌ها تنها در سرورهای ماستدون کار می‌کند. اگر {domains} {domainsCount, plural, one {یک سرور ماستدون نباشد} other {سرورهای ماستدون نباشند}}، اشاره‌ای به خصوصی‌بودن نوشتهٔ شما نخواهد شد و شاید نوشتهٔ شما هم‌رسان شود یا برای کاربرانی که نمی‌خواهید نمایش یابد.",
-  "compose_form.publish": "بوق",
-  "compose_form.sensitive": "تصاویر حساس هستند",
-  "compose_form.spoiler_placeholder": "هشدار محتوا",
-  "compose_form.spoiler": "نوشته را پشت هشدار پنهان کنید",
-  "emoji_button.label": "افزودن شکلک",
-  "emoji_button.search": "جستجو...",
-  "emoji_button.people": "مردم",
-  "emoji_button.nature": "طبیعت",
-  "emoji_button.food": "غذا و نوشیدنی",
-  "emoji_button.activity": "فعالیت",
-  "emoji_button.travel": "سفر و مکان",
-  "emoji_button.objects": "اشیا",
-  "emoji_button.symbols": "نمادها",
-  "emoji_button.flags": "پرچم‌ها",
-  "empty_column.community": "فهرست نوشته‌های محلی خالی است. چیزی بنویسید تا چرخش بچرخد!",
-  "empty_column.hashtag": "هنوز هیچ چیزی با این هشتگ نیست.",
-  "empty_column.home.public_timeline": "فهرست نوشته‌های همه‌جا",
-  "empty_column.home": "شما هنوز پیگیر کسی نیستید. {public} را ببینید یا چیزی را جستجو کنید تا کاربران دیگر را ببینید.",
-  "empty_column.notifications": "هنوز هیچ اعلانی ندارید. به نوشته‌های دیگران واکنش نشان دهید تا گفتگو آغاز شود.",
-  "empty_column.public": "این‌جا هنوز چیزی نیست! خودتان چیزی بنویسید یا کاربران دیگر را پی بگیرید تا این‌جا پر شود",
-  "follow_request.authorize": "اجازه دهید",
-  "follow_request.reject": "اجازه ندهید",
-  "getting_started.apps": "اپ‌های گوناگونی در دسترس‌اند",
-  "getting_started.heading": "آغاز کنید",
-  "getting_started.open_source_notice": "ماستدون یک نرم‌افزار آزاد است. می‌توانید در ساخت آن مشارکت کنید یا مشکلاتش را در {github} گزارش دهید. {apps}.",
-  "home.column_settings.advanced": "پیشرفته",
-  "home.column_settings.basic": "اصلی",
-  "home.column_settings.filter_regex": "با عبارت‌های باقاعده فیلتر کنید",
-  "home.column_settings.show_reblogs": "نمایش بازبوق‌ها",
-  "home.column_settings.show_replies": "نمایش پاسخ‌ها",
-  "home.settings": "تنظیمات ستون",
-  "lightbox.close": "بستن",
-  "loading_indicator.label": "بارگیری...",
-  "media_gallery.toggle_visible": "تغییر پیدایی",
-  "missing_indicator.label": "پیدا نشد",
-  "navigation_bar.blocks": "کاربران مسدودشده",
-  "navigation_bar.community_timeline": "نوشته‌های محلی",
-  "navigation_bar.edit_profile": "ویرایش نمایه",
-  "navigation_bar.favourites": "پسندیده‌ها",
-  "navigation_bar.follow_requests": "درخواست‌های پیگیری",
-  "navigation_bar.info": "اطلاعات تکمیلی",
-  "navigation_bar.logout": "خروج",
-  "navigation_bar.mutes": "کاربران بی‌صداشده",
-  "navigation_bar.preferences": "ترجیحات",
-  "navigation_bar.public_timeline": "نوشته‌های همه‌جا",
-  "notification.favourite": "{name} نوشتهٔ شما را پسندید",
-  "notification.follow": "{name} پیگیر شما شد",
-  "notification.mention": "{name} از شما نام برد",
-  "notification.reblog": "{name} نوشتهٔ شما را بازبوقید",
-  "notifications.clear_confirmation": "واقعاً می‌خواهید همهٔ اعلان‌هایتان را برای همیشه پاک کنید؟",
-  "notifications.clear": "پاک‌کردن اعلان‌ها",
-  "notifications.column_settings.alert": "اعلان در کامپیوتر",
-  "notifications.column_settings.favourite": "پسندیده‌ها:",
-  "notifications.column_settings.follow": "پیگیران تازه:",
-  "notifications.column_settings.mention": "نام‌بردن‌ها:",
-  "notifications.column_settings.reblog": "بازبوق‌ها:",
-  "notifications.column_settings.show": "در ستون نشان بده",
-  "notifications.column_settings.sound": "صدا را پخش کن",
-  "notifications.settings": "تنظیمات ستون",
-  "privacy.change": "تنظیم حریم خصوصی نوشته‌ها",
-  "privacy.direct.long": "تنها به کاربران نام‌برده‌شده نشان بده",
-  "privacy.direct.short": "مستقیم",
-  "privacy.private.long": "تنها به پیگیران نشان بده",
-  "privacy.private.short": "خصوصی",
-  "privacy.public.long": "در فهرست نوشته‌های عمومی نشان بده",
-  "privacy.public.short": "عمومی",
-  "privacy.unlisted.long": "در فهرست نوشته‌های همه‌جا نشان نده",
-  "privacy.unlisted.short": "فهرست‌نشده",
-  "reply_indicator.cancel": "لغو",
-  "report.heading": "گزارش تازه",
-  "report.placeholder": "توضیح اضافه",
-  "report.submit": "بفرست",
-  "report.target": "گزارش‌دادن",
-  "search_results.total": "{count, number} {count, plural, one {نتیجه} other {نتیجه}}",
-  "search.placeholder": "جستجو",
-  "search.status_by": "نوشتهٔ {name}",
-  "status.delete": "پاک‌کردن",
-  "status.favourite": "پسندیدن",
-  "status.load_more": "بیشتر نشان بده",
-  "status.media_hidden": "تصویر پنهان شده",
-  "status.mention": "از @{name} نام ببرید",
-  "status.open": "این نوشته را باز کن",
-  "status.reblog": "بوق",
-  "status.cannot_reblog": "این نوشته را نمی‌شود بازبوقید",
-  "status.reblogged_by": "{name} بازبوقید",
-  "status.reply": "پاسخ",
-  "status.replyAll": "به نوشته پاسخ دهید",
-  "status.report": "@{name} را گزارش دهید",
-  "status.sensitive_toggle": "برای دیدن کلیک کنید",
-  "status.sensitive_warning": "محتوای حساس",
-  "status.show_less": "نهفتن",
-  "status.show_more": "نمایش",
-  "tabs_bar.compose": "بنویسید",
-  "tabs_bar.federated_timeline": "همگانی",
-  "tabs_bar.home": "خانه",
-  "tabs_bar.local_timeline": "محلی",
-  "tabs_bar.notifications": "اعلان‌ها",
-  "upload_area.title": "برای بارگذاری به این‌جا بکشید",
-  "upload_button.label": "افزودن تصویر",
-  "upload_form.undo": "واگردانی",
-  "upload_progress.label": "بارگذاری...",
-  "video_player.toggle_sound": "تغییر صداداری",
-  "video_player.toggle_visible": "تغییر پیدایی",
-  "video_player.expand": "بازکردن ویدیو",
-  "video_player.video_error": "ویدیو نمی‌تواند پخش شود",
-};
-
-export default fa;
diff --git a/app/assets/javascripts/components/locales/fi.jsx b/app/assets/javascripts/components/locales/fi.jsx
deleted file mode 100644
index b3ae4bc56..000000000
--- a/app/assets/javascripts/components/locales/fi.jsx
+++ /dev/null
@@ -1,68 +0,0 @@
-const fi = {
-  "column_back_button.label": "Takaisin",
-  "lightbox.close": "Sulje",
-  "loading_indicator.label": "Ladataan...",
-  "status.mention": "Mainitse @{name}",
-  "status.delete": "Poista",
-  "status.reply": "Vastaa",
-  "status.reblog": "Buustaa",
-  "status.favourite": "Tykkää",
-  "status.reblogged_by": "{name} buustasi",
-  "status.sensitive_warning": "Arkaluontoista sisältöä",
-  "status.sensitive_toggle": "Klikkaa nähdäksesi",
-  "video_player.toggle_sound": "Äänet päälle/pois",
-  "account.mention": "Mainitse @{name}",
-  "account.edit_profile": "Muokkaa",
-  "account.unblock": "Salli @{name}",
-  "account.unfollow": "Lopeta seuraaminen",
-  "account.block": "Estä @{name}",
-  "account.follow": "Seuraa",
-  "account.posts": "Postit",
-  "account.follows": "Seuraa",
-  "account.followers": "Seuraajia",
-  "account.follows_you": "Seuraa sinua",
-  "account.requested": "Odottaa hyväksyntää",
-  "getting_started.heading": "Aloitus",
-  "getting_started.about_addressing": "Voit seurata ihmisiä jos tiedät heidän käyttäjänimensä ja domainin missä he ovat syöttämällä e-mail-esque osoitteen Etsi kenttään.",
-  "getting_started.about_shortcuts": "Jos etsimäsi henkilö on samassa domainissa kuin sinä, pelkkä käyttäjänimi kelpaa. Sama pätee kun mainitset ihmisiä statuksessasi",
-  "getting_started.open_source_notice": "Mastodon Mastodon on avoimen lähdekoodin ohjelma. Voit avustaa tai raportoida ongelmia GitHub palvelussa {github}. {apps}.",
-  "column.home": "Koti",
-  "column.community": "Paikallinen aikajana",
-  "column.public": "Yleinen aikajana",
-  "column.notifications": "Ilmoitukset",
-  "tabs_bar.compose": "Luo",
-  "tabs_bar.home": "Koti",
-  "tabs_bar.mentions": "Maininnat",
-  "tabs_bar.public": "Yleinen aikajana",
-  "tabs_bar.notifications": "Ilmoitukset",
-  "compose_form.placeholder": "Mitä sinulla on mielessä?",
-  "compose_form.publish": "Toot",
-  "compose_form.sensitive": "Merkitse media herkäksi",
-  "compose_form.spoiler": "Piiloita teksti varoituksen taakse",
-  "compose_form.private": "Merkitse yksityiseksi",
-  "compose_form.privacy_disclaimer": "Sinun yksityinen status toimitetaan mainitsemallesi käyttäjille domaineissa {domains}. Luotatko {domainsCount, plural, one {tähän palvelimeen} other {näihin palvelimiin}}? Postauksen yksityisyys toimii van Mastodon palvelimilla. Jos {domains} {domainsCount, plural, one {ei ole Mastodon palvelin} other {eivät ole Mastodon palvelin}}, viestiin ei tule Yksityinen-merkintää, ja sitä voidaan boostata tai muuten tehdä näkyväksi muille vastaanottajille.",
-  "compose_form.unlisted": "Älä näytä yleisillä aikajanoilla",
-  "navigation_bar.edit_profile": "Muokkaa profiilia",
-  "navigation_bar.preferences": "Ominaisuudet",
-  "navigation_bar.community_timeline": "Paikallinen aikajana",
-  "navigation_bar.public_timeline": "Yleinen aikajana",
-  "navigation_bar.logout": "Kirjaudu ulos",
-  "reply_indicator.cancel": "Peruuta",
-  "search.placeholder": "Hae",
-  "search.account": "Tili",
-  "search.hashtag": "Hashtag",
-  "upload_button.label": "Lisää mediaa",
-  "upload_form.undo": "Peru",
-  "notification.follow": "{name} seurasi sinua",
-  "notification.favourite": "{name} tykkäsi statuksestasi",
-  "notification.reblog": "{name} buustasi statustasi",
-  "notification.mention": "{name} mainitsi sinut",
-  "notifications.column_settings.alert": "Työpöytä ilmoitukset",
-  "notifications.column_settings.show": "Näytä sarakkeessa",
-  "notifications.column_settings.follow": "Uusia seuraajia:",
-  "notifications.column_settings.favourite": "Tykkäyksiä:",
-  "notifications.column_settings.mention": "Mainintoja:",
-  "notifications.column_settings.reblog": "Buusteja:",
-};
-
-export default fi;
diff --git a/app/assets/javascripts/components/locales/fr.jsx b/app/assets/javascripts/components/locales/fr.jsx
deleted file mode 100644
index de13284e5..000000000
--- a/app/assets/javascripts/components/locales/fr.jsx
+++ /dev/null
@@ -1,155 +0,0 @@
-/**
- * Note aux contributeurs⋅trices:
- * Pour rendre plus simple la vie des autres personnes
- * apportant leur contribution, merci de penser aux choses suivantes :
- *   1. Ajoutez les nouvelles chaînes traduites par ordre alphabétique
- *   2. Pensez à supprimer les chaînes inutilisées
- * Merci !
- */
-const fr = {
-  "account.block": "Bloquer",
-  "account.disclaimer": "Ce compte est situé sur une autre instance. Les nombres peuvent être plus grands.",
-  "account.edit_profile": "Modifier le profil",
-  "account.followers": "Abonné⋅e⋅s",
-  "account.follows": "Abonnements",
-  "account.follow": "Suivre",
-  "account.follows_you": "Vous suit",
-  "account.mention": "Mentionner",
-  "account.mute": "Masquer",
-  "account.posts": "Statuts",
-  "account.report": "Signaler",
-  "account.requested": "Invitation envoyée",
-  "account.unblock": "Débloquer",
-  "account.unfollow": "Ne plus suivre",
-  "account.unmute": "Ne plus masquer",
-  "column_back_button.label": "Retour",
-  "column.blocks": "Comptes bloqués",
-  "column.community": "Fil public local",
-  "column.favourites": "Favoris",
-  "column.follow_requests": "Demandes de suivi",
-  "column.home": "Accueil",
-  "column.notifications": "Notifications",
-  "column.public": "Fil public global",
-  "compose_form.placeholder": "Qu’avez-vous en tête ?",
-  "compose_form.privacy_disclaimer": "Votre statut privé va être transmis aux personnes mentionnées sur {domains}. Avez-vous confiance en {domainsCount, plural, one {ce serveur} other {ces serveurs}} pour ne pas divulguer votre statut ? Les statuts privés ne fonctionnent que sur les instances de Mastodon. Si {domains} {domainsCount, plural, one {n’est pas une instance de Mastodon} other {ne sont pas des instances de Mastodon}}, il n’y aura aucune indication que votre statut est privé, et il pourrait être partagé ou rendu visible d’une autre manière à d’autres personnes imprévues.",
-  "compose_form.private": "Rendre privé",
-  "compose_form.publish": "Pouet",
-  "compose_form.sensitive": "Marquer le média comme délicat",
-  "compose_form.spoiler": "Masquer le texte derrière un avertissement",
-  "compose_form.spoiler_placeholder": "Avertissement",
-  "compose_form.unlisted": "Ne pas afficher dans les fils publics",
-  "emoji_button.label": "Insérer un emoji",
-  "empty_column.community": "Le fil public local est vide. Écrivez-donc quelque chose pour le remplir !",
-  "empty_column.hashtag": "Il n’y a encore aucun contenu relatif à ce hashtag",
-  "empty_column.home.public_timeline": "le fil public",
-  "empty_column.home": "Vous ne suivez encore personne. Visitez {public} ou bien utilisez la recherche pour vous connecter à d’autres utilisateurs⋅trices.",
-  "empty_column.notifications": "Vous n’avez pas encore de notification. Interagissez avec d’autres utilisateurs⋅trices pour débuter la conversation.",
-  "empty_column.public": "Il n’y a rien ici ! Écrivez quelque chose publiquement, ou bien suivez manuellement des utilisateurs⋅trices d’autres instances pour remplir le fil public.",
-  "follow_request.authorize": "Autoriser",
-  "follow_request.reject": "Rejeter",
-  "getting_started.about_addressing": "Vous pouvez suivre les statuts de quelqu’un en entrant dans le champ de recherche leur identifiant et le domaine de leur instance, séparés par un @ à la manière d’une adresse courriel.",
-  "getting_started.about_developer": "Pour suivre le développeur de ce projet, c’est Gargron@mastodon.social",
-  "getting_started.about_shortcuts": "Si cette personne utilise la même instance que vous, l’identifiant suffit. C’est le même principe pour mentionner quelqu’un dans vos statuts.",
-  "getting_started.heading": "Pour commencer",
-  "getting_started.open_source_notice": "Mastodon est un logiciel libre. Vous pouvez contribuer et envoyer vos commentaires et rapports de bogues via {github} sur GitHub.",
-  "home.column_settings.advanced": "Avancé",
-  "home.column_settings.basic": "Basique",
-  "home.column_settings.filter_regex": "Filtrer avec une expression rationnelle",
-  "home.column_settings.show_reblogs": "Afficher les partages",
-  "home.column_settings.show_replies": "Afficher les réponses",
-  "home.settings": "Paramètres de la colonne",
-  "lightbox.close": "Fermer",
-  "loading_indicator.label": "Chargement…",
-  "media_gallery.toggle_visible": "Modifier la visibilité",
-  "missing_indicator.label": "Non trouvé",
-  "navigation_bar.blocks": "Comptes bloqués",
-  "navigation_bar.community_timeline": "Fil public local",
-  "navigation_bar.edit_profile": "Modifier le profil",
-  "navigation_bar.favourites": "Favoris",
-  "navigation_bar.follow_requests": "Demandes de suivi",
-  "navigation_bar.info": "Plus d’informations",
-  "navigation_bar.logout": "Déconnexion",
-  "navigation_bar.mutes": "Comptes silencés",
-  "navigation_bar.preferences": "Préférences",
-  "navigation_bar.public_timeline": "Fil public global",
-  "notification.favourite": "{name} a ajouté à ses favoris :",
-  "notification.follow": "{name} vous suit.",
-  "notification.mention": "{name} vous a mentionné⋅e :",
-  "notification.reblog": "{name} a partagé votre statut :",
-  "notifications.clear_confirmation": "Voulez-vous vraiment supprimer toutes vos notifications ?",
-  "notifications.clear": "Nettoyer",
-  "notifications.column_settings.alert": "Notifications locales",
-  "notifications.column_settings.favourite": "Favoris :",
-  "notifications.column_settings.follow": "Nouveaux abonné⋅e⋅s :",
-  "notifications.column_settings.mention": "Mentions :",
-  "notifications.column_settings.reblog": "Partages :",
-  "notifications.column_settings.show": "Afficher dans la colonne",
-  "notifications.column_settings.sound": "Émettre un son",
-  "notifications.settings": "Paramètres de la colonne",
-  "onboarding.next": "Suivant",
-  "onboarding.page_five.public_timelines": "Le fil public global affiche les posts de tou⋅te⋅s les utilisateurs⋅trices suivi⋅es par les membres de {domain}. Le fil public local est identique mais se limite aux utilisateurs⋅trices de {domain}.",
-  "onboarding.page_four.home": "L’Accueil affiche les posts de tou⋅te⋅s les utilisateurs⋅trices que vous suivez",
-  "onboarding.page_four.notifications": "Les Notifications vous informent lorsque quelqu’un interagit avec vous",
-  "onboarding.page_one.federation": "Mastodon est un réseau social qui appartient à tou⋅te⋅s.",
-  "onboarding.page_one.handle": "Vous êtes sur {domain}, une des nombreuses instances indépendantes de Mastodon. Votre nom d’utilisateur⋅trice complet est {handle}",
-  "onboarding.page_one.welcome": "Bienvenue sur Mastodon !",
-  "onboarding.page_six.admin": "L’administrateur⋅trice de votre instance est {admin}",
-  "onboarding.page_six.almost_done": "Nous y sommes presque…",
-  "onboarding.page_six.apps_available": "De nombreuses {apps} sont disponibles pour iOS, Android et autres. Et maintenant… Bon Appetoot!",
-  "onboarding.page_six.github": "Mastodon est un logiciel libre, gratuit et open-source. Vous pouvez rapporter des bogues, suggérer des fonctionnalités, ou contribuer à son développement sur {github}.",
-  "onboarding.page_six.guidelines": "règles de la communauté",
-  "onboarding.page_six.read_guidelines": "S’il vous plaît, n’oubliez pas de lire les {guidelines} !",
-  "onboarding.page_six.various_app": "applications mobiles",
-  "onboarding.page_three.profile": "Modifiez votre profil pour changer votre avatar, votre description ainsi que votre nom. Vous y trouverez également d’autres préférences.",
-  "onboarding.page_three.search": "Utilisez la barre de recherche pour trouver des utilisateurs⋅trices et regarder des hashtags tels que {illustration} et {introductions}. Pour trouver quelqu’un qui n’est pas sur cette instance, utilisez son nom d’utilisateur⋅trice complet.",
-  "onboarding.page_two.compose": "Écrivez depuis la colonne de composition. Vous pouvez ajouter des images, changer les réglages de confidentialité, et ajouter des avertissements de contenu (Content Warning) grâce aux icônes en dessous.",
-  "onboarding.skip": "Passer",
-  "privacy.change": "Ajuster la confidentialité du message",
-  "privacy.direct.long": "N’afficher que pour les personnes mentionnées",
-  "privacy.direct.short": "Direct",
-  "privacy.private.long": "N’afficher que pour vos abonné⋅e⋅s",
-  "privacy.private.short": "Privé",
-  "privacy.public.long": "Afficher dans les fils publics",
-  "privacy.public.short": "Public",
-  "privacy.unlisted.long": "Ne pas afficher dans les fils publics",
-  "privacy.unlisted.short": "Non-listé",
-  "reply_indicator.cancel": "Annuler",
-  "report.heading": "Nouveau signalement",
-  "report.placeholder": "Commentaires additionnels",
-  "report.submit": "Envoyer",
-  "report.target": "Signalement",
-  "search.account": "Compte",
-  "search.hashtag": "Mot-clé",
-  "search.placeholder": "Rechercher",
-  "search_results.total": "{count, number} {count, plural, one {résultat} other {résultats}}",
-  "search.status_by": "Statuts de {name}",
-  "status.delete": "Effacer",
-  "status.favourite": "Ajouter aux favoris",
-  "status.load_more": "Charger plus",
-  "status.media_hidden": "Média caché",
-  "status.mention": "Mentionner",
-  "status.open": "Déplier ce statut",
-  "status.reblogged_by": "{name} a partagé :",
-  "status.reblog": "Partager",
-  "status.reply": "Répondre",
-  "status.report": "Signaler @{name}",
-  "status.sensitive_toggle": "Cliquer pour dévoiler",
-  "status.sensitive_warning": "Contenu délicat",
-  "status.show_less": "Replier",
-  "status.show_more": "Déplier",
-  "tabs_bar.compose": "Composer",
-  "tabs_bar.federated_timeline": "Fil public global",
-  "tabs_bar.home": "Accueil",
-  "tabs_bar.local_timeline": "Fil public local",
-  "tabs_bar.mentions": "Mentions",
-  "tabs_bar.notifications": "Notifications",
-  "tabs_bar.public": "Fil public global",
-  "upload_area.title": "Glissez et déposez pour envoyer",
-  "upload_button.label": "Joindre un média",
-  "upload_form.undo": "Annuler",
-  "upload_progress.label": "Envoi en cours…",
-  "video_player.toggle_sound": "Mettre/Couper le son",
-  "video_player.toggle_visible": "Afficher/Cacher la vidéo",
-};
-
-export default fr;
diff --git a/app/assets/javascripts/components/locales/he.jsx b/app/assets/javascripts/components/locales/he.jsx
deleted file mode 100644
index 0fcb3d33e..000000000
--- a/app/assets/javascripts/components/locales/he.jsx
+++ /dev/null
@@ -1,177 +0,0 @@
-/**
- * הערה לתורמים:
- * קובץ זה (he.jsx)מבוסס על en.jsx ויש לעדכנו מפעם לפעם כשיוצאות גרסאות חדשות.
- * אנא הקלו על התורמים העתידיים:
- *   1. הוסיפו לכאן מחרוזות חדשות
- *   2. הסירו מחרוזות ישנות שכבר לא בשימוש בגרסא האנגלית
- *   3. מיינו את השורות לפי סדר ABC כמו בקובץ המקורי.
- *   4. ובבקשה כבדו את סגנון התרגום שהנחלנו כאן, או תאמו איתנו אם ישנם שינויים יסודיים
- * תודה!
- */
-const he = {
-  "account.block": "חסימת @{name}",
-  "account.disclaimer": "&rlm;משתמש זה מגיע מקהילה אחרת. המספר הזה עשוי להיות גדול יותר.",
-  "account.edit_profile": "עריכת פרופיל",
-  "account.follow": "מעקב",
-  "account.followers": "עוקבים",
-  "account.follows_you": "במעקב אחריך",
-  "account.follows": "נעקבים",
-  "account.mention": "אזכור של @{name}",
-  "account.mute": "להשתיק את @{name}",
-  "account.posts": "הודעות",
-  "account.report": "לדווח על @{name}",
-  "account.requested": "בהמתנה לאישור",
-  "account.unblock": "הסרת חסימה מעל @{name}",
-  "account.unfollow": "הפסקת מעקב",
-  "account.unmute": "הפסקת השתקת @{name}",
-  "boost_modal.combo": "ניתן להקיש {combo} כדי לדלג בפעם הבאה",
-  "column.blocks": "חסימות",
-  "column.community": "פיד מקומי",
-  "column.favourites": "חיבובים",
-  "column.follow_requests": "בקשות מעקב",
-  "column.home": "בבית",
-  "column.mutes": "השתקות",
-  "column.notifications": "התראות",
-  "column.public": "בפרהסיה",
-  "column_back_button.label": "אחורה",
-  "column_subheading.navigation": "ניווט",
-  "column_subheading.settings": "אפשרויות",
-  "compose_form.lock_disclaimer": "חשבונך אינו {locked}. כל אחד יוכל לעקוב אחריך כדי לקרוא את הודעותיך המיועדות לעוקבים בלבד.",
-  "compose_form.lock_disclaimer.lock": "נעול",
-  "compose_form.placeholder": "&rlm;מה עובר לך בראש?",
-  "compose_form.privacy_disclaimer": "&rlm;הודעתך הפרטית תשלח למשתמשים על {domains}. האם ניתן לסמוך על {domainsCount, plural, one {שרת זה} other {שרתים אלו}}? פרטיות ההודעה קיימת רק על שרתי מסטודון. אם {domains} {domainsCount, plural, one {הוא לא שרת מסטודון} other {הם לא שרתי מסטודון}}, לא יהיה שום סימן שההודעה פרטית, והוא עשוי להיות מקודם או להחשף למשתמשים שלא ברשימת היעד.",
-  "compose_form.publish": "&rlm;לחצרץ",
-  "compose_form.sensitive": "סימון תוכן כרגיש",
-  "compose_form.spoiler": "הסתרה מאחורי אזהרת תוכן",
-  "compose_form.spoiler_placeholder": "אזהרת תוכן",
-  "confirmation_modal.cancel": "ביטול",
-  "confirmations.block.confirm": "לחסום",
-  "confirmations.block.message": "לחסום את {name}?",
-  "confirmations.delete.confirm": "למחוק",
-  "confirmations.delete.message": "למחוק את ההודעה?",
-  "confirmations.mute.confirm": "להשתיק",
-  "confirmations.mute.message": "להשתיק את {name}?",
-  "emoji_button.activity": "פעילות",
-  "emoji_button.flags": "דגלים",
-  "emoji_button.food": "אוכל ושתיה",
-  "emoji_button.label": "הוספת אמוג'י",
-  "emoji_button.nature": "טבע",
-  "emoji_button.objects": "חפצים",
-  "emoji_button.people": "אנשים",
-  "emoji_button.search": "&rlm;חיפוש...",
-  "emoji_button.symbols": "סמלים",
-  "emoji_button.travel": "טיולים ואתרים",
-  "empty_column.community": "&rlm;טור הסביבה ריק. יש לפרסם משהו כדי שדברים יתרחילו להתגלגל!",
-  "empty_column.hashtag": "&rlm;אין כלום בהאשתג הזה עדיין.",
-  "empty_column.home.public_timeline": "בפרהסיה",
-  "empty_column.home": "&rlm;אף אחד לא במעקב עדיין. אפשר לבקר ב{public} או להשתמש בחיפוש כדי להתחיל ולהכיר חצוצרנים אחרים.",
-  "empty_column.notifications": "&rlm;אין התראות עדיין. יאללה, הגיע הזמן להתחיל להתערבב!",
-  "empty_column.public": "&rlm;אין פה כלום! כדי למלא את הטור הזה אפשר לכתוב משהו, או להתחיל לעקוב אחרי אנשים מקהילות אחרות.",
-  "follow_request.authorize": "קבלה",
-  "follow_request.reject": "דחיה",
-  "getting_started.apps": "קיים מבחר יישומונים לניידים",
-  "getting_started.heading": "על ההתחלה",
-  "getting_started.open_source_notice": "מסטודון היא תוכנה חופשית (בקוד פתוח). ניתן לתרום או לדווח על בעיות בגיטהאב: {github}. {apps}.",
-  "home.column_settings.advanced": "למתקדמים",
-  "home.column_settings.basic": "למתחילים",
-  "home.column_settings.filter_regex": "&rlm;סינון באמצעות ביטויים רגולריים (regular expressions)",
-  "home.column_settings.show_reblogs": "הצגת הדהודים",
-  "home.column_settings.show_replies": "הצגת תגובות",
-  "home.settings": "הגדרות טור",
-  "lightbox.close": "סגירה",
-  "loading_indicator.label": "טוען...",
-  "media_gallery.toggle_visible": "נראה\\בלתי נראה",
-  "missing_indicator.label": "לא נמצא",
-  "navigation_bar.blocks": "חסימות",
-  "navigation_bar.community_timeline": "פיד מקומי",
-  "navigation_bar.edit_profile": "עריכת פרופיל",
-  "navigation_bar.favourites": "חיבובים",
-  "navigation_bar.follow_requests": "בקשות מעקב",
-  "navigation_bar.info": "מידע נוסף",
-  "navigation_bar.logout": "יציאה",
-  "navigation_bar.mutes": "השתקות",
-  "navigation_bar.preferences": "העדפות",
-  "navigation_bar.public_timeline": "בפרהסיה",
-  "notification.favourite": "חצרוצך חובב על ידי {name}",
-  "notification.follow": "{name} במעקב אחרייך",
-  "notification.mention": 'אוזכרת ע"י {name}',
-  "notification.reblog": "חצרוצך הודהד על ידי {name}",
-  "notifications.clear": "הסרת התראות",
-  "notifications.clear_confirmation": "&rlm;להסיר את כל ההתראות? בטוח?",
-  "notifications.column_settings.alert": "התראות לשולחן העבודה",
-  "notifications.column_settings.favourite": "מחובבים:",
-  "notifications.column_settings.follow": "עוקבים חדשים:",
-  "notifications.column_settings.mention": "&rlm;פניות:",
-  "notifications.column_settings.reblog": "&rlm;הדהודים:",
-  "notifications.column_settings.show": "הצגה בטור",
-  "notifications.column_settings.sound": "שמע מופעל",
-  "notifications.settings": "הגדרות טור",
-  "onboarding.done": "יציאה",
-  "onboarding.next": "הלאה",
-  "onboarding.page_five.public_timelines": "ציר הזמן המקומי מראה הודעות פומביות מכל באי קהילת {domain}. ציר הזמן העולמי מראה הודעות פומביות מאת כי מי שבאי קהילת {domain} עוקבים אחריו. אלו צירי הזמן הפומביים, דרך נהדרת לגלות אנשים חדשים.",
-  "onboarding.page_four.home": "ציר זמן הבית מראה הודעות מהנעקבים שלך.",
-  "onboarding.page_four.notifications": "טור ההתראות מראה כשמישהו מתייחס להודעות שלך.",
-  "onboarding.page_one.federation": "מסטודון היא רשת של שרתים עצמאיים מצורפים ביחד לכדי רשת חברתית אחת גדולה. אנחנו מכנים את השרתים האלו: קהילות",
-  "onboarding.page_one.handle": "אתם בקהילה {domain}, ולכן מזהה המשתמש המלא שלכם הוא {handle}",
-  "onboarding.page_one.welcome": "ברוכים הבאים למסטודון!",
-  "onboarding.page_six.admin": "הקהילה מנוהלת בידי {admin}.",
-  "onboarding.page_six.almost_done": "כמעט סיימנו...",
-  "onboarding.page_six.appetoot": "בתותאבון!",
-  "onboarding.page_six.apps_available": "קיימים {apps} זמינים עבור אנדרואיד, אייפון ופלטפורמות נוספות.",
-  "onboarding.page_six.github": "מסטודון הוא תוכנה חופשית. ניתן לדווח על באגים, לבקש יכולות, או לתרום לקוד באתר {github}.",
-  "onboarding.page_six.guidelines": "חוקי הקהילה",
-  "onboarding.page_six.read_guidelines": "&rlm;נא לקרוא את {guidelines} של {domain}!",
-  "onboarding.page_six.various_app": "יישומונים ניידים",
-  "onboarding.page_three.profile": "ץתחת 'עריכת פרופיל' ניתן להחליף את תמונת הפרופיל שלך, תיאור קצר, והשם המוצג. שם גם ניתן למצוא אפשרויות והעדפות נוספות.",
-  "onboarding.page_three.search": "בחלונית החיפוש ניתן לחפש אנשים והאשתגים, כמו למשל {illustration} או {introductions}. כדי למצוא מישהו שלא על האינסטנס המקומי, יש להשתמש בכינוי המשתמש המלא.",
-  "onboarding.page_two.compose": "הודעות כותבים מטור הכתיבה. ניתן לנעלות תמונות, לשנות הגדרות פרטיות, ולהוסיף אזהרות תוכן בעזרת האייקונים שמתחת.",
-  "onboarding.skip": "לדלג",
-  "privacy.change": "שינוי פרטיות ההודעה",
-  "privacy.direct.long": "הצג רק למי שהודעה זו פונה אליו",
-  "privacy.direct.short": "הודעה ישירה",
-  "privacy.private.long": "הצג לעוקבים מקומיים בלבד",
-  "privacy.private.short": "לעוקבים בלבד",
-  "privacy.public.long": "פרסם בפומבי",
-  "privacy.public.short": "פומבי",
-  "privacy.unlisted.long": "לא יופיע בפידים הציבוריים המשותפים",
-  "privacy.unlisted.short": "לא לפיד הכללי",
-  "reply_indicator.cancel": "ביטול",
-  "report.heading": "דווח חדש",
-  "report.placeholder": "הערות נוספות",
-  "report.submit": "שליחה",
-  "report.target": "דיווח",
-  "search.placeholder": "חיפוש",
-  "search.status_by": "הודעה מאת {name}",
-  "search_results.total": "{count, number} {count, plural, one {תוצאה} other {תוצאות}}",
-  "status.cannot_reblog": "לא ניתן להדהד הודעה זו",
-  "status.delete": "מחיקה",
-  "status.favourite": "חיבוב",
-  "status.load_more": "עוד",
-  "status.media_hidden": "מדיה מוסתרת",
-  "status.mention": "פניה אל @{name}",
-  "status.open": "הרחבת הודעה",
-  "status.reblog": "הדהוד",
-  "status.reblogged_by": "הודהד על ידי {name}",
-  "status.reply": "תגובה",
-  "status.replyAll": "תגובה לכולם",
-  "status.report": "דיווח על @{name}",
-  "status.sensitive_warning": "תוכן רגיש",
-  "status.sensitive_toggle": "לחצו כדי לראות",
-  "status.show_less": "הראה פחות",
-  "status.show_more": "הראה יותר",
-  "tabs_bar.compose": "חיבור",
-  "tabs_bar.federated_timeline": "בפדרציה",
-  "tabs_bar.home": "בבית",
-  "tabs_bar.local_timeline": "פיד מקומי",
-  "tabs_bar.notifications": "התראות",
-  "upload_area.title": "ניתן להעלות על ידי Drag & drop",
-  "upload_button.label": "הוספת מדיה",
-  "upload_form.undo": "ביטול",
-  "upload_progress.label": "עולה...",
-  "video_player.expand": "הרחבת וידאו",
-  "video_player.toggle_sound": "הפעלת\\ביטול שמע",
-  "video_player.toggle_visible": "הפעלת\\ביטול תצוגה",
-  "video_player.video_error": "לא ניתן לנגן וידאו",
-};
-
-export default he;
diff --git a/app/assets/javascripts/components/locales/hr.jsx b/app/assets/javascripts/components/locales/hr.jsx
deleted file mode 100644
index 0ca3ef73e..000000000
--- a/app/assets/javascripts/components/locales/hr.jsx
+++ /dev/null
@@ -1,121 +0,0 @@
-const hr = {
-  "account.block": "Blokiraj @{name}",
-  "account.disclaimer": "Ovaj korisnik je sa druge instance. Ovaj broj bi mogao biti veći.",
-  "account.edit_profile": "Uredi profil",
-  "account.follow": "Slijedi",
-  "account.followers": "Sljedbenici",
-  "account.follows_you": "te slijedi",
-  "account.follows": "Slijedi",
-  "account.mention": "Spomeni @{name}",
-  "account.mute": "Utišaj @{name}",
-  "account.posts": "Postovi",
-  "account.report": "Prijavi @{name}",
-  "account.requested": "Čeka pristanak",
-  "account.unblock": "Deblokiraj @{name}",
-  "account.unfollow": "Prestani slijediti",
-  "account.unmute": "Poništi utišavanje @{name}",
-  "boost_modal.combo": "Možeš pritisnuti {combo} kako bi ovo preskočio sljedeći put",
-  "column_back_button.label": "Natrag",
-  "column.blocks": "Blokirani korisnici",
-  "column.community": "Lokalni timeline",
-  "column.favourites": "Favoriti",
-  "column.follow_requests": "Zahtjevi za slijeđenje",
-  "column.home": "Dom",
-  "column.notifications": "Notifikacije",
-  "column.public": "Federalni timeline",
-  "compose_form.placeholder": "Što ti je na umu?",
-  "compose_form.privacy_disclaimer": "Tvoj privatni status će biti dostavljen spomenutim korisnicima na {domains}. Vjeruješ li {domainsCount, plural, one {that server} drugim {those servers}}? Privatnost postova radi samo na Mastodon instancama. Ako {domains} {domainsCount, plural, one {is not a Mastodon instance} other {are not Mastodon instances}}, neće biti indikacije da je tvoj post privatan, i mogao bit biti podignut ili biti učinjen vidljivim na drugi način neželjenim primateljima.",
-  "compose_form.publish": "Toot",
-  "compose_form.sensitive": "Označi media sadržaj kao osjetljiv",
-  "compose_form.spoiler_placeholder": "Upozorenje o sadržaju",
-  "compose_form.spoiler": "Sakrij text iza upozorenja",
-  "emoji_button.label": "Umetni smajlije",
-  "empty_column.community": "Lokalni timeline je prazan. Napiši nešto javno kako bi pokrenuo stvari!",
-  "empty_column.hashtag": "Još ne postoji ništa s ovim hashtagom.",
-  "empty_column.home.public_timeline": "javni timeline",
-  "empty_column.home": "Još ne slijediš nikoga. Posjeti {public} ili koristi tražilicu kako bi počeo i upoznao druge korisnike.",
-  "empty_column.notifications": "Još nemaš notifikacija. Komuniciraj sa drugima kako bi započeo razgovor.",
-  "empty_column.public": "Ovdje nema ništa! Napiši nešto javno, ili ručno slijedi korisnike sa drugih instanci kako bi popunio",
-  "follow_request.authorize": "Authoriziraj",
-  "follow_request.reject": "Odbij",
-  "getting_started.apps": "Dostupne su razne aplikacije",
-  "getting_started.heading": "Počnimo",
-  "getting_started.open_source_notice": "Mastodon je softver otvorenog koda. Možeš pridonijeti ili prijaviti probleme na GitHubu  {github}. {apps}.",
-  "home.column_settings.advanced": "Napredno",
-  "home.column_settings.basic": "Osnovno",
-  "home.column_settings.filter_regex": "Filtriraj s regularnim izrazima",
-  "home.column_settings.show_reblogs": "Pokaži boosts",
-  "home.column_settings.show_replies": "Pokaži odgovore",
-  "home.settings": "Postavke Stupca",
-  "lightbox.close": "Zatvori",
-  "loading_indicator.label": "Učitavam...",
-  "media_gallery.toggle_visible": "Preklopi vidljivost",
-  "missing_indicator.label": "Nije nađen",
-  "navigation_bar.blocks": "Blokirani korisnici",
-  "navigation_bar.community_timeline": "Lokalni timeline",
-  "navigation_bar.edit_profile": "Uredi profil",
-  "navigation_bar.favourites": "Favoriti",
-  "navigation_bar.follow_requests": "Zahtjevi za sljeđenje",
-  "navigation_bar.info": "Proširena informacija",
-  "navigation_bar.logout": "Odjavi se",
-  "navigation_bar.preferences": "Postavke",
-  "navigation_bar.public_timeline": "Federalni timeline",
-  "notification.favourite": "{name} je lajkao tvoj status",
-  "notification.follow": "{name} te sada slijedi",
-  "notification.reblog": "{name} je podigao tvoj status",
-  "notifications.clear_confirmation": "Želiš li zaista obrisati sve svoje notifikacije?",
-  "notifications.clear": "Očisti notifikacije",
-  "notifications.column_settings.alert": "Desktop notifikacije",
-  "notifications.column_settings.favourite": "Favoriti:",
-  "notifications.column_settings.follow": "Novi sljedbenici:",
-  "notifications.column_settings.mention": "Spominjanja:",
-  "notifications.column_settings.reblog": "Boosts:",
-  "notifications.column_settings.show": "Prikaži u stupcu",
-  "notifications.column_settings.sound": "Sviraj zvuk",
-  "notifications.settings": "Postavke rubrike",
-  "privacy.change": "Podesi status privatnosti",
-  "privacy.direct.long": "Prikaži samo spomenutim korisnicima",
-  "privacy.direct.short": "Direktno",
-  "privacy.private.long": "Prikaži samo sljedbenicima",
-  "privacy.private.short": "Privatno",
-  "privacy.public.long": "Postaj na javne timeline",
-  "privacy.public.short": "Javno",
-  "privacy.unlisted.long": "Ne prikazuj u javnim timelineovima",
-  "privacy.unlisted.short": "Unlisted",
-  "reply_indicator.cancel": "Otkaži",
-  "report.heading": "Nova prijava",
-  "report.placeholder": "Dodatni komentari",
-  "report.submit": "Pošalji",
-  "report.target": "Prijavljivanje",
-  "search_results.total": "{count, number} {count, plural, one {result} other {results}}",
-  "search.placeholder": "Traži",
-  "search.status_by": "Status od {name}",
-  "status.delete": "Obriši",
-  "status.favourite": "Označi omiljenim",
-  "status.load_more": "Učitaj više",
-  "status.media_hidden": "Sakriven media sadržaj",
-  "status.mention": "Spomeni @{name}",
-  "status.open": "Proširi ovaj status",
-  "status.reblog": "Podigni",
-  "status.reblogged_by": "{name} je podigao",
-  "status.reply": "Odgovori",
-  "status.report": "Prijavi @{name}",
-  "status.sensitive_toggle": "Klikni da bi vidio",
-  "status.sensitive_warning": "Osjetljiv sadržaj",
-  "status.show_less": "Pokaži manje",
-  "status.show_more": "Pokaži više",
-  "tabs_bar.compose": "Sastavi",
-  "tabs_bar.federated_timeline": "Federalni",
-  "tabs_bar.home": "Dom",
-  "tabs_bar.local_timeline": "Lokalno",
-  "tabs_bar.notifications": "Notifikacije",
-  "upload_area.title": "Povuci & spusti kako bi uploadao",
-  "upload_button.label": "Dodaj media",
-  "upload_form.undo": "Poništi",
-  "upload_progress.label": "Uploadam...",
-  "video_player.toggle_sound": "Toggle zvuk",
-  "video_player.toggle_visible": "Preklopi vidljivost",
-  "video_player.expand": "Proširi video",
-};
-
-export default hr;
diff --git a/app/assets/javascripts/components/locales/hu.jsx b/app/assets/javascripts/components/locales/hu.jsx
deleted file mode 100644
index b68df66fd..000000000
--- a/app/assets/javascripts/components/locales/hu.jsx
+++ /dev/null
@@ -1,57 +0,0 @@
-const hu = {
-  "column_back_button.label": "Vissza",
-  "lightbox.close": "Bezárás",
-  "loading_indicator.label": "Betöltés...",
-  "status.mention": "Említés",
-  "status.delete": "Törlés",
-  "status.reply": "Válasz",
-  "status.reblog": "Reblog",
-  "status.favourite": "Kedvenc",
-  "status.reblogged_by": "{name} reblogolta",
-  "status.sensitive_warning": "Érzékeny tartalom",
-  "status.sensitive_toggle": "Katt a megtekintéshez",
-  "video_player.toggle_sound": "Hang kapcsolása",
-  "account.mention": "Említés",
-  "account.edit_profile": "Profil szerkesztése",
-  "account.unblock": "Blokkolás levétele",
-  "account.unfollow": "Követés abbahagyása",
-  "account.block": "Blokkolás",
-  "account.follow": "Követés",
-  "account.posts": "Posts",
-  "account.follows": "Követve",
-  "account.followers": "Követők",
-  "account.follows_you": "Követnek téged",
-  "getting_started.heading": "Első lépések",
-  "getting_started.about_addressing": "Követhetsz embereket felhasználónevük és a doménjük ismeretében, amennyiben megadod ezt az e-mail-szerű címet az oldalsáv tetején lévő rubrikában.",
-  "getting_started.about_shortcuts": "Ha a célzott személy azonos doménen tartózkodik, a felhasználónév elegendő. Ugyanez érvényes mikor személyeket említesz az állapotokban.",
-  "getting_started.about_developer": "A projekt fejlesztője követhető, mint Gargron@mastodon.social",
-  "column.home": "Kezdőlap",
-  "column.mentions": "Említések",
-  "column.public": "Nyilvános",
-  "column.notifications": "Értesítések",
-  "tabs_bar.compose": "Összeállítás",
-  "tabs_bar.home": "Kezdőlap",
-  "tabs_bar.mentions": "Említések",
-  "tabs_bar.public": "Nyilvános",
-  "tabs_bar.notifications": "Notifications",
-  "compose_form.placeholder": "Mire gondolsz?",
-  "compose_form.publish": "Tülk!",
-  "compose_form.sensitive": "Tartalom érzékenynek jelölése",
-  "compose_form.unlisted": "Listázatlan mód",
-  "navigation_bar.edit_profile": "Profil szerkesztése",
-  "navigation_bar.preferences": "Beállítások",
-  "navigation_bar.public_timeline": "Nyilvános időfolyam",
-  "navigation_bar.logout": "Kijelentkezés",
-  "reply_indicator.cancel": "Mégsem",
-  "search.placeholder": "Keresés",
-  "search.account": "Fiók",
-  "search.hashtag": "Hashtag",
-  "upload_button.label": "Média hozzáadása",
-  "upload_form.undo": "Mégsem",
-  "notification.follow": "{name} követ téged",
-  "notification.favourite": "{name} kedvencnek jelölte az állapotod",
-  "notification.reblog": "{name} reblogolta az állapotod",
-  "notification.mention": "{name} megemlített"
-};
-
-export default hu;
diff --git a/app/assets/javascripts/components/locales/id.jsx b/app/assets/javascripts/components/locales/id.jsx
deleted file mode 100644
index 08ea6bf15..000000000
--- a/app/assets/javascripts/components/locales/id.jsx
+++ /dev/null
@@ -1,167 +0,0 @@
-const id = {
-  "account.block": "Blokir @{name}",
-  "account.disclaimer": "Pengguna ini berasal dari server lain. Angka berikut mungkin lebih besar.",
-  "account.edit_profile": "Ubah profil",
-  "account.follow": "Ikuti",
-  "account.followers": "Pengikut",
-  "account.follows_you": "Mengikuti anda",
-  "account.follows": "Mengikuti",
-  "account.mention": "Balasan @{name}",
-  "account.mute": "Bisukan @{name}",
-  "account.posts": "Postingan",
-  "account.report": "Laporkan @{name}",
-  "account.requested": "Menunggu persetujuan",
-  "account.unblock": "Hapus blokir @{name}",
-  "account.unfollow": "Berhenti mengikuti",
-  "account.unmute": "Berhenti membisukan @{name}",
-  "boost_modal.combo": "Anda dapat menekan {combo} untuk melewati ini",
-  "column.blocks": "Pengguna diblokir",
-  "column.community": "Linimasa Lokal",
-  "column.favourites": "Favorit",
-  "column.follow_requests": "Permintaan mengikuti",
-  "column.home": "Beranda",
-  "column.mutes": "Pengguna dibisukan",
-  "column.notifications": "Notifikasi",
-  "column.public": "Linimasa gabunggan",
-  "column_back_button.label": "Kembali",
-  "column_subheading.navigation": "Navigasi",
-  "column_subheading.settings": "Pengaturan",
-  "compose_form.lock_disclaimer": "Akun anda tidak {locked}. Semua orang dapat mengikuti anda untuk melihat postingan khusus untuk pengikut anda.",
-  "compose_form.lock_disclaimer.lock": "dikunci",
-  "compose_form.placeholder": "Apa yang ada di pikiran anda?",
-  "compose_form.privacy_disclaimer": "Status pribadi anda akan dikirim ke pengguna yang disebut dalam {domains}. Apa anda mempercayai {domainsCount, plural, one {server tersebut} other {server tersebut}}? Privasi postingan hanya bekerja dalam server Mastodon. Jika {domains} {domainsCount, plural, one {bukan server Mastodon} other {bukan server Mastodon}}, akan ada indikasi bahwa postingan anda adalah postingan pribadi, dan dapat di-boost atau dapat dilihat oleh orang lain.",
-  "compose_form.publish": "Toot",
-  "compose_form.sensitive": "Tandai media sensitif",
-  "compose_form.spoiler": "Sembunyikan teks dibalik peringatan",
-  "compose_form.spoiler_placeholder": "Peringatan konten",
-  "confirmation_modal.cancel": "Batal",
-  "confirmations.block.confirm": "Blokir",
-  "confirmations.block.message": "Apa anda yakin ingin memblokir {name}?",
-  "confirmations.delete.confirm": "Hapus",
-  "confirmations.delete.message": "Apa anda yakin akan menghapus status ini?",
-  "confirmations.mute.confirm": "Bisukan",
-  "confirmations.mute.message": "Apa anda yakin ingin membisukan {name}?",
-  "emoji_button.activity": "Aktivitas",
-  "emoji_button.flags": "Bendera",
-  "emoji_button.food": "Makanan & Minuman",
-  "emoji_button.label": "Tambahkan emoji",
-  "emoji_button.nature": "Alam",
-  "emoji_button.objects": "Benda-benda",
-  "emoji_button.people": "Orang",
-  "emoji_button.search": "Cari...",
-  "emoji_button.symbols": "Simbol",
-  "emoji_button.travel": "Tempat Wisata",
-  "empty_column.community": "Linimasa lokal masih kosong. Tulis sesuatu secara publik dan buat roda berputar!",
-  "empty_column.hashtag": "Tidak ada apapun dalam hashtag ini.",
-  "empty_column.home.public_timeline": "linimasa publik",
-  "empty_column.home": "Anda sedang tidak mengikuti siapapun. Kunjungi {public} atau gunakan pencarian untuk memulai dan bertemu pengguna lain.",
-  "empty_column.notifications": "Anda tidak memiliki notifikasi apapun. Berinteraksi dengan orang lain untuk memulai percakapan.",
-  "empty_column.public": "Tidak ada apapun disini! Tulis sesuatu, atau ikuti pengguna lain dari server lain untuk mengisinya secara manual",
-  "follow_request.authorize": "Izinkan",
-  "follow_request.reject": "Tolak",
-  "getting_started.apps": "Tersedia dalam berbagai aplikasi",
-  "getting_started.heading": "Mulai",
-  "getting_started.open_source_notice": "Mastodon adalah perangkat lunak yang bersifat open source. Anda dapat berkontribusi atau melaporkan permasalahan/bug di Github {github}. {apps}.",
-  "home.column_settings.advanced": "Tingkat Lanjut",
-  "home.column_settings.basic": "Dasar",
-  "home.column_settings.filter_regex": "Penyaringan dengan Regular Expression",
-  "home.column_settings.show_reblogs": "Tampilkan Boost",
-  "home.column_settings.show_replies": "Tampilkan balasan",
-  "home.settings": "Pengaturan kolom",
-  "lightbox.close": "Tutup",
-  "loading_indicator.label": "Tunggu sebentar...",
-  "media_gallery.toggle_visible": "Tampil/Sembunyikan",
-  "missing_indicator.label": "Tidak ditemukan",
-  "navigation_bar.blocks": "Pengguna diblokir",
-  "navigation_bar.community_timeline": "Linimasa lokal",
-  "navigation_bar.edit_profile": "Ubah profil",
-  "navigation_bar.favourites": "Favorit",
-  "navigation_bar.follow_requests": "Permintaan mengikuti",
-  "navigation_bar.info": "Informasi selengkapnya",
-  "navigation_bar.logout": "Keluar",
-  "navigation_bar.mutes": "Pengguna dibisukan",
-  "navigation_bar.preferences": "Pengaturan",
-  "navigation_bar.public_timeline": "Linimasa gabungan",
-  "notification.favourite": "{name} menyukai status anda",
-  "notification.follow": "{name} mengikuti anda",
-  "notification.reblog": "{name} mem-boost status anda",
-  "notifications.clear": "Hapus notifikasi",
-  "notifications.clear_confirmation": "Apa anda yakin hendak menghapus semua notifikasi anda?",
-  "notifications.column_settings.alert": "Notifikasi desktop",
-  "notifications.column_settings.favourite": "Favorit:",
-  "notifications.column_settings.follow": "Pengikut baru:",
-  "notifications.column_settings.mention": "Balasan:",
-  "notifications.column_settings.reblog": "Boost:",
-  "notifications.column_settings.show": "Tampilkan dalam kolom",
-  "notifications.column_settings.sound": "Mainkan suara",
-  "notifications.settings": "Pengaturan kolom",
-  "onboarding.done": "Selesei",
-  "onboarding.next": "Selanjutnya",
-  "onboarding.page_five.public_timelines": "Linimasa lokal menampilkan semua postingan publik dari semua orang di {domain}. Linimasa gabungan menampilkan postingan publik dari semua orang yang diikuti oleh {domain}. Ini semua adalah Linimasa Publik, cara terbaik untuk bertemu orang lain.",
-  "onboarding.page_four.home": "Linimasa beranda menampilkan postingan dari orang-orang yang anda ikuti.",
-  "onboarding.page_four.notifications": "Kolom notifikasi menampilkan ketika seseorang berinteraksi dengan anda.",
-  "onboarding.page_one.federation": "Mastodon adalah jaringan dari beberapa server independen yang bergabung untuk membuat jejaring sosial yang besar.",
-  "onboarding.page_one.handle": "Ada berada dalam {domain}, jadi nama user lengkap anda adalah {handle}",
-  "onboarding.page_one.welcome": "Selamat datang di Mastodon!",
-  "onboarding.page_six.admin": "Admin serveer anda adalah {admin}.",
-  "onboarding.page_six.almost_done": "Hampir selesei...",
-  "onboarding.page_six.appetoot": "Bon Appetoot!",
-  "onboarding.page_six.apps_available": "Ada beberapa apl yang tersedia untuk iOS, Android, dan platform lainnya.",
-  "onboarding.page_six.github": "Mastodon adalah software open-source. Anda bisa melaporkan bug, meminta fitur, atau berkontribusi dengan kode di {github}.",
-  "onboarding.page_six.guidelines": "pedoman komunitas",
-  "onboarding.page_six.read_guidelines": "Silakan baca {guidelines} {domain}!",
-  "onboarding.page_six.various_app": "apl handphone",
-  "onboarding.page_three.profile": "Ubah profil anda untuk mengganti avatar, bio, dan nama pengguna anda. Disitu, anda juga bisa mengatur opsi lainnya.",
-  "onboarding.page_three.search": "Gunakan kolom pencarian untuk mencari orang atau melihat hashtag, seperti {illustration} dan {introductions}. Untuk mencari pengguna yang tidak berada dalam server ini, gunakan nama pengguna mereka selengkapnya.",
-  "onboarding.page_two.compose": "Tulis postingan melalui kolom posting. Anda dapat mengunggah gambar, mengganti pengaturan privasi, dan menambahkan peringatan konten dengan ikon-ikon dibawah ini.",
-  "onboarding.skip": "Lewati",
-  "privacy.change": "Tentukan privasi status",
-  "privacy.direct.long": "Kirim hanya ke pengguna yang disebut",
-  "privacy.direct.short": "Langsung",
-  "privacy.private.long": "Kirim hanya ke pengikut",
-  "privacy.private.short": "Pribadi",
-  "privacy.public.long": "Kirim ke linimasa publik",
-  "privacy.public.short": "Publik",
-  "privacy.unlisted.long": "Tidak ditampilkan di linimasa publik",
-  "privacy.unlisted.short": "Tak Terdaftar",
-  "reply_indicator.cancel": "Batal",
-  "report.heading": "Laporan baru",
-  "report.placeholder": "Komentar tambahan",
-  "report.submit": "Kirim",
-  "report.target": "Melaporkan",
-  "search.status_by": "Status yang dibuat oleh {name}",
-  "search_results.total": "{count} {count, plural, one {hasil} other {hasil}}",
-  "status.cannot_reblog": "Postingan ini tidak dapat di-boost",
-  "search.placeholder": "Pencarian",
-  "search.status_by": "Status oleh {name}",
-  "status.delete": "Hapus",
-  "status.favourite": "Difavoritkan",
-  "status.load_more": "Tampilkan semua",
-  "status.media_hidden": "Media disembunyikan",
-  "status.mention": "Balasan @{name}",
-  "status.open": "Tampilkan status ini",
-  "status.reblog": "Boost",
-  "status.reblogged_by": "di-boost {name}",
-  "status.reply": "Balas",
-  "status.replyAll": "Balas ke semua",
-  "status.report": "Laporkan @{name}",
-  "status.sensitive_toggle": "Klik untuk menampilkan",
-  "status.sensitive_warning": "Konten sensitif",
-  "status.show_less": "Tampilkan lebih sedikit",
-  "status.show_more": "Tampilkan semua",
-  "tabs_bar.compose": "Tulis",
-  "tabs_bar.federated_timeline": "Gabungan",
-  "tabs_bar.home": "Beranda",
-  "tabs_bar.local_timeline": "Lokal",
-  "tabs_bar.notifications": "Notifikasi",
-  "upload_area.title": "Seret & lepaskan untuk mengunggah",
-  "upload_button.label": "Tambahkan media",
-  "upload_form.undo": "Undo",
-  "upload_progress.label": "Mengunggah...",
-  "video_player.toggle_sound": "Suara",
-  "video_player.toggle_visible": "Tampilan",
-  "video_player.expand": "Tampilkan video",
-  "video_player.video_error": "Video tidak dapat diputar",
-};
-
-export default id;
diff --git a/app/assets/javascripts/components/locales/index.jsx b/app/assets/javascripts/components/locales/index.jsx
deleted file mode 100644
index 0c8472401..000000000
--- a/app/assets/javascripts/components/locales/index.jsx
+++ /dev/null
@@ -1,57 +0,0 @@
-import ar from './ar';
-import en from './en';
-import de from './de';
-import es from './es';
-import fa from './fa';
-import he from './he';
-import hr from './hr';
-import hu from './hu';
-import io from './io';
-import it from './it';
-import fr from './fr';
-import nl from './nl';
-import no from './no';
-import oc from './oc';
-import pt from './pt';
-import pt_br from './pt-br';
-import uk from './uk';
-import fi from './fi';
-import eo from './eo';
-import ru from './ru';
-import ja from './ja';
-import zh_hk from './zh-hk';
-import zh_cn from './zh-cn';
-import bg from './bg';
-import id from './id';
-
-const locales = {
-  ar,
-  en,
-  de,
-  es,
-  fa,
-  he,
-  hr,
-  hu,
-  io,
-  it,
-  fr,
-  nl,
-  no,
-  oc,
-  pt,
-  'pt-BR': pt_br,
-  uk,
-  fi,
-  eo,
-  ru,
-  ja,
-  'zh-HK': zh_hk,
-  'zh-CN': zh_cn,
-  bg,
-  id,
-};
-
-export default function getMessagesForLocale (locale) {
-  return locales[locale];
-};
diff --git a/app/assets/javascripts/components/locales/io.jsx b/app/assets/javascripts/components/locales/io.jsx
deleted file mode 100644
index 6715663aa..000000000
--- a/app/assets/javascripts/components/locales/io.jsx
+++ /dev/null
@@ -1,126 +0,0 @@
-const io = {
-  "account.block": "Blokusar @{name}",
-  "account.disclaimer": "Ca uzero esas de altra instaluro. Ca nombro forsan esas plu granda.",
-  "account.edit_profile": "Modifikar profilo",
-  "account.follow": "Sequar",
-  "account.followers": "Sequanti",
-  "account.follows_you": "Sequas tu",
-  "account.follows": "Sequas",
-  "account.mention": "Mencionar @{name}",
-  "account.mute": "Celar @{name}",
-  "account.posts": "Mesaji",
-  "account.report": "Denuncar @{name}",
-  "account.requested": "Vartante aprobo",
-  "account.unblock": "Desblokusar @{name}",
-  "account.unfollow": "Ne plus sequar",
-  "account.unmute": "Ne plus celar @{name}",
-  "boost_modal.combo": "Tu povas presar sur {combo} por omisar co en la venonta foyo",
-  "column_back_button.label": "Retro",
-  "column.blocks": "Blokusita uzeri",
-  "column.community": "Lokala tempolineo",
-  "column.favourites": "Favorati",
-  "column.follow_requests": "Demandi di sequado",
-  "column.home": "Hemo",
-  "column.mutes": "Celita uzeri",
-  "column.notifications": "Savigi",
-  "column.public": "Federata tempolineo",
-  "compose_form.placeholder": "Quo esas en tua spirito?",
-  "compose_form.privacy_disclaimer": "Tua privata mesajo livresos a mencionata uzeri en {domains}. Ka tu fidas {domainsCount, plural, one {ta servero} other {ta serveri}}? Privateso di mesaji funcionas nur en instaluri di Mastodon. Se {domains} {domainsCount, plural, one {ne esas instaluro di Mastodon} other {ne esas instaluri di Mastodon}}, esos nula indiko, ke tua mesajo esas privata, ed ol povos repetesar od altre divenar videbla da nedezirinda recevanti.",
-  "compose_form.publish": "Siflar",
-  "compose_form.sensitive": "Markizar kontenajo kom trubliva",
-  "compose_form.spoiler_placeholder": "Averto di kontenajo",
-  "compose_form.spoiler": "Celar texto dop averto",
-  "emoji_button.label": "Insertar emoji",
-  "empty_column.community": "La lokala tempolineo esas vakua. Skribez ulo publike por iniciar la agiveso!",
-  "empty_column.hashtag": "Esas ankore nulo en ta gretovorto.",
-  "empty_column.home.public_timeline": "la publika tempolineo",
-  "empty_column.home": "Tu sequas ankore nulu. Vizitez {public} od uzez la serchilo por komencar e renkontrar altra uzeri.",
-  "empty_column.notifications": "Tu havas ankore nula savigo. Komunikez kun altri por debutar la konverso.",
-  "empty_column.public": "Esas nulo hike! Skribez ulo publike, o manuale sequez uzeri de altra instaluri por plenigar ol.",
-  "follow_request.authorize": "Yurizar",
-  "follow_request.reject": "Refuzar",
-  "getting_started.apps": "Apliki diversa esas disponebla",
-  "getting_started.heading": "Debuto",
-  "getting_started.open_source_notice": "Mastodon esas programaro kun apertita kodexo. Tu povas kontributar o signalar problemi en GitHub ye {github}. {apps}.",
-  "home.column_settings.advanced": "Komplexa",
-  "home.column_settings.basic": "Simpla",
-  "home.column_settings.filter_regex": "Ekfiltrar per reguloza expresuri",
-  "home.column_settings.show_reblogs": "Montrar repeti",
-  "home.column_settings.show_replies": "Montrar respondi",
-  "home.settings": "Aranji di la kolumno",
-  "lightbox.close": "Klozar",
-  "loading_indicator.label": "Kargante...",
-  "media_gallery.toggle_visible": "Chanjar videbleso",
-  "missing_indicator.label": "Ne trovita",
-  "navigation_bar.blocks": "Blokusita uzeri",
-  "navigation_bar.community_timeline": "Lokala tempolineo",
-  "navigation_bar.edit_profile": "Modifikar profilo",
-  "navigation_bar.favourites": "Favorati",
-  "navigation_bar.follow_requests": "Demandi di sequado",
-  "navigation_bar.info": "Detaloza informi",
-  "navigation_bar.logout": "Ekirar",
-  "navigation_bar.mutes": "Celita uzeri",
-  "navigation_bar.preferences": "Preferi",
-  "navigation_bar.public_timeline": "Federata tempolineo",
-  "notification.favourite": "{name} favorizis tua mesajo",
-  "notification.follow": "{name} sequeskis tu",
-  "notification.mention": "{name} mencionis tu",
-  "notification.reblog": "{name} repetis tua mesajo",
-  "notifications.clear_confirmation": "Ka tu esas certa, ke tu volas efacar omna tua savigi?",
-  "notifications.clear": "Efacar savigi",
-  "notifications.column_settings.alert": "Surtabla savigi",
-  "notifications.column_settings.favourite": "Favorati:",
-  "notifications.column_settings.follow": "Nova sequanti:",
-  "notifications.column_settings.mention": "Mencioni:",
-  "notifications.column_settings.reblog": "Repeti:",
-  "notifications.column_settings.show": "Montrar en kolumno",
-  "notifications.column_settings.sound": "Plear sono",
-  "notifications.settings": "Aranji di kolumno",
-  "privacy.change": "Aranjar privateso di mesaji",
-  "privacy.direct.long": "Sendar nur a mencionata uzeri",
-  "privacy.direct.short": "Direte",
-  "privacy.private.long": "Sendar nur a sequanti",
-  "privacy.private.short": "Private",
-  "privacy.public.long": "Sendar a publika tempolinei",
-  "privacy.public.short": "Publike",
-  "privacy.unlisted.long": "Ne montrar en publika tempolinei",
-  "privacy.unlisted.short": "Ne enlistigota",
-  "reply_indicator.cancel": "Nihiligar",
-  "report.heading": "Nova denunco",
-  "report.placeholder": "Plusa komenti",
-  "report.submit": "Sendar",
-  "report.target": "Denuncante",
-  "search_results.total": "{count, number} {count, plural, one {rezulto} other {rezulti}}",
-  "search.placeholder": "Serchez",
-  "search.status_by": "Mesajo da {name}",
-  "status.delete": "Efacar",
-  "status.favourite": "Favorizar",
-  "status.load_more": "Kargar pluse",
-  "status.media_hidden": "Kontenajo celita",
-  "status.mention": "Mencionar @{name}",
-  "status.open": "Detaligar ca mesajo",
-  "status.reblog": "Repetar",
-  "status.reblogged_by": "{name} repetita",
-  "status.reply": "Respondar",
-  "status.replyAll": "Respondar a filo",
-  "status.report": "Denuncar @{name}",
-  "status.sensitive_toggle": "Kliktar por vidar",
-  "status.sensitive_warning": "Trubliva kontenajo",
-  "status.show_less": "Montrar mine",
-  "status.show_more": "Montrar plue",
-  "tabs_bar.compose": "Kompozar",
-  "tabs_bar.federated_timeline": "Federata",
-  "tabs_bar.home": "Hemo",
-  "tabs_bar.local_timeline": "Lokala",
-  "tabs_bar.notifications": "Savigi",
-  "upload_area.title": "Tranar faligar por kargar",
-  "upload_button.label": "Adjuntar kontenajo",
-  "upload_form.undo": "Desfacar",
-  "upload_progress.label": "Kargante...",
-  "video_player.toggle_sound": "Acendar sono",
-  "video_player.toggle_visible": "Chanjar videbleso",
-  "video_player.expand": "Extensar video",
-  "video_player.video_error": "Video ne povus pleesar",
-};
-
-export default io;
diff --git a/app/assets/javascripts/components/locales/it.jsx b/app/assets/javascripts/components/locales/it.jsx
deleted file mode 100644
index 04ff1311f..000000000
--- a/app/assets/javascripts/components/locales/it.jsx
+++ /dev/null
@@ -1,125 +0,0 @@
-const it = {
-  "account.block": "Blocca @{name}",
-  "account.disclaimer": "Questo utente si trova su un altro server. Questo numero potrebbe essere maggiore.",
-  "account.edit_profile": "Modifica profilo",
-  "account.follow": "Segui",
-  "account.followers": "Seguaci",
-  "account.follows_you": "Ti segue",
-  "account.follows": "Segue",
-  "account.mention": "Menziona @{name}",
-  "account.mute": "Silenzia @{name}",
-  "account.posts": "Posts",
-  "account.report": "Segnala @{name}",
-  "account.requested": "In attesa di approvazione",
-  "account.unblock": "Sblocca @{name}",
-  "account.unfollow": "Non seguire",
-  "account.unmute": "Non silenziare @{name}",
-  "boost_modal.combo": "Puoi premere {combo} per saltare questo passaggio la prossima volta",
-  "column_back_button.label": "Indietro",
-  "column.blocks": "Utenti bloccati",
-  "column.community": "Timeline locale",
-  "column.favourites": "Apprezzati",
-  "column.follow_requests": "Richieste di amicizia",
-  "column.home": "Home",
-  "column.mutes": "Utenti silenziati",
-  "column.notifications": "Notifiche",
-  "column.public": "Timeline federata",
-  "compose_form.placeholder": "A cosa stai pensando?",
-  "compose_form.privacy_disclaimer": "Il tuo status privato verrà condiviso con gli utenti menzionati su {domains}. Ti fidi di {domainsCount, plural, one {quel server} other {quei server}}? Le impostazioni sulla privacy valgono solo su server Mastodon. Se {domains} {domainsCount, plural, one {non è un server Mastodon} other {non sono server Mastodon}}, non ci saranno indicazioni sulla privacy del tuo status, e potrebbe essere condiviso o reso visibile a destinatari indesiderati.",
-  "compose_form.publish": "Toot",
-  "compose_form.sensitive": "Segnala file come sensibile",
-  "compose_form.spoiler_placeholder": "Content warning",
-  "compose_form.spoiler": "Nascondi testo con avvertimento",
-  "emoji_button.label": "Inserisci emoji",
-  "empty_column.community": "La timeline locale è vuota. Condividi qualcosa pubblicamente per dare inizio alla festa!",
-  "empty_column.hashtag": "Non c'è ancora nessun post con questo hashtag.",
-  "empty_column.home.public_timeline": "la timeline pubblica",
-  "empty_column.home": "Non stai ancora seguendo nessuno. Visita {public} o usa la ricerca per incontrare nuove persone.",
-  "empty_column.notifications": "Non hai ancora nessuna notifica. Interagisci con altri per iniziare conversazioni.",
-  "empty_column.public": "Qui non c'è nulla! Scrivi qualcosa pubblicamente, o aggiungi utenti da altri server per riempire questo spazio.",
-  "follow_request.authorize": "Autorizza",
-  "follow_request.reject": "Rifiuta",
-  "getting_started.apps": "Sono disponibili diverse app",
-  "getting_started.heading": "Come iniziare",
-  "getting_started.open_source_notice": "Mastodon è un software open source. Puoi contribuire o segnalare errori su GitHub all'indirizzo {github}. {apps}.",
-  "home.column_settings.advanced": "Avanzato",
-  "home.column_settings.basic": "Semplice",
-  "home.column_settings.filter_regex": "Filtra con espressioni regolari",
-  "home.column_settings.show_reblogs": "Mostra post condivisi",
-  "home.column_settings.show_replies": "Mostra risposte",
-  "home.settings": "Impostazioni colonna",
-  "lightbox.close": "Chiudi",
-  "loading_indicator.label": "Carico...",
-  "media_gallery.toggle_visible": "Imposta visibilità",
-  "missing_indicator.label": "Non trovato",
-  "navigation_bar.blocks": "Utenti bloccati",
-  "navigation_bar.community_timeline": "Timeline locale",
-  "navigation_bar.edit_profile": "Modifica profilo",
-  "navigation_bar.favourites": "Apprezzati",
-  "navigation_bar.follow_requests": "Richieste di amicizia",
-  "navigation_bar.info": "Informazioni estese",
-  "navigation_bar.logout": "Logout",
-  "navigation_bar.mutes": "Utenti silenziati",
-  "navigation_bar.preferences": "Impostazioni",
-  "navigation_bar.public_timeline": "Timeline federata",
-  "notification.favourite": "{name} ha apprezzato il tuo post",
-  "notification.follow": "{name} ha iniziato a seguirti",
-  "notification.mention": "{name} ti ha menzionato",
-  "notification.reblog": "{name} ha condiviso il tuo post",
-  "notifications.clear_confirmation": "Vuoi davvero cancellare tutte le notifiche?",
-  "notifications.clear": "Cancella notifiche",
-  "notifications.column_settings.alert": "Notifiche desktop",
-  "notifications.column_settings.favourite": "Apprezzati:",
-  "notifications.column_settings.follow": "Nuovi seguaci:",
-  "notifications.column_settings.mention": "Menzioni:",
-  "notifications.column_settings.reblog": "Post condivisi:",
-  "notifications.column_settings.show": "Mostra in colonna",
-  "notifications.column_settings.sound": "Riproduci suono",
-  "notifications.settings": "Impostazioni colonna",
-  "privacy.change": "Modifica privacy post",
-  "privacy.direct.long": "Invia solo a utenti menzionati",
-  "privacy.direct.short": "Diretto",
-  "privacy.private.long": "Invia solo ai seguaci",
-  "privacy.private.short": "Privato",
-  "privacy.public.long": "Invia alla timeline pubblica",
-  "privacy.public.short": "Pubblico",
-  "privacy.unlisted.long": "Non mostrare sulla timeline pubblica",
-  "privacy.unlisted.short": "Non elencato",
-  "reply_indicator.cancel": "Annulla",
-  "report.heading": "Nuova segnalazione",
-  "report.placeholder": "Commenti aggiuntivi",
-  "report.submit": "Invia",
-  "report.target": "Invio la segnalazione",
-  "search_results.total": "{count} {count, plural, one {risultato} other {risultati}}",
-  "search.placeholder": "Cerca",
-  "search.status_by": "Status per {name}",
-  "status.delete": "Elimina",
-  "status.favourite": "Apprezzato",
-  "status.load_more": "Mostra di più",
-  "status.media_hidden": "Allegato nascosto",
-  "status.mention": "Nomina @{name}",
-  "status.open": "Espandi questo post",
-  "status.reblog": "Condividi",
-  "status.reblogged_by": "{name} ha condiviso",
-  "status.reply": "Rispondi",
-  "status.report": "Segnala @{name}",
-  "status.sensitive_toggle": "Clicca per vedere",
-  "status.sensitive_warning": "Materiale sensibile",
-  "status.show_less": "Mostra meno",
-  "status.show_more": "Mostra di più",
-  "tabs_bar.compose": "Scrivi",
-  "tabs_bar.federated_timeline": "Federazione",
-  "tabs_bar.home": "Home",
-  "tabs_bar.local_timeline": "Locale",
-  "tabs_bar.notifications": "Notifiche",
-  "upload_area.title": "Trascina per caricare",
-  "upload_button.label": "Aggiungi file multimediale",
-  "upload_form.undo": "Annulla",
-  "upload_progress.label": "Sto caricando...",
-  "video_player.toggle_sound": "Attiva suono",
-  "video_player.toggle_visible": "Attiva visibilità",
-  "video_player.expand": "Espandi video",
-  "video_player.video_error": "Il video non può essere riprodotto",
-};
-
-export default it;
\ No newline at end of file
diff --git a/app/assets/javascripts/components/locales/ja.jsx b/app/assets/javascripts/components/locales/ja.jsx
deleted file mode 100644
index 6a7536527..000000000
--- a/app/assets/javascripts/components/locales/ja.jsx
+++ /dev/null
@@ -1,167 +0,0 @@
-const ja = {
-  "account.block": "ブロック",
-  "account.disclaimer": "このユーザーは他のインスタンスに所属しているため、数字が正確で無い場合があります。",
-  "account.edit_profile": "プロフィールを編集",
-  "account.follow": "フォロー",
-  "account.followers": "フォロワー",
-  "account.follows": "フォロー",
-  "account.follows_you": "フォローされています",
-  "account.mention": "返信",
-  "account.mute": "ミュート",
-  "account.posts": "投稿",
-  "account.report": "通報",
-  "account.requested": "承認待ち",
-  "account.unblock": "ブロック解除",
-  "account.unfollow": "フォロー解除",
-  "account.unmute": "ミュート解除",
-  "boost_modal.combo": "次からは{combo}を押せば、これをスキップできます。",
-  "column.blocks": "ブロックしたユーザー",
-  "column.community": "ローカルタイムライン",
-  "column.favourites": "お気に入り",
-  "column.follow_requests": "フォローリクエスト",
-  "column.home": "ホーム",
-  "column.mutes": "ミュートしたユーザー",
-  "column.notifications": "通知",
-  "column.public": "連合タイムライン",
-  "column_back_button.label": "戻る",
-  "column_subheading.navigation": "ナビゲーション",
-  "column_subheading.settings": "設定",
-  "compose_form.lock_disclaimer": "あなたのアカウントは{locked}になっていません。誰でもあなたをフォローすることができ、フォロワー限定の投稿を見ることができます。",
-  "compose_form.lock_disclaimer.lock": "非公開",
-  "compose_form.placeholder": "今なにしてる?",
-  "compose_form.privacy_disclaimer": "あなたの非公開トゥートは返信先ユーザーが所属する {domains} に送信されます。{domainsCount, plural, one {このサーバー} other {これらのサーバー}}は信頼できますか?投稿のプライバシー保護はMastodonサーバー内でのみ有効です。 {domains} {domainsCount, plural, one {がMastodonインスタンス} other {がMastodonインスタンス}}でない場合、あなたの投稿がプライベートなものとして扱われず、ブーストされたり予期しないユーザーに見られる可能性があります。",
-  "compose_form.publish": "トゥート",
-  "compose_form.sensitive": "メディアを閲覧注意としてマークする",
-  "compose_form.spoiler": "テキストを隠す",
-  "compose_form.spoiler_placeholder": "警告",
-  "confirmation_modal.cancel": "キャンセル",
-  "confirmations.block.confirm": "ブロック",
-  "confirmations.block.message": "本当に {name} をブロックしますか?",
-  "confirmations.delete.confirm": "削除",
-  "confirmations.delete.message": "本当に削除しますか?",
-  "confirmations.mute.confirm": "ミュート",
-  "confirmations.mute.message": "本当に {name} をミュートしますか?",
-  "emoji_button.label": "絵文字を追加",
-  "emoji_button.search": "検索...",
-  "emoji_button.people": "人々",
-  "emoji_button.nature": "自然",
-  "emoji_button.food": "食べ物",
-  "emoji_button.activity": "活動",
-  "emoji_button.travel": "旅行と場所",
-  "emoji_button.objects": "物",
-  "emoji_button.symbols": "記号",
-  "emoji_button.flags": "国旗",
-  "empty_column.community": "ローカルタイムラインはまだ使われていません。何か書いてみましょう!",
-  "empty_column.hashtag": "このハッシュタグはまだ使われていません。",
-  "empty_column.home": "まだ誰もフォローしていません。{public}を見に行くか、検索を使って他のユーザーを見つけましょう。",
-  "empty_column.home.public_timeline": "連合タイムライン",
-  "empty_column.notifications": "まだ通知がありません。他の人とふれ合って会話を始めましょう。",
-  "empty_column.public": "ここにはまだ何もありません!公開で何かを投稿したり、他のインスタンスのユーザーをフォローしたりしていっぱいにしましょう!",
-  "follow_request.authorize": "許可",
-  "follow_request.reject": "拒否",
-  "getting_started.apps": "さまざまなアプリで利用できます。",
-  "getting_started.heading": "スタート",
-  "getting_started.open_source_notice": "Mastodon はオープンソースソフトウェアです。誰でも GitHub({github})から開発に参加したり、問題を報告したりできます。 {apps}",
-  "home.column_settings.advanced": "上級者向け",
-  "home.column_settings.basic": "シンプル",
-  "home.column_settings.filter_regex": "正規表現でフィルター",
-  "home.column_settings.show_reblogs": "ブースト表示",
-  "home.column_settings.show_replies": "返信表示",
-  "home.settings": "カラム設定",
-  "lightbox.close": "閉じる",
-  "loading_indicator.label": "読み込み中...",
-  "media_gallery.toggle_visible": "表示切り替え",
-  "missing_indicator.label": "見つかりません",
-  "navigation_bar.blocks": "ブロックしたユーザー",
-  "navigation_bar.community_timeline": "ローカルタイムライン",
-  "navigation_bar.edit_profile": "プロフィールを編集",
-  "navigation_bar.favourites": "お気に入り",
-  "navigation_bar.follow_requests": "フォローリクエスト",
-  "navigation_bar.info": "サーバー情報",
-  "navigation_bar.logout": "ログアウト",
-  "navigation_bar.mutes": "ミュートしたユーザー",
-  "navigation_bar.preferences": "ユーザー設定",
-  "navigation_bar.public_timeline": "連合タイムライン",
-  "notification.favourite": "{name} さんがあなたのトゥートをお気に入りに登録しました",
-  "notification.follow": "{name} さんにフォローされました",
-  "notification.mention": "{name} さんがあなたに返信しました",
-  "notification.reblog": "{name} さんがあなたのトゥートをブーストしました",
-  "notifications.clear": "通知を消去",
-  "notifications.clear_confirmation": "本当に通知を消去しますか?",
-  "notifications.column_settings.alert": "デスクトップ通知",
-  "notifications.column_settings.favourite": "お気に入り",
-  "notifications.column_settings.follow": "新しいフォロワー",
-  "notifications.column_settings.mention": "返信",
-  "notifications.column_settings.reblog": "ブースト",
-  "notifications.column_settings.show": "カラムに表示",
-  "notifications.column_settings.sound": "通知音を再生",
-  "notifications.settings": "カラム設定",
-  "onboarding.done": "完了",
-  "onboarding.next": "次へ",
-  "onboarding.page_one.welcome": "Mastodonへようこそ!",
-  "onboarding.page_one.federation": "Mastodonは誰でも参加できるSNSです。",
-  "onboarding.page_one.handle": "あなたは今数あるMastodonインスタンスの1つである{domain}にいます。あなたのフルハンドルは{handle}です。",
-  "onboarding.page_two.compose": "フォームから投稿できます。イメージや、公開範囲の設定や、表示時の警告の設定は下部のアイコンから行なえます。",
-  "onboarding.page_three.search": "検索バーで、{illustration}や{introductions}のように特定のハッシュタグの投稿を見たり、ユーザーを探したりできます。",
-  "onboarding.page_three.profile": "「プロフィールを編集」から、あなたの自己紹介や表示名を変更できます。またそこでは他の設定ができます。",
-  "onboarding.page_four.home": "「ホーム」タイムラインではあなたがフォローしている人の投稿を表示します。",
-  "onboarding.page_four.notifications": "「通知」ではあなたへの他の人からの関わりを表示します。",
-  "onboarding.page_five.public_timelines": "連合タイムラインでは{domain}の人がフォローしているMastodon全体での公開投稿を表示します。同じくローカルタイムラインでは{domain}のみの公開投稿を表示します。",
-  "onboarding.page_six.almost_done": "以上です。",
-  "onboarding.page_six.admin": "あなたのインスタンスの管理者は{admin}です。",
-  "onboarding.page_six.read_guidelines": "{guidelines}を読むことを忘れないようにしてください。",
-  "onboarding.page_six.guidelines": "コミュニティガイドライン",
-  "onboarding.page_six.github": "MastodonはOSSです。バグ報告や機能要望あるいは貢献を{github}から行なえます。",
-  "onboarding.page_six.apps_available": "iOS、Androidあるいは他のプラットフォームで使える{apps}があります。",
-  "onboarding.page_six.various_app": "様々なモバイルアプリ",
-  "onboarding.page_six.appetoot": "Bon Appetoot!",
-  "onboarding.skip": "スキップ",
-  "privacy.change": "投稿のプライバシーを変更",
-  "privacy.direct.long": "メンションしたユーザーだけに公開",
-  "privacy.direct.short": "ダイレクト",
-  "privacy.private.long": "フォロワーだけに公開",
-  "privacy.private.short": "非公開",
-  "privacy.public.long": "公開TLに投稿する",
-  "privacy.public.short": "公開",
-  "privacy.unlisted.long": "公開TLで表示しない",
-  "privacy.unlisted.short": "未収載",
-  "reply_indicator.cancel": "キャンセル",
-  "report.heading": "新規通報",
-  "report.placeholder": "コメント",
-  "report.submit": "通報する",
-  "report.target": "問題のユーザー",
-  "search.placeholder": "検索",
-  "search.status_by": "{name}からの投稿",
-  "search_results.total": "{count, number} 件の結果",
-  "status.cannot_reblog": "この投稿はブーストできません",
-  "status.delete": "削除",
-  "status.favourite": "お気に入り",
-  "status.load_more": "もっと見る",
-  "status.media_hidden": "非表示のメデイア",
-  "status.mention": "返信",
-  "status.open": "詳細を表示",
-  "status.reblog": "ブースト",
-  "status.reblogged_by": "{name} さんにブーストされました",
-  "status.reply": "返信",
-  "status.replyAll": "全員に返信",
-  "status.report": "通報",
-  "status.sensitive_toggle": "クリックして表示",
-  "status.sensitive_warning": "閲覧注意",
-  "status.show_less": "隠す",
-  "status.show_more": "もっと見る",
-  "tabs_bar.compose": "投稿",
-  "tabs_bar.federated_timeline": "連合",
-  "tabs_bar.home": "ホーム",
-  "tabs_bar.local_timeline": "ローカル",
-  "tabs_bar.notifications": "通知",
-  "upload_area.title": "ドラッグ&ドロップでアップロード",
-  "upload_button.label": "メディアを追加",
-  "upload_form.undo": "やり直す",
-  "upload_progress.label": "アップロード中…",
-  "video_player.expand": "動画の詳細",
-  "video_player.toggle_sound": "音の切り替え",
-  "video_player.toggle_visible": "表示切り替え",
-  "video_player.video_error": "動画の再生に失敗しました",
-};
-
-export default ja;
diff --git a/app/assets/javascripts/components/locales/nl.jsx b/app/assets/javascripts/components/locales/nl.jsx
deleted file mode 100644
index 388169cd5..000000000
--- a/app/assets/javascripts/components/locales/nl.jsx
+++ /dev/null
@@ -1,130 +0,0 @@
-const nl = {
-  "account.block": "Blokkeer @{name}",
-  "account.edit_profile": "Profiel bewerken",
-  "account.followers": "Volgers",
-  "account.follows": "Volgt",
-  "account.follows_you": "Volgt jou",
-  "account.follow": "Volgen",
-  "account.mention": "Vermeld @{name}",
-  "account.mute": "Negeer @{name}",
-  "account.posts": "Berichten",
-  "account.report": "Rapporteer @{name}",
-  "account.requested": "Wacht op goedkeuring",
-  "account.unblock": "Deblokkeer @{name}",
-  "account.unfollow": "Ontvolgen",
-  "account.unmute": "Negeer @{name} niet meer",
-  "boost_modal.combo": "Je kunt {combo} klikken om dit de volgende keer over te slaan",
-  "column_back_button.label": "terug",
-  "column.blocks": "Geblokkeerde gebruikers",
-  "column.community": "Lokale tijdlijn",
-  "column.favourites": "Favorieten",
-  "column.home": "Jouw tijdlijn",
-  "column.mutes": "Genegeerde gebruikers",
-  "column.notifications": "Meldingen",
-  "column.public": "Globale tijdlijn",
-  "column_subheading.navigation": "Navigatie",
-  "column_subheading.settings": "Instellingen",
-  "compose_form.placeholder": "Wat wil je kwijt?",
-  "compose_form.privacy_disclaimer": "Jouw privétoot wordt afgeleverd aan de vermelde gebruikers op {domains}. Vertrouw jij {domainsCount, plural, one {die server} other {die servers}}? Het privé plaatsen van toots werkt alleen op Mastodon-servers. Wanneer {domains} {domainsCount, plural, one {geen Mastodon-server is} other {geen Mastodon-servers zijn}}, dan wordt er niet aangegeven dat de toot privé is, waardoor het kan worden geboost of op een andere manier zichtbaar wordt gemaakt voor mensen waarvoor het niet was bedoeld.",
-  "compose_form.private": "Als privé markeren",
-  "compose_form.publish": "Toot",
-  "compose_form.sensitive": "Media als gevoelig markeren",
-  "compose_form.spoiler_placeholder": "Waarschuwingstekst",
-  "compose_form.spoiler": "Tekst achter waarschuwing verbergen",
-  "compose_form.unlisted": "Niet op openbare tijdlijnen tonen",
-  "emoji_button.activity": "Activiteiten",
-  "emoji_button.flags": "Vlaggen",
-  "emoji_button.food": "Eten en drinken",
-  "emoji_button.label": "Emoji toevoegen",
-  "emoji_button.nature": "Natuur",
-  "emoji_button.objects": "Voorwerpen",
-  "emoji_button.people": "Mensen",
-  "emoji_button.search": "Zoeken...",
-  "emoji_button.symbols": "Symbolen",
-  "emoji_button.travel": "Reizen en plekken",
-  "getting_started.about_addressing": "Je kunt mensen volgen als je hun gebruikersnaam en het domein van hun server kent. Voer hiervoor het e-mailachtige adres in het zoekveld in.",
-  "getting_started.about_shortcuts": "Als de gezochte gebruiker op hetzelfde domein zit als jijzelf, is invoeren van de gebruikersnaam genoeg. Dat geldt ook als je mensen in toots wilt vermelden.",
-  "getting_started.apps": "Er zijn meerdere apps beschikbaar",
-  "getting_started.heading": "Beginnen",
-  "getting_started.open_source_notice": "Mastodon is open-sourcesoftware. Je kunt bijdragen of problemen melden op GitHub via {github}. {apps}.",
-  "lightbox.close": "Sluiten",
-  "loading_indicator.label": "Laden…",
-  "navigation_bar.blocks": "Geblokkeerde gebruikers",
-  "navigation_bar.community_timeline": "Lokale tijdlijn",
-  "navigation_bar.edit_profile": "Profiel bewerken",
-  "navigation_bar.favourites": "Favorieten",
-  "navigation_bar.follow_requests": "Volgverzoeken",
-  "navigation_bar.info": "Uitgebreide informatie",
-  "navigation_bar.logout": "Afmelden",
-  "navigation_bar.mutes": "Genegeerde gebruikers",
-  "navigation_bar.preferences": "Instellingen",
-  "navigation_bar.public_timeline": "Globale tijdlijn",
-  "notification.favourite": "{name} markeerde jouw toot als favoriet",
-  "notification.follow": "{name} volgt jou nu",
-  "notification.mention": "{name} vermeldde jou",
-  "notification.reblog": "{name} boostte jouw toot",
-  "notifications.clear_confirmation": "Weet je zeker dat je al jouw meldingen wilt verwijderen?",
-  "notifications.clear": "Meldingen verwijderen",
-  "notifications.column_settings.alert": "Desktopmeldingen",
-  "notifications.column_settings.favourite": "Favorieten:",
-  "notifications.column_settings.follow": "Nieuwe volgers:",
-  "notifications.column_settings.mention": "Vermeldingen:",
-  "notifications.column_settings.reblog": "Boosts:",
-  "notifications.column_settings.show": "In kolom tonen",
-  "notifications.column_settings.sound": "Geluid afspelen",
-  "notifications.settings": "Kolom-instellingen",
-  "onboarding.next": "Volgende",
-  "onboarding.page_five.public_timelines": "De lokale tijdlijn toont openbare toots van iedereen op {domain}. De globale tijdlijn toont openbare toots van iedereen die door gebruikers van {domain} worden gevolgd, dus ook mensen van andere Mastodon-servers. Dit zijn de openbare tijdlijnen en vormen een uitstekende manier om nieuwe mensen te ontdekken.",
-  "onboarding.page_four.home": "Jouw tijdlijn laat toots zien van mensen die jij volgt.",
-  "onboarding.page_four.notifications": "De kolom met meldingen toont alle interacties die je met andere Mastodon-gebruikers hebt.",
-  "onboarding.page_one.federation": "Mastodon is een netwerk van onafhankelijke servers die samen een groot sociaal netwerk vormen.",
-  "onboarding.page_one.handle": "Je bevindt je nu op {domain}, dus is jouw volledige Mastodon-adres {handle}",
-  "onboarding.page_one.welcome": "Welkom op Mastodon!",
-  "onboarding.page_six.admin": "De beheerder van jouw Mastodon-server is {admin}.",
-  "onboarding.page_six.almost_done": "Bijna klaar...",
-  "onboarding.page_six.appetoot": "Veel succes!",
-  "onboarding.page_six.apps_available": "Er zijn {apps} beschikbaar voor iOS, Android en andere platformen.",
-  "onboarding.page_six.github": "Mastodon kost niets, en is open-source- en vrije software. Je kan bugs melden, nieuwe mogelijkheden aanvragen en als ontwikkelaar meewerken op {github}.",
-  "onboarding.page_six.guidelines": "communityrichtlijnen",
-  "onboarding.page_six.read_guidelines": "Vergeet niet de {guidelines} van {domain} te lezen!",
-  "onboarding.page_six.various_app": "mobiele apps",
-  "onboarding.page_three.profile": "Bewerk jouw profiel om jouw avatar, bio en weergavenaam te veranderen. Daar vind je ook andere instellingen.",
-  "onboarding.page_three.search": "Gebruik de zoekbalk linksboven om andere mensen op Mastodon te vinden en om te zoeken op hashtags, zoals {illustration} en {introductions}. Om iemand te vinden die niet op deze Mastodon-server zit, moet je het volledige Mastodon-adres van deze persoon invoeren.",
-  "onboarding.page_two.compose": "Schrijf berichten (wij noemen dit toots) in het tekstvak in de linkerkolom. Je kan met de pictogrammen daaronder afbeeldingen uploaden, privacy-instellingen veranderen en je tekst een waarschuwing meegeven.",
-  "onboarding.skip": "Overslaan",
-  "privacy.change": "Privacy toot aanpassen",
-  "privacy.direct.long": "Toot alleen naar vermelde gebruikers",
-  "privacy.direct.short": "Direct",
-  "privacy.private.long": "Alleen aan volgers tonen",
-  "privacy.private.short": "Alleen volgers",
-  "privacy.public.long": "Op openbare tijdlijnen tonen",
-  "privacy.public.short": "Openbaar",
-  "privacy.unlisted.long": "Niet op openbare tijdlijnen tonen",
-  "privacy.unlisted.short": "Minder openbaar",
-  "reply_indicator.cancel": "Annuleren",
-  "search.account": "Account",
-  "search.hashtag": "Hashtag",
-  "search.placeholder": "Zoeken",
-  "search_results.total": "{count, number} {count, plural, one {resultaat} other {resultaten}}",
-  "status.delete": "Verwijderen",
-  "status.favourite": "Favoriet",
-  "status.mention": "@{name} vermelden",
-  "status.reblog": "Boost",
-  "status.reblogged_by": "{name} boostte",
-  "status.reply": "Reageren",
-  "status.sensitive_toggle": "Klik om te zien",
-  "status.sensitive_warning": "Gevoelige inhoud",
-  "status.show_less": "Minder tonen",
-  "status.show_more": "Meer tonen",
-  "tabs_bar.compose": "Schrijven",
-  "tabs_bar.home": "Jouw tijdlijn",
-  "tabs_bar.mentions": "Vermeldingen",
-  "tabs_bar.notifications": "Meldingen",
-  "tabs_bar.public": "Globale tijdlijn",
-  "upload_button.label": "Media toevoegen",
-  "upload_form.undo": "Ongedaan maken",
-  "video_player.toggle_sound": "Geluid in-/uitschakelen",
-
-};
-
-export default nl;
diff --git a/app/assets/javascripts/components/locales/no.jsx b/app/assets/javascripts/components/locales/no.jsx
deleted file mode 100644
index 17a0c2099..000000000
--- a/app/assets/javascripts/components/locales/no.jsx
+++ /dev/null
@@ -1,130 +0,0 @@
-const no = {
-  "account.block": "Blokkér @{name}",
-  "account.disclaimer": "Denne brukeren er fra en annen instans. Dette tallet kan være høyere.",
-  "account.edit_profile": "Rediger profil",
-  "account.follow": "Følg",
-  "account.followers": "Følgere",
-  "account.follows_you": "Følger deg",
-  "account.follows": "Følger",
-  "account.mention": "Nevn @{name}",
-  "account.mute": "Demp @{name}",
-  "account.posts": "Innlegg",
-  "account.report": "Rapportér @{name}",
-  "account.requested": "Venter på godkjennelse",
-  "account.unblock": "Avblokker @{name}",
-  "account.unfollow": "Avfølg",
-  "account.unmute": "Avdemp @{name}",
-  "boost_modal.combo": "You kan trykke {combo} for å hoppe over dette neste gang",
-  "column_back_button.label": "Tilbake",
-  "column.blocks": "Blokkerte brukere",
-  "column.community": "Lokal tidslinje",
-  "column.favourites": "Likt",
-  "column.follow_requests": "Følgeforespørsler",
-  "column.home": "Hjem",
-  "column.notifications": "Varslinger",
-  "column.public": "Felles tidslinje",
-  "compose_form.placeholder": "Hva har du på hjertet?",
-  "compose_form.privacy_disclaimer": "Din private status vil leveres til nevnte brukere på {domains}. Stoler du på {domainsCount, plural, one {den serveren} other {de serverne}}? Synlighet fungerer kun på Mastodon-instanser. Hvis {domains} {domainsCount, plural, one {ike er en Mastodon-instans} other {ikke er Mastodon-instanser}}, vil det ikke indikeres at posten din er privat, og den kan kanskje bli fremhevd eller på annen måte bli synlig for uventede mottakere.",
-  "compose_form.publish": "Tut",
-  "compose_form.sensitive": "Merk media som følsomt",
-  "compose_form.spoiler_placeholder": "Innholdsadvarsel",
-  "compose_form.spoiler": "Skjul tekst bak advarsel",
-  "emoji_button.label": "Sett inn emoji",
-  "empty_column.community": "Den lokale tidslinjen er tom. Skriv noe offentlig for å få snøballen til å rulle!",
-  "empty_column.hashtag": "Det er ingenting i denne hashtagen ennå.",
-  "empty_column.home.public_timeline": "en offentlig tidslinje",
-  "empty_column.home": "Du har ikke fulgt noen ennå. Besøk {publlic} eller bruk søk for å komme i gang og møte andre brukere.",
-  "empty_column.notifications": "Du har ingen varsler ennå. Kommuniser med andre for å begynne samtalen.",
-  "empty_column.public": "Det er ingenting her! Skriv noe offentlig, eller følg brukere manuelt fra andre instanser for å fylle den opp",
-  "follow_request.authorize": "Autorisér",
-  "follow_request.reject": "Avvis",
-  "getting_started.apps": "Diverse apper er tilgjengelige",
-  "getting_started.heading": "Kom i gang",
-  "getting_started.open_source_notice": "Mastodon er fri programvare. Du kan bidra eller rapportere problemer på GitHub på {github}. {apps}.",
-  "home.column_settings.advanced": "Advansert",
-  "home.column_settings.basic": "Enkel",
-  "home.column_settings.filter_regex": "Filtrér med regulære uttrykk",
-  "home.column_settings.show_reblogs": "Vis fremhevinger",
-  "home.column_settings.show_replies": "Vis svar",
-  "home.settings": "Kolonneinnstillinger",
-  "lightbox.close": "Lukk",
-  "loading_indicator.label": "Laster...",
-  "media_gallery.toggle_visible": "Veksle synlighet",
-  "missing_indicator.label": "Ikke funnet",
-  "navigation_bar.blocks": "Blokkerte brukere",
-  "navigation_bar.community_timeline": "Lokal tidslinje",
-  "navigation_bar.edit_profile": "Rediger profil",
-  "navigation_bar.favourites": "Likt",
-  "navigation_bar.follow_requests": "Følgeforespørsler",
-  "navigation_bar.info": "Utvidet informasjon",
-  "navigation_bar.logout": "Logg ut",
-  "navigation_bar.preferences": "Preferanser",
-  "navigation_bar.public_timeline": "Felles tidslinje",
-  "notification.favourite": "{name} likte din status",
-  "notification.follow": "{name} fulgte deg",
-  "notification.reblog": "{name} fremhevde din status",
-  "notifications.clear_confirmation": "Er du sikker på at du vil fjerne alle dine varsler?",
-  "notifications.clear": "Fjern varsler",
-  "notifications.column_settings.alert": "Skrivebordsvarslinger",
-  "notifications.column_settings.favourite": "Likt:",
-  "notifications.column_settings.follow": "Nye følgere:",
-  "notifications.column_settings.mention": "Nevninger:",
-  "notifications.column_settings.reblog": "Fremhevinger:",
-  "notifications.column_settings.show": "Vis i kolonne",
-  "notifications.column_settings.sound": "Spill lyd",
-  "notifications.settings": "Kolonneinstillinger",
-  "privacy.change": "Justér synlighet",
-  "privacy.direct.long": "Post kun til nevnte brukere",
-  "privacy.direct.short": "Direkte",
-  "privacy.private.long": "Post kun til følgere",
-  "privacy.private.short": "Privat",
-  "privacy.public.long": "Post kun til offentlige tidslinjer",
-  "privacy.public.short": "Offentlig",
-  "privacy.unlisted.long": "Ikke vis i offentlige tidslinjer",
-  "privacy.unlisted.short": "Uoppført",
-  "reply_indicator.cancel": "Avbryt",
-  "report.heading": "Ny rapport",
-  "report.placeholder": "Tilleggskommentarer",
-  "report.submit": "Send inn",
-  "report.target": "Rapporterer",
-  "search_results.total": "{count, number} {count, plural, one {resultat} other {resultater}}",
-  "search.placeholder": "Søk",
-  "search.status_by": "Status fra {name}",
-  "status.delete": "Slett",
-  "status.favourite": "Lik",
-  "status.load_more": "Last mer",
-  "status.media_hidden": "Media skjult",
-  "status.mention": "Nevn @{name}",
-  "status.open": "Utvid denne statusen",
-  "status.reblog": "Fremhev",
-  "status.reblogged_by": "Fremhevd av {name}",
-  "status.reply": "Svar",
-  "status.report": "Rapporter @{name}",
-  "status.sensitive_toggle": "Klikk for å vise",
-  "status.sensitive_warning": "Følsomt innhold",
-  "status.show_less": "Vis mindre",
-  "status.show_more": "Vis mer",
-  "tabs_bar.compose": "Komponer",
-  "tabs_bar.federated_timeline": "Felles",
-  "tabs_bar.home": "Hjem",
-  "tabs_bar.local_timeline": "Lokal",
-  "tabs_bar.notifications": "Varslinger",
-  "upload_area.title": "Dra og slipp for å laste opp",
-  "upload_button.label": "Legg til media",
-  "upload_form.undo": "Angre",
-  "upload_progress.label": "Laster opp...",
-  "video_player.toggle_sound": "Veksle lyd",
-  "video_player.toggle_visible": "Veksle synlighet",
-  "video_player.expand": "Utvid video",
-  "getting_started.about_addressing": "Du kan følge noen hvis du vet brukernavnet deres og domenet de er på ved å skrive en e-postadresse inn i søkeskjemaet.",
-  "getting_started.about_shortcuts": "Hvis målbrukeren er på samme domene som deg, vil kun brukernavnet også fungere. Den samme regelen gjelder når man nevner noen i statuser.",
-  "tabs_bar.mentions": "Nevninger",
-  "tabs_bar.public": "Felles tidslinje",
-  "compose_form.private": "Merk som privat",
-  "compose_form.unlisted": "Ikke vis på offentlige tidslinjer",
-  "search.account": "Konto",
-  "search.hashtag": "Hashtag",
-  "notification.mention": "{name} nevnte deg"
-};
-
-export default no;
diff --git a/app/assets/javascripts/components/locales/oc.jsx b/app/assets/javascripts/components/locales/oc.jsx
deleted file mode 100644
index 14f64e762..000000000
--- a/app/assets/javascripts/components/locales/oc.jsx
+++ /dev/null
@@ -1,128 +0,0 @@
-const oc = {
-  "column_back_button.label": "Tornar",
-  "lightbox.close": "Tampar",
-  "loading_indicator.label": "Cargament…",
-  "status.mention": "Mencionar",
-  "status.delete": "Escafar",
-  "status.reply": "Respondre",
-  "status.reblog": "Partejar",
-  "status.favourite": "Apondre als favorits",
-  "status.reblogged_by": "{name} a partejat :",
-  "status.sensitive_warning": "Contengut embarrassant",
-  "status.sensitive_toggle": "Clicar per mostrar",
-  "status.show_more": "Desplegar",
-  "status.show_less": "Tornar plegar",
-  "status.open": "Desplegar aqueste estatut",
-  "status.report": "Senhalar @{name}",
-  "status.load_more": "Cargar mai",
-  "status.media_hidden": "Mèdia rescondut",
-  "video_player.toggle_sound": "Activar/Desactivar lo son",
-  "video_player.toggle_visible": "Mostrar/Rescondre la vidèo",
-  "account.mention": "Mencionar",
-  "account.edit_profile": "Modificar lo perfil",
-  "account.unblock": "Desblocar",
-  "account.unfollow": "Quitar de sègre",
-  "account.block": "Blocar",
-  "account.mute": "Rescondre",
-  "account.unmute": "Quitar de rescondre",
-  "account.follow": "Sègre",
-  "account.posts": "Estatuts",
-  "account.follows": "Abonaments",
-  "account.followers": "Abonats",
-  "account.follows_you": "Vos sèc",
-  "account.requested": "Invitacion mandada",
-  "account.report": "Senhalar",
-  "account.disclaimer": "Aqueste compte es sus una autra instància. Los nombres pòdon èsser mai grandes.",
-  "getting_started.heading": "Per començar",
-  "getting_started.about_addressing": "Podètz sègre los estatuts de qualqu’un en picant son identificant e lo domeni de l’instància separat amb un @ coma una adreça de corrièl dins lo camp de recèrca.",
-  "getting_started.about_shortcuts": "S’aquesta persona emplega la meteissa instància que vos l’identifican basta. Atal foncionan tanben las mencions dins vòstres estatuts.",
-  "getting_started.about_developer": "Per sègre lo desvolopaire d’aqueste projècte : Gargron@mastodon.social",
-  "getting_started.open_source_notice": "Mastodon es un logicial liure. Podètz contribuir e mandar vòstres comentaris e rapòrt de bug via{github} sus GitHub.",
-  "column.home": "Acuèlh",
-  "column.community": "Fil public local",
-  "column.public": "Fil public global",
-  "column.notifications": "Notificacions",
-  "column.blocks": "Personas blocadas",
-  "column.favourites": "Favorits",
-  "column.follow_requests": "Demandas d’abonament",
-  "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 fil public.",
-  "empty_column.home": "Pel moment segètz pas segun. Visitatz {public} o utilizatz la recèrca per vos connectar a d’autras personas.",
-  "empty_column.home.public_timeline": "lo fil public",
-  "empty_column.community": "Lo fil public local es void. Escribètz quicòm per lo garnir !",
-  "empty_column.hashtag": "I a pas encara de contengut ligat a aqueste hashtag",
-  "tabs_bar.compose": "Compausar",
-  "tabs_bar.home": "Acuèlh",
-  "tabs_bar.mentions": "Mencions",
-  "tabs_bar.public": "Fil public global",
-  "tabs_bar.notifications": "Notifications",
-  "tabs_bar.local_timeline": "Fil public local",
-  "tabs_bar.federated_timeline": "Fil public global",
-  "compose_form.placeholder": "A de qué pensatz ?",
-  "compose_form.publish": "Tut",
-  "compose_form.sensitive": "Marcar lo mèdia coma embarrassant",
-  "compose_form.spoiler": "Rescondre lo tèxte darrièr un avertiment",
-  "compose_form.spoiler_placeholder": "Avertiment",
-  "compose_form.private": "Far venir privat",
-  "compose_form.privacy_disclaimer": "Vòstre estatut privat serà enviat a las personas mencionadas sus {domains}. Vos fisatz d’aqueste{domainsCount, plural, one { servidor} other {s servidors}} per divulgar pas vòstre estatut ? Los estatuts privats foncionan pas que sus las instàncias a Mastodons. Se {domains} {domainsCount, plural, one {es pas una instància a Mastodon} other {son pas d'instàncias a Mastodon}}, i aurà pas d’indicacion disent que vòstre estatut es privat e poirà èsser partejat o èsser visible a de mond pas prevists",
-  "compose_form.unlisted": "Mostrar pas dins los fils publics",
-  "emoji_button.label": "Inserir un emoji",
-  "navigation_bar.edit_profile": "Modificar lo perfil",
-  "navigation_bar.preferences": "Preferéncias",
-  "navigation_bar.community_timeline": "Fil public local",
-  "navigation_bar.public_timeline": "Fil public global",
-  "navigation_bar.blocks": "Personas blocadas",
-  "navigation_bar.favourites": "Favorits",
-  "navigation_bar.info": "Mai informacions",
-  "navigation_bar.logout": "Desconnexion",
-  "navigation_bar.follow_requests": "Demandas d'abonament",
-  "reply_indicator.cancel": "Anullar",
-  "search.placeholder": "Recercar",
-  "search.account": "Compte",
-  "search.hashtag": "Mot-clau",
-  "search_results.total": "{count, number} {count, plural, one {resultat} other {resultats}}",
-  "search.status_by": "Estatuts de {name}",
-  "upload_button.label": "Apondre un mèdia",
-  "upload_form.undo": "Anullar",
-  "upload_progress.label": "Mandadís…",
-  "upload_area.title": "Lisatz e depausatz per mandar",
-  "notification.follow": "{name} vos sèc.",
-  "notification.favourite": "{name} a apondut a sos favorits :",
-  "notification.reblog": "{name} a partejat vòstre estatut :",
-  "notification.mention": "{name} vos a mencionat :",
-  "notifications.column_settings.alert": "Notificacions localas",
-  "notifications.column_settings.show": "Mostrar dins la colomna",
-  "notifications.column_settings.sound": "Emetre un son",
-  "notifications.column_settings.follow": "Nòus abonats :",
-  "notifications.column_settings.favourite": "Favorits :",
-  "notifications.column_settings.mention": "Mencions :",
-  "notifications.column_settings.reblog": "Partatges :",
-  "notifications.clear": "Levar",
-  "notifications.clear_confirmation": "Volètz vertadièrament levar totas vòstras las notificacions ?",
-  "notifications.settings": "Paramètres de la colomna",
-  "privacy.public.short": "Public",
-  "privacy.public.long": "Mostrar dins los fils publics",
-  "privacy.unlisted.short": "Pas-listat",
-  "privacy.unlisted.long": "Mostrar pas dins los fils publics",
-  "privacy.private.short": "Privat",
-  "privacy.private.long": "Mostrar pas qu'a vòstres abonats",
-  "privacy.direct.short": "Dirècte",
-  "privacy.direct.long": "Mostrar pas qu'a las personas mencionadas",
-  "privacy.change": "Ajustar la confidencialitat del messatge",
-  "media_gallery.toggle_visible": "Modificar la visibilitat",
-  "missing_indicator.label": "Pas trobat",
-  "follow_request.authorize": "Autorizar",
-  "follow_request.reject": "Regetar",
-  "home.settings": "Paramètres de la colomna",
-  "home.column_settings.basic": "Basic",
-  "home.column_settings.show_reblogs": "Mostrar los partatges",
-  "home.column_settings.show_replies": "Mostrar las responsas",
-  "home.column_settings.advanced": "Avançat",
-  "home.column_settings.filter_regex": "Filtrar amb una expression racionala",
-  "report.heading": "Nòu senhalament",
-  "report.placeholder": "Comentaris addicionals",
-  "report.submit": "Mandat",
-  "report.target": "Senhalament"
-};
-
-export default oc;
diff --git a/app/assets/javascripts/components/locales/pt-br.jsx b/app/assets/javascripts/components/locales/pt-br.jsx
deleted file mode 100644
index 91c74bb19..000000000
--- a/app/assets/javascripts/components/locales/pt-br.jsx
+++ /dev/null
@@ -1,125 +0,0 @@
-const pt_br = {
-  "account.block": "Bloquear @{name}",
-  "account.disclaimer": "Essa conta está localizado em outra instância. Os nomes podem ser maiores.",
-  "account.edit_profile": "Editar perfil",
-  "account.follow": "Seguir",
-  "account.followers": "Seguidores",
-  "account.follows_you": "É teu seguidor",
-  "account.follows": "Segue",
-  "account.mention": "Mencionar @{name}",
-  "account.mute": "Silenciar @{name}",
-  "account.posts": "Posts",
-  "account.report": "Denunciar @{name}",
-  "account.requested": "A aguardar aprovação",
-  "account.unblock": "Não bloquear @{name}",
-  "account.unfollow": "Deixar de seguir",
-  "account.unmute": "Não silenciar @{name}",
-  "boost_modal.combo": "Pode clicar {combo} para não voltar a ver",
-  "column_back_button.label": "Voltar",
-  "column.blocks": "Utilizadores Bloqueados",
-  "column.community": "Local",
-  "column.favourites": "Favoritos",
-  "column.follow_requests": "Seguidores Pendentes",
-  "column.home": "Home",
-  "column.mutes": "Utilizadores silenciados",
-  "column.notifications": "Notificações",
-  "column.public": "Global",
-  "compose_form.placeholder": "Em que estás a pensar?",
-  "compose_form.privacy_disclaimer": "O teu conteúdo privado vai ser partilhado com os utilizadores do {domains}. Confias {domainsCount, plural, one {neste servidor} other {nestes servidores}}? A privacidade só funciona em instâncias do Mastodon. Se {domains} {domainsCount, plural, one {não é uma instância} other {não são instâncias}}, não existem indicadores da privacidade da tua partilha, e podem ser partilhados com outros.",
-  "compose_form.publish": "Publicar",
-  "compose_form.sensitive": "Marcar media como conteúdo sensível",
-  "compose_form.spoiler_placeholder": "Aviso de conteúdo",
-  "compose_form.spoiler": "Esconder texto com aviso",
-  "emoji_button.label": "Inserir Emoji",
-  "empty_column.community": "Ainda não existem conteúdo local para mostrar!",
-  "empty_column.hashtag": "Ainda não existe qualquer conteúdo com essa hashtag",
-  "empty_column.home.public_timeline": "global",
-  "empty_column.home": "Ainda não segues qualquer utilizador. Visita {public} ou utiliza a pesquisa para procurar outros utilizadores.",
-  "empty_column.notifications": "Não tens notificações. Interage com outros utilizadores para iniciar uma conversa.",
-  "empty_column.public": "Não há nada aqui! Escreve algo publicamente ou segue outros utilizadores para ver aqui os conteúdos públicos.",
-  "follow_request.authorize": "Autorizar",
-  "follow_request.reject": "Rejeitar",
-  "getting_started.apps": "Existem várias aplicações disponíveis",
-  "getting_started.heading": "Primeiros passos",
-  "getting_started.open_source_notice": "Mastodon é software de fonte aberta. Podes contribuir ou repostar problemas no GitHub do projecto: {github}. {apps}.",
-  "home.column_settings.advanced": "Avançado",
-  "home.column_settings.basic": "Básico",
-  "home.column_settings.filter_regex": "Filtrar com uma expressão regular",
-  "home.column_settings.show_reblogs": "Mostrar as partilhas",
-  "home.column_settings.show_replies": "Mostrar as respostas",
-  "home.settings": "Parâmetros da listagem Home",
-  "lightbox.close": "Fechar",
-  "loading_indicator.label": "Carregando...",
-  "media_gallery.toggle_visible": "Esconder/Mostrar",
-  "missing_indicator.label": "Não encontrado",
-  "navigation_bar.blocks": "Utilizadores bloqueados",
-  "navigation_bar.community_timeline": "Local",
-  "navigation_bar.edit_profile": "Editar perfil",
-  "navigation_bar.favourites": "Favoritos",
-  "navigation_bar.follow_requests": "Seguidores pendentes",
-  "navigation_bar.info": "Mais informações",
-  "navigation_bar.logout": "Sair",
-  "navigation_bar.mutes": "Utilizadores silenciados",
-  "navigation_bar.preferences": "Preferências",
-  "navigation_bar.public_timeline": "Global",
-  "notification.favourite": "{name} adicionou o teu post aos favoritos",
-  "notification.follow": "{name} seguiu-te",
-  "notification.mention": "{name} mencionou-te",
-  "notification.reblog": "{name} partilhou o teu post",
-  "notifications.clear_confirmation": "Queres mesmo limpar todas as notificações?",
-  "notifications.clear": "Limpar notificações",
-  "notifications.column_settings.alert": "Notificações no computador",
-  "notifications.column_settings.favourite": "Favoritos:",
-  "notifications.column_settings.follow": "Novos seguidores:",
-  "notifications.column_settings.mention": "Menções:",
-  "notifications.column_settings.reblog": "Partilhas:",
-  "notifications.column_settings.show": "Mostrar nas colunas",
-  "notifications.column_settings.sound": "Reproduzir som",
-  "notifications.settings": "Parâmetros da listagem de Notificações",
-  "privacy.change": "Ajustar a privacidade da mensagem",
-  "privacy.direct.long": "Apenas para utilizadores mencionados",
-  "privacy.direct.short": "Directo",
-  "privacy.private.long": "Apenas para os seguidores",
-  "privacy.private.short": "Privado",
-  "privacy.public.long": "Publicar em todos os feeds",
-  "privacy.public.short": "Público",
-  "privacy.unlisted.long": "Não publicar nos feeds públicos",
-  "privacy.unlisted.short": "Não listar",
-  "reply_indicator.cancel": "Cancelar",
-  "report.heading": "Nova denúncia",
-  "report.placeholder": "Comentários adicionais",
-  "report.submit": "Enviar",
-  "report.target": "Denunciar",
-  "search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
-  "search.placeholder": "Pesquisar",
-  "search.status_by": "Post de {name}",
-  "status.delete": "Eliminar",
-  "status.favourite": "Adicionar aos favoritos",
-  "status.load_more": "Carregar mais",
-  "status.media_hidden": "Media escondida",
-  "status.mention": "Mencionar @{name}",
-  "status.open": "Expandir",
-  "status.reblog": "Partilhar",
-  "status.reblogged_by": "{name} partilhou",
-  "status.reply": "Responder",
-  "status.report": "Denúnciar @{name}",
-  "status.sensitive_toggle": "Clique para ver",
-  "status.sensitive_warning": "Conteúdo sensível",
-  "status.show_less": "Mostrar menos",
-  "status.show_more": "Mostrar mais",
-  "tabs_bar.compose": "Criar",
-  "tabs_bar.federated_timeline": "Global",
-  "tabs_bar.home": "Home",
-  "tabs_bar.local_timeline": "Local",
-  "tabs_bar.notifications": "Notificações",
-  "upload_area.title": "Arraste e solte para enviar",
-  "upload_button.label": "Adicionar media",
-  "upload_form.undo": "Anular",
-  "upload_progress.label": "A gravar...",
-  "video_player.toggle_sound": "Ligar/Desligar som",
-  "video_player.toggle_visible": "Ligar/Desligar vídeo",
-  "video_player.expand": "Expandir vídeo",
-  "video_player.video_error": "Não é possível ver o vídeo",
-};
-
-export default pt_br;
diff --git a/app/assets/javascripts/components/locales/pt.jsx b/app/assets/javascripts/components/locales/pt.jsx
deleted file mode 100644
index 03095d20d..000000000
--- a/app/assets/javascripts/components/locales/pt.jsx
+++ /dev/null
@@ -1,125 +0,0 @@
-const pt = {
-  "account.block": "Bloquear @{name}",
-  "account.disclaimer": "Essa conta está localizado em outra instância. Os nomes podem ser maiores.",
-  "account.edit_profile": "Editar perfil",
-  "account.follow": "Seguir",
-  "account.followers": "Seguidores",
-  "account.follows_you": "É teu seguidor",
-  "account.follows": "Segue",
-  "account.mention": "Mencionar @{name}",
-  "account.mute": "Silenciar @{name}",
-  "account.posts": "Posts",
-  "account.report": "Denunciar @{name}",
-  "account.requested": "A aguardar aprovação",
-  "account.unblock": "Não bloquear @{name}",
-  "account.unfollow": "Deixar de seguir",
-  "account.unmute": "Não silenciar @{name}",
-  "boost_modal.combo": "Pode clicar {combo} para não voltar a ver",
-  "column_back_button.label": "Voltar",
-  "column.blocks": "Utilizadores Bloqueados",
-  "column.community": "Local",
-  "column.favourites": "Favoritos",
-  "column.follow_requests": "Seguidores Pendentes",
-  "column.home": "Home",
-  "column.mutes": "Utilizadores silenciados",
-  "column.notifications": "Notificações",
-  "column.public": "Global",
-  "compose_form.placeholder": "Em que estás a pensar?",
-  "compose_form.privacy_disclaimer": "O teu conteúdo privado vai ser partilhado com os utilizadores do {domains}. Confias {domainsCount, plural, one {neste servidor} other {nestes servidores}}? A privacidade só funciona em instâncias do Mastodon. Se {domains} {domainsCount, plural, one {não é uma instância} other {não são instâncias}}, não existem indicadores da privacidade da tua partilha, e podem ser partilhados com outros.",
-  "compose_form.publish": "Publicar",
-  "compose_form.sensitive": "Marcar media como conteúdo sensível",
-  "compose_form.spoiler_placeholder": "Aviso de conteúdo",
-  "compose_form.spoiler": "Esconder texto com aviso",
-  "emoji_button.label": "Inserir Emoji",
-  "empty_column.community": "Ainda não existem conteúdo local para mostrar!",
-  "empty_column.hashtag": "Ainda não existe qualquer conteúdo com essa hashtag",
-  "empty_column.home.public_timeline": "global",
-  "empty_column.home": "Ainda não segues qualquer utilizador. Visita {public} ou utiliza a pesquisa para procurar outros utilizadores.",
-  "empty_column.notifications": "Não tens notificações. Interage com outros utilizadores para iniciar uma conversa.",
-  "empty_column.public": "Não há nada aqui! Escreve algo publicamente ou segue outros utilizadores para ver aqui os conteúdos públicos.",
-  "follow_request.authorize": "Autorizar",
-  "follow_request.reject": "Rejeitar",
-  "getting_started.apps": "Existem várias aplicações disponíveis",
-  "getting_started.heading": "Primeiros passos",
-  "getting_started.open_source_notice": "Mastodon é software de fonte aberta. Podes contribuir ou repostar problemas no GitHub do projecto: {github}. {apps}.",
-  "home.column_settings.advanced": "Avançado",
-  "home.column_settings.basic": "Básico",
-  "home.column_settings.filter_regex": "Filtrar com uma expressão regular",
-  "home.column_settings.show_reblogs": "Mostrar as partilhas",
-  "home.column_settings.show_replies": "Mostrar as respostas",
-  "home.settings": "Parâmetros da listagem Home",
-  "lightbox.close": "Fechar",
-  "loading_indicator.label": "Carregando...",
-  "media_gallery.toggle_visible": "Esconder/Mostrar",
-  "missing_indicator.label": "Não encontrado",
-  "navigation_bar.blocks": "Utilizadores bloqueados",
-  "navigation_bar.community_timeline": "Local",
-  "navigation_bar.edit_profile": "Editar perfil",
-  "navigation_bar.favourites": "Favoritos",
-  "navigation_bar.follow_requests": "Seguidores pendentes",
-  "navigation_bar.info": "Mais informações",
-  "navigation_bar.logout": "Sair",
-  "navigation_bar.mutes": "Utilizadores silenciados",
-  "navigation_bar.preferences": "Preferências",
-  "navigation_bar.public_timeline": "Global",
-  "notification.favourite": "{name} adicionou o teu post aos favoritos",
-  "notification.follow": "{name} seguiu-te",
-  "notification.mention": "{name} mencionou-te",
-  "notification.reblog": "{name} partilhou o teu post",
-  "notifications.clear_confirmation": "Queres mesmo limpar todas as notificações?",
-  "notifications.clear": "Limpar notificações",
-  "notifications.column_settings.alert": "Notificações no computador",
-  "notifications.column_settings.favourite": "Favoritos:",
-  "notifications.column_settings.follow": "Novos seguidores:",
-  "notifications.column_settings.mention": "Menções:",
-  "notifications.column_settings.reblog": "Partilhas:",
-  "notifications.column_settings.show": "Mostrar nas colunas",
-  "notifications.column_settings.sound": "Reproduzir som",
-  "notifications.settings": "Parâmetros da listagem de Notificações",
-  "privacy.change": "Ajustar a privacidade da mensagem",
-  "privacy.direct.long": "Apenas para utilizadores mencionados",
-  "privacy.direct.short": "Directo",
-  "privacy.private.long": "Apenas para os seguidores",
-  "privacy.private.short": "Privado",
-  "privacy.public.long": "Publicar em todos os feeds",
-  "privacy.public.short": "Público",
-  "privacy.unlisted.long": "Não publicar nos feeds públicos",
-  "privacy.unlisted.short": "Não listar",
-  "reply_indicator.cancel": "Cancelar",
-  "report.heading": "Nova denúncia",
-  "report.placeholder": "Comentários adicionais",
-  "report.submit": "Enviar",
-  "report.target": "Denunciar",
-  "search_results.total": "{count, number} {count, plural, one {resultado} other {resultados}}",
-  "search.placeholder": "Pesquisar",
-  "search.status_by": "Post de {name}",
-  "status.delete": "Eliminar",
-  "status.favourite": "Adicionar aos favoritos",
-  "status.load_more": "Carregar mais",
-  "status.media_hidden": "Media escondida",
-  "status.mention": "Mencionar @{name}",
-  "status.open": "Expandir",
-  "status.reblog": "Partilhar",
-  "status.reblogged_by": "{name} partilhou",
-  "status.reply": "Responder",
-  "status.report": "Denúnciar @{name}",
-  "status.sensitive_toggle": "Clique para ver",
-  "status.sensitive_warning": "Conteúdo sensível",
-  "status.show_less": "Mostrar menos",
-  "status.show_more": "Mostrar mais",
-  "tabs_bar.compose": "Criar",
-  "tabs_bar.federated_timeline": "Global",
-  "tabs_bar.home": "Home",
-  "tabs_bar.local_timeline": "Local",
-  "tabs_bar.notifications": "Notificações",
-  "upload_area.title": "Arraste e solte para enviar",
-  "upload_button.label": "Adicionar media",
-  "upload_form.undo": "Anular",
-  "upload_progress.label": "A gravar...",
-  "video_player.toggle_sound": "Ligar/Desligar som",
-  "video_player.toggle_visible": "Ligar/Desligar vídeo",
-  "video_player.expand": "Expandir vídeo",
-  "video_player.video_error": "Não é possível ver o vídeo",
-};
-
-export default pt;
diff --git a/app/assets/javascripts/components/locales/ru.jsx b/app/assets/javascripts/components/locales/ru.jsx
deleted file mode 100644
index a1c9044bf..000000000
--- a/app/assets/javascripts/components/locales/ru.jsx
+++ /dev/null
@@ -1,138 +0,0 @@
-const ru = {
-  "account.block": "Блокировать",
-  "account.disclaimer": "Это пользователь с другого узла. Число может быть больше.",
-  "account.edit_profile": "Изменить профиль",
-  "account.follow": "Подписаться",
-  "account.followers": "Подписаны",
-  "account.follows": "Подписки",
-  "account.follows_you": "Подписан(а) на Вас",
-  "account.mention": "Упомянуть",
-  "account.mute": "Заглушить",
-  "account.posts": "Посты",
-  "account.report": "Пожаловаться",
-  "account.requested": "Ожидает подтверждения",
-  "account.unblock": "Разблокировать",
-  "account.unfollow": "Отписаться",
-  "account.unmute": "Снять глушение",
-  "boost_modal.combo": "Нажмите {combo}, чтобы пропустить это в следующий раз",
-  "column.blocks": "Список блокировки",
-  "column.community": "Локальная лента",
-  "column.favourites": "Понравившееся",
-  "column.follow_requests": "Запросы на подписку",
-  "column.home": "Главная",
-  "column.mutes": "Список глушения",
-  "column.notifications": "Уведомления",
-  "column.public": "Глобальная лента",
-  "column_back_button.label": "Назад",
-  "column_subheading.navigation": "Навигация",
-  "column_subheading.settings": "Настройки",
-  "compose_form.placeholder": "О чем Вы думаете?",
-  "compose_form.privacy_disclaimer": "Ваш приватный статус будет доставлен упомянутым пользователям на доменах {domains}. Доверяете ли вы {domainsCount, plural, one {этому серверу} other {этим серверам}}? Приватность постов работает только на узлах Mastodon. Если {domains} {domainsCount, plural, one {не является узлом Mastodon} other {не являются узлами Mastodon}}, приватность поста не будет указана, и он может оказаться продвинут или иным образом показан не обозначенным Вами пользователям.",
-  "compose_form.publish": "Трубить",
-  "compose_form.sensitive": "Отметить как чувствительный контент",
-  "compose_form.spoiler": "Скрыть текст за предупреждением",
-  "compose_form.spoiler_placeholder": "Предупреждение о скрытом тексте",
-  "emoji_button.activity": "Занятия",
-  "emoji_button.flags": "Флаги",
-  "emoji_button.food": "Еда и напитки",
-  "emoji_button.label": "Вставить эмодзи",
-  "emoji_button.nature": "Природа",
-  "emoji_button.objects": "Предметы",
-  "emoji_button.people": "Люди",
-  "emoji_button.search": "Найти...",
-  "emoji_button.symbols": "Символы",
-  "emoji_button.travel": "Путешествия",
-  "empty_column.community": "Локальная лента пуста. Напишите что-нибудь, чтобы разогреть народ!",
-  "empty_column.hashtag": "Статусов с таким хэштегом еще не существует.",
-  "empty_column.home": "Пока Вы ни на кого не подписаны. Полистайте {public} или используйте поиск, чтобы освоиться и завести новые знакомства.",
-  "empty_column.home.public_timeline": "публичные ленты",
-  "empty_column.notifications": "У Вас еще нет уведомлений. Заведите знакомство с другими пользователями, чтобы начать разговор.",
-  "empty_column.public": "Здесь ничего нет! Опубликуйте что-нибудь или подпишитесь на пользователей с других узлов, чтобы заполнить ленту.",
-  "follow_request.authorize": "Авторизовать",
-  "follow_request.reject": "Отказать",
-  "getting_started.apps": "Доступны различные приложения.",
-  "getting_started.heading": "Добро пожаловать",
-  "getting_started.open_source_notice": "Mastodon - программа с открытым исходным кодом. Вы можете помочь проекту или сообщить о проблемах на GitHub по адресу {github}. {apps}.",
-  "home.column_settings.advanced": "Дополнительные",
-  "home.column_settings.basic": "Основные",
-  "home.column_settings.filter_regex": "Отфильтровать регулярным выражением",
-  "home.column_settings.show_reblogs": "Показывать продвижения",
-  "home.column_settings.show_replies": "Показывать ответы",
-  "home.settings": "Настройки колонки",
-  "lightbox.close": "Закрыть",
-  "loading_indicator.label": "Загрузка...",
-  "media_gallery.toggle_visible": "Показать/скрыть",
-  "missing_indicator.label": "Не найдено",
-  "navigation_bar.blocks": "Список блокировки",
-  "navigation_bar.community_timeline": "Локальная лента",
-  "navigation_bar.edit_profile": "Изменить профиль",
-  "navigation_bar.favourites": "Понравившееся",
-  "navigation_bar.follow_requests": "Запросы на подписку",
-  "navigation_bar.info": "Об узле",
-  "navigation_bar.logout": "Выйти",
-  "navigation_bar.mutes": "Список глушения",
-  "navigation_bar.preferences": "Опции",
-  "navigation_bar.public_timeline": "Глобальная лента",
-  "notification.favourite": "{name} понравился Ваш статус",
-  "notification.follow": "{name} подписался(-лась) на Вас",
-  "notification.mention": "{name} упомянул(а) Вас",
-  "notification.reblog": "{name} продвинул(а) Ваш статус",
-  "notifications.clear": "Очистить уведомления",
-  "notifications.clear_confirmation": "Вы уверены, что хотите очистить все уведомления?",
-  "notifications.column_settings.alert": "Десктопные уведомления",
-  "notifications.column_settings.favourite": "Нравится:",
-  "notifications.column_settings.follow": "Новые подписчики:",
-  "notifications.column_settings.mention": "Упоминания:",
-  "notifications.column_settings.reblog": "Продвижения:",
-  "notifications.column_settings.show": "Показывать в колонке",
-  "notifications.column_settings.sound": "Проигрывать звук",
-  "notifications.settings": "Настройки колонки",
-  "privacy.change": "Изменить видимость статуса",
-  "privacy.direct.long": "Показать только упомянутым",
-  "privacy.direct.short": "Направленный",
-  "privacy.private.long": "Показать только подписчикам",
-  "privacy.private.short": "Приватный",
-  "privacy.public.long": "Показать в публичных лентах",
-  "privacy.public.short": "Публичный",
-  "privacy.unlisted.long": "Не показывать в лентах",
-  "privacy.unlisted.short": "Скрытый",
-  "reply_indicator.cancel": "Отмена",
-  "report.heading": "Новая жалоба",
-  "report.placeholder": "Комментарий",
-  "report.submit": "Отправить",
-  "report.target": "Жалуемся на",
-  "search.placeholder": "Поиск",
-  "search.status_by": "Статус от {name}",
-  "search_results.total": "{count, number} {count, plural, one {результат} few {результата} many {результатов} other {результатов}}",
-  "status.cannot_reblog": "Этот статус не может быть продвинут",
-  "status.delete": "Удалить",
-  "status.favourite": "Нравится",
-  "status.load_more": "Показать еще",
-  "status.media_hidden": "Медиаконтент скрыт",
-  "status.mention": "Упомянуть @{name}",
-  "status.open": "Развернуть статус",
-  "status.reblog": "Продвинуть",
-  "status.reblogged_by": "{name} продвинул(а)",
-  "status.reply": "Ответить",
-  "status.replyAll": "Ответить на тред",
-  "status.report": "Пожаловаться",
-  "status.sensitive_toggle": "Нажмите для просмотра",
-  "status.sensitive_warning": "Чувствительный контент",
-  "status.show_less": "Свернуть",
-  "status.show_more": "Развернуть",
-  "tabs_bar.compose": "Написать",
-  "tabs_bar.federated_timeline": "Глобальная",
-  "tabs_bar.home": "Главная",
-  "tabs_bar.local_timeline": "Локальная",
-  "tabs_bar.notifications": "Уведомления",
-  "upload_area.title": "Перетащите сюда, чтобы загрузить",
-  "upload_button.label": "Добавить медиаконтент",
-  "upload_form.undo": "Отменить",
-  "upload_progress.label": "Загрузка...",
-  "video_player.expand": "Развернуть видео",
-  "video_player.toggle_sound": "Вкл./выкл. звук",
-  "video_player.toggle_visible": "Показать/скрыть",
-  "video_player.video_error": "Видео не может быть проиграно",
-};
-
-export default ru;
diff --git a/app/assets/javascripts/components/locales/uk.jsx b/app/assets/javascripts/components/locales/uk.jsx
deleted file mode 100644
index 84a348c21..000000000
--- a/app/assets/javascripts/components/locales/uk.jsx
+++ /dev/null
@@ -1,57 +0,0 @@
-const uk = {
-  "column_back_button.label": "Назад",
-  "lightbox.close": "Закрити",
-  "loading_indicator.label": "Завантаження...",
-  "status.mention": "Згадати",
-  "status.delete": "Видалити",
-  "status.reply": "Відповісти",
-  "status.reblog": "Передмухнути",
-  "status.favourite": "Подобається",
-  "status.reblogged_by": "{name} передмухнув(-ла)",
-  "status.sensitive_warning": "Непристойний зміст",
-  "status.sensitive_toggle": "Натисніть, щоб подивитися",
-  "video_player.toggle_sound": "Увімкнути/вимкнути звук",
-  "account.mention": "Згадати",
-  "account.edit_profile": "Налаштування профілю",
-  "account.unblock": "Розблокувати",
-  "account.unfollow": "Відписатися",
-  "account.block": "Заблокувати",
-  "account.follow": "Підписатися",
-  "account.posts": "Пости",
-  "account.follows": "Підписки",
-  "account.followers": "Підписники",
-  "account.follows_you": "Підписаний",
-  "getting_started.heading": "Ласкаво просимо",
-  "getting_started.about_addressing": "Ви можете підписуватись на людей, якщо ви знаєте їх ім'я користувача чи домен, шляхом введення email-подібної адреси у верхньому рядку бокової панелі.",
-  "getting_started.about_shortcuts": "Якщо користувач, якого ви шукаєте, знаходиться на тому ж домені, що й ви, можна просто ввести ім'я користувача. Це правило стосується й згадування людей у статусах.",
-  "getting_started.about_developer": "Розробник проекту знаходиться за адресою Gargron@mastodon.social",
-  "column.home": "Головна",
-  "column.mentions": "Згадування",
-  "column.public": "Стіна",
-  "column.notifications": "Сповіщення",
-  "tabs_bar.compose": "Написати",
-  "tabs_bar.home": "Головна",
-  "tabs_bar.mentions": "Згадування",
-  "tabs_bar.public": "Стіна",
-  "tabs_bar.notifications": "Сповіщення",
-  "compose_form.placeholder": "Що у Вас на думці?",
-  "compose_form.publish": "Дмухнути",
-  "compose_form.sensitive": "Непристойний зміст",
-  "compose_form.unlisted": "Таємний режим",
-  "navigation_bar.edit_profile": "Редагувати профіль",
-  "navigation_bar.preferences": "Налаштування",
-  "navigation_bar.public_timeline": "Публічна стіна",
-  "navigation_bar.logout": "Вийти",
-  "reply_indicator.cancel": "Відмінити",
-  "search.placeholder": "Пошук",
-  "search.account": "Аккаунт",
-  "search.hashtag": "Хештеґ",
-  "upload_button.label": "Додати медіа",
-  "upload_form.undo": "Відмінити",
-  "notification.follow": "{name} підписався(-лась) на Вас",
-  "notification.favourite": "{name} сподобався ваш допис",
-  "notification.reblog": "{name} передмухнув(-ла) Ваш статус",
-  "notification.mention": "{name} згадав(-ла) Вас"
-};
-
-export default uk;
diff --git a/app/assets/javascripts/components/locales/zh-cn.jsx b/app/assets/javascripts/components/locales/zh-cn.jsx
deleted file mode 100644
index ee4eee427..000000000
--- a/app/assets/javascripts/components/locales/zh-cn.jsx
+++ /dev/null
@@ -1,157 +0,0 @@
-import zh from 'react-intl/locale-data/zh';
-
-const localeData = zh.reduce(function (acc, localeData) {
-  if (localeData.locale === "zh-Hans-CN") {
-    // rename the locale "zh-Hans-CN" as "zh-CN"
-    // (match the code usually used in Accepted-Language header)
-    acc.push(Object.assign({},
-      localeData,
-      {
-        "locale": "zh-CN",
-        "parentLocale": "zh-Hans-CN",
-      }
-    ));
-  }
-  return acc;
-}, []);
-
-export { localeData as localeData };
-
-const zh_cn = {
-  "account.block": "屏蔽 @{name}",
-  "account.disclaimer": "由于这个账户处于另一个服务站,实际数字会比这个更多。",
-  "account.edit_profile": "修改个人资料",
-  "account.follow": "关注",
-  "account.followers": "关注者",
-  "account.follows_you": "关注你",
-  "account.follows": "正关注",
-  "account.mention": "提及 @{name}",
-  "account.mute": "将 @{name} 静音",
-  "account.posts": "嘟文",
-  "account.report": "举报 @{name}",
-  "account.requested": "等候审批",
-  "account.unblock": "解除对 @{name} 的屏蔽",
-  "account.unfollow": "取消关注",
-  "account.unmute": "取消 @{name} 的静音",
-  "boost_modal.combo": "如你想在下次路过时显示,请按{combo},",
-  "column_back_button.label": "返回",
-  "column.blocks": "屏蔽用户",
-  "column.community": "本站时间轴",
-  // intentional departure from existing "推文" translation for posts:
-  // "推文" refers to "推特", the official translation for Twitter.
-  // Currently using a semi-phonetic translation "嘟", which refers
-  // to train horn sounds, for "toot".
-  "column.favourites": "赞过的嘟文",
-  "column.follow_requests": "关注请求",
-  "column.home": "主页",
-  "column.notifications": "通知",
-  "column.public": "跨站公共时间轴",
-  "compose_form.placeholder": "在想啥?",
-  "compose_form.privacy_disclaimer": "你的私人嘟文,将被发送至你所提及的 {domains} 用户。你是否信任{domainsCount, plural, one {这个网站} other {这些网站}}?请留意,嘟文隐私设置只适用于各 Mastodon 服务站,如果 {domains} {domainsCount, plural, one {不是 Mastodon 服务站} other {之中有些不是 Mastodon 服务站}},对方将无法收到这篇嘟文的隐私设置,然后可能被转嘟给不能预知的用户阅读。",
-  "compose_form.private": "标示为“只有关注你的人能看”",
-  // Going "toot-toot" here below.
-  "compose_form.publish": "嘟嘟",
-  "compose_form.sensitive": "将媒体文件标示为“敏感内容”",
-  "compose_form.spoiler_placeholder": "敏感内容的警告消息",
-  "compose_form.spoiler": "将部分文本藏于警告消息之后",
-  "compose_form.unlisted": "请勿在公共时间轴显示",
-  "emoji_button.label": "加入表情符号",
-  "empty_column.community": "本站时间轴暂时未有内容,快贴文来抢头香啊!",
-  "empty_column.hashtag": "这个标签暂时未有内容。",
-  "empty_column.home": "你还没有关注任何用户。快看看{public},向其他用户搭讪吧。",
-  "empty_column.home.public_timeline": "公共时间轴",
-  "empty_column.home": "你还没有关注任何用户。快看看{public},向其他用户搭讪吧。",
-  "empty_column.notifications": "你没有任何通知纪录,快向其他用户搭讪吧。",
-  "empty_column.public": "跨站公共时间轴暂时没有内容!快写一些公共的嘟文,或者关注另一些服务站的用户吧!你和本站、友站的交流,将决定这里出现的内容。",
-  "follow_request.authorize": "批准",
-  "follow_request.reject": "拒绝",
-  "getting_started.about_addressing": "只要你知道一位用户的用户名称和域名,你可以用“@用户名称@域名”的格式在搜索栏寻找该用户。",
-  "getting_started.about_shortcuts": "只要该用户是在你现在的服务站开立,你就可以直接输入用户名搜索。在嘟文中提及别的用户也是如此。",
-  "getting_started.apps": "手机或桌面应用程序",
-  "getting_started.heading": "开始使用",
-  "getting_started.open_source_notice": "Mastodon 是一个开放源码的软件。你可以在官方 GitHub ({github}) 贡献或者回报问题。你亦可通过{apps}阅读 Mastodon 上的消息。",
-  "home.column_settings.advanced": "高端",
-  "home.column_settings.basic": "基本",
-  "home.column_settings.filter_regex": "使用正则表达式 (regex) 过滤",
-  "home.column_settings.show_reblogs": "显示被转的嘟文",
-  "home.column_settings.show_replies": "显示回应嘟文",
-  "home.settings": "字段设置",
-  "lightbox.close": "关闭",
-  "loading_indicator.label": "加载中……",
-  "media_gallery.toggle_visible": "打开或关上",
-  "missing_indicator.label": "找不到内容",
-  "navigation_bar.blocks": "被屏蔽的用户",
-  "navigation_bar.community_timeline": "本站时间轴",
-  "navigation_bar.edit_profile": "修改个人资料",
-  "navigation_bar.favourites": "赞的内容",
-  "navigation_bar.follow_requests": "关注请求",
-  "navigation_bar.info": "关于本服务站",
-  "navigation_bar.logout": "注销",
-  // intentional departure from https://github.com/tootsuite/mastodon/blob/f864fee1/config/locales/zh-CN.yml#L126:
-  // clashes for settings/preferences
-  "navigation_bar.preferences": "首选项",
-  "navigation_bar.public_timeline": "跨站公共时间轴",
-  "notification.favourite": "{name} 赞你的嘟文",
-  "notification.follow": "{name} 开始关注你",
-  "notification.mention": "{name} 提及你",
-  "notification.reblog": "{name} 转嘟你的嘟文",
-  "notifications.clear_confirmation": "你确定要清空通知纪录吗?",
-  "notifications.clear": "清空通知纪录",
-  "notifications.column_settings.alert": "显示桌面通知",
-  "notifications.column_settings.favourite": "赞你的嘟文:",
-  "notifications.column_settings.follow": "关注你:",
-  "notifications.column_settings.mention": "提及你:",
-  "notifications.column_settings.reblog": "转你的嘟文:",
-  "notifications.column_settings.show": "在通知栏显示",
-  "notifications.column_settings.sound": "播放音效",
-  "notifications.settings": "字段设置",
-  "privacy.change": "调整隐私设置",
-  "privacy.direct.long": "只有提及的用户能看到",
-  "privacy.direct.short": "私人消息",
-  "privacy.private.long": "只有关注你用户能看到",
-  "privacy.private.short": "关注者",
-  "privacy.public.long": "在公共时间轴显示",
-  "privacy.public.short": "公共",
-  "privacy.unlisted.long": "公开,但不在公共时间轴显示",
-  "privacy.unlisted.short": "公开",
-  "reply_indicator.cancel": "取消",
-  "report.heading": "举报",
-  "report.placeholder": "额外消息",
-  "report.submit": "提交",
-  "report.target": "Reporting",
-  "search_results.total": "{count, number} 项结果",
-  "search.account": "用户",
-  "search.hashtag": "标签",
-  "search.placeholder": "搜索",
-  "search.status_by": "按{name}搜索嘟文",
-  "status.delete": "删除",
-  "status.favourite": "赞",
-  "status.load_more": "加载更多",
-  "status.media_hidden": "隐藏媒体内容",
-  "status.mention": "提及 @{name}",
-  "status.open": "展开嘟文",
-  "status.reblog": "转嘟",
-  "status.reblogged_by": "{name} 转嘟",
-  "status.reply": "回应",
-  "status.report": "举报 @{name}",
-  "status.sensitive_toggle": "点击显示",
-  "status.sensitive_warning": "敏感内容",
-  "status.show_less": "减少显示",
-  "status.show_more": "显示更多",
-  "tabs_bar.compose": "撰写",
-  "tabs_bar.federated_timeline": "跨站",
-  "tabs_bar.home": "主页",
-  "tabs_bar.local_timeline": "本站",
-  "tabs_bar.mentions": "提及",
-  "tabs_bar.notifications": "通知",
-  "tabs_bar.public": "跨站公共时间轴",
-  "upload_area.title": "将文件拖放至此上传",
-  "upload_button.label": "上传媒体文件",
-  "upload_form.undo": "还原",
-  "upload_progress.label": "上传中……",
-  "video_player.expand": "展开影片",
-  "video_player.toggle_sound": "开关音效",
-  "video_player.toggle_visible": "打开或关上",
-};
-
-export default zh_cn;
diff --git a/app/assets/javascripts/components/locales/zh-hk.jsx b/app/assets/javascripts/components/locales/zh-hk.jsx
deleted file mode 100644
index 3ecb4737b..000000000
--- a/app/assets/javascripts/components/locales/zh-hk.jsx
+++ /dev/null
@@ -1,150 +0,0 @@
-import zh from 'react-intl/locale-data/zh';
-
-const localeData = zh.reduce(function (acc, localeData) {
-  if (localeData.locale === "zh-Hant-HK") {
-    // rename the locale "zh-Hant-HK" as "zh-HK"
-    // (match the code usually used in Accepted-Language header)
-    acc.push(Object.assign({},
-      localeData,
-      {
-        "locale": "zh-HK",
-        "parentLocale": "zh-Hant-HK",
-      }
-    ));
-  }
-  return acc;
-}, []);
-
-export { localeData as localeData };
-
-const zh_hk = {
-  "account.block": "封鎖 @{name}",
-  "account.disclaimer": "由於這個用戶在另一個服務站,實際數字會比這個更多。",
-  "account.edit_profile": "修改個人資料",
-  "account.follow": "關注",
-  "account.followers": "關注的人",
-  "account.follows_you": "關注你",
-  "account.follows": "正在關注",
-  "account.mention": "提及 @{name}",
-  "account.mute": "將 @{name} 靜音",
-  "account.posts": "文章",
-  "account.report": "舉報 @{name}",
-  "account.requested": "等候審批",
-  "account.unblock": "解除對 @{name} 的封鎖",
-  "account.unfollow": "取消關注",
-  "account.unmute": "取消 @{name} 的靜音",
-  "boost_modal.combo": "如你想在下次路過這顯示,請按{combo},",
-  "column_back_button.label": "返回",
-  "column.blocks": "封鎖用戶",
-  "column.community": "本站時間軸",
-  "column.favourites": "喜歡的文章",
-  "column.follow_requests": "關注請求",
-  "column.home": "主頁",
-  "column.notifications": "通知",
-  "column.public": "跨站公共時間軸",
-  "compose_form.placeholder": "你在想甚麼?",
-  "compose_form.privacy_disclaimer": "你的私人文章,將被遞送至你所提及的 {domains} 用戶。你是否信任{domainsCount, plural, one {這個網站} other {這些網站}}?請留意,文章私隱設定只適用於各 Mastodon 服務站,如果 {domains} {domainsCount, plural, one {不是 Mastodon 服務站} other {之中有些不是 Mastodon 服務站}},對方將無法收到這篇文章的私隱設定,然後可能被轉推給不能預知的用戶閱讀。",
-  "compose_form.private": "標示為「只有關注你的人能看」",
-  "compose_form.publish": "發文",
-  "compose_form.sensitive": "將媒體檔案標示為「敏感內容」",
-  "compose_form.spoiler_placeholder": "敏感警告訊息",
-  "compose_form.spoiler": "將部份文字藏於警告訊息之後",
-  "compose_form.unlisted": "請勿在公共時間軸顯示",
-  "emoji_button.label": "加入表情符號",
-  "empty_column.community": "本站時間軸暫時未有內容,快貼文來搶頭香啊!",
-  "empty_column.hashtag": "這個標籤暫時未有內容。",
-  "empty_column.home": "你還沒有關注任何用戶。快看看{public},向其他用戶搭訕吧。",
-  "empty_column.home.public_timeline": "公共時間軸",
-  "empty_column.home": "你還沒有關注任何用戶。快看看{public},向其他用戶搭訕吧。",
-  "empty_column.notifications": "你沒有任何通知紀錄,快向其他用戶搭訕吧。",
-  "empty_column.public": "跨站公共時間軸暫時沒有內容!快寫一些公共的文章,或者關注另一些服務站的用戶吧!你和本站、友站的交流,將決定這裏出現的內容。",
-  "follow_request.authorize": "批准",
-  "follow_request.reject": "拒絕",
-  "getting_started.about_addressing": "只要你知道一位用戶的用戶名稱和域名,你可以用「@用戶名稱@域名」的格式在搜尋欄尋找該用戶。",
-  "getting_started.about_shortcuts": "只要該用戶是在你現在的服務站開立,你可以直接輸入用戶𠱷搜尋。同樣的規則適用於在文章提及別的用戶。",
-  "getting_started.apps": "手機或桌面應用程式",
-  "getting_started.heading": "開始使用",
-  "getting_started.open_source_notice": "Mastodon 是一個開放源碼的軟件。你可以在官方 GitHub ({github}) 貢獻或者回報問題。你亦可透過{apps}閱讀 Mastodon 上的消息。",
-  "home.column_settings.advanced": "進階",
-  "home.column_settings.basic": "基本",
-  "home.column_settings.filter_regex": "使用正規表達式 (regular expression) 過濾",
-  "home.column_settings.show_reblogs": "顯示被轉推的文章",
-  "home.column_settings.show_replies": "顯示回應文章",
-  "home.settings": "欄位設定",
-  "lightbox.close": "Close",
-  "loading_indicator.label": "載入中...",
-  "media_gallery.toggle_visible": "打開或關上",
-  "missing_indicator.label": "找不到內容",
-  "navigation_bar.blocks": "被封鎖的用戶",
-  "navigation_bar.community_timeline": "本站時間軸",
-  "navigation_bar.edit_profile": "修改個人資料",
-  "navigation_bar.favourites": "喜歡的內容",
-  "navigation_bar.follow_requests": "關注請求",
-  "navigation_bar.info": "關於本服務站",
-  "navigation_bar.logout": "登出",
-  "navigation_bar.preferences": "偏好設定",
-  "navigation_bar.public_timeline": "跨站公共時間軸",
-  "notification.favourite": "{name} 喜歡你的文章",
-  "notification.follow": "{name} 開始關注你",
-  "notification.mention": "{name} 提及你",
-  "notification.reblog": "{name} 轉推你的文章",
-  "notifications.clear_confirmation": "你確定要清空通知紀錄嗎?",
-  "notifications.clear": "清空通知紀錄",
-  "notifications.column_settings.alert": "顯示桌面通知",
-  "notifications.column_settings.favourite": "喜歡你的文章:",
-  "notifications.column_settings.follow": "關注你:",
-  "notifications.column_settings.mention": "提及你:",
-  "notifications.column_settings.reblog": "轉推你的文章:",
-  "notifications.column_settings.show": "在通知欄顯示",
-  "notifications.column_settings.sound": "播放音效",
-  "notifications.settings": "欄位設定",
-  "privacy.change": "調整私隱設定",
-  "privacy.direct.long": "只有提及的用戶能看到",
-  "privacy.direct.short": "私人訊息",
-  "privacy.private.long": "只有關注你用戶能看到",
-  "privacy.private.short": "關注者",
-  "privacy.public.long": "在公共時間軸顯示",
-  "privacy.public.short": "公共",
-  "privacy.unlisted.long": "公開,但不在公共時間軸顯示",
-  "privacy.unlisted.short": "公開",
-  "reply_indicator.cancel": "取消",
-  "report.heading": "舉報",
-  "report.placeholder": "額外訊息",
-  "report.submit": "提交",
-  "report.target": "Reporting",
-  "search_results.total": "{count, number} 項結果",
-  "search.account": "用戶",
-  "search.hashtag": "標籤",
-  "search.placeholder": "搜尋",
-  "search.status_by": "按{name}搜尋文章",
-  "status.delete": "刪除",
-  "status.favourite": "喜歡",
-  "status.load_more": "載入更多",
-  "status.media_hidden": "隱藏媒體內容",
-  "status.mention": "提及 @{name}",
-  "status.open": "展開文章",
-  "status.reblog": "轉推",
-  "status.reblogged_by": "{name} 轉推",
-  "status.reply": "回應",
-  "status.report": "舉報 @{name}",
-  "status.sensitive_toggle": "點擊顯示",
-  "status.sensitive_warning": "敏感內容",
-  "status.show_less": "減少顯示",
-  "status.show_more": "顯示更多",
-  "tabs_bar.compose": "撰寫",
-  "tabs_bar.federated_timeline": "跨站",
-  "tabs_bar.home": "主頁",
-  "tabs_bar.local_timeline": "本站",
-  "tabs_bar.mentions": "提及",
-  "tabs_bar.notifications": "通知",
-  "tabs_bar.public": "跨站公共時間軸",
-  "upload_area.title": "將檔案拖放至此上載",
-  "upload_button.label": "上載媒體檔案",
-  "upload_form.undo": "還原",
-  "upload_progress.label": "上載中……",
-  "video_player.expand": "展開影片",
-  "video_player.toggle_sound": "開關音效",
-  "video_player.toggle_visible": "打開或關上",
-};
-
-export default zh_hk;
diff --git a/app/assets/javascripts/components/middleware/errors.jsx b/app/assets/javascripts/components/middleware/errors.jsx
deleted file mode 100644
index 9a51257cb..000000000
--- a/app/assets/javascripts/components/middleware/errors.jsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import { showAlert } from '../actions/alerts';
-
-const defaultSuccessSuffix = 'SUCCESS';
-const defaultFailSuffix = 'FAIL';
-
-export default function errorsMiddleware() {
-  return ({ dispatch }) => next => action => {
-    if (action.type && !action.skipAlert) {
-      const isFail = new RegExp(`${defaultFailSuffix}$`, 'g');
-      const isSuccess = new RegExp(`${defaultSuccessSuffix}$`, 'g');
-
-      if (action.type.match(isFail)) {
-        if (action.error.response) {
-          const { data, status, statusText } = action.error.response;
-
-          let message = statusText;
-          let title   = `${status}`;
-
-          if (data.error) {
-            message = data.error;
-          }
-
-          dispatch(showAlert(title, message));
-        } else {
-          console.error(action.error); // eslint-disable-line no-console
-          dispatch(showAlert('Oops!', 'An unexpected error occurred.'));
-        }
-      }
-    }
-
-    return next(action);
-  };
-};
diff --git a/app/assets/javascripts/components/middleware/loading_bar.jsx b/app/assets/javascripts/components/middleware/loading_bar.jsx
deleted file mode 100644
index a98f1bb2b..000000000
--- a/app/assets/javascripts/components/middleware/loading_bar.jsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { showLoading, hideLoading } from 'react-redux-loading-bar';
-
-const defaultTypeSuffixes = ['PENDING', 'FULFILLED', 'REJECTED'];
-
-export default function loadingBarMiddleware(config = {}) {
-  const promiseTypeSuffixes = config.promiseTypeSuffixes || defaultTypeSuffixes;
-
-  return ({ dispatch }) => next => (action) => {
-    if (action.type && !action.skipLoading) {
-      const [PENDING, FULFILLED, REJECTED] = promiseTypeSuffixes;
-
-      const isPending = new RegExp(`${PENDING}$`, 'g');
-      const isFulfilled = new RegExp(`${FULFILLED}$`, 'g');
-      const isRejected = new RegExp(`${REJECTED}$`, 'g');
-
-      if (action.type.match(isPending)) {
-        dispatch(showLoading());
-      } else if (action.type.match(isFulfilled) || action.type.match(isRejected)) {
-        dispatch(hideLoading());
-      }
-    }
-
-    return next(action);
-  };
-};
diff --git a/app/assets/javascripts/components/middleware/sounds.jsx b/app/assets/javascripts/components/middleware/sounds.jsx
deleted file mode 100644
index 200efa3d7..000000000
--- a/app/assets/javascripts/components/middleware/sounds.jsx
+++ /dev/null
@@ -1,22 +0,0 @@
-const play = audio => {
-  if (!audio.paused) {
-    audio.pause();
-    audio.fastSeek(0);
-  }
-
-  audio.play();
-};
-
-export default function soundsMiddleware() {
-  const soundCache = {
-    boop: new Audio(['/sounds/boop.mp3'])
-  };
-
-  return ({ dispatch }) => next => (action) => {
-    if (action.meta && action.meta.sound && soundCache[action.meta.sound]) {
-      play(soundCache[action.meta.sound]);
-    }
-
-    return next(action);
-  };
-};
diff --git a/app/assets/javascripts/components/reducers/accounts.jsx b/app/assets/javascripts/components/reducers/accounts.jsx
deleted file mode 100644
index 60d283b0f..000000000
--- a/app/assets/javascripts/components/reducers/accounts.jsx
+++ /dev/null
@@ -1,131 +0,0 @@
-import {
-  ACCOUNT_FETCH_SUCCESS,
-  FOLLOWERS_FETCH_SUCCESS,
-  FOLLOWERS_EXPAND_SUCCESS,
-  FOLLOWING_FETCH_SUCCESS,
-  FOLLOWING_EXPAND_SUCCESS,
-  ACCOUNT_TIMELINE_FETCH_SUCCESS,
-  ACCOUNT_TIMELINE_EXPAND_SUCCESS,
-  FOLLOW_REQUESTS_FETCH_SUCCESS,
-  FOLLOW_REQUESTS_EXPAND_SUCCESS,
-  ACCOUNT_FOLLOW_SUCCESS,
-  ACCOUNT_UNFOLLOW_SUCCESS
-} from '../actions/accounts';
-import {
-  BLOCKS_FETCH_SUCCESS,
-  BLOCKS_EXPAND_SUCCESS
-} from '../actions/blocks';
-import {
-  MUTES_FETCH_SUCCESS,
-  MUTES_EXPAND_SUCCESS
-} from '../actions/mutes';
-import { COMPOSE_SUGGESTIONS_READY } from '../actions/compose';
-import {
-  REBLOG_SUCCESS,
-  UNREBLOG_SUCCESS,
-  FAVOURITE_SUCCESS,
-  UNFAVOURITE_SUCCESS,
-  REBLOGS_FETCH_SUCCESS,
-  FAVOURITES_FETCH_SUCCESS
-} from '../actions/interactions';
-import {
-  TIMELINE_REFRESH_SUCCESS,
-  TIMELINE_UPDATE,
-  TIMELINE_EXPAND_SUCCESS
-} from '../actions/timelines';
-import {
-  STATUS_FETCH_SUCCESS,
-  CONTEXT_FETCH_SUCCESS
-} from '../actions/statuses';
-import { SEARCH_FETCH_SUCCESS } from '../actions/search';
-import {
-  NOTIFICATIONS_UPDATE,
-  NOTIFICATIONS_REFRESH_SUCCESS,
-  NOTIFICATIONS_EXPAND_SUCCESS
-} from '../actions/notifications';
-import {
-  FAVOURITED_STATUSES_FETCH_SUCCESS,
-  FAVOURITED_STATUSES_EXPAND_SUCCESS
-} from '../actions/favourites';
-import { STORE_HYDRATE } from '../actions/store';
-import Immutable from 'immutable';
-
-const normalizeAccount = (state, account) => state.set(account.id, Immutable.fromJS(account));
-
-const normalizeAccounts = (state, accounts) => {
-  accounts.forEach(account => {
-    state = normalizeAccount(state, account);
-  });
-
-  return state;
-};
-
-const normalizeAccountFromStatus = (state, status) => {
-  state = normalizeAccount(state, status.account);
-
-  if (status.reblog && status.reblog.account) {
-    state = normalizeAccount(state, status.reblog.account);
-  }
-
-  return state;
-};
-
-const normalizeAccountsFromStatuses = (state, statuses) => {
-  statuses.forEach(status => {
-    state = normalizeAccountFromStatus(state, status);
-  });
-
-  return state;
-};
-
-const initialState = Immutable.Map();
-
-export default function accounts(state = initialState, action) {
-  switch(action.type) {
-  case STORE_HYDRATE:
-    return state.merge(action.state.get('accounts'));
-  case ACCOUNT_FETCH_SUCCESS:
-  case NOTIFICATIONS_UPDATE:
-    return normalizeAccount(state, action.account);
-  case FOLLOWERS_FETCH_SUCCESS:
-  case FOLLOWERS_EXPAND_SUCCESS:
-  case FOLLOWING_FETCH_SUCCESS:
-  case FOLLOWING_EXPAND_SUCCESS:
-  case REBLOGS_FETCH_SUCCESS:
-  case FAVOURITES_FETCH_SUCCESS:
-  case COMPOSE_SUGGESTIONS_READY:
-  case FOLLOW_REQUESTS_FETCH_SUCCESS:
-  case FOLLOW_REQUESTS_EXPAND_SUCCESS:
-  case BLOCKS_FETCH_SUCCESS:
-  case BLOCKS_EXPAND_SUCCESS:
-  case MUTES_FETCH_SUCCESS:
-  case MUTES_EXPAND_SUCCESS:
-    return normalizeAccounts(state, action.accounts);
-  case NOTIFICATIONS_REFRESH_SUCCESS:
-  case NOTIFICATIONS_EXPAND_SUCCESS:
-  case SEARCH_FETCH_SUCCESS:
-    return normalizeAccountsFromStatuses(normalizeAccounts(state, action.accounts), action.statuses);
-  case TIMELINE_REFRESH_SUCCESS:
-  case TIMELINE_EXPAND_SUCCESS:
-  case ACCOUNT_TIMELINE_FETCH_SUCCESS:
-  case ACCOUNT_TIMELINE_EXPAND_SUCCESS:
-  case CONTEXT_FETCH_SUCCESS:
-  case FAVOURITED_STATUSES_FETCH_SUCCESS:
-  case FAVOURITED_STATUSES_EXPAND_SUCCESS:
-    return normalizeAccountsFromStatuses(state, action.statuses);
-  case REBLOG_SUCCESS:
-  case FAVOURITE_SUCCESS:
-  case UNREBLOG_SUCCESS:
-  case UNFAVOURITE_SUCCESS:
-    return normalizeAccountFromStatus(state, action.response);
-  case TIMELINE_UPDATE:
-  case STATUS_FETCH_SUCCESS:
-    return normalizeAccountFromStatus(state, action.status);
-  case ACCOUNT_FOLLOW_SUCCESS:
-    return state.updateIn([action.relationship.id, 'followers_count'], num => num + 1);
-  case ACCOUNT_UNFOLLOW_SUCCESS:
-    return state.updateIn([action.relationship.id, 'followers_count'], num => Math.max(0, num - 1));
-  default:
-    return state;
-  }
-};
diff --git a/app/assets/javascripts/components/reducers/alerts.jsx b/app/assets/javascripts/components/reducers/alerts.jsx
deleted file mode 100644
index dc0145824..000000000
--- a/app/assets/javascripts/components/reducers/alerts.jsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import {
-  ALERT_SHOW,
-  ALERT_DISMISS,
-  ALERT_CLEAR
-} from '../actions/alerts';
-import Immutable from 'immutable';
-
-const initialState = Immutable.List([]);
-
-export default function alerts(state = initialState, action) {
-  switch(action.type) {
-  case ALERT_SHOW:
-    return state.push(Immutable.Map({
-      key: state.size > 0 ? state.last().get('key') + 1 : 0,
-      title: action.title,
-      message: action.message
-    }));
-  case ALERT_DISMISS:
-    return state.filterNot(item => item.get('key') === action.alert.key);
-  case ALERT_CLEAR:
-    return state.clear();
-  default:
-    return state;
-  }
-};
diff --git a/app/assets/javascripts/components/reducers/cards.jsx b/app/assets/javascripts/components/reducers/cards.jsx
deleted file mode 100644
index 3c9395011..000000000
--- a/app/assets/javascripts/components/reducers/cards.jsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import { STATUS_CARD_FETCH_SUCCESS } from '../actions/cards';
-
-import Immutable from 'immutable';
-
-const initialState = Immutable.Map();
-
-export default function cards(state = initialState, action) {
-  switch(action.type) {
-  case STATUS_CARD_FETCH_SUCCESS:
-    return state.set(action.id, Immutable.fromJS(action.card));
-  default:
-    return state;
-  }
-};
diff --git a/app/assets/javascripts/components/reducers/compose.jsx b/app/assets/javascripts/components/reducers/compose.jsx
deleted file mode 100644
index c87384780..000000000
--- a/app/assets/javascripts/components/reducers/compose.jsx
+++ /dev/null
@@ -1,232 +0,0 @@
-import {
-  COMPOSE_MOUNT,
-  COMPOSE_UNMOUNT,
-  COMPOSE_CHANGE,
-  COMPOSE_REPLY,
-  COMPOSE_REPLY_CANCEL,
-  COMPOSE_MENTION,
-  COMPOSE_SUBMIT_REQUEST,
-  COMPOSE_SUBMIT_SUCCESS,
-  COMPOSE_SUBMIT_FAIL,
-  COMPOSE_UPLOAD_REQUEST,
-  COMPOSE_UPLOAD_SUCCESS,
-  COMPOSE_UPLOAD_FAIL,
-  COMPOSE_UPLOAD_UNDO,
-  COMPOSE_UPLOAD_PROGRESS,
-  COMPOSE_SUGGESTIONS_CLEAR,
-  COMPOSE_SUGGESTIONS_READY,
-  COMPOSE_SUGGESTION_SELECT,
-  COMPOSE_SENSITIVITY_CHANGE,
-  COMPOSE_SPOILERNESS_CHANGE,
-  COMPOSE_SPOILER_TEXT_CHANGE,
-  COMPOSE_VISIBILITY_CHANGE,
-  COMPOSE_LISTABILITY_CHANGE,
-  COMPOSE_EMOJI_INSERT
-} from '../actions/compose';
-import { TIMELINE_DELETE } from '../actions/timelines';
-import { STORE_HYDRATE } from '../actions/store';
-import Immutable from 'immutable';
-import uuid from '../uuid';
-
-const initialState = Immutable.Map({
-  mounted: false,
-  sensitive: false,
-  spoiler: false,
-  spoiler_text: '',
-  privacy: null,
-  text: '',
-  focusDate: null,
-  preselectDate: null,
-  in_reply_to: null,
-  is_submitting: false,
-  is_uploading: false,
-  progress: 0,
-  media_attachments: Immutable.List(),
-  suggestion_token: null,
-  suggestions: Immutable.List(),
-  me: null,
-  default_privacy: 'public',
-  resetFileKey: Math.floor((Math.random() * 0x10000)),
-  idempotencyKey: null
-});
-
-function statusToTextMentions(state, status) {
-  let set = Immutable.OrderedSet([]);
-  let me  = state.get('me');
-
-  if (status.getIn(['account', 'id']) !== me) {
-    set = set.add(`@${status.getIn(['account', 'acct'])} `);
-  }
-
-  return set.union(status.get('mentions').filterNot(mention => mention.get('id') === me).map(mention => `@${mention.get('acct')} `)).join('');
-};
-
-function clearAll(state) {
-  return state.withMutations(map => {
-    map.set('text', '');
-    map.set('spoiler', false);
-    map.set('spoiler_text', '');
-    map.set('is_submitting', false);
-    map.set('in_reply_to', null);
-    map.set('privacy', state.get('default_privacy'));
-    map.set('sensitive', false);
-    map.update('media_attachments', list => list.clear());
-    map.set('idempotencyKey', uuid());
-  });
-};
-
-function appendMedia(state, media) {
-  return state.withMutations(map => {
-    map.update('media_attachments', list => list.push(media));
-    map.set('is_uploading', false);
-    map.set('resetFileKey', Math.floor((Math.random() * 0x10000)));
-    map.update('text', oldText => `${oldText.trim()} ${media.get('text_url')}`);
-    map.set('focusDate', new Date());
-    map.set('idempotencyKey', uuid());
-  });
-};
-
-function removeMedia(state, mediaId) {
-  const media    = state.get('media_attachments').find(item => item.get('id') === mediaId);
-  const prevSize = state.get('media_attachments').size;
-
-  return state.withMutations(map => {
-    map.update('media_attachments', list => list.filterNot(item => item.get('id') === mediaId));
-    map.update('text', text => text.replace(media.get('text_url'), '').trim());
-    map.set('idempotencyKey', uuid());
-
-    if (prevSize === 1) {
-      map.set('sensitive', false);
-    }
-  });
-};
-
-const insertSuggestion = (state, position, token, completion) => {
-  return state.withMutations(map => {
-    map.update('text', oldText => `${oldText.slice(0, position)}${completion} ${oldText.slice(position + token.length)}`);
-    map.set('suggestion_token', null);
-    map.update('suggestions', Immutable.List(), list => list.clear());
-    map.set('focusDate', new Date());
-    map.set('idempotencyKey', uuid());
-  });
-};
-
-const insertEmoji = (state, position, emojiData) => {
-  const emoji = emojiData.shortname;
-
-  return state.withMutations(map => {
-    map.update('text', oldText => `${oldText.slice(0, position)}${emoji} ${oldText.slice(position)}`);
-    map.set('focusDate', new Date());
-    map.set('idempotencyKey', uuid());
-  });
-};
-
-const privacyPreference = (a, b) => {
-  if (a === 'direct' || b === 'direct') {
-    return 'direct';
-  } else if (a === 'private' || b === 'private') {
-    return 'private';
-  } else if (a === 'unlisted' || b === 'unlisted') {
-    return 'unlisted';
-  } else {
-    return 'public';
-  }
-};
-
-export default function compose(state = initialState, action) {
-  switch(action.type) {
-  case STORE_HYDRATE:
-    return clearAll(state.merge(action.state.get('compose')));
-  case COMPOSE_MOUNT:
-    return state.set('mounted', true);
-  case COMPOSE_UNMOUNT:
-    return state.set('mounted', false);
-  case COMPOSE_SENSITIVITY_CHANGE:
-    return state
-      .set('sensitive', !state.get('sensitive'))
-      .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());
-    });
-  case COMPOSE_SPOILER_TEXT_CHANGE:
-    return state
-      .set('spoiler_text', action.text)
-      .set('idempotencyKey', uuid());
-  case COMPOSE_VISIBILITY_CHANGE:
-    return state
-      .set('privacy', action.value)
-      .set('idempotencyKey', uuid());
-  case COMPOSE_CHANGE:
-    return state
-      .set('text', action.text)
-      .set('idempotencyKey', uuid());
-  case COMPOSE_REPLY:
-    return state.withMutations(map => {
-      map.set('in_reply_to', action.status.get('id'));
-      map.set('text', statusToTextMentions(state, action.status));
-      map.set('privacy', privacyPreference(action.status.get('visibility'), state.get('default_privacy')));
-      map.set('focusDate', new Date());
-      map.set('preselectDate', new Date());
-      map.set('idempotencyKey', uuid());
-
-      if (action.status.get('spoiler_text').length > 0) {
-        map.set('spoiler', true);
-        map.set('spoiler_text', action.status.get('spoiler_text'));
-      } else {
-        map.set('spoiler', false);
-        map.set('spoiler_text', '');
-      }
-    });
-  case COMPOSE_REPLY_CANCEL:
-    return state.withMutations(map => {
-      map.set('in_reply_to', null);
-      map.set('text', '');
-      map.set('spoiler', false);
-      map.set('spoiler_text', '');
-      map.set('privacy', state.get('default_privacy'));
-      map.set('idempotencyKey', uuid());
-    });
-  case COMPOSE_SUBMIT_REQUEST:
-    return state.set('is_submitting', true);
-  case COMPOSE_SUBMIT_SUCCESS:
-    return clearAll(state);
-  case COMPOSE_SUBMIT_FAIL:
-    return state.set('is_submitting', false);
-  case COMPOSE_UPLOAD_REQUEST:
-    return state.withMutations(map => {
-      map.set('is_uploading', true);
-    });
-  case COMPOSE_UPLOAD_SUCCESS:
-    return appendMedia(state, Immutable.fromJS(action.media));
-  case COMPOSE_UPLOAD_FAIL:
-    return state.set('is_uploading', false);
-  case COMPOSE_UPLOAD_UNDO:
-    return removeMedia(state, action.media_id);
-  case COMPOSE_UPLOAD_PROGRESS:
-    return state.set('progress', Math.round((action.loaded / action.total) * 100));
-  case COMPOSE_MENTION:
-    return state
-      .update('text', text => `${text}@${action.account.get('acct')} `)
-      .set('focusDate', new Date())
-      .set('idempotencyKey', uuid());
-  case COMPOSE_SUGGESTIONS_CLEAR:
-    return state.update('suggestions', Immutable.List(), list => list.clear()).set('suggestion_token', null);
-  case COMPOSE_SUGGESTIONS_READY:
-    return state.set('suggestions', Immutable.List(action.accounts.map(item => item.id))).set('suggestion_token', action.token);
-  case COMPOSE_SUGGESTION_SELECT:
-    return insertSuggestion(state, action.position, action.token, action.completion);
-  case TIMELINE_DELETE:
-    if (action.id === state.get('in_reply_to')) {
-      return state.set('in_reply_to', null);
-    } else {
-      return state;
-    }
-  case COMPOSE_EMOJI_INSERT:
-    return insertEmoji(state, action.position, action.emoji);
-  default:
-    return state;
-  }
-};
diff --git a/app/assets/javascripts/components/reducers/index.jsx b/app/assets/javascripts/components/reducers/index.jsx
deleted file mode 100644
index 147030cca..000000000
--- a/app/assets/javascripts/components/reducers/index.jsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import { combineReducers } from 'redux-immutable';
-import timelines from './timelines';
-import meta from './meta';
-import compose from './compose';
-import alerts from './alerts';
-import { loadingBarReducer } from 'react-redux-loading-bar';
-import modal from './modal';
-import user_lists from './user_lists';
-import accounts from './accounts';
-import statuses from './statuses';
-import relationships from './relationships';
-import search from './search';
-import notifications from './notifications';
-import settings from './settings';
-import status_lists from './status_lists';
-import cards from './cards';
-import reports from './reports';
-
-export default combineReducers({
-  timelines,
-  meta,
-  compose,
-  alerts,
-  loadingBar: loadingBarReducer,
-  modal,
-  user_lists,
-  status_lists,
-  accounts,
-  statuses,
-  relationships,
-  search,
-  notifications,
-  settings,
-  cards,
-  reports
-});
diff --git a/app/assets/javascripts/components/reducers/meta.jsx b/app/assets/javascripts/components/reducers/meta.jsx
deleted file mode 100644
index acf6d4be1..000000000
--- a/app/assets/javascripts/components/reducers/meta.jsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import { STORE_HYDRATE } from '../actions/store';
-import Immutable from 'immutable';
-
-const initialState = Immutable.Map({
-  streaming_api_base_url: null,
-  access_token: null,
-  me: null
-});
-
-export default function meta(state = initialState, action) {
-  switch(action.type) {
-  case STORE_HYDRATE:
-    return state.merge(action.state.get('meta'));
-  default:
-    return state;
-  }
-};
diff --git a/app/assets/javascripts/components/reducers/modal.jsx b/app/assets/javascripts/components/reducers/modal.jsx
deleted file mode 100644
index 3566820ef..000000000
--- a/app/assets/javascripts/components/reducers/modal.jsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { MODAL_OPEN, MODAL_CLOSE } from '../actions/modal';
-import Immutable from 'immutable';
-
-const initialState = {
-  modalType: null,
-  modalProps: {}
-};
-
-export default function modal(state = initialState, action) {
-  switch(action.type) {
-  case MODAL_OPEN:
-    return { modalType: action.modalType, modalProps: action.modalProps };
-  case MODAL_CLOSE:
-    return initialState;
-  default:
-    return state;
-  }
-};
diff --git a/app/assets/javascripts/components/reducers/notifications.jsx b/app/assets/javascripts/components/reducers/notifications.jsx
deleted file mode 100644
index 1406a388a..000000000
--- a/app/assets/javascripts/components/reducers/notifications.jsx
+++ /dev/null
@@ -1,104 +0,0 @@
-import {
-  NOTIFICATIONS_UPDATE,
-  NOTIFICATIONS_REFRESH_SUCCESS,
-  NOTIFICATIONS_EXPAND_SUCCESS,
-  NOTIFICATIONS_REFRESH_REQUEST,
-  NOTIFICATIONS_EXPAND_REQUEST,
-  NOTIFICATIONS_REFRESH_FAIL,
-  NOTIFICATIONS_EXPAND_FAIL,
-  NOTIFICATIONS_CLEAR,
-  NOTIFICATIONS_SCROLL_TOP
-} from '../actions/notifications';
-import { ACCOUNT_BLOCK_SUCCESS } from '../actions/accounts';
-import Immutable from 'immutable';
-
-const initialState = Immutable.Map({
-  items: Immutable.List(),
-  next: null,
-  top: true,
-  unread: 0,
-  loaded: false,
-  isLoading: true
-});
-
-const notificationToMap = notification => Immutable.Map({
-  id: notification.id,
-  type: notification.type,
-  account: notification.account.id,
-  status: notification.status ? notification.status.id : null
-});
-
-const normalizeNotification = (state, notification) => {
-  if (!state.get('top')) {
-    state = state.update('unread', unread => unread + 1);
-  }
-
-  return state.update('items', list => list.unshift(notificationToMap(notification)));
-};
-
-const normalizeNotifications = (state, notifications, next) => {
-  let items    = Immutable.List();
-  const loaded = state.get('loaded');
-
-  notifications.forEach((n, i) => {
-    items = items.set(i, notificationToMap(n));
-  });
-
-  if (state.get('next') === null) {
-    state = state.set('next', next);
-  }
-
-  return state
-    .update('items', list => loaded ? list.unshift(...items) : list.push(...items))
-    .set('loaded', true)
-    .set('isLoading', false);
-};
-
-const appendNormalizedNotifications = (state, notifications, next) => {
-  let items = Immutable.List();
-
-  notifications.forEach((n, i) => {
-    items = items.set(i, notificationToMap(n));
-  });
-
-  return state
-    .update('items', list => list.push(...items))
-    .set('next', next)
-    .set('isLoading', false);
-};
-
-const filterNotifications = (state, relationship) => {
-  return state.update('items', list => list.filterNot(item => item.get('account') === relationship.id));
-};
-
-const updateTop = (state, top) => {
-  if (top) {
-    state = state.set('unread', 0);
-  }
-
-  return state.set('top', top);
-};
-
-export default function notifications(state = initialState, action) {
-  switch(action.type) {
-  case NOTIFICATIONS_REFRESH_REQUEST:
-  case NOTIFICATIONS_EXPAND_REQUEST:
-  case NOTIFICATIONS_REFRESH_FAIL:
-  case NOTIFICATIONS_EXPAND_FAIL:
-    return state.set('isLoading', true);
-  case NOTIFICATIONS_SCROLL_TOP:
-    return updateTop(state, action.top);
-  case NOTIFICATIONS_UPDATE:
-    return normalizeNotification(state, action.notification);
-  case NOTIFICATIONS_REFRESH_SUCCESS:
-    return normalizeNotifications(state, action.notifications, action.next);
-  case NOTIFICATIONS_EXPAND_SUCCESS:
-    return appendNormalizedNotifications(state, action.notifications, action.next);
-  case ACCOUNT_BLOCK_SUCCESS:
-    return filterNotifications(state, action.relationship);
-  case NOTIFICATIONS_CLEAR:
-    return state.set('items', Immutable.List()).set('next', null);
-  default:
-    return state;
-  }
-};
diff --git a/app/assets/javascripts/components/reducers/relationships.jsx b/app/assets/javascripts/components/reducers/relationships.jsx
deleted file mode 100644
index c65c48b43..000000000
--- a/app/assets/javascripts/components/reducers/relationships.jsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import {
-  ACCOUNT_FOLLOW_SUCCESS,
-  ACCOUNT_UNFOLLOW_SUCCESS,
-  ACCOUNT_BLOCK_SUCCESS,
-  ACCOUNT_UNBLOCK_SUCCESS,
-  ACCOUNT_MUTE_SUCCESS,
-  ACCOUNT_UNMUTE_SUCCESS,
-  RELATIONSHIPS_FETCH_SUCCESS
-} from '../actions/accounts';
-import Immutable from 'immutable';
-
-const normalizeRelationship = (state, relationship) => state.set(relationship.id, Immutable.fromJS(relationship));
-
-const normalizeRelationships = (state, relationships) => {
-  relationships.forEach(relationship => {
-    state = normalizeRelationship(state, relationship);
-  });
-
-  return state;
-};
-
-const initialState = Immutable.Map();
-
-export default function relationships(state = initialState, action) {
-  switch(action.type) {
-  case ACCOUNT_FOLLOW_SUCCESS:
-  case ACCOUNT_UNFOLLOW_SUCCESS:
-  case ACCOUNT_BLOCK_SUCCESS:
-  case ACCOUNT_UNBLOCK_SUCCESS:
-  case ACCOUNT_MUTE_SUCCESS:
-  case ACCOUNT_UNMUTE_SUCCESS:
-    return normalizeRelationship(state, action.relationship);
-  case RELATIONSHIPS_FETCH_SUCCESS:
-    return normalizeRelationships(state, action.relationships);
-  default:
-    return state;
-  }
-};
diff --git a/app/assets/javascripts/components/reducers/reports.jsx b/app/assets/javascripts/components/reducers/reports.jsx
deleted file mode 100644
index eab004377..000000000
--- a/app/assets/javascripts/components/reducers/reports.jsx
+++ /dev/null
@@ -1,60 +0,0 @@
-import {
-  REPORT_INIT,
-  REPORT_SUBMIT_REQUEST,
-  REPORT_SUBMIT_SUCCESS,
-  REPORT_SUBMIT_FAIL,
-  REPORT_CANCEL,
-  REPORT_STATUS_TOGGLE,
-  REPORT_COMMENT_CHANGE
-} from '../actions/reports';
-import Immutable from 'immutable';
-
-const initialState = Immutable.Map({
-  new: Immutable.Map({
-    isSubmitting: false,
-    account_id: null,
-    status_ids: Immutable.Set(),
-    comment: ''
-  })
-});
-
-export default function reports(state = initialState, action) {
-  switch(action.type) {
-  case REPORT_INIT:
-    return state.withMutations(map => {
-      map.setIn(['new', 'isSubmitting'], false);
-      map.setIn(['new', 'account_id'], action.account.get('id'));
-
-      if (state.getIn(['new', 'account_id']) !== action.account.get('id')) {
-        map.setIn(['new', 'status_ids'], action.status ? Immutable.Set([action.status.getIn(['reblog', 'id'], action.status.get('id'))]) : Immutable.Set());
-        map.setIn(['new', 'comment'], '');
-      } else {
-        map.updateIn(['new', 'status_ids'], Immutable.Set(), set => set.add(action.status.getIn(['reblog', 'id'], action.status.get('id'))));
-      }
-    });
-  case REPORT_STATUS_TOGGLE:
-    return state.updateIn(['new', 'status_ids'], Immutable.Set(), set => {
-      if (action.checked) {
-        return set.add(action.statusId);
-      }
-
-      return set.remove(action.statusId);
-    });
-  case REPORT_COMMENT_CHANGE:
-    return state.setIn(['new', 'comment'], action.comment);
-  case REPORT_SUBMIT_REQUEST:
-    return state.setIn(['new', 'isSubmitting'], true);
-  case REPORT_SUBMIT_FAIL:
-    return state.setIn(['new', 'isSubmitting'], false);
-  case REPORT_CANCEL:
-  case REPORT_SUBMIT_SUCCESS:
-    return state.withMutations(map => {
-      map.setIn(['new', 'account_id'], null);
-      map.setIn(['new', 'status_ids'], Immutable.Set());
-      map.setIn(['new', 'comment'], '');
-      map.setIn(['new', 'isSubmitting'], false);
-    });
-  default:
-    return state;
-  }
-};
diff --git a/app/assets/javascripts/components/reducers/search.jsx b/app/assets/javascripts/components/reducers/search.jsx
deleted file mode 100644
index b3fe6c7be..000000000
--- a/app/assets/javascripts/components/reducers/search.jsx
+++ /dev/null
@@ -1,96 +0,0 @@
-import {
-  SEARCH_CHANGE,
-  SEARCH_CLEAR,
-  SEARCH_FETCH_SUCCESS,
-  SEARCH_SHOW
-} from '../actions/search';
-import { COMPOSE_MENTION, COMPOSE_REPLY } from '../actions/compose';
-import Immutable from 'immutable';
-
-const initialState = Immutable.Map({
-  value: '',
-  submitted: false,
-  hidden: false,
-  results: Immutable.Map()
-});
-
-const normalizeSuggestions = (state, value, accounts, hashtags, statuses) => {
-  let newSuggestions = [];
-
-  if (accounts.length > 0) {
-    newSuggestions.push({
-      title: 'account',
-      items: accounts.map(item => ({
-        type: 'account',
-        id: item.id,
-        value: item.acct
-      }))
-    });
-  }
-
-  if (value.indexOf('@') === -1 && value.indexOf(' ') === -1 || hashtags.length > 0) {
-    let hashtagItems = hashtags.map(item => ({
-      type: 'hashtag',
-      id: item,
-      value: `#${item}`
-    }));
-
-    if (value.indexOf('@') === -1 && value.indexOf(' ') === -1 && !value.startsWith('http://') && !value.startsWith('https://') && hashtags.indexOf(value) === -1) {
-      hashtagItems.unshift({
-        type: 'hashtag',
-        id: value,
-        value: `#${value}`
-      });
-    }
-
-    if (hashtagItems.length > 0) {
-      newSuggestions.push({
-        title: 'hashtag',
-        items: hashtagItems
-      });
-    }
-  }
-
-  if (statuses.length > 0) {
-    newSuggestions.push({
-      title: 'status',
-      items: statuses.map(item => ({
-        type: 'status',
-        id: item.id,
-        value: item.id
-      }))
-    });
-  }
-
-  return state.withMutations(map => {
-    map.set('suggestions', newSuggestions);
-    map.set('loaded_value', value);
-  });
-};
-
-export default function search(state = initialState, action) {
-  switch(action.type) {
-  case SEARCH_CHANGE:
-    return state.set('value', action.value);
-  case SEARCH_CLEAR:
-    return state.withMutations(map => {
-      map.set('value', '');
-      map.set('results', Immutable.Map());
-      map.set('submitted', false);
-      map.set('hidden', false);
-    });
-  case SEARCH_SHOW:
-    return state.set('hidden', false);
-  case COMPOSE_REPLY:
-  case COMPOSE_MENTION:
-    return state.set('hidden', true);
-  case SEARCH_FETCH_SUCCESS:
-    return state.set('results', Immutable.Map({
-      accounts: Immutable.List(action.results.accounts.map(item => item.id)),
-      statuses: Immutable.List(action.results.statuses.map(item => item.id)),
-      hashtags: Immutable.List(action.results.hashtags)
-    })).set('submitted', true);
-  default:
-    return state;
-  }
-};
diff --git a/app/assets/javascripts/components/reducers/settings.jsx b/app/assets/javascripts/components/reducers/settings.jsx
deleted file mode 100644
index 820af99ed..000000000
--- a/app/assets/javascripts/components/reducers/settings.jsx
+++ /dev/null
@@ -1,48 +0,0 @@
-import { SETTING_CHANGE } from '../actions/settings';
-import { STORE_HYDRATE } from '../actions/store';
-import Immutable from 'immutable';
-
-const initialState = Immutable.Map({
-  onboarded: false,
-
-  home: Immutable.Map({
-    shows: Immutable.Map({
-      reblog: true,
-      reply: true
-    })
-  }),
-
-  notifications: Immutable.Map({
-    alerts: Immutable.Map({
-      follow: true,
-      favourite: true,
-      reblog: true,
-      mention: true
-    }),
-
-    shows: Immutable.Map({
-      follow: true,
-      favourite: true,
-      reblog: true,
-      mention: true
-    }),
-
-    sounds: Immutable.Map({
-      follow: true,
-      favourite: true,
-      reblog: true,
-      mention: true
-    })
-  })
-});
-
-export default function settings(state = initialState, action) {
-  switch(action.type) {
-  case STORE_HYDRATE:
-    return state.mergeDeep(action.state.get('settings'));
-  case SETTING_CHANGE:
-    return state.setIn(action.key, action.value);
-  default:
-    return state;
-  }
-};
diff --git a/app/assets/javascripts/components/reducers/status_lists.jsx b/app/assets/javascripts/components/reducers/status_lists.jsx
deleted file mode 100644
index b883b1c58..000000000
--- a/app/assets/javascripts/components/reducers/status_lists.jsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import {
-  FAVOURITED_STATUSES_FETCH_SUCCESS,
-  FAVOURITED_STATUSES_EXPAND_SUCCESS
-} from '../actions/favourites';
-import Immutable from 'immutable';
-
-const initialState = Immutable.Map({
-  favourites: Immutable.Map({
-    next: null,
-    loaded: false,
-    items: Immutable.List()
-  })
-});
-
-const normalizeList = (state, listType, statuses, next) => {
-  return state.update(listType, listMap => listMap.withMutations(map => {
-    map.set('next', next);
-    map.set('loaded', true);
-    map.set('items', Immutable.List(statuses.map(item => item.id)));
-  }));
-};
-
-const appendToList = (state, listType, statuses, next) => {
-  return state.update(listType, listMap => listMap.withMutations(map => {
-    map.set('next', next);
-    map.set('items', map.get('items').push(...statuses.map(item => item.id)));
-  }));
-};
-
-export default function statusLists(state = initialState, action) {
-  switch(action.type) {
-  case FAVOURITED_STATUSES_FETCH_SUCCESS:
-    return normalizeList(state, 'favourites', action.statuses, action.next);
-  case FAVOURITED_STATUSES_EXPAND_SUCCESS:
-    return appendToList(state, 'favourites', action.statuses, action.next);
-  default:
-    return state;
-  }
-};
diff --git a/app/assets/javascripts/components/reducers/statuses.jsx b/app/assets/javascripts/components/reducers/statuses.jsx
deleted file mode 100644
index 2002d2223..000000000
--- a/app/assets/javascripts/components/reducers/statuses.jsx
+++ /dev/null
@@ -1,124 +0,0 @@
-import {
-  REBLOG_REQUEST,
-  REBLOG_SUCCESS,
-  REBLOG_FAIL,
-  UNREBLOG_SUCCESS,
-  FAVOURITE_REQUEST,
-  FAVOURITE_SUCCESS,
-  FAVOURITE_FAIL,
-  UNFAVOURITE_SUCCESS
-} from '../actions/interactions';
-import {
-  STATUS_FETCH_SUCCESS,
-  CONTEXT_FETCH_SUCCESS
-} from '../actions/statuses';
-import {
-  TIMELINE_REFRESH_SUCCESS,
-  TIMELINE_UPDATE,
-  TIMELINE_DELETE,
-  TIMELINE_EXPAND_SUCCESS
-} from '../actions/timelines';
-import {
-  ACCOUNT_TIMELINE_FETCH_SUCCESS,
-  ACCOUNT_TIMELINE_EXPAND_SUCCESS,
-  ACCOUNT_BLOCK_SUCCESS
-} from '../actions/accounts';
-import {
-  NOTIFICATIONS_UPDATE,
-  NOTIFICATIONS_REFRESH_SUCCESS,
-  NOTIFICATIONS_EXPAND_SUCCESS
-} from '../actions/notifications';
-import {
-  FAVOURITED_STATUSES_FETCH_SUCCESS,
-  FAVOURITED_STATUSES_EXPAND_SUCCESS
-} from '../actions/favourites';
-import { SEARCH_FETCH_SUCCESS } from '../actions/search';
-import Immutable from 'immutable';
-
-const normalizeStatus = (state, status) => {
-  if (!status) {
-    return state;
-  }
-
-  const normalStatus   = { ...status };
-  normalStatus.account = status.account.id;
-
-  if (status.reblog && status.reblog.id) {
-    state               = normalizeStatus(state, status.reblog);
-    normalStatus.reblog = status.reblog.id;
-  }
-
-  const linebreakComplemented = status.content.replace(/<br \/>/g, '\n').replace(/<\/p><p>/g, '\n\n');
-  normalStatus.unescaped_content = new DOMParser().parseFromString(linebreakComplemented, 'text/html').documentElement.textContent;
-
-  return state.update(status.id, Immutable.Map(), map => map.mergeDeep(Immutable.fromJS(normalStatus)));
-};
-
-const normalizeStatuses = (state, statuses) => {
-  statuses.forEach(status => {
-    state = normalizeStatus(state, status);
-  });
-
-  return state;
-};
-
-const deleteStatus = (state, id, references) => {
-  references.forEach(ref => {
-    state = deleteStatus(state, ref[0], []);
-  });
-
-  return state.delete(id);
-};
-
-const filterStatuses = (state, relationship) => {
-  state.forEach(status => {
-    if (status.get('account') !== relationship.id) {
-      return;
-    }
-
-    state = deleteStatus(state, status.get('id'), state.filter(item => item.get('reblog') === status.get('id')));
-  });
-
-  return state;
-};
-
-const initialState = Immutable.Map();
-
-export default function statuses(state = initialState, action) {
-  switch(action.type) {
-  case TIMELINE_UPDATE:
-  case STATUS_FETCH_SUCCESS:
-  case NOTIFICATIONS_UPDATE:
-    return normalizeStatus(state, action.status);
-  case REBLOG_SUCCESS:
-  case UNREBLOG_SUCCESS:
-  case FAVOURITE_SUCCESS:
-  case UNFAVOURITE_SUCCESS:
-    return normalizeStatus(state, action.response);
-  case FAVOURITE_REQUEST:
-    return state.setIn([action.status.get('id'), 'favourited'], true);
-  case FAVOURITE_FAIL:
-    return state.setIn([action.status.get('id'), 'favourited'], false);
-  case REBLOG_REQUEST:
-    return state.setIn([action.status.get('id'), 'reblogged'], true);
-  case REBLOG_FAIL:
-    return state.setIn([action.status.get('id'), 'reblogged'], false);
-  case TIMELINE_REFRESH_SUCCESS:
-  case TIMELINE_EXPAND_SUCCESS:
-  case ACCOUNT_TIMELINE_FETCH_SUCCESS:
-  case ACCOUNT_TIMELINE_EXPAND_SUCCESS:
-  case CONTEXT_FETCH_SUCCESS:
-  case NOTIFICATIONS_REFRESH_SUCCESS:
-  case NOTIFICATIONS_EXPAND_SUCCESS:
-  case FAVOURITED_STATUSES_FETCH_SUCCESS:
-  case FAVOURITED_STATUSES_EXPAND_SUCCESS:
-  case SEARCH_FETCH_SUCCESS:
-    return normalizeStatuses(state, action.statuses);
-  case TIMELINE_DELETE:
-    return deleteStatus(state, action.id, action.references);
-  case ACCOUNT_BLOCK_SUCCESS:
-    return filterStatuses(state, action.relationship);
-  default:
-    return state;
-  }
-};
diff --git a/app/assets/javascripts/components/reducers/timelines.jsx b/app/assets/javascripts/components/reducers/timelines.jsx
deleted file mode 100644
index fa9863250..000000000
--- a/app/assets/javascripts/components/reducers/timelines.jsx
+++ /dev/null
@@ -1,317 +0,0 @@
-import {
-  TIMELINE_REFRESH_REQUEST,
-  TIMELINE_REFRESH_SUCCESS,
-  TIMELINE_REFRESH_FAIL,
-  TIMELINE_UPDATE,
-  TIMELINE_DELETE,
-  TIMELINE_EXPAND_SUCCESS,
-  TIMELINE_EXPAND_REQUEST,
-  TIMELINE_EXPAND_FAIL,
-  TIMELINE_SCROLL_TOP,
-  TIMELINE_CONNECT,
-  TIMELINE_DISCONNECT
-} from '../actions/timelines';
-import {
-  REBLOG_SUCCESS,
-  UNREBLOG_SUCCESS,
-  FAVOURITE_SUCCESS,
-  UNFAVOURITE_SUCCESS
-} from '../actions/interactions';
-import {
-  ACCOUNT_TIMELINE_FETCH_REQUEST,
-  ACCOUNT_TIMELINE_FETCH_SUCCESS,
-  ACCOUNT_TIMELINE_FETCH_FAIL,
-  ACCOUNT_TIMELINE_EXPAND_REQUEST,
-  ACCOUNT_TIMELINE_EXPAND_SUCCESS,
-  ACCOUNT_TIMELINE_EXPAND_FAIL,
-  ACCOUNT_BLOCK_SUCCESS,
-  ACCOUNT_MUTE_SUCCESS
-} from '../actions/accounts';
-import {
-  CONTEXT_FETCH_SUCCESS
-} from '../actions/statuses';
-import Immutable from 'immutable';
-
-const initialState = Immutable.Map({
-  home: Immutable.Map({
-    path: () => '/api/v1/timelines/home',
-    next: null,
-    isLoading: false,
-    online: false,
-    loaded: false,
-    top: true,
-    unread: 0,
-    items: Immutable.List()
-  }),
-
-  public: Immutable.Map({
-    path: () => '/api/v1/timelines/public',
-    next: null,
-    isLoading: false,
-    online: false,
-    loaded: false,
-    top: true,
-    unread: 0,
-    items: Immutable.List()
-  }),
-
-  community: Immutable.Map({
-    path: () => '/api/v1/timelines/public',
-    next: null,
-    params: { local: true },
-    isLoading: false,
-    online: false,
-    loaded: false,
-    top: true,
-    unread: 0,
-    items: Immutable.List()
-  }),
-
-  tag: Immutable.Map({
-    path: (id) => `/api/v1/timelines/tag/${id}`,
-    next: null,
-    isLoading: false,
-    id: null,
-    loaded: false,
-    top: true,
-    unread: 0,
-    items: Immutable.List()
-  }),
-
-  accounts_timelines: Immutable.Map(),
-  ancestors: Immutable.Map(),
-  descendants: Immutable.Map()
-});
-
-const normalizeStatus = (state, status) => {
-  const replyToId = status.get('in_reply_to_id');
-  const id        = status.get('id');
-
-  if (replyToId) {
-    if (!state.getIn(['descendants', replyToId], Immutable.List()).includes(id)) {
-      state = state.updateIn(['descendants', replyToId], Immutable.List(), set => set.push(id));
-    }
-
-    if (!state.getIn(['ancestors', id], Immutable.List()).includes(replyToId)) {
-      state = state.updateIn(['ancestors', id], Immutable.List(), set => set.push(replyToId));
-    }
-  }
-
-  return state;
-};
-
-const normalizeTimeline = (state, timeline, statuses, next) => {
-  let ids      = Immutable.List();
-  const loaded = state.getIn([timeline, 'loaded']);
-
-  statuses.forEach((status, i) => {
-    state = normalizeStatus(state, status);
-    ids   = ids.set(i, status.get('id'));
-  });
-
-  state = state.setIn([timeline, 'loaded'], true);
-  state = state.setIn([timeline, 'isLoading'], false);
-
-  if (state.getIn([timeline, 'next']) === null) {
-    state = state.setIn([timeline, 'next'], next);
-  }
-
-  return state.updateIn([timeline, 'items'], Immutable.List(), list => (loaded ? list.unshift(...ids) : ids));
-};
-
-const appendNormalizedTimeline = (state, timeline, statuses, next) => {
-  let moreIds = Immutable.List();
-
-  statuses.forEach((status, i) => {
-    state   = normalizeStatus(state, status);
-    moreIds = moreIds.set(i, status.get('id'));
-  });
-
-  state = state.setIn([timeline, 'isLoading'], false);
-  state = state.setIn([timeline, 'next'], next);
-
-  return state.updateIn([timeline, 'items'], Immutable.List(), list => list.push(...moreIds));
-};
-
-const normalizeAccountTimeline = (state, accountId, statuses, replace = false) => {
-  let ids = Immutable.List();
-
-  statuses.forEach((status, i) => {
-    state = normalizeStatus(state, status);
-    ids   = ids.set(i, status.get('id'));
-  });
-
-  return state.updateIn(['accounts_timelines', accountId], Immutable.Map(), map => map
-    .set('isLoading', false)
-    .set('loaded', true)
-    .set('next', true)
-    .update('items', Immutable.List(), list => (replace ? ids : list.unshift(...ids))));
-};
-
-const appendNormalizedAccountTimeline = (state, accountId, statuses, next) => {
-  let moreIds = Immutable.List([]);
-
-  statuses.forEach((status, i) => {
-    state   = normalizeStatus(state, status);
-    moreIds = moreIds.set(i, status.get('id'));
-  });
-
-  return state.updateIn(['accounts_timelines', accountId], Immutable.Map(), map => map
-    .set('isLoading', false)
-    .set('next', next)
-    .update('items', list => list.push(...moreIds)));
-};
-
-const updateTimeline = (state, timeline, status, references) => {
-  const top = state.getIn([timeline, 'top']);
-
-  state = normalizeStatus(state, status);
-
-  if (!top) {
-    state = state.updateIn([timeline, 'unread'], unread => unread + 1);
-  }
-
-  state = state.updateIn([timeline, 'items'], Immutable.List(), list => {
-    if (top && list.size > 40) {
-      list = list.take(20);
-    }
-
-    if (list.includes(status.get('id'))) {
-      return list;
-    }
-
-    const reblogOfId = status.getIn(['reblog', 'id'], null);
-
-    if (reblogOfId !== null) {
-      list = list.filterNot(itemId => references.includes(itemId));
-    }
-
-    return list.unshift(status.get('id'));
-  });
-
-  return state;
-};
-
-const deleteStatus = (state, id, accountId, references, reblogOf) => {
-  if (reblogOf) {
-    // If we are deleting a reblog, just replace reblog with its original
-    return state.updateIn(['home', 'items'], list => list.map(item => item === id ? reblogOf : item));
-  }
-
-  // Remove references from timelines
-  ['home', 'public', 'community', 'tag'].forEach(function (timeline) {
-    state = state.updateIn([timeline, 'items'], list => list.filterNot(item => item === id));
-  });
-
-  // Remove references from account timelines
-  state = state.updateIn(['accounts_timelines', accountId, 'items'], Immutable.List([]), list => list.filterNot(item => item === id));
-
-  // Remove references from context
-  state.getIn(['descendants', id], Immutable.List()).forEach(descendantId => {
-    state = state.updateIn(['ancestors', descendantId], Immutable.List(), list => list.filterNot(itemId => itemId === id));
-  });
-
-  state.getIn(['ancestors', id], Immutable.List()).forEach(ancestorId => {
-    state = state.updateIn(['descendants', ancestorId], Immutable.List(), list => list.filterNot(itemId => itemId === id));
-  });
-
-  state = state.deleteIn(['descendants', id]).deleteIn(['ancestors', id]);
-
-  // Remove reblogs of deleted status
-  references.forEach(ref => {
-    state = deleteStatus(state, ref[0], ref[1], []);
-  });
-
-  return state;
-};
-
-const filterTimelines = (state, relationship, statuses) => {
-  let references;
-
-  statuses.forEach(status => {
-    if (status.get('account') !== relationship.id) {
-      return;
-    }
-
-    references = statuses.filter(item => item.get('reblog') === status.get('id')).map(item => [item.get('id'), item.get('account')]);
-    state = deleteStatus(state, status.get('id'), status.get('account'), references);
-  });
-
-  return state;
-};
-
-const normalizeContext = (state, id, ancestors, descendants) => {
-  const ancestorsIds   = ancestors.map(ancestor => ancestor.get('id'));
-  const descendantsIds = descendants.map(descendant => descendant.get('id'));
-
-  return state.withMutations(map => {
-    map.setIn(['ancestors', id], ancestorsIds);
-    map.setIn(['descendants', id], descendantsIds);
-  });
-};
-
-const resetTimeline = (state, timeline, id) => {
-  if (timeline === 'tag' && typeof id !== 'undefined' && state.getIn([timeline, 'id']) !== id) {
-    state = state.update(timeline, map => map
-        .set('id', id)
-        .set('isLoading', true)
-        .set('loaded', false)
-        .set('next', null)
-        .set('top', true)
-        .update('items', list => list.clear()));
-  } else {
-    state = state.setIn([timeline, 'isLoading'], true);
-  }
-
-  return state;
-};
-
-const updateTop = (state, timeline, top) => {
-  if (top) {
-    state = state.setIn([timeline, 'unread'], 0);
-  }
-
-  return state.setIn([timeline, 'top'], top);
-};
-
-export default function timelines(state = initialState, action) {
-  switch(action.type) {
-  case TIMELINE_REFRESH_REQUEST:
-  case TIMELINE_EXPAND_REQUEST:
-    return resetTimeline(state, action.timeline, action.id);
-  case TIMELINE_REFRESH_FAIL:
-  case TIMELINE_EXPAND_FAIL:
-    return state.setIn([action.timeline, 'isLoading'], false);
-  case TIMELINE_REFRESH_SUCCESS:
-    return normalizeTimeline(state, action.timeline, Immutable.fromJS(action.statuses), action.next);
-  case TIMELINE_EXPAND_SUCCESS:
-    return appendNormalizedTimeline(state, action.timeline, Immutable.fromJS(action.statuses), action.next);
-  case TIMELINE_UPDATE:
-    return updateTimeline(state, action.timeline, Immutable.fromJS(action.status), action.references);
-  case TIMELINE_DELETE:
-    return deleteStatus(state, action.id, action.accountId, action.references, action.reblogOf);
-  case CONTEXT_FETCH_SUCCESS:
-    return normalizeContext(state, action.id, Immutable.fromJS(action.ancestors), Immutable.fromJS(action.descendants));
-  case ACCOUNT_TIMELINE_FETCH_REQUEST:
-  case ACCOUNT_TIMELINE_EXPAND_REQUEST:
-    return state.updateIn(['accounts_timelines', action.id], Immutable.Map(), map => map.set('isLoading', true));
-  case ACCOUNT_TIMELINE_FETCH_FAIL:
-  case ACCOUNT_TIMELINE_EXPAND_FAIL:
-    return state.updateIn(['accounts_timelines', action.id], Immutable.Map(), map => map.set('isLoading', false));
-  case ACCOUNT_TIMELINE_FETCH_SUCCESS:
-    return normalizeAccountTimeline(state, action.id, Immutable.fromJS(action.statuses), action.replace);
-  case ACCOUNT_TIMELINE_EXPAND_SUCCESS:
-    return appendNormalizedAccountTimeline(state, action.id, Immutable.fromJS(action.statuses), action.next);
-  case ACCOUNT_BLOCK_SUCCESS:
-  case ACCOUNT_MUTE_SUCCESS:
-    return filterTimelines(state, action.relationship, action.statuses);
-  case TIMELINE_SCROLL_TOP:
-    return updateTop(state, action.timeline, action.top);
-  case TIMELINE_CONNECT:
-    return state.setIn([action.timeline, 'online'], true);
-  case TIMELINE_DISCONNECT:
-    return state.setIn([action.timeline, 'online'], false);
-  default:
-    return state;
-  }
-};
diff --git a/app/assets/javascripts/components/reducers/user_lists.jsx b/app/assets/javascripts/components/reducers/user_lists.jsx
deleted file mode 100644
index 7f55c3641..000000000
--- a/app/assets/javascripts/components/reducers/user_lists.jsx
+++ /dev/null
@@ -1,80 +0,0 @@
-import {
-  FOLLOWERS_FETCH_SUCCESS,
-  FOLLOWERS_EXPAND_SUCCESS,
-  FOLLOWING_FETCH_SUCCESS,
-  FOLLOWING_EXPAND_SUCCESS,
-  FOLLOW_REQUESTS_FETCH_SUCCESS,
-  FOLLOW_REQUESTS_EXPAND_SUCCESS,
-  FOLLOW_REQUEST_AUTHORIZE_SUCCESS,
-  FOLLOW_REQUEST_REJECT_SUCCESS
-} from '../actions/accounts';
-import {
-  REBLOGS_FETCH_SUCCESS,
-  FAVOURITES_FETCH_SUCCESS
-} from '../actions/interactions';
-import {
-  BLOCKS_FETCH_SUCCESS,
-  BLOCKS_EXPAND_SUCCESS
-} from '../actions/blocks';
-import {
-  MUTES_FETCH_SUCCESS,
-  MUTES_EXPAND_SUCCESS
-} from '../actions/mutes';
-import Immutable from 'immutable';
-
-const initialState = Immutable.Map({
-  followers: Immutable.Map(),
-  following: Immutable.Map(),
-  reblogged_by: Immutable.Map(),
-  favourited_by: Immutable.Map(),
-  follow_requests: Immutable.Map(),
-  blocks: Immutable.Map(),
-  mutes: Immutable.Map()
-});
-
-const normalizeList = (state, type, id, accounts, next) => {
-  return state.setIn([type, id], Immutable.Map({
-    next,
-    items: Immutable.List(accounts.map(item => item.id))
-  }));
-};
-
-const appendToList = (state, type, id, accounts, next) => {
-  return state.updateIn([type, id], map => {
-    return map.set('next', next).update('items', list => list.push(...accounts.map(item => item.id)));
-  });
-};
-
-export default function userLists(state = initialState, action) {
-  switch(action.type) {
-  case FOLLOWERS_FETCH_SUCCESS:
-    return normalizeList(state, 'followers', action.id, action.accounts, action.next);
-  case FOLLOWERS_EXPAND_SUCCESS:
-    return appendToList(state, 'followers', action.id, action.accounts, action.next);
-  case FOLLOWING_FETCH_SUCCESS:
-    return normalizeList(state, 'following', action.id, action.accounts, action.next);
-  case FOLLOWING_EXPAND_SUCCESS:
-    return appendToList(state, 'following', action.id, action.accounts, action.next);
-  case REBLOGS_FETCH_SUCCESS:
-    return state.setIn(['reblogged_by', action.id], Immutable.List(action.accounts.map(item => item.id)));
-  case FAVOURITES_FETCH_SUCCESS:
-    return state.setIn(['favourited_by', action.id], Immutable.List(action.accounts.map(item => item.id)));
-  case FOLLOW_REQUESTS_FETCH_SUCCESS:
-    return state.setIn(['follow_requests', 'items'], Immutable.List(action.accounts.map(item => item.id))).setIn(['follow_requests', 'next'], action.next);
-  case FOLLOW_REQUESTS_EXPAND_SUCCESS:
-    return state.updateIn(['follow_requests', 'items'], list => list.push(...action.accounts.map(item => item.id))).setIn(['follow_requests', 'next'], action.next);
-  case FOLLOW_REQUEST_AUTHORIZE_SUCCESS:
-  case FOLLOW_REQUEST_REJECT_SUCCESS:
-    return state.updateIn(['follow_requests', 'items'], list => list.filterNot(item => item === action.id));
-  case BLOCKS_FETCH_SUCCESS:
-    return state.setIn(['blocks', 'items'], Immutable.List(action.accounts.map(item => item.id))).setIn(['blocks', 'next'], action.next);
-  case BLOCKS_EXPAND_SUCCESS:
-    return state.updateIn(['blocks', 'items'], list => list.push(...action.accounts.map(item => item.id))).setIn(['blocks', 'next'], action.next);
-  case MUTES_FETCH_SUCCESS:
-    return state.setIn(['mutes', 'items'], Immutable.List(action.accounts.map(item => item.id))).setIn(['mutes', 'next'], action.next);
-  case MUTES_EXPAND_SUCCESS:
-    return state.updateIn(['mutes', 'items'], list => list.push(...action.accounts.map(item => item.id))).setIn(['mutes', 'next'], action.next);
-  default:
-    return state;
-  }
-};
diff --git a/app/assets/javascripts/components/rtl.jsx b/app/assets/javascripts/components/rtl.jsx
deleted file mode 100644
index 8f14bb338..000000000
--- a/app/assets/javascripts/components/rtl.jsx
+++ /dev/null
@@ -1,27 +0,0 @@
-// U+0590  to U+05FF  - Hebrew
-// U+0600  to U+06FF  - Arabic
-// U+0700  to U+074F  - Syriac
-// U+0750  to U+077F  - Arabic Supplement
-// U+0780  to U+07BF  - Thaana
-// U+07C0  to U+07FF  - N'Ko
-// U+0800  to U+083F  - Samaritan
-// U+08A0  to U+08FF  - Arabic Extended-A
-// U+FB1D  to U+FB4F  - Hebrew presentation forms
-// U+FB50  to U+FDFF  - Arabic presentation forms A
-// U+FE70  to U+FEFF  - Arabic presentation forms B
-
-const rtlChars = /[\u0590-\u083F]|[\u08A0-\u08FF]|[\uFB1D-\uFDFF]|[\uFE70-\uFEFF]/mg;
-
-export function isRtl(text) {
-  if (text.length === 0) {
-    return false;
-  }
-
-  const matches = text.match(rtlChars);
-
-  if (!matches) {
-    return false;
-  }
-
-  return matches.length / text.trim().length > 0.3;
-};
diff --git a/app/assets/javascripts/components/selectors/index.jsx b/app/assets/javascripts/components/selectors/index.jsx
deleted file mode 100644
index 01a6cb264..000000000
--- a/app/assets/javascripts/components/selectors/index.jsx
+++ /dev/null
@@ -1,72 +0,0 @@
-import { createSelector } from 'reselect';
-import Immutable from 'immutable';
-
-const getStatuses = state => state.get('statuses');
-const getAccounts = state => state.get('accounts');
-
-const getAccountBase         = (state, id) => state.getIn(['accounts', id], null);
-const getAccountRelationship = (state, id) => state.getIn(['relationships', id], null);
-
-export const makeGetAccount = () => {
-  return createSelector([getAccountBase, getAccountRelationship], (base, relationship) => {
-    if (base === null) {
-      return null;
-    }
-
-    return base.set('relationship', relationship);
-  });
-};
-
-export const makeGetStatus = () => {
-  return createSelector(
-    [
-      (state, id) => state.getIn(['statuses', id]),
-      (state, id) => state.getIn(['statuses', state.getIn(['statuses', id, 'reblog'])]),
-      (state, id) => state.getIn(['accounts', state.getIn(['statuses', id, 'account'])]),
-      (state, id) => state.getIn(['accounts', state.getIn(['statuses', state.getIn(['statuses', id, 'reblog']), 'account'])]),
-    ],
-
-    (statusBase, statusReblog, accountBase, accountReblog) => {
-      if (!statusBase) {
-        return null;
-      }
-
-      if (statusReblog) {
-        statusReblog = statusReblog.set('account', accountReblog);
-      } else {
-        statusReblog = null;
-      }
-
-      return statusBase.withMutations(map => {
-        map.set('reblog', statusReblog);
-        map.set('account', accountBase);
-      });
-    }
-  );
-};
-
-const getAlertsBase = state => state.get('alerts');
-
-export const getAlerts = createSelector([getAlertsBase], (base) => {
-  let arr = [];
-
-  base.forEach(item => {
-    arr.push({
-      message: item.get('message'),
-      title: item.get('title'),
-      key: item.get('key'),
-      dismissAfter: 5000
-    });
-  });
-
-  return arr;
-});
-
-export const makeGetNotification = () => {
-  return createSelector([
-    (_, base)             => base,
-    (state, _, accountId) => state.getIn(['accounts', accountId])
-  ], (base, account) => {
-    return base.set('account', account);
-  });
-};
diff --git a/app/assets/javascripts/components/store/configureStore.jsx b/app/assets/javascripts/components/store/configureStore.jsx
deleted file mode 100644
index a92d756f5..000000000
--- a/app/assets/javascripts/components/store/configureStore.jsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { createStore, applyMiddleware, compose } from 'redux';
-import thunk from 'redux-thunk';
-import appReducer from '../reducers';
-import loadingBarMiddleware from '../middleware/loading_bar';
-import errorsMiddleware from '../middleware/errors';
-import soundsMiddleware from '../middleware/sounds';
-import Immutable from 'immutable';
-
-export default function configureStore() {
-  return createStore(appReducer, compose(applyMiddleware(
-    thunk,
-    loadingBarMiddleware({ promiseTypeSuffixes: ['REQUEST', 'SUCCESS', 'FAIL'] }),
-    errorsMiddleware(),
-    soundsMiddleware()
-  ), window.devToolsExtension ? window.devToolsExtension() : f => f));
-};
diff --git a/app/assets/javascripts/components/stream.jsx b/app/assets/javascripts/components/stream.jsx
deleted file mode 100644
index 08da71607..000000000
--- a/app/assets/javascripts/components/stream.jsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import WebSocketClient from 'websocket.js';
-
-const createWebSocketURL = (url) => {
-  const a = document.createElement('a');
-
-  a.href     = url;
-  a.href     = a.href;
-  a.protocol = a.protocol.replace('http', 'ws');
-
-  return a.href;
-};
-
-export default function getStream(streamingAPIBaseURL, accessToken, stream, { connected, received, disconnected, reconnected }) {
-  const ws = new WebSocketClient(`${createWebSocketURL(streamingAPIBaseURL)}/api/v1/streaming/?access_token=${accessToken}&stream=${stream}`);
-
-  ws.onopen      = connected;
-  ws.onmessage   = e => received(JSON.parse(e.data));
-  ws.onclose     = disconnected;
-  ws.onreconnect = reconnected;
-
-  return ws;
-};
diff --git a/app/assets/javascripts/components/uuid.jsx b/app/assets/javascripts/components/uuid.jsx
deleted file mode 100644
index be1899305..000000000
--- a/app/assets/javascripts/components/uuid.jsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export default function uuid(a) {
-  return a ? (a^Math.random() * 16 >> a / 4).toString(16) : ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, uuid);
-};
diff --git a/app/assets/javascripts/extras.jsx b/app/assets/javascripts/extras.jsx
deleted file mode 100644
index 7e3b7a256..000000000
--- a/app/assets/javascripts/extras.jsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import emojify from './components/emoji';
-import { length } from 'stringz';
-
-$(() => {
-  $.each($('.emojify'), (_, content) => {
-    const $content = $(content);
-    $content.html(emojify($content.html()));
-  });
-
-  $('.video-player video').on('click', e => {
-    if (e.target.paused) {
-      e.target.play();
-    } else {
-      e.target.pause();
-    }
-  });
-
-  $('.media-spoiler').on('click', e => {
-    $(e.target).hide();
-  });
-
-  $('.webapp-btn').on('click', e => {
-    if (e.button === 0) {
-      e.preventDefault();
-      window.location.href = $(e.target).attr('href');
-    }
-  });
-
-  $('.status__content__spoiler-link').on('click', e => {
-    e.preventDefault();
-    const contentEl = $(e.target).parent().parent().find('div');
-
-    if (contentEl.is(':visible')) {
-      contentEl.hide();
-      $(e.target).parent().attr('style', 'margin-bottom: 0');
-    } else {
-      contentEl.show();
-      $(e.target).parent().attr('style', null);
-    }
-  });
-
-  // used on /settings/profile
-  $('.account_display_name').on('input', e => {
-    $('.name-counter').text(30 - length($(e.target).val()));
-  });
-  $('.account_note').on('input', e => {
-    $('.note-counter').text(160 - length($(e.target).val()));
-  });
-});