about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--.eslintrc.yml13
-rw-r--r--app/javascript/mastodon/actions/accounts.js5
-rw-r--r--app/javascript/mastodon/actions/domain_blocks.js4
-rw-r--r--app/javascript/mastodon/actions/notifications.js2
-rw-r--r--app/javascript/mastodon/actions/statuses.js6
-rw-r--r--app/javascript/mastodon/components/dropdown_menu.js2
-rw-r--r--app/javascript/mastodon/components/media_gallery.js2
-rw-r--r--app/javascript/mastodon/components/status.js1
-rw-r--r--app/javascript/mastodon/components/status_content.js1
-rw-r--r--app/javascript/mastodon/components/status_list.js2
-rw-r--r--app/javascript/mastodon/containers/mastodon.js1
-rw-r--r--app/javascript/mastodon/containers/status_container.js2
-rw-r--r--app/javascript/mastodon/features/account/components/header.js2
-rw-r--r--app/javascript/mastodon/features/account_gallery/index.js1
-rw-r--r--app/javascript/mastodon/features/community_timeline/components/column_settings.js5
-rw-r--r--app/javascript/mastodon/features/community_timeline/containers/column_settings_container.js6
-rw-r--r--app/javascript/mastodon/features/community_timeline/index.js1
-rw-r--r--app/javascript/mastodon/features/compose/components/compose_form.js5
-rw-r--r--app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js2
-rw-r--r--app/javascript/mastodon/features/compose/components/navigation_bar.js3
-rw-r--r--app/javascript/mastodon/features/compose/components/privacy_dropdown.js2
-rw-r--r--app/javascript/mastodon/features/compose/components/search.js2
-rw-r--r--app/javascript/mastodon/features/compose/components/search_results.js2
-rw-r--r--app/javascript/mastodon/features/compose/components/upload_button.js2
-rw-r--r--app/javascript/mastodon/features/compose/containers/navigation_container.js2
-rw-r--r--app/javascript/mastodon/features/compose/containers/reply_indicator_container.js2
-rw-r--r--app/javascript/mastodon/features/compose/containers/upload_form_container.js2
-rw-r--r--app/javascript/mastodon/features/compose/containers/upload_progress_container.js2
-rw-r--r--app/javascript/mastodon/features/compose/index.js1
-rw-r--r--app/javascript/mastodon/features/favourited_statuses/index.js7
-rw-r--r--app/javascript/mastodon/features/follow_requests/containers/account_authorize_container.js4
-rw-r--r--app/javascript/mastodon/features/getting_started/index.js1
-rw-r--r--app/javascript/mastodon/features/hashtag_timeline/index.js1
-rw-r--r--app/javascript/mastodon/features/home_timeline/components/column_settings.js10
-rw-r--r--app/javascript/mastodon/features/notifications/components/column_settings.js27
-rw-r--r--app/javascript/mastodon/features/notifications/components/notification.js1
-rw-r--r--app/javascript/mastodon/features/notifications/components/setting_toggle.js9
-rw-r--r--app/javascript/mastodon/features/public_timeline/containers/column_settings_container.js6
-rw-r--r--app/javascript/mastodon/features/public_timeline/index.js1
-rw-r--r--app/javascript/mastodon/features/report/index.js2
-rw-r--r--app/javascript/mastodon/features/status/index.js13
-rw-r--r--app/javascript/mastodon/features/ui/components/boost_modal.js3
-rw-r--r--app/javascript/mastodon/features/ui/components/confirmation_modal.js2
-rw-r--r--app/javascript/mastodon/features/ui/components/image_loader.js2
-rw-r--r--app/javascript/mastodon/features/ui/components/media_modal.js1
-rw-r--r--app/javascript/mastodon/features/ui/components/onboarding_modal.js5
-rw-r--r--app/javascript/mastodon/features/ui/components/video_modal.js1
-rw-r--r--app/javascript/mastodon/features/ui/containers/notifications_container.js7
-rw-r--r--app/javascript/mastodon/features/ui/index.js3
-rw-r--r--app/javascript/mastodon/middleware/errors.js2
-rw-r--r--app/javascript/mastodon/middleware/sounds.js2
-rw-r--r--app/javascript/mastodon/reducers/compose.js1
-rw-r--r--app/javascript/mastodon/reducers/modal.js1
-rw-r--r--app/javascript/mastodon/reducers/search.js54
-rw-r--r--app/javascript/mastodon/reducers/timelines.js6
-rw-r--r--app/javascript/mastodon/selectors/index.js3
-rw-r--r--app/javascript/mastodon/store/configureStore.js1
-rw-r--r--config/webpack/production.js1
-rw-r--r--db/migrate/20170610000000_add_statuses_index_on_account_id_id.rb13
-rw-r--r--db/schema.rb4
-rw-r--r--lib/mastodon/version.rb2
-rw-r--r--package.json2
-rw-r--r--spec/javascript/.eslintrc.yml3
-rw-r--r--spec/javascript/components/loading_indicator.test.js8
-rw-r--r--spec/models/tag_spec.rb4
-rw-r--r--storybook/config.js1
-rw-r--r--storybook/stories/character_counter.story.js1
-rw-r--r--storybook/stories/loading_indicator.story.js1
-rw-r--r--streaming/index.js6
-rw-r--r--yarn.lock56
70 files changed, 153 insertions, 208 deletions
diff --git a/.eslintrc.yml b/.eslintrc.yml
index 2fb54ae66..a816bffef 100644
--- a/.eslintrc.yml
+++ b/.eslintrc.yml
@@ -1,7 +1,9 @@
 ---
+root: true
+
 env:
   browser: true
-  node: false
+  node: true
   es6: true
 
 parser: babel-eslint
@@ -52,8 +54,14 @@ rules:
   no-mixed-spaces-and-tabs: warn
   no-nested-ternary: warn
   no-trailing-spaces: warn
+  no-undef: error
   no-unreachable: error
   no-unused-expressions: error
+  no-unused-vars:
+  - error
+  - vars: all
+    args: after-used
+    ignoreRestSiblings: true
   object-curly-spacing:
   - error
   - always
@@ -81,7 +89,10 @@ rules:
   - 2
   react/jsx-no-bind: error
   react/jsx-no-duplicate-props: error
+  react/jsx-no-undef: error
   react/jsx-tag-spacing: error
+  react/jsx-uses-react: error
+  react/jsx-uses-vars: error
   react/jsx-wrap-multilines: error
   react/no-multi-comp: off
   react/no-string-refs: error
diff --git a/app/javascript/mastodon/actions/accounts.js b/app/javascript/mastodon/actions/accounts.js
index a862798f9..03e3d3d9f 100644
--- a/app/javascript/mastodon/actions/accounts.js
+++ b/app/javascript/mastodon/actions/accounts.js
@@ -1,5 +1,4 @@
 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';
@@ -597,7 +596,7 @@ export function authorizeFollowRequest(id) {
 
     api(getState)
       .post(`/api/v1/follow_requests/${id}/authorize`)
-      .then(response => dispatch(authorizeFollowRequestSuccess(id)))
+      .then(() => dispatch(authorizeFollowRequestSuccess(id)))
       .catch(error => dispatch(authorizeFollowRequestFail(id, error)));
   };
 };
@@ -631,7 +630,7 @@ export function rejectFollowRequest(id) {
 
     api(getState)
       .post(`/api/v1/follow_requests/${id}/reject`)
-      .then(response => dispatch(rejectFollowRequestSuccess(id)))
+      .then(() => dispatch(rejectFollowRequestSuccess(id)))
       .catch(error => dispatch(rejectFollowRequestFail(id, error)));
   };
 };
diff --git a/app/javascript/mastodon/actions/domain_blocks.js b/app/javascript/mastodon/actions/domain_blocks.js
index 530ba9cf1..44363697a 100644
--- a/app/javascript/mastodon/actions/domain_blocks.js
+++ b/app/javascript/mastodon/actions/domain_blocks.js
@@ -16,7 +16,7 @@ export function blockDomain(domain, accountId) {
   return (dispatch, getState) => {
     dispatch(blockDomainRequest(domain));
 
-    api(getState).post('/api/v1/domain_blocks', { domain }).then(response => {
+    api(getState).post('/api/v1/domain_blocks', { domain }).then(() => {
       dispatch(blockDomainSuccess(domain, accountId));
     }).catch(err => {
       dispatch(blockDomainFail(domain, err));
@@ -51,7 +51,7 @@ export function unblockDomain(domain, accountId) {
   return (dispatch, getState) => {
     dispatch(unblockDomainRequest(domain));
 
-    api(getState).delete('/api/v1/domain_blocks', { params: { domain } }).then(response => {
+    api(getState).delete('/api/v1/domain_blocks', { params: { domain } }).then(() => {
       dispatch(unblockDomainSuccess(domain, accountId));
     }).catch(err => {
       dispatch(unblockDomainFail(domain, err));
diff --git a/app/javascript/mastodon/actions/notifications.js b/app/javascript/mastodon/actions/notifications.js
index d3de2d871..cda636139 100644
--- a/app/javascript/mastodon/actions/notifications.js
+++ b/app/javascript/mastodon/actions/notifications.js
@@ -17,7 +17,7 @@ export const NOTIFICATIONS_EXPAND_FAIL    = 'NOTIFICATIONS_EXPAND_FAIL';
 export const NOTIFICATIONS_CLEAR      = 'NOTIFICATIONS_CLEAR';
 export const NOTIFICATIONS_SCROLL_TOP = 'NOTIFICATIONS_SCROLL_TOP';
 
-const messages = defineMessages({
+defineMessages({
   mention: { id: 'notification.mention', defaultMessage: '{name} mentioned you' },
 });
 
diff --git a/app/javascript/mastodon/actions/statuses.js b/app/javascript/mastodon/actions/statuses.js
index 6956447ba..8d385715c 100644
--- a/app/javascript/mastodon/actions/statuses.js
+++ b/app/javascript/mastodon/actions/statuses.js
@@ -74,7 +74,7 @@ export function deleteStatus(id) {
   return (dispatch, getState) => {
     dispatch(deleteStatusRequest(id));
 
-    api(getState).delete(`/api/v1/statuses/${id}`).then(response => {
+    api(getState).delete(`/api/v1/statuses/${id}`).then(() => {
       dispatch(deleteStatusSuccess(id));
       dispatch(deleteFromTimelines(id));
     }).catch(error => {
@@ -152,7 +152,7 @@ export function muteStatus(id) {
   return (dispatch, getState) => {
     dispatch(muteStatusRequest(id));
 
-    api(getState).post(`/api/v1/statuses/${id}/mute`).then(response => {
+    api(getState).post(`/api/v1/statuses/${id}/mute`).then(() => {
       dispatch(muteStatusSuccess(id));
     }).catch(error => {
       dispatch(muteStatusFail(id, error));
@@ -186,7 +186,7 @@ export function unmuteStatus(id) {
   return (dispatch, getState) => {
     dispatch(unmuteStatusRequest(id));
 
-    api(getState).post(`/api/v1/statuses/${id}/unmute`).then(response => {
+    api(getState).post(`/api/v1/statuses/${id}/unmute`).then(() => {
       dispatch(unmuteStatusSuccess(id));
     }).catch(error => {
       dispatch(unmuteStatusFail(id, error));
diff --git a/app/javascript/mastodon/components/dropdown_menu.js b/app/javascript/mastodon/components/dropdown_menu.js
index 6e394a95d..deaab938e 100644
--- a/app/javascript/mastodon/components/dropdown_menu.js
+++ b/app/javascript/mastodon/components/dropdown_menu.js
@@ -56,7 +56,7 @@ class DropdownMenu extends React.PureComponent {
       return <li key={`sep-${i}`} className='dropdown__sep' />;
     }
 
-    const { text, action, href = '#' } = item;
+    const { text, href = '#' } = item;
 
     return (
       <li className='dropdown__content-list-item' key={`${text}-${i}`}>
diff --git a/app/javascript/mastodon/components/media_gallery.js b/app/javascript/mastodon/components/media_gallery.js
index 465130cec..cbed90f82 100644
--- a/app/javascript/mastodon/components/media_gallery.js
+++ b/app/javascript/mastodon/components/media_gallery.js
@@ -138,7 +138,7 @@ class MediaGallery extends React.PureComponent {
     visible: !this.props.sensitive,
   };
 
-  handleOpen = (e) => {
+  handleOpen = () => {
     this.setState({ visible: !this.state.visible });
   }
 
diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js
index 1363956cf..3be3685ec 100644
--- a/app/javascript/mastodon/components/status.js
+++ b/app/javascript/mastodon/components/status.js
@@ -7,7 +7,6 @@ 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';
diff --git a/app/javascript/mastodon/components/status_content.js b/app/javascript/mastodon/components/status_content.js
index d02083d3e..605d42138 100644
--- a/app/javascript/mastodon/components/status_content.js
+++ b/app/javascript/mastodon/components/status_content.js
@@ -32,7 +32,6 @@ class StatusContent extends React.PureComponent {
     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);
diff --git a/app/javascript/mastodon/components/status_list.js b/app/javascript/mastodon/components/status_list.js
index b73a73d1b..40e9d38c1 100644
--- a/app/javascript/mastodon/components/status_list.js
+++ b/app/javascript/mastodon/components/status_list.js
@@ -99,7 +99,7 @@ class StatusList extends ImmutablePureComponent {
   }
 
   render () {
-    const { statusIds, onScrollToBottom, scrollKey, trackScroll, shouldUpdateScroll, isLoading, hasMore, prepend, emptyMessage } = this.props;
+    const { statusIds, scrollKey, trackScroll, shouldUpdateScroll, isLoading, hasMore, prepend, emptyMessage } = this.props;
 
     let loadMore       = null;
     let scrollableArea = null;
diff --git a/app/javascript/mastodon/containers/mastodon.js b/app/javascript/mastodon/containers/mastodon.js
index d44cb1be4..e3cb815c9 100644
--- a/app/javascript/mastodon/containers/mastodon.js
+++ b/app/javascript/mastodon/containers/mastodon.js
@@ -3,7 +3,6 @@ import { Provider } from 'react-redux';
 import PropTypes from 'prop-types';
 import configureStore from '../store/configureStore';
 import {
-  refreshTimelineSuccess,
   updateTimeline,
   deleteFromTimelines,
   refreshHomeTimeline,
diff --git a/app/javascript/mastodon/containers/status_container.js b/app/javascript/mastodon/containers/status_container.js
index 2592e9a69..438ecfe43 100644
--- a/app/javascript/mastodon/containers/status_container.js
+++ b/app/javascript/mastodon/containers/status_container.js
@@ -19,8 +19,6 @@ import {
 import { muteStatus, unmuteStatus, 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({
diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js
index 653023cba..80a671a01 100644
--- a/app/javascript/mastodon/features/account/components/header.js
+++ b/app/javascript/mastodon/features/account/components/header.js
@@ -17,7 +17,7 @@ const messages = defineMessages({
 });
 
 const makeMapStateToProps = () => {
-  const mapStateToProps = (state, props) => ({
+  const mapStateToProps = state => ({
     autoPlayGif: state.getIn(['meta', 'auto_play_gif']),
   });
 
diff --git a/app/javascript/mastodon/features/account_gallery/index.js b/app/javascript/mastodon/features/account_gallery/index.js
index fcbee3c89..1e4af30a4 100644
--- a/app/javascript/mastodon/features/account_gallery/index.js
+++ b/app/javascript/mastodon/features/account_gallery/index.js
@@ -7,7 +7,6 @@ import { refreshAccountMediaTimeline, expandAccountMediaTimeline } from '../../a
 import LoadingIndicator from '../../components/loading_indicator';
 import Column from '../ui/components/column';
 import ColumnBackButton from '../../components/column_back_button';
-import Immutable from 'immutable';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import { getAccountGallery } from '../../selectors';
 import MediaItem from './components/media_item';
diff --git a/app/javascript/mastodon/features/community_timeline/components/column_settings.js b/app/javascript/mastodon/features/community_timeline/components/column_settings.js
index dbbe8ceaa..aa487e34e 100644
--- a/app/javascript/mastodon/features/community_timeline/components/column_settings.js
+++ b/app/javascript/mastodon/features/community_timeline/components/column_settings.js
@@ -2,8 +2,6 @@ import React from 'react';
 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 '../../../components/setting_text';
 
 const messages = defineMessages({
@@ -16,12 +14,11 @@ class ColumnSettings extends React.PureComponent {
   static propTypes = {
     settings: ImmutablePropTypes.map.isRequired,
     onChange: PropTypes.func.isRequired,
-    onSave: PropTypes.func.isRequired,
     intl: PropTypes.object.isRequired,
   };
 
   render () {
-    const { settings, onChange, onSave, intl } = this.props;
+    const { settings, onChange, intl } = this.props;
 
     return (
       <div>
diff --git a/app/javascript/mastodon/features/community_timeline/containers/column_settings_container.js b/app/javascript/mastodon/features/community_timeline/containers/column_settings_container.js
index 1efc2ef33..f3489b409 100644
--- a/app/javascript/mastodon/features/community_timeline/containers/column_settings_container.js
+++ b/app/javascript/mastodon/features/community_timeline/containers/column_settings_container.js
@@ -1,6 +1,6 @@
 import { connect } from 'react-redux';
 import ColumnSettings from '../components/column_settings';
-import { changeSetting, saveSettings } from '../../../actions/settings';
+import { changeSetting } from '../../../actions/settings';
 
 const mapStateToProps = state => ({
   settings: state.getIn(['settings', 'community']),
@@ -12,10 +12,6 @@ const mapDispatchToProps = dispatch => ({
     dispatch(changeSetting(['community', ...key], checked));
   },
 
-  onSave () {
-    dispatch(saveSettings());
-  },
-
 });
 
 export default connect(mapStateToProps, mapDispatchToProps)(ColumnSettings);
diff --git a/app/javascript/mastodon/features/community_timeline/index.js b/app/javascript/mastodon/features/community_timeline/index.js
index 4fbe67038..6c4b5dacf 100644
--- a/app/javascript/mastodon/features/community_timeline/index.js
+++ b/app/javascript/mastodon/features/community_timeline/index.js
@@ -14,7 +14,6 @@ import {
 } from '../../actions/timelines';
 import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import ColumnBackButtonSlim from '../../components/column_back_button_slim';
 import ColumnSettingsContainer from './containers/column_settings_container';
 import createStream from '../../stream';
 
diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js
index 0ee45c978..d75bbdf9c 100644
--- a/app/javascript/mastodon/features/compose/components/compose_form.js
+++ b/app/javascript/mastodon/features/compose/components/compose_form.js
@@ -7,15 +7,13 @@ import ReplyIndicatorContainer from '../containers/reply_indicator_container';
 import AutosuggestTextarea from '../../../components/autosuggest_textarea';
 import { debounce } from 'lodash';
 import UploadButtonContainer from '../containers/upload_button_container';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import Toggle from 'react-toggle';
+import { defineMessages, injectIntl } from 'react-intl';
 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';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 import { length } from 'stringz';
@@ -141,7 +139,6 @@ class ComposeForm extends ImmutablePureComponent {
     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>;
diff --git a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js
index 9ac674bb3..afaff1be1 100644
--- a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js
+++ b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js
@@ -52,7 +52,7 @@ class EmojiPickerDropdown extends React.PureComponent {
       import(/* webpackChunkName: "emojione_picker" */ 'emojione-picker').then(TheEmojiPicker => {
         EmojiPicker = TheEmojiPicker.default;
         this.setState({ loading: false });
-      }).catch(err => {
+      }).catch(() => {
         // TODO: show the user an error?
         this.setState({ loading: false });
       });
diff --git a/app/javascript/mastodon/features/compose/components/navigation_bar.js b/app/javascript/mastodon/features/compose/components/navigation_bar.js
index 1c135a733..00f27dea1 100644
--- a/app/javascript/mastodon/features/compose/components/navigation_bar.js
+++ b/app/javascript/mastodon/features/compose/components/navigation_bar.js
@@ -1,11 +1,8 @@
 import React from 'react';
 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-dom/Link';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 
 class NavigationBar extends ImmutablePureComponent {
diff --git a/app/javascript/mastodon/features/compose/components/privacy_dropdown.js b/app/javascript/mastodon/features/compose/components/privacy_dropdown.js
index 49f1179a0..f368186a5 100644
--- a/app/javascript/mastodon/features/compose/components/privacy_dropdown.js
+++ b/app/javascript/mastodon/features/compose/components/privacy_dropdown.js
@@ -64,7 +64,7 @@ class PrivacyDropdown extends React.PureComponent {
   }
 
   render () {
-    const { value, onChange, intl } = this.props;
+    const { value, intl } = this.props;
     const { open } = this.state;
 
     const options = [
diff --git a/app/javascript/mastodon/features/compose/components/search.js b/app/javascript/mastodon/features/compose/components/search.js
index 800080a7d..21b3cf34b 100644
--- a/app/javascript/mastodon/features/compose/components/search.js
+++ b/app/javascript/mastodon/features/compose/components/search.js
@@ -1,6 +1,6 @@
 import React from 'react';
 import PropTypes from 'prop-types';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import { defineMessages, injectIntl } from 'react-intl';
 
 const messages = defineMessages({
   placeholder: { id: 'search.placeholder', defaultMessage: 'Search' },
diff --git a/app/javascript/mastodon/features/compose/components/search_results.js b/app/javascript/mastodon/features/compose/components/search_results.js
index 26d766a1c..1a2605c15 100644
--- a/app/javascript/mastodon/features/compose/components/search_results.js
+++ b/app/javascript/mastodon/features/compose/components/search_results.js
@@ -1,6 +1,6 @@
 import React from 'react';
 import ImmutablePropTypes from 'react-immutable-proptypes';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import { FormattedMessage } from 'react-intl';
 import AccountContainer from '../../../containers/account_container';
 import StatusContainer from '../../../containers/status_container';
 import Link from 'react-router-dom/Link';
diff --git a/app/javascript/mastodon/features/compose/components/upload_button.js b/app/javascript/mastodon/features/compose/components/upload_button.js
index 326b9851a..0f11b9e8b 100644
--- a/app/javascript/mastodon/features/compose/components/upload_button.js
+++ b/app/javascript/mastodon/features/compose/components/upload_button.js
@@ -11,7 +11,7 @@ const messages = defineMessages({
 });
 
 const makeMapStateToProps = () => {
-  const mapStateToProps = (state, props) => ({
+  const mapStateToProps = state => ({
     acceptContentTypes: state.getIn(['media_attachments', 'accept_content_types']),
   });
 
diff --git a/app/javascript/mastodon/features/compose/containers/navigation_container.js b/app/javascript/mastodon/features/compose/containers/navigation_container.js
index 75f288f18..8cc53c087 100644
--- a/app/javascript/mastodon/features/compose/containers/navigation_container.js
+++ b/app/javascript/mastodon/features/compose/containers/navigation_container.js
@@ -1,7 +1,7 @@
 import { connect }   from 'react-redux';
 import NavigationBar from '../components/navigation_bar';
 
-const mapStateToProps = (state, props) => {
+const mapStateToProps = state => {
   return {
     account: state.getIn(['accounts', state.getIn(['meta', 'me'])]),
   };
diff --git a/app/javascript/mastodon/features/compose/containers/reply_indicator_container.js b/app/javascript/mastodon/features/compose/containers/reply_indicator_container.js
index 7f3eeb89c..73f394c1a 100644
--- a/app/javascript/mastodon/features/compose/containers/reply_indicator_container.js
+++ b/app/javascript/mastodon/features/compose/containers/reply_indicator_container.js
@@ -6,7 +6,7 @@ import ReplyIndicator from '../components/reply_indicator';
 const makeMapStateToProps = () => {
   const getStatus = makeGetStatus();
 
-  const mapStateToProps = (state, props) => ({
+  const mapStateToProps = state => ({
     status: getStatus(state, state.getIn(['compose', 'in_reply_to'])),
   });
 
diff --git a/app/javascript/mastodon/features/compose/containers/upload_form_container.js b/app/javascript/mastodon/features/compose/containers/upload_form_container.js
index 3125564c2..4612599f1 100644
--- a/app/javascript/mastodon/features/compose/containers/upload_form_container.js
+++ b/app/javascript/mastodon/features/compose/containers/upload_form_container.js
@@ -2,7 +2,7 @@ import { connect } from 'react-redux';
 import UploadForm from '../components/upload_form';
 import { undoUploadCompose } from '../../../actions/compose';
 
-const mapStateToProps = (state, props) => ({
+const mapStateToProps = state => ({
   media: state.getIn(['compose', 'media_attachments']),
 });
 
diff --git a/app/javascript/mastodon/features/compose/containers/upload_progress_container.js b/app/javascript/mastodon/features/compose/containers/upload_progress_container.js
index 51af4440c..0cfee96da 100644
--- a/app/javascript/mastodon/features/compose/containers/upload_progress_container.js
+++ b/app/javascript/mastodon/features/compose/containers/upload_progress_container.js
@@ -1,7 +1,7 @@
 import { connect } from 'react-redux';
 import UploadProgress from '../components/upload_progress';
 
-const mapStateToProps = (state, props) => ({
+const mapStateToProps = state => ({
   active: state.getIn(['compose', 'is_uploading']),
   progress: state.getIn(['compose', 'progress']),
 });
diff --git a/app/javascript/mastodon/features/compose/index.js b/app/javascript/mastodon/features/compose/index.js
index a87e48a23..0452de856 100644
--- a/app/javascript/mastodon/features/compose/index.js
+++ b/app/javascript/mastodon/features/compose/index.js
@@ -1,6 +1,5 @@
 import React from 'react';
 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';
diff --git a/app/javascript/mastodon/features/favourited_statuses/index.js b/app/javascript/mastodon/features/favourited_statuses/index.js
index 2b343ba5a..caf0d2ca2 100644
--- a/app/javascript/mastodon/features/favourited_statuses/index.js
+++ b/app/javascript/mastodon/features/favourited_statuses/index.js
@@ -1,7 +1,6 @@
 import React from 'react';
 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';
@@ -15,19 +14,15 @@ const messages = defineMessages({
 });
 
 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 ImmutablePureComponent {
 
   static propTypes = {
     dispatch: PropTypes.func.isRequired,
-    statusIds: ImmutablePropTypes.list.isRequired,
     loaded: PropTypes.bool,
     intl: PropTypes.object.isRequired,
-    me: PropTypes.number.isRequired,
   };
 
   componentWillMount () {
@@ -39,7 +34,7 @@ class Favourites extends ImmutablePureComponent {
   }
 
   render () {
-    const { statusIds, loaded, intl, me } = this.props;
+    const { loaded, intl } = this.props;
 
     if (!loaded) {
       return (
diff --git a/app/javascript/mastodon/features/follow_requests/containers/account_authorize_container.js b/app/javascript/mastodon/features/follow_requests/containers/account_authorize_container.js
index a423bc79b..8db471f73 100644
--- a/app/javascript/mastodon/features/follow_requests/containers/account_authorize_container.js
+++ b/app/javascript/mastodon/features/follow_requests/containers/account_authorize_container.js
@@ -14,11 +14,11 @@ const makeMapStateToProps = () => {
 };
 
 const mapDispatchToProps = (dispatch, { id }) => ({
-  onAuthorize (account) {
+  onAuthorize () {
     dispatch(authorizeFollowRequest(id));
   },
 
-  onReject (account) {
+  onReject () {
     dispatch(rejectFollowRequest(id));
   },
 });
diff --git a/app/javascript/mastodon/features/getting_started/index.js b/app/javascript/mastodon/features/getting_started/index.js
index a4549e609..c1eb06fcb 100644
--- a/app/javascript/mastodon/features/getting_started/index.js
+++ b/app/javascript/mastodon/features/getting_started/index.js
@@ -2,7 +2,6 @@ import React from 'react';
 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-dom/Link';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import { connect } from 'react-redux';
 import PropTypes from 'prop-types';
diff --git a/app/javascript/mastodon/features/hashtag_timeline/index.js b/app/javascript/mastodon/features/hashtag_timeline/index.js
index 3b2f1ba93..853434d4b 100644
--- a/app/javascript/mastodon/features/hashtag_timeline/index.js
+++ b/app/javascript/mastodon/features/hashtag_timeline/index.js
@@ -11,7 +11,6 @@ import {
   deleteFromTimelines,
 } from '../../actions/timelines';
 import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
-import ColumnBackButtonSlim from '../../components/column_back_button_slim';
 import { FormattedMessage } from 'react-intl';
 import createStream from '../../stream';
 
diff --git a/app/javascript/mastodon/features/home_timeline/components/column_settings.js b/app/javascript/mastodon/features/home_timeline/components/column_settings.js
index 104d8ff37..47cd340af 100644
--- a/app/javascript/mastodon/features/home_timeline/components/column_settings.js
+++ b/app/javascript/mastodon/features/home_timeline/components/column_settings.js
@@ -2,7 +2,6 @@ import React from 'react';
 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 '../../../components/setting_text';
 
@@ -16,29 +15,28 @@ class ColumnSettings extends React.PureComponent {
   static propTypes = {
     settings: ImmutablePropTypes.map.isRequired,
     onChange: PropTypes.func.isRequired,
-    onSave: PropTypes.func.isRequired,
     intl: PropTypes.object.isRequired,
   };
 
   render () {
-    const { settings, onChange, onSave, intl } = this.props;
+    const { settings, onChange, intl } = this.props;
 
     return (
       <div>
         <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' />} />
+          <SettingToggle prefix='home_timeline' 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' />} />
+          <SettingToggle prefix='home_timeline' 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)} />
+          <SettingText prefix='home_timeline' settings={settings} settingKey={['regex', 'body']} onChange={onChange} label={intl.formatMessage(messages.filter_regex)} />
         </div>
       </div>
     );
diff --git a/app/javascript/mastodon/features/notifications/components/column_settings.js b/app/javascript/mastodon/features/notifications/components/column_settings.js
index 7bfd02f11..2051e0c86 100644
--- a/app/javascript/mastodon/features/notifications/components/column_settings.js
+++ b/app/javascript/mastodon/features/notifications/components/column_settings.js
@@ -2,7 +2,6 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import { FormattedMessage } from 'react-intl';
-import ColumnCollapsable from '../../../components/column_collapsable';
 import ClearColumnButton from './clear_column_button';
 import SettingToggle from './setting_toggle';
 
@@ -16,7 +15,7 @@ class ColumnSettings extends React.PureComponent {
   };
 
   render () {
-    const { settings, onChange, onSave, onClear } = this.props;
+    const { settings, onChange, onClear } = 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' />;
@@ -31,33 +30,33 @@ class ColumnSettings extends React.PureComponent {
         <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} />
+          <SettingToggle prefix='notifications' settings={settings} settingKey={['alerts', 'follow']} onChange={onChange} label={alertStr} />
+          <SettingToggle prefix='notifications' settings={settings} settingKey={['shows', 'follow']} onChange={onChange} label={showStr} />
+          <SettingToggle prefix='notifications' settings={settings} settingKey={['sounds', 'follow']} onChange={onChange} label={soundStr} />
         </div>
 
         <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} />
+          <SettingToggle prefix='notifications' settings={settings} settingKey={['alerts', 'favourite']} onChange={onChange} label={alertStr} />
+          <SettingToggle prefix='notifications' settings={settings} settingKey={['shows', 'favourite']} onChange={onChange} label={showStr} />
+          <SettingToggle prefix='notifications' settings={settings} settingKey={['sounds', 'favourite']} onChange={onChange} label={soundStr} />
         </div>
 
         <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} />
+          <SettingToggle prefix='notifications' settings={settings} settingKey={['alerts', 'mention']} onChange={onChange} label={alertStr} />
+          <SettingToggle prefix='notifications' settings={settings} settingKey={['shows', 'mention']} onChange={onChange} label={showStr} />
+          <SettingToggle prefix='notifications' settings={settings} settingKey={['sounds', 'mention']} onChange={onChange} label={soundStr} />
         </div>
 
         <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} />
+          <SettingToggle prefix='notifications' settings={settings} settingKey={['alerts', 'reblog']} onChange={onChange} label={alertStr} />
+          <SettingToggle prefix='notifications' settings={settings} settingKey={['shows', 'reblog']} onChange={onChange} label={showStr} />
+          <SettingToggle prefix='notifications' settings={settings} settingKey={['sounds', 'reblog']} onChange={onChange} label={soundStr} />
         </div>
       </div>
     );
diff --git a/app/javascript/mastodon/features/notifications/components/notification.js b/app/javascript/mastodon/features/notifications/components/notification.js
index 6ec4d5dc6..ede37f66a 100644
--- a/app/javascript/mastodon/features/notifications/components/notification.js
+++ b/app/javascript/mastodon/features/notifications/components/notification.js
@@ -2,7 +2,6 @@ import React from 'react';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import StatusContainer from '../../../containers/status_container';
 import AccountContainer from '../../../containers/account_container';
-import Avatar from '../../../components/avatar';
 import { FormattedMessage } from 'react-intl';
 import Permalink from '../../../components/permalink';
 import emojify from '../../../emoji';
diff --git a/app/javascript/mastodon/features/notifications/components/setting_toggle.js b/app/javascript/mastodon/features/notifications/components/setting_toggle.js
index a37abbd9c..8707a993e 100644
--- a/app/javascript/mastodon/features/notifications/components/setting_toggle.js
+++ b/app/javascript/mastodon/features/notifications/components/setting_toggle.js
@@ -6,19 +6,20 @@ import Toggle from 'react-toggle';
 class SettingToggle extends React.PureComponent {
 
   static propTypes = {
+    prefix: PropTypes.string,
     settings: ImmutablePropTypes.map.isRequired,
     settingKey: PropTypes.array.isRequired,
     label: PropTypes.node.isRequired,
     onChange: PropTypes.func.isRequired,
   }
 
-  onChange = (e) => {
-    this.props.onChange(this.props.settingKey, e.target.checked);
+  onChange = ({ target }) => {
+    this.props.onChange(this.props.settingKey, target.checked);
   }
 
   render () {
-    const { settings, settingKey, label, onChange } = this.props;
-    const id = `setting-toggle-${settingKey.join('-')}`;
+    const { prefix, settings, settingKey, label } = this.props;
+    const id = ['setting-toggle', prefix, ...settingKey].filter(Boolean).join('-');
 
     return (
       <div className='setting-toggle'>
diff --git a/app/javascript/mastodon/features/public_timeline/containers/column_settings_container.js b/app/javascript/mastodon/features/public_timeline/containers/column_settings_container.js
index 62d4e7e5a..203e1da92 100644
--- a/app/javascript/mastodon/features/public_timeline/containers/column_settings_container.js
+++ b/app/javascript/mastodon/features/public_timeline/containers/column_settings_container.js
@@ -1,6 +1,6 @@
 import { connect } from 'react-redux';
 import ColumnSettings from '../../community_timeline/components/column_settings';
-import { changeSetting, saveSettings } from '../../../actions/settings';
+import { changeSetting } from '../../../actions/settings';
 
 const mapStateToProps = state => ({
   settings: state.getIn(['settings', 'public']),
@@ -12,10 +12,6 @@ const mapDispatchToProps = dispatch => ({
     dispatch(changeSetting(['public', ...key], checked));
   },
 
-  onSave () {
-    dispatch(saveSettings());
-  },
-
 });
 
 export default connect(mapStateToProps, mapDispatchToProps)(ColumnSettings);
diff --git a/app/javascript/mastodon/features/public_timeline/index.js b/app/javascript/mastodon/features/public_timeline/index.js
index 02ddb418f..3de54ef8b 100644
--- a/app/javascript/mastodon/features/public_timeline/index.js
+++ b/app/javascript/mastodon/features/public_timeline/index.js
@@ -14,7 +14,6 @@ import {
 } from '../../actions/timelines';
 import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import ColumnBackButtonSlim from '../../components/column_back_button_slim';
 import ColumnSettingsContainer from './containers/column_settings_container';
 import createStream from '../../stream';
 
diff --git a/app/javascript/mastodon/features/report/index.js b/app/javascript/mastodon/features/report/index.js
index 23aba39de..0a5268430 100644
--- a/app/javascript/mastodon/features/report/index.js
+++ b/app/javascript/mastodon/features/report/index.js
@@ -1,6 +1,6 @@
 import React from 'react';
 import { connect } from 'react-redux';
-import { cancelReport, changeReportComment, submitReport } from '../../actions/reports';
+import { changeReportComment, submitReport } from '../../actions/reports';
 import { refreshAccountTimeline } from '../../actions/timelines';
 import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js
index 19cee2435..afd8a7811 100644
--- a/app/javascript/mastodon/features/status/index.js
+++ b/app/javascript/mastodon/features/status/index.js
@@ -3,8 +3,6 @@ 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';
@@ -21,17 +19,12 @@ import {
 } from '../../actions/compose';
 import { deleteStatus } from '../../actions/statuses';
 import { initReport } from '../../actions/reports';
-import {
-  makeGetStatus,
-  getStatusAncestors,
-  getStatusDescendants,
-} from '../../selectors';
+import { makeGetStatus } 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';
+import { defineMessages, injectIntl } from 'react-intl';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 
 const messages = defineMessages({
@@ -159,8 +152,6 @@ class Status extends ImmutablePureComponent {
       );
     }
 
-    const account = status.get('account');
-
     if (ancestorsIds && ancestorsIds.size > 0) {
       ancestors = <div>{this.renderChildren(ancestorsIds)}</div>;
     }
diff --git a/app/javascript/mastodon/features/ui/components/boost_modal.js b/app/javascript/mastodon/features/ui/components/boost_modal.js
index da2be5264..9a8b96333 100644
--- a/app/javascript/mastodon/features/ui/components/boost_modal.js
+++ b/app/javascript/mastodon/features/ui/components/boost_modal.js
@@ -2,7 +2,6 @@ import React from 'react';
 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';
@@ -49,7 +48,7 @@ class BoostModal extends ImmutablePureComponent {
   }
 
   render () {
-    const { status, intl, onClose } = this.props;
+    const { status, intl } = this.props;
 
     return (
       <div className='modal-root__modal boost-modal'>
diff --git a/app/javascript/mastodon/features/ui/components/confirmation_modal.js b/app/javascript/mastodon/features/ui/components/confirmation_modal.js
index f33bfd445..a45c220fa 100644
--- a/app/javascript/mastodon/features/ui/components/confirmation_modal.js
+++ b/app/javascript/mastodon/features/ui/components/confirmation_modal.js
@@ -1,6 +1,6 @@
 import React from 'react';
 import PropTypes from 'prop-types';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+import { injectIntl, FormattedMessage } from 'react-intl';
 import Button from '../../../components/button';
 
 class ConfirmationModal extends React.PureComponent {
diff --git a/app/javascript/mastodon/features/ui/components/image_loader.js b/app/javascript/mastodon/features/ui/components/image_loader.js
index a2514d6be..94bf55bad 100644
--- a/app/javascript/mastodon/features/ui/components/image_loader.js
+++ b/app/javascript/mastodon/features/ui/components/image_loader.js
@@ -41,7 +41,7 @@ class ImageLoader extends React.PureComponent {
 
   render() {
     const { alt, src, previewSrc, width, height } = this.props;
-    const { loading, error } = this.state;
+    const { loading } = this.state;
 
     return (
       <div className='image-loader'>
diff --git a/app/javascript/mastodon/features/ui/components/media_modal.js b/app/javascript/mastodon/features/ui/components/media_modal.js
index c6b293aeb..0209bc99b 100644
--- a/app/javascript/mastodon/features/ui/components/media_modal.js
+++ b/app/javascript/mastodon/features/ui/components/media_modal.js
@@ -1,6 +1,5 @@
 import React from 'react';
 import ReactSwipeable from 'react-swipeable';
-import LoadingIndicator from '../../../components/loading_indicator';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import PropTypes from 'prop-types';
 import ExtendedVideoPlayer from '../../../components/extended_video_player';
diff --git a/app/javascript/mastodon/features/ui/components/onboarding_modal.js b/app/javascript/mastodon/features/ui/components/onboarding_modal.js
index c8985dc83..279599169 100644
--- a/app/javascript/mastodon/features/ui/components/onboarding_modal.js
+++ b/app/javascript/mastodon/features/ui/components/onboarding_modal.js
@@ -72,7 +72,7 @@ PageTwo.propTypes = {
   me: ImmutablePropTypes.map.isRequired,
 };
 
-const PageThree = ({ me, domain }) => (
+const PageThree = ({ me }) => (
   <div className='onboarding-modal__page onboarding-modal__page-three'>
     <div className='figure non-interactive'>
       <Search
@@ -95,7 +95,6 @@ const PageThree = ({ me, domain }) => (
 
 PageThree.propTypes = {
   me: ImmutablePropTypes.map.isRequired,
-  domain: PropTypes.string.isRequired,
 };
 
 const PageFour = ({ domain, intl }) => (
@@ -187,7 +186,7 @@ class OnboardingModal extends React.PureComponent {
     this.pages = [
       <PageOne acct={me.get('acct')} domain={domain} />,
       <PageTwo me={me} />,
-      <PageThree me={me} domain={domain} />,
+      <PageThree me={me} />,
       <PageFour domain={domain} intl={intl} />,
       <PageSix admin={admin} domain={domain} />,
     ];
diff --git a/app/javascript/mastodon/features/ui/components/video_modal.js b/app/javascript/mastodon/features/ui/components/video_modal.js
index c622085f9..3599ab775 100644
--- a/app/javascript/mastodon/features/ui/components/video_modal.js
+++ b/app/javascript/mastodon/features/ui/components/video_modal.js
@@ -1,5 +1,4 @@
 import React from 'react';
-import LoadingIndicator from '../../../components/loading_indicator';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import PropTypes from 'prop-types';
 import ExtendedVideoPlayer from '../../../components/extended_video_player';
diff --git a/app/javascript/mastodon/features/ui/containers/notifications_container.js b/app/javascript/mastodon/features/ui/containers/notifications_container.js
index 8bc30df35..5924197f1 100644
--- a/app/javascript/mastodon/features/ui/containers/notifications_container.js
+++ b/app/javascript/mastodon/features/ui/containers/notifications_container.js
@@ -1,12 +1,9 @@
 import { connect } from 'react-redux';
 import { NotificationStack } from 'react-notification';
-import {
-  dismissAlert,
-  clearAlerts,
-} from '../../../actions/alerts';
+import { dismissAlert } from '../../../actions/alerts';
 import { getAlerts } from '../../../selectors';
 
-const mapStateToProps = (state, props) => ({
+const mapStateToProps = state => ({
   notifications: getAlerts(state),
 });
 
diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js
index 39600607f..e48e9dbe9 100644
--- a/app/javascript/mastodon/features/ui/index.js
+++ b/app/javascript/mastodon/features/ui/index.js
@@ -74,9 +74,6 @@ class WrappedRoute extends React.Component {
 
 }
 
-const noOp = () => false;
-
-
 class UI extends React.PureComponent {
 
   static propTypes = {
diff --git a/app/javascript/mastodon/middleware/errors.js b/app/javascript/mastodon/middleware/errors.js
index 4aca75f1e..b2c5f0898 100644
--- a/app/javascript/mastodon/middleware/errors.js
+++ b/app/javascript/mastodon/middleware/errors.js
@@ -1,13 +1,11 @@
 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) {
diff --git a/app/javascript/mastodon/middleware/sounds.js b/app/javascript/mastodon/middleware/sounds.js
index fd5a2b960..372e7c835 100644
--- a/app/javascript/mastodon/middleware/sounds.js
+++ b/app/javascript/mastodon/middleware/sounds.js
@@ -32,7 +32,7 @@ export default function soundsMiddleware() {
     ]),
   };
 
-  return ({ dispatch }) => next => (action) => {
+  return () => next => action => {
     if (action.meta && action.meta.sound && soundCache[action.meta.sound]) {
       play(soundCache[action.meta.sound]);
     }
diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js
index 2413df9e2..d0b47a85c 100644
--- a/app/javascript/mastodon/reducers/compose.js
+++ b/app/javascript/mastodon/reducers/compose.js
@@ -20,7 +20,6 @@ import {
   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';
diff --git a/app/javascript/mastodon/reducers/modal.js b/app/javascript/mastodon/reducers/modal.js
index 8fd9a69cd..599a2443e 100644
--- a/app/javascript/mastodon/reducers/modal.js
+++ b/app/javascript/mastodon/reducers/modal.js
@@ -1,5 +1,4 @@
 import { MODAL_OPEN, MODAL_CLOSE } from '../actions/modal';
-import Immutable from 'immutable';
 
 const initialState = {
   modalType: null,
diff --git a/app/javascript/mastodon/reducers/search.js b/app/javascript/mastodon/reducers/search.js
index ed395427e..0a3adac05 100644
--- a/app/javascript/mastodon/reducers/search.js
+++ b/app/javascript/mastodon/reducers/search.js
@@ -14,60 +14,6 @@ const initialState = Immutable.Map({
   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:
diff --git a/app/javascript/mastodon/reducers/timelines.js b/app/javascript/mastodon/reducers/timelines.js
index 2bc1c8050..1b738a16a 100644
--- a/app/javascript/mastodon/reducers/timelines.js
+++ b/app/javascript/mastodon/reducers/timelines.js
@@ -12,12 +12,6 @@ import {
   TIMELINE_DISCONNECT,
 } from '../actions/timelines';
 import {
-  REBLOG_SUCCESS,
-  UNREBLOG_SUCCESS,
-  FAVOURITE_SUCCESS,
-  UNFAVOURITE_SUCCESS,
-} from '../actions/interactions';
-import {
   ACCOUNT_BLOCK_SUCCESS,
   ACCOUNT_MUTE_SUCCESS,
 } from '../actions/accounts';
diff --git a/app/javascript/mastodon/selectors/index.js b/app/javascript/mastodon/selectors/index.js
index d5d736e2f..07d9a2629 100644
--- a/app/javascript/mastodon/selectors/index.js
+++ b/app/javascript/mastodon/selectors/index.js
@@ -1,9 +1,6 @@
 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 getAccountCounters     = (state, id) => state.getIn(['accounts_counters', id], null);
 const getAccountRelationship = (state, id) => state.getIn(['relationships', id], null);
diff --git a/app/javascript/mastodon/store/configureStore.js b/app/javascript/mastodon/store/configureStore.js
index a92d756f5..1376d4cba 100644
--- a/app/javascript/mastodon/store/configureStore.js
+++ b/app/javascript/mastodon/store/configureStore.js
@@ -4,7 +4,6 @@ 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(
diff --git a/config/webpack/production.js b/config/webpack/production.js
index 303fca81b..0d2c9acfb 100644
--- a/config/webpack/production.js
+++ b/config/webpack/production.js
@@ -12,6 +12,7 @@ module.exports = merge(sharedConfig, {
   stats: 'normal',
 
   plugins: [
+    new webpack.optimize.ModuleConcatenationPlugin(),
     new webpack.optimize.UglifyJsPlugin({
       sourceMap: true,
       mangle: true,
diff --git a/db/migrate/20170610000000_add_statuses_index_on_account_id_id.rb b/db/migrate/20170610000000_add_statuses_index_on_account_id_id.rb
new file mode 100644
index 000000000..3e74346a8
--- /dev/null
+++ b/db/migrate/20170610000000_add_statuses_index_on_account_id_id.rb
@@ -0,0 +1,13 @@
+class AddStatusesIndexOnAccountIdId < ActiveRecord::Migration[5.1]
+  disable_ddl_transaction!
+
+  def change
+    # Statuses queried by account_id are often sorted by id. Querying statuses
+    # of an account to show them in his status page is one of the most
+    # significant examples.
+    # Add this index to improve the performance in such cases.
+    add_index 'statuses', ['account_id', 'id'], algorithm: :concurrently, name: 'index_statuses_on_account_id_id'
+
+    remove_index 'statuses', algorithm: :concurrently, column: 'account_id', name: 'index_statuses_on_account_id'
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index ba6c0e876..2f12c7308 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema.define(version: 20170609145826) do
+ActiveRecord::Schema.define(version: 20170610000000) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
@@ -279,7 +279,7 @@ ActiveRecord::Schema.define(version: 20170609145826) do
     t.integer "reblogs_count", default: 0, null: false
     t.string "language"
     t.bigint "conversation_id"
-    t.index ["account_id"], name: "index_statuses_on_account_id"
+    t.index ["account_id", "id"], name: "index_statuses_on_account_id_id"
     t.index ["conversation_id"], name: "index_statuses_on_conversation_id"
     t.index ["in_reply_to_id"], name: "index_statuses_on_in_reply_to_id"
     t.index ["reblog_of_id"], name: "index_statuses_on_reblog_of_id"
diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb
index 5f90e83bb..44f3e4390 100644
--- a/lib/mastodon/version.rb
+++ b/lib/mastodon/version.rb
@@ -13,7 +13,7 @@ module Mastodon
     end
 
     def patch
-      4
+      6
     end
 
     def pre
diff --git a/package.json b/package.json
index 4a611e685..dc08fc106 100644
--- a/package.json
+++ b/package.json
@@ -106,7 +106,7 @@
     "tiny-queue": "^0.2.1",
     "uuid": "^3.0.1",
     "uws": "^0.14.5",
-    "webpack": "^2.5.1",
+    "webpack": "^3.0.0",
     "webpack-bundle-analyzer": "^2.8.2",
     "webpack-manifest-plugin": "^1.1.0",
     "webpack-merge": "^4.1.0",
diff --git a/spec/javascript/.eslintrc.yml b/spec/javascript/.eslintrc.yml
new file mode 100644
index 000000000..6db2a46c5
--- /dev/null
+++ b/spec/javascript/.eslintrc.yml
@@ -0,0 +1,3 @@
+---
+env:
+  mocha: true
diff --git a/spec/javascript/components/loading_indicator.test.js b/spec/javascript/components/loading_indicator.test.js
deleted file mode 100644
index 0859dd192..000000000
--- a/spec/javascript/components/loading_indicator.test.js
+++ /dev/null
@@ -1,8 +0,0 @@
-import { expect } from 'chai';
-import { shallow } from 'enzyme';
-import React from 'react';
-import LoadingIndicator from '../../../app/javascript/mastodon/components/loading_indicator';
-
-describe('<LoadingIndicator />', () => {
-
-});
diff --git a/spec/models/tag_spec.rb b/spec/models/tag_spec.rb
index 2496946cb..7c574eabe 100644
--- a/spec/models/tag_spec.rb
+++ b/spec/models/tag_spec.rb
@@ -11,6 +11,10 @@ RSpec.describe Tag, type: :model do
     it 'does not match URLs with hashtag-like anchors' do
       expect(subject.match('https://en.wikipedia.org/wiki/Ghostbusters_(song)#Lawsuit')).to be_nil
     end
+
+    it 'matches #aesthetic' do
+      expect(subject.match('this is #aesthetic')).to_not be_nil
+    end
   end
 
   describe '.search_for' do
diff --git a/storybook/config.js b/storybook/config.js
index 1078059a7..87479560f 100644
--- a/storybook/config.js
+++ b/storybook/config.js
@@ -1,5 +1,4 @@
 import { configure } from '@storybook/react';
-import React from 'react';
 import { addLocaleData } from 'react-intl';
 import en from 'react-intl/locale-data/en';
 import '../app/javascript/styles/application.scss';
diff --git a/storybook/stories/character_counter.story.js b/storybook/stories/character_counter.story.js
index 15a401a25..39d9afb56 100644
--- a/storybook/stories/character_counter.story.js
+++ b/storybook/stories/character_counter.story.js
@@ -1,6 +1,5 @@
 import React from 'react';
 import { storiesOf } from '@storybook/react';
-import { action } from '@storybook/addon-actions';
 import CharacterCounter from 'mastodon/features/compose/components/character_counter';
 
 storiesOf('CharacterCounter', module)
diff --git a/storybook/stories/loading_indicator.story.js b/storybook/stories/loading_indicator.story.js
index 3e12f61ca..6ee822758 100644
--- a/storybook/stories/loading_indicator.story.js
+++ b/storybook/stories/loading_indicator.story.js
@@ -1,7 +1,6 @@
 import React from 'react';
 import { IntlProvider } from 'react-intl';
 import { storiesOf } from '@storybook/react';
-import { action } from '@storybook/addon-actions';
 import en from 'mastodon/locales/en.json';
 import LoadingIndicator from 'mastodon/components/loading_indicator';
 
diff --git a/streaming/index.js b/streaming/index.js
index 5afdd5961..fb23be34d 100644
--- a/streaming/index.js
+++ b/streaming/index.js
@@ -242,7 +242,7 @@ const startWorker = (workerId) => {
     accountFromRequest(req, next);
   };
 
-  const errorMiddleware = (err, req, res, next) => {
+  const errorMiddleware = (err, req, res) => {
     log.error(req.requestId, err.toString());
     res.writeHead(err.statusCode || 500, { 'Content-Type': 'application/json' });
     res.end(JSON.stringify({ error: err.statusCode ? err.toString() : 'An unexpected error occurred' }));
@@ -366,7 +366,7 @@ const startWorker = (workerId) => {
       }
     });
 
-    ws.on('error', e => {
+    ws.on('error', () => {
       log.verbose(req.requestId, `Ending stream for ${req.accountId}`);
       unsubscribe(id, listener);
       if (closeHandler) {
@@ -443,7 +443,7 @@ const startWorker = (workerId) => {
     }
   });
 
-  const wsInterval = setInterval(() => {
+  setInterval(() => {
     wss.clients.forEach(ws => {
       if (ws.isAlive === false) {
         ws.terminate();
diff --git a/yarn.lock b/yarn.lock
index 2eb1a5c75..85de4b546 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -192,6 +192,10 @@ ajv-keywords@^1.0.0, ajv-keywords@^1.1.1:
   version "1.5.1"
   resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c"
 
+ajv-keywords@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.0.tgz#a296e17f7bfae7c1ce4f7e0de53d29cb32162df0"
+
 ajv@^4.7.0, ajv@^4.9.1:
   version "4.11.8"
   resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536"
@@ -199,7 +203,7 @@ ajv@^4.7.0, ajv@^4.9.1:
     co "^4.6.0"
     json-stable-stringify "^1.0.1"
 
-ajv@^5.0.0:
+ajv@^5.0.0, ajv@^5.1.5:
   version "5.1.5"
   resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.1.5.tgz#8734931b601f00d4feef7c65738d77d1b65d1f68"
   dependencies:
@@ -3944,7 +3948,7 @@ loader-utils@^0.2.16:
     json5 "^0.5.0"
     object-assign "^4.0.1"
 
-loader-utils@^1.0.0, loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.x:
+loader-utils@^1.0.0, loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.x:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd"
   dependencies:
@@ -6681,7 +6685,7 @@ sugarss@^1.0.0:
   dependencies:
     postcss "^6.0.0"
 
-supports-color@3.1.2, supports-color@^3.1.0, supports-color@^3.1.1:
+supports-color@3.1.2, supports-color@^3.1.1:
   version "3.1.2"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5"
   dependencies:
@@ -6691,7 +6695,7 @@ supports-color@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
 
-supports-color@^3.2.3:
+supports-color@^3.1.0, supports-color@^3.2.3:
   version "3.2.3"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6"
   dependencies:
@@ -6879,10 +6883,27 @@ uglify-js@^2.8.27:
   optionalDependencies:
     uglify-to-browserify "~1.0.0"
 
+uglify-js@^2.8.29:
+  version "2.8.29"
+  resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd"
+  dependencies:
+    source-map "~0.5.1"
+    yargs "~3.10.0"
+  optionalDependencies:
+    uglify-to-browserify "~1.0.0"
+
 uglify-to-browserify@~1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7"
 
+uglifyjs-webpack-plugin@^0.4.4:
+  version "0.4.6"
+  resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-0.4.6.tgz#b951f4abb6bd617e66f63eb891498e391763e309"
+  dependencies:
+    source-map "^0.5.6"
+    uglify-js "^2.8.29"
+    webpack-sources "^1.0.1"
+
 uid-number@^0.0.6:
   version "0.0.6"
   resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81"
@@ -7164,6 +7185,33 @@ webpack@^2.5.1:
     webpack-sources "^0.2.3"
     yargs "^6.0.0"
 
+webpack@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.0.0.tgz#ee9bcebf21247f7153cb410168cab45e3a59d4d7"
+  dependencies:
+    acorn "^5.0.0"
+    acorn-dynamic-import "^2.0.0"
+    ajv "^5.1.5"
+    ajv-keywords "^2.0.0"
+    async "^2.1.2"
+    enhanced-resolve "^3.0.0"
+    escope "^3.6.0"
+    interpret "^1.0.0"
+    json-loader "^0.5.4"
+    json5 "^0.5.1"
+    loader-runner "^2.3.0"
+    loader-utils "^1.1.0"
+    memory-fs "~0.4.1"
+    mkdirp "~0.5.0"
+    node-libs-browser "^2.0.0"
+    source-map "^0.5.3"
+    supports-color "^3.1.0"
+    tapable "~0.2.5"
+    uglifyjs-webpack-plugin "^0.4.4"
+    watchpack "^1.3.1"
+    webpack-sources "^1.0.1"
+    yargs "^6.0.0"
+
 websocket-driver@>=0.5.1:
   version "0.6.5"
   resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.6.5.tgz#5cb2556ceb85f4373c6d8238aa691c8454e13a36"