about summary refs log tree commit diff
path: root/app/javascript/flavours/glitch/features/compose
diff options
context:
space:
mode:
authorkibigo! <marrus-sh@users.noreply.github.com>2017-12-23 22:16:45 -0800
committerkibigo! <marrus-sh@users.noreply.github.com>2018-01-04 18:21:59 -0800
commit924ffe81d477a8cf890c8117efb94b908760bccc (patch)
treeacefef7362929f4495424fbb037c3be59cca318f /app/javascript/flavours/glitch/features/compose
parentfc884d015a1a2d6c31976af3d63039390fa15939 (diff)
WIPgit status <Compose> Refactor; <Composer> ed.
Diffstat (limited to 'app/javascript/flavours/glitch/features/compose')
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/advanced_options.js62
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/advanced_options_toggle.js35
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/attach_options.js131
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/autosuggest_account.js24
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/character_counter.js25
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/compose_form.js286
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/dropdown.js77
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/emoji_picker_dropdown.js376
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/navigation_bar.js38
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.js200
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/reply_indicator.js67
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/search.js129
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/search_results.js65
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/text_icon_button.js29
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/upload.js96
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/upload_button.js77
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/upload_form.js29
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/upload_progress.js42
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/warning.js26
-rw-r--r--app/javascript/flavours/glitch/features/compose/containers/advanced_options_container.js20
-rw-r--r--app/javascript/flavours/glitch/features/compose/containers/autosuggest_account_container.js15
-rw-r--r--app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js71
-rw-r--r--app/javascript/flavours/glitch/features/compose/containers/emoji_picker_dropdown_container.js82
-rw-r--r--app/javascript/flavours/glitch/features/compose/containers/navigation_container.js11
-rw-r--r--app/javascript/flavours/glitch/features/compose/containers/privacy_dropdown_container.js24
-rw-r--r--app/javascript/flavours/glitch/features/compose/containers/reply_indicator_container.js24
-rw-r--r--app/javascript/flavours/glitch/features/compose/containers/search_container.js35
-rw-r--r--app/javascript/flavours/glitch/features/compose/containers/search_results_container.js8
-rw-r--r--app/javascript/flavours/glitch/features/compose/containers/sensitive_button_container.js71
-rw-r--r--app/javascript/flavours/glitch/features/compose/containers/spoiler_button_container.js25
-rw-r--r--app/javascript/flavours/glitch/features/compose/containers/upload_button_container.js18
-rw-r--r--app/javascript/flavours/glitch/features/compose/containers/upload_container.js21
-rw-r--r--app/javascript/flavours/glitch/features/compose/containers/upload_form_container.js8
-rw-r--r--app/javascript/flavours/glitch/features/compose/containers/upload_progress_container.js9
-rw-r--r--app/javascript/flavours/glitch/features/compose/containers/warning_container.js24
-rw-r--r--app/javascript/flavours/glitch/features/compose/index.js126
36 files changed, 0 insertions, 2406 deletions
diff --git a/app/javascript/flavours/glitch/features/compose/components/advanced_options.js b/app/javascript/flavours/glitch/features/compose/components/advanced_options.js
deleted file mode 100644
index 045bad2e5..000000000
--- a/app/javascript/flavours/glitch/features/compose/components/advanced_options.js
+++ /dev/null
@@ -1,62 +0,0 @@
-//  Package imports.
-import React from 'react';
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import { injectIntl, defineMessages } from 'react-intl';
-
-//  Our imports.
-import ComposeAdvancedOptionsToggle from './advanced_options_toggle';
-import ComposeDropdown from './dropdown';
-
-const messages = defineMessages({
-  local_only_short            :
-    { id: 'advanced-options.local-only.short', defaultMessage: 'Local-only' },
-  local_only_long             :
-    { id: 'advanced-options.local-only.long', defaultMessage: 'Do not post to other instances' },
-  advanced_options_icon_title :
-    { id: 'advanced_options.icon_title', defaultMessage: 'Advanced options' },
-});
-
-@injectIntl
-export default class ComposeAdvancedOptions extends React.PureComponent {
-
-  static propTypes = {
-    values   : ImmutablePropTypes.contains({
-      do_not_federate : PropTypes.bool.isRequired,
-    }).isRequired,
-    onChange : PropTypes.func.isRequired,
-    intl     : PropTypes.object.isRequired,
-  };
-
-  render () {
-    const { intl, values } = this.props;
-    const options = [
-      { icon: 'wifi', shortText: messages.local_only_short, longText: messages.local_only_long, name: 'do_not_federate' },
-    ];
-    const anyEnabled = values.some((enabled) => enabled);
-
-    const optionElems = options.map((option) => {
-      return (
-        <ComposeAdvancedOptionsToggle
-          onChange={this.props.onChange}
-          active={values.get(option.name)}
-          key={option.name}
-          name={option.name}
-          shortText={intl.formatMessage(option.shortText)}
-          longText={intl.formatMessage(option.longText)}
-        />
-      );
-    });
-
-    return (
-      <ComposeDropdown
-        title={intl.formatMessage(messages.advanced_options_icon_title)}
-        icon='home'
-        highlight={anyEnabled}
-      >
-        {optionElems}
-      </ComposeDropdown>
-    );
-  }
-
-}
diff --git a/app/javascript/flavours/glitch/features/compose/components/advanced_options_toggle.js b/app/javascript/flavours/glitch/features/compose/components/advanced_options_toggle.js
deleted file mode 100644
index 98b3b6a44..000000000
--- a/app/javascript/flavours/glitch/features/compose/components/advanced_options_toggle.js
+++ /dev/null
@@ -1,35 +0,0 @@
-//  Package imports.
-import React from 'react';
-import PropTypes from 'prop-types';
-import Toggle from 'react-toggle';
-
-export default class ComposeAdvancedOptionsToggle extends React.PureComponent {
-
-  static propTypes = {
-    onChange: PropTypes.func.isRequired,
-    active: PropTypes.bool.isRequired,
-    name: PropTypes.string.isRequired,
-    shortText: PropTypes.string.isRequired,
-    longText: PropTypes.string.isRequired,
-  }
-
-  onToggle = () => {
-    this.props.onChange(this.props.name);
-  }
-
-  render() {
-    const { active, shortText, longText } = this.props;
-    return (
-      <div role='button' tabIndex='0' className='advanced-options-dropdown__option' onClick={this.onToggle}>
-        <div className='advanced-options-dropdown__option__toggle'>
-          <Toggle checked={active} onChange={this.onToggle} />
-        </div>
-        <div className='advanced-options-dropdown__option__content'>
-          <strong>{shortText}</strong>
-          {longText}
-        </div>
-      </div>
-    );
-  }
-
-}
diff --git a/app/javascript/flavours/glitch/features/compose/components/attach_options.js b/app/javascript/flavours/glitch/features/compose/components/attach_options.js
deleted file mode 100644
index 6c7a1f55f..000000000
--- a/app/javascript/flavours/glitch/features/compose/components/attach_options.js
+++ /dev/null
@@ -1,131 +0,0 @@
-//  Package imports  //
-import React from 'react';
-import PropTypes from 'prop-types';
-import { connect } from 'react-redux';
-import { injectIntl, defineMessages } from 'react-intl';
-
-//  Our imports  //
-import ComposeDropdown from './dropdown';
-import { uploadCompose } from 'flavours/glitch/actions/compose';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-import { openModal } from 'flavours/glitch/actions/modal';
-
-const messages = defineMessages({
-  upload :
-    { id: 'compose.attach.upload', defaultMessage: 'Upload a file' },
-  doodle :
-    { id: 'compose.attach.doodle', defaultMessage: 'Draw something' },
-  attach :
-    { id: 'compose.attach', defaultMessage: 'Attach...' },
-});
-
-const mapStateToProps = state => ({
-  // This horrible expression is copied from vanilla upload_button_container
-  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']),
-  acceptContentTypes: state.getIn(['media_attachments', 'accept_content_types']),
-});
-
-const mapDispatchToProps = dispatch => ({
-  onSelectFile (files) {
-    dispatch(uploadCompose(files));
-  },
-  onOpenDoodle () {
-    dispatch(openModal('DOODLE', { noEsc: true }));
-  },
-});
-
-@injectIntl
-@connect(mapStateToProps, mapDispatchToProps)
-export default class ComposeAttachOptions extends ImmutablePureComponent {
-
-  static propTypes = {
-    intl     : PropTypes.object.isRequired,
-    resetFileKey: PropTypes.number,
-    acceptContentTypes: ImmutablePropTypes.listOf(PropTypes.string).isRequired,
-    disabled: PropTypes.bool,
-    onSelectFile: PropTypes.func.isRequired,
-    onOpenDoodle: PropTypes.func.isRequired,
-  };
-
-  handleItemClick = bt => {
-    if (bt === 'upload') {
-      this.fileElement.click();
-    }
-
-    if (bt === 'doodle') {
-      this.props.onOpenDoodle();
-    }
-
-    this.dropdown.setState({ open: false });
-  };
-
-  handleFileChange = (e) => {
-    if (e.target.files.length > 0) {
-      this.props.onSelectFile(e.target.files);
-    }
-  }
-
-  setFileRef = (c) => {
-    this.fileElement = c;
-  }
-
-  setDropdownRef = (c) => {
-    this.dropdown = c;
-  }
-
-  render () {
-    const { intl, resetFileKey, disabled, acceptContentTypes } = this.props;
-
-    const options = [
-      { icon: 'cloud-upload', text: messages.upload, name: 'upload' },
-      { icon: 'paint-brush', text: messages.doodle, name: 'doodle' },
-    ];
-
-    const optionElems = options.map((item) => {
-      const hdl = () => this.handleItemClick(item.name);
-      return (
-        <div
-          role='button'
-          tabIndex='0'
-          key={item.name}
-          onClick={hdl}
-          className='privacy-dropdown__option'
-        >
-          <div className='privacy-dropdown__option__icon'>
-            <i className={`fa fa-fw fa-${item.icon}`} />
-          </div>
-
-          <div className='privacy-dropdown__option__content'>
-            <strong>{intl.formatMessage(item.text)}</strong>
-          </div>
-        </div>
-      );
-    });
-
-    return (
-      <div>
-        <ComposeDropdown
-          title={intl.formatMessage(messages.attach)}
-          icon='paperclip'
-          disabled={disabled}
-          ref={this.setDropdownRef}
-        >
-          {optionElems}
-        </ComposeDropdown>
-        <input
-          key={resetFileKey}
-          ref={this.setFileRef}
-          type='file'
-          multiple={false}
-          accept={acceptContentTypes.toArray().join(',')}
-          onChange={this.handleFileChange}
-          disabled={disabled}
-          style={{ display: 'none' }}
-        />
-      </div>
-    );
-  }
-
-}
diff --git a/app/javascript/flavours/glitch/features/compose/components/autosuggest_account.js b/app/javascript/flavours/glitch/features/compose/components/autosuggest_account.js
deleted file mode 100644
index 3d474af30..000000000
--- a/app/javascript/flavours/glitch/features/compose/components/autosuggest_account.js
+++ /dev/null
@@ -1,24 +0,0 @@
-import React from 'react';
-import Avatar from 'flavours/glitch/components/avatar';
-import DisplayName from 'flavours/glitch/components/display_name';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-
-export default class AutosuggestAccount extends ImmutablePureComponent {
-
-  static propTypes = {
-    account: ImmutablePropTypes.map.isRequired,
-  };
-
-  render () {
-    const { account } = this.props;
-
-    return (
-      <div className='autosuggest-account'>
-        <div className='autosuggest-account-icon'><Avatar account={account} size={18} /></div>
-        <DisplayName account={account} />
-      </div>
-    );
-  }
-
-}
diff --git a/app/javascript/flavours/glitch/features/compose/components/character_counter.js b/app/javascript/flavours/glitch/features/compose/components/character_counter.js
deleted file mode 100644
index 0ecfc9141..000000000
--- a/app/javascript/flavours/glitch/features/compose/components/character_counter.js
+++ /dev/null
@@ -1,25 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import { length } from 'stringz';
-
-export default class CharacterCounter extends React.PureComponent {
-
-  static propTypes = {
-    text: PropTypes.string.isRequired,
-    max: PropTypes.number.isRequired,
-  };
-
-  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);
-  }
-
-}
diff --git a/app/javascript/flavours/glitch/features/compose/components/compose_form.js b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
deleted file mode 100644
index 67ce935f4..000000000
--- a/app/javascript/flavours/glitch/features/compose/components/compose_form.js
+++ /dev/null
@@ -1,286 +0,0 @@
-import React from 'react';
-import CharacterCounter from './character_counter';
-import Button from 'flavours/glitch/components/button';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import ReplyIndicatorContainer from '../containers/reply_indicator_container';
-import AutosuggestTextarea from 'flavours/glitch/components/autosuggest_textarea';
-import { defineMessages, injectIntl } from 'react-intl';
-import Collapsable from 'flavours/glitch/components/collapsable';
-import SpoilerButtonContainer from '../containers/spoiler_button_container';
-import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
-import ComposeAdvancedOptionsContainer from '../containers/advanced_options_container';
-import SensitiveButtonContainer from '../containers/sensitive_button_container';
-import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container';
-import UploadFormContainer from '../containers/upload_form_container';
-import WarningContainer from '../containers/warning_container';
-import { isMobile } from 'flavours/glitch/util/is_mobile';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-import { length } from 'stringz';
-import { countableText } from 'flavours/glitch/util/counter';
-import ComposeAttachOptions from './attach_options';
-import initialState from 'flavours/glitch/util/initial_state';
-
-const maxChars = initialState.max_toot_chars;
-
-const messages = defineMessages({
-  placeholder: { id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' },
-  spoiler_placeholder: { id: 'compose_form.spoiler_placeholder', defaultMessage: 'Write your warning here' },
-  publish: { id: 'compose_form.publish', defaultMessage: 'Toot' },
-  publishLoud: { id: 'compose_form.publish_loud', defaultMessage: '{publish}!' },
-});
-
-@injectIntl
-export default class ComposeForm extends ImmutablePureComponent {
-
-  static propTypes = {
-    intl: PropTypes.object.isRequired,
-    text: PropTypes.string.isRequired,
-    suggestion_token: PropTypes.string,
-    suggestions: ImmutablePropTypes.list,
-    spoiler: PropTypes.bool,
-    privacy: PropTypes.string,
-    advanced_options: ImmutablePropTypes.contains({
-      do_not_federate: PropTypes.bool,
-    }),
-    spoiler_text: PropTypes.string,
-    focusDate: PropTypes.instanceOf(Date),
-    preselectDate: PropTypes.instanceOf(Date),
-    is_submitting: PropTypes.bool,
-    is_uploading: PropTypes.bool,
-    onChange: PropTypes.func.isRequired,
-    onSubmit: PropTypes.func.isRequired,
-    onClearSuggestions: PropTypes.func.isRequired,
-    onFetchSuggestions: PropTypes.func.isRequired,
-    onPrivacyChange: PropTypes.func.isRequired,
-    onSuggestionSelected: PropTypes.func.isRequired,
-    onChangeSpoilerText: PropTypes.func.isRequired,
-    onPaste: PropTypes.func.isRequired,
-    onPickEmoji: PropTypes.func.isRequired,
-    showSearch: PropTypes.bool,
-    settings : ImmutablePropTypes.map.isRequired,
-  };
-
-  static defaultProps = {
-    showSearch: false,
-  };
-
-  handleChange = (e) => {
-    this.props.onChange(e.target.value);
-  }
-
-  handleKeyDown = (e) => {
-    if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
-      this.handleSubmit();
-    }
-  }
-
-  handleSubmit2 = () => {
-    this.props.onPrivacyChange(this.props.settings.get('side_arm'));
-    this.handleSubmit();
-  }
-
-  handleSubmit = () => {
-    if (this.props.text !== this.autosuggestTextarea.textarea.value) {
-      // Something changed the text inside the textarea (e.g. browser extensions like Grammarly)
-      // Update the state to match the current text
-      this.props.onChange(this.autosuggestTextarea.textarea.value);
-    }
-
-    this.props.onSubmit();
-  }
-
-  onSuggestionsClearRequested = () => {
-    this.props.onClearSuggestions();
-  }
-
-  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();
-    } else if(prevProps.is_submitting && !this.props.is_submitting) {
-      this.autosuggestTextarea.textarea.focus();
-    }
-  }
-
-  setAutosuggestTextarea = (c) => {
-    this.autosuggestTextarea = c;
-  }
-
-  handleEmojiPick = (data) => {
-    const position     = this.autosuggestTextarea.textarea.selectionStart;
-    const emojiChar    = data.native;
-    this._restoreCaret = position + emojiChar.length + 1;
-    this.props.onPickEmoji(position, data);
-  }
-
-  render () {
-    const { intl, onPaste, showSearch } = this.props;
-    const disabled = this.props.is_submitting;
-    const maybeEye = (this.props.advanced_options && this.props.advanced_options.do_not_federate) ? ' 👁️' : '';
-    const text     = [this.props.spoiler_text, countableText(this.props.text), maybeEye].join('');
-
-    const secondaryVisibility = this.props.settings.get('side_arm');
-    let showSideArm = secondaryVisibility !== 'none';
-
-    let publishText = '';
-    let publishText2 = '';
-    let title = '';
-    let title2 = '';
-
-    const privacyIcons = {
-      none: '',
-      public: 'globe',
-      unlisted: 'unlock-alt',
-      private: 'lock',
-      direct: 'envelope',
-    };
-
-    title = `${intl.formatMessage(messages.publish)}: ${intl.formatMessage({ id: `privacy.${this.props.privacy}.short` })}`;
-
-    if (showSideArm) {
-      // Enhanced behavior with dual toot buttons
-      publishText = (
-        <span>
-          {
-            <i
-              className={`fa fa-${privacyIcons[this.props.privacy]}`}
-              style={{ paddingRight: '5px' }}
-            />
-          }{intl.formatMessage(messages.publish)}
-        </span>
-      );
-
-      title2 = `${intl.formatMessage(messages.publish)}: ${intl.formatMessage({ id: `privacy.${secondaryVisibility}.short` })}`;
-      publishText2 = (
-        <i
-          className={`fa fa-${privacyIcons[secondaryVisibility]}`}
-          aria-label={title2}
-        />
-      );
-    } else {
-      // Original vanilla behavior - no icon if public or unlisted
-      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 = this.props.privacy !== 'unlisted' ? intl.formatMessage(messages.publishLoud, { publish: intl.formatMessage(messages.publish) }) : intl.formatMessage(messages.publish);
-      }
-    }
-
-    const submitDisabled = disabled || this.props.is_uploading || length(text) > maxChars || (text.length !== 0 && text.trim().length === 0);
-
-    return (
-      <div className='compose-form'>
-        <Collapsable isVisible={this.props.spoiler} fullHeight={50}>
-          <div className='spoiler-input'>
-            <label>
-              <span style={{ display: 'none' }}>{intl.formatMessage(messages.spoiler_placeholder)}</span>
-              <input placeholder={intl.formatMessage(messages.spoiler_placeholder)} value={this.props.spoiler_text} onChange={this.handleChangeSpoilerText} onKeyDown={this.handleKeyDown} type='text' className='spoiler-input__input'  id='cw-spoiler-input' />
-            </label>
-          </div>
-        </Collapsable>
-
-        <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}
-            autoFocus={!showSearch && !isMobile(window.innerWidth)}
-          />
-
-          <EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} />
-        </div>
-
-        <div className='compose-form__modifiers'>
-          <UploadFormContainer />
-        </div>
-
-        <div className='compose-form__buttons'>
-          <ComposeAttachOptions />
-          <SensitiveButtonContainer />
-          <div className='compose-form__buttons-separator' />
-          <PrivacyDropdownContainer />
-          <SpoilerButtonContainer />
-          <ComposeAdvancedOptionsContainer />
-        </div>
-
-        <div className='compose-form__publish'>
-          <div className='character-counter__wrapper'><CharacterCounter max={maxChars} text={text} /></div>
-          <div className='compose-form__publish-button-wrapper'>
-            {
-              showSideArm ?
-                <Button
-                  className='compose-form__publish__side-arm'
-                  text={publishText2}
-                  title={title2}
-                  onClick={this.handleSubmit2}
-                  disabled={submitDisabled}
-                /> : ''
-            }
-            <Button
-              className='compose-form__publish__primary'
-              text={publishText}
-              title={title}
-              onClick={this.handleSubmit}
-              disabled={submitDisabled}
-            />
-          </div>
-        </div>
-      </div>
-    );
-  }
-
-}
diff --git a/app/javascript/flavours/glitch/features/compose/components/dropdown.js b/app/javascript/flavours/glitch/features/compose/components/dropdown.js
deleted file mode 100644
index 1b0000fb7..000000000
--- a/app/javascript/flavours/glitch/features/compose/components/dropdown.js
+++ /dev/null
@@ -1,77 +0,0 @@
-//  Package imports.
-import React from 'react';
-import PropTypes from 'prop-types';
-
-//  Our imports.
-import IconButton from 'flavours/glitch/components/icon_button';
-
-const iconStyle = {
-  height     : null,
-  lineHeight : '27px',
-};
-
-export default class ComposeDropdown extends React.PureComponent {
-
-  static propTypes = {
-    title: PropTypes.string.isRequired,
-    icon: PropTypes.string,
-    highlight: PropTypes.bool,
-    disabled: PropTypes.bool,
-    children: PropTypes.arrayOf(PropTypes.node).isRequired,
-  };
-
-  state = {
-    open: false,
-  };
-
-  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);
-  }
-
-  onToggleDropdown = () => {
-    if (this.props.disabled) return;
-    this.setState({ open: !this.state.open });
-  };
-
-  setRef = (c) => {
-    this.node = c;
-  };
-
-  render () {
-    const { open } = this.state;
-    let { highlight, title, icon, disabled } = this.props;
-
-    if (!icon) icon = 'ellipsis-h';
-
-    return (
-      <div ref={this.setRef} className={`advanced-options-dropdown ${open ?  'open' : ''} ${highlight ? 'active' : ''} `}>
-        <div className='advanced-options-dropdown__value'>
-          <IconButton
-            className={'inverted'}
-            title={title}
-            icon={icon} active={open || highlight}
-            size={18}
-            style={iconStyle}
-            disabled={disabled}
-            onClick={this.onToggleDropdown}
-          />
-        </div>
-        <div className='advanced-options-dropdown__dropdown'>
-          {this.props.children}
-        </div>
-      </div>
-    );
-  }
-
-}
diff --git a/app/javascript/flavours/glitch/features/compose/components/emoji_picker_dropdown.js b/app/javascript/flavours/glitch/features/compose/components/emoji_picker_dropdown.js
deleted file mode 100644
index cf89f91d3..000000000
--- a/app/javascript/flavours/glitch/features/compose/components/emoji_picker_dropdown.js
+++ /dev/null
@@ -1,376 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import { defineMessages, injectIntl } from 'react-intl';
-import { EmojiPicker as EmojiPickerAsync } from 'flavours/glitch/util/async-components';
-import Overlay from 'react-overlays/lib/Overlay';
-import classNames from 'classnames';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import detectPassiveEvents from 'detect-passive-events';
-import { buildCustomEmojis } from 'flavours/glitch/util/emoji';
-
-const messages = defineMessages({
-  emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' },
-  emoji_search: { id: 'emoji_button.search', defaultMessage: 'Search...' },
-  emoji_not_found: { id: 'emoji_button.not_found', defaultMessage: 'No emojos!! (╯°□°)╯︵ ┻━┻' },
-  custom: { id: 'emoji_button.custom', defaultMessage: 'Custom' },
-  recent: { id: 'emoji_button.recent', defaultMessage: 'Frequently used' },
-  search_results: { id: 'emoji_button.search_results', defaultMessage: 'Search results' },
-  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 assetHost = process.env.CDN_HOST || '';
-let EmojiPicker, Emoji; // load asynchronously
-
-const backgroundImageFn = () => `${assetHost}/emoji/sheet.png`;
-const listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false;
-
-const categoriesSort = [
-  'recent',
-  'custom',
-  'people',
-  'nature',
-  'foods',
-  'activity',
-  'places',
-  'objects',
-  'symbols',
-  'flags',
-];
-
-class ModifierPickerMenu extends React.PureComponent {
-
-  static propTypes = {
-    active: PropTypes.bool,
-    onSelect: PropTypes.func.isRequired,
-    onClose: PropTypes.func.isRequired,
-  };
-
-  handleClick = e => {
-    this.props.onSelect(e.currentTarget.getAttribute('data-index') * 1);
-  }
-
-  componentWillReceiveProps (nextProps) {
-    if (nextProps.active) {
-      this.attachListeners();
-    } else {
-      this.removeListeners();
-    }
-  }
-
-  componentWillUnmount () {
-    this.removeListeners();
-  }
-
-  handleDocumentClick = e => {
-    if (this.node && !this.node.contains(e.target)) {
-      this.props.onClose();
-    }
-  }
-
-  attachListeners () {
-    document.addEventListener('click', this.handleDocumentClick, false);
-    document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
-  }
-
-  removeListeners () {
-    document.removeEventListener('click', this.handleDocumentClick, false);
-    document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions);
-  }
-
-  setRef = c => {
-    this.node = c;
-  }
-
-  render () {
-    const { active } = this.props;
-
-    return (
-      <div className='emoji-picker-dropdown__modifiers__menu' style={{ display: active ? 'block' : 'none' }} ref={this.setRef}>
-        <button onClick={this.handleClick} data-index={1}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={1} backgroundImageFn={backgroundImageFn} /></button>
-        <button onClick={this.handleClick} data-index={2}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={2} backgroundImageFn={backgroundImageFn} /></button>
-        <button onClick={this.handleClick} data-index={3}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={3} backgroundImageFn={backgroundImageFn} /></button>
-        <button onClick={this.handleClick} data-index={4}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={4} backgroundImageFn={backgroundImageFn} /></button>
-        <button onClick={this.handleClick} data-index={5}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={5} backgroundImageFn={backgroundImageFn} /></button>
-        <button onClick={this.handleClick} data-index={6}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={6} backgroundImageFn={backgroundImageFn} /></button>
-      </div>
-    );
-  }
-
-}
-
-class ModifierPicker extends React.PureComponent {
-
-  static propTypes = {
-    active: PropTypes.bool,
-    modifier: PropTypes.number,
-    onChange: PropTypes.func,
-    onClose: PropTypes.func,
-    onOpen: PropTypes.func,
-  };
-
-  handleClick = () => {
-    if (this.props.active) {
-      this.props.onClose();
-    } else {
-      this.props.onOpen();
-    }
-  }
-
-  handleSelect = modifier => {
-    this.props.onChange(modifier);
-    this.props.onClose();
-  }
-
-  render () {
-    const { active, modifier } = this.props;
-
-    return (
-      <div className='emoji-picker-dropdown__modifiers'>
-        <Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={modifier} onClick={this.handleClick} backgroundImageFn={backgroundImageFn} />
-        <ModifierPickerMenu active={active} onSelect={this.handleSelect} onClose={this.props.onClose} />
-      </div>
-    );
-  }
-
-}
-
-@injectIntl
-class EmojiPickerMenu extends React.PureComponent {
-
-  static propTypes = {
-    custom_emojis: ImmutablePropTypes.list,
-    frequentlyUsedEmojis: PropTypes.arrayOf(PropTypes.string),
-    loading: PropTypes.bool,
-    onClose: PropTypes.func.isRequired,
-    onPick: PropTypes.func.isRequired,
-    style: PropTypes.object,
-    placement: PropTypes.string,
-    arrowOffsetLeft: PropTypes.string,
-    arrowOffsetTop: PropTypes.string,
-    intl: PropTypes.object.isRequired,
-    skinTone: PropTypes.number.isRequired,
-    onSkinTone: PropTypes.func.isRequired,
-  };
-
-  static defaultProps = {
-    style: {},
-    loading: true,
-    placement: 'bottom',
-    frequentlyUsedEmojis: [],
-  };
-
-  state = {
-    modifierOpen: false,
-  };
-
-  handleDocumentClick = e => {
-    if (this.node && !this.node.contains(e.target)) {
-      this.props.onClose();
-    }
-  }
-
-  componentDidMount () {
-    document.addEventListener('click', this.handleDocumentClick, false);
-    document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
-  }
-
-  componentWillUnmount () {
-    document.removeEventListener('click', this.handleDocumentClick, false);
-    document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions);
-  }
-
-  setRef = c => {
-    this.node = c;
-  }
-
-  getI18n = () => {
-    const { intl } = this.props;
-
-    return {
-      search: intl.formatMessage(messages.emoji_search),
-      notfound: intl.formatMessage(messages.emoji_not_found),
-      categories: {
-        search: intl.formatMessage(messages.search_results),
-        recent: intl.formatMessage(messages.recent),
-        people: intl.formatMessage(messages.people),
-        nature: intl.formatMessage(messages.nature),
-        foods: intl.formatMessage(messages.food),
-        activity: intl.formatMessage(messages.activity),
-        places: intl.formatMessage(messages.travel),
-        objects: intl.formatMessage(messages.objects),
-        symbols: intl.formatMessage(messages.symbols),
-        flags: intl.formatMessage(messages.flags),
-        custom: intl.formatMessage(messages.custom),
-      },
-    };
-  }
-
-  handleClick = emoji => {
-    if (!emoji.native) {
-      emoji.native = emoji.colons;
-    }
-
-    this.props.onClose();
-    this.props.onPick(emoji);
-  }
-
-  handleModifierOpen = () => {
-    this.setState({ modifierOpen: true });
-  }
-
-  handleModifierClose = () => {
-    this.setState({ modifierOpen: false });
-  }
-
-  handleModifierChange = modifier => {
-    this.props.onSkinTone(modifier);
-  }
-
-  render () {
-    const { loading, style, intl, custom_emojis, skinTone, frequentlyUsedEmojis } = this.props;
-
-    if (loading) {
-      return <div style={{ width: 299 }} />;
-    }
-
-    const title = intl.formatMessage(messages.emoji);
-    const { modifierOpen } = this.state;
-
-    return (
-      <div className={classNames('emoji-picker-dropdown__menu', { selecting: modifierOpen })} style={style} ref={this.setRef}>
-        <EmojiPicker
-          perLine={8}
-          emojiSize={22}
-          sheetSize={32}
-          custom={buildCustomEmojis(custom_emojis)}
-          color=''
-          emoji=''
-          set='twitter'
-          title={title}
-          i18n={this.getI18n()}
-          onClick={this.handleClick}
-          include={categoriesSort}
-          recent={frequentlyUsedEmojis}
-          skin={skinTone}
-          showPreview={false}
-          backgroundImageFn={backgroundImageFn}
-          emojiTooltip
-        />
-
-        <ModifierPicker
-          active={modifierOpen}
-          modifier={skinTone}
-          onOpen={this.handleModifierOpen}
-          onClose={this.handleModifierClose}
-          onChange={this.handleModifierChange}
-        />
-      </div>
-    );
-  }
-
-}
-
-@injectIntl
-export default class EmojiPickerDropdown extends React.PureComponent {
-
-  static propTypes = {
-    custom_emojis: ImmutablePropTypes.list,
-    frequentlyUsedEmojis: PropTypes.arrayOf(PropTypes.string),
-    intl: PropTypes.object.isRequired,
-    onPickEmoji: PropTypes.func.isRequired,
-    onSkinTone: PropTypes.func.isRequired,
-    skinTone: PropTypes.number.isRequired,
-  };
-
-  state = {
-    active: false,
-    loading: false,
-  };
-
-  setRef = (c) => {
-    this.dropdown = c;
-  }
-
-  onShowDropdown = () => {
-    this.setState({ active: true });
-
-    if (!EmojiPicker) {
-      this.setState({ loading: true });
-
-      EmojiPickerAsync().then(EmojiMart => {
-        EmojiPicker = EmojiMart.Picker;
-        Emoji       = EmojiMart.Emoji;
-
-        this.setState({ loading: false });
-      }).catch(() => {
-        this.setState({ loading: false });
-      });
-    }
-  }
-
-  onHideDropdown = () => {
-    this.setState({ active: false });
-  }
-
-  onToggle = (e) => {
-    if (!this.state.loading && (!e.key || e.key === 'Enter')) {
-      if (this.state.active) {
-        this.onHideDropdown();
-      } else {
-        this.onShowDropdown();
-      }
-    }
-  }
-
-  handleKeyDown = e => {
-    if (e.key === 'Escape') {
-      this.onHideDropdown();
-    }
-  }
-
-  setTargetRef = c => {
-    this.target = c;
-  }
-
-  findTarget = () => {
-    return this.target;
-  }
-
-  render () {
-    const { intl, onPickEmoji, onSkinTone, skinTone, frequentlyUsedEmojis } = this.props;
-    const title = intl.formatMessage(messages.emoji);
-    const { active, loading } = this.state;
-
-    return (
-      <div className='emoji-picker-dropdown' onKeyDown={this.handleKeyDown}>
-        <div ref={this.setTargetRef} className='emoji-button' title={title} aria-label={title} aria-expanded={active} role='button' onClick={this.onToggle} onKeyDown={this.onToggle} tabIndex={0}>
-          <img
-            className={classNames('emojione', { 'pulse-loading': active && loading })}
-            alt='🙂'
-            src={`${assetHost}/emoji/1f602.svg`}
-          />
-        </div>
-
-        <Overlay show={active} placement='bottom' target={this.findTarget}>
-          <EmojiPickerMenu
-            custom_emojis={this.props.custom_emojis}
-            loading={loading}
-            onClose={this.onHideDropdown}
-            onPick={onPickEmoji}
-            onSkinTone={onSkinTone}
-            skinTone={skinTone}
-            frequentlyUsedEmojis={frequentlyUsedEmojis}
-          />
-        </Overlay>
-      </div>
-    );
-  }
-
-}
diff --git a/app/javascript/flavours/glitch/features/compose/components/navigation_bar.js b/app/javascript/flavours/glitch/features/compose/components/navigation_bar.js
deleted file mode 100644
index 1b6d74123..000000000
--- a/app/javascript/flavours/glitch/features/compose/components/navigation_bar.js
+++ /dev/null
@@ -1,38 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import Avatar from 'flavours/glitch/components/avatar';
-import IconButton from 'flavours/glitch/components/icon_button';
-import Permalink from 'flavours/glitch/components/permalink';
-import { FormattedMessage } from 'react-intl';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-
-export default class NavigationBar extends ImmutablePureComponent {
-
-  static propTypes = {
-    account: ImmutablePropTypes.map.isRequired,
-    onClose: PropTypes.func.isRequired,
-  };
-
-  render () {
-    return (
-      <div className='navigation-bar'>
-        <Permalink href={this.props.account.get('url')} to={`/accounts/${this.props.account.get('id')}`}>
-          <span style={{ display: 'none' }}>{this.props.account.get('acct')}</span>
-          <Avatar account={this.props.account} 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>
-
-        <IconButton title='' icon='close' onClick={this.props.onClose} />
-      </div>
-    );
-  }
-
-}
diff --git a/app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.js b/app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.js
deleted file mode 100644
index 90f062f8f..000000000
--- a/app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.js
+++ /dev/null
@@ -1,200 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import { injectIntl, defineMessages } from 'react-intl';
-import IconButton from 'flavours/glitch/components/icon_button';
-import Overlay from 'react-overlays/lib/Overlay';
-import Motion from 'flavours/glitch/util/optional_motion';
-import spring from 'react-motion/lib/spring';
-import detectPassiveEvents from 'detect-passive-events';
-import classNames from 'classnames';
-
-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 listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false;
-
-class PrivacyDropdownMenu extends React.PureComponent {
-
-  static propTypes = {
-    style: PropTypes.object,
-    items: PropTypes.array.isRequired,
-    value: PropTypes.string.isRequired,
-    onClose: PropTypes.func.isRequired,
-    onChange: PropTypes.func.isRequired,
-  };
-
-  handleDocumentClick = e => {
-    if (this.node && !this.node.contains(e.target)) {
-      this.props.onClose();
-    }
-  }
-
-  handleClick = e => {
-    if (e.key === 'Escape') {
-      this.props.onClose();
-    } else if (!e.key || e.key === 'Enter') {
-      const value = e.currentTarget.getAttribute('data-index');
-
-      e.preventDefault();
-
-      this.props.onClose();
-      this.props.onChange(value);
-    }
-  }
-
-  componentDidMount () {
-    document.addEventListener('click', this.handleDocumentClick, false);
-    document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
-  }
-
-  componentWillUnmount () {
-    document.removeEventListener('click', this.handleDocumentClick, false);
-    document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions);
-  }
-
-  setRef = c => {
-    this.node = c;
-  }
-
-  render () {
-    const { style, items, value } = this.props;
-
-    return (
-      <Motion defaultStyle={{ opacity: 0, scaleX: 0.85, scaleY: 0.75 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}>
-        {({ opacity, scaleX, scaleY }) => (
-          <div className='privacy-dropdown__dropdown' style={{ ...style, opacity: opacity, transform: `scale(${scaleX}, ${scaleY})` }} ref={this.setRef}>
-            {items.map(item =>
-              <div role='button' tabIndex='0' key={item.value} data-index={item.value} onKeyDown={this.handleClick} onClick={this.handleClick} className={classNames('privacy-dropdown__option', { active: item.value === value })}>
-                <div className='privacy-dropdown__option__icon'>
-                  <i className={`fa fa-fw fa-${item.icon}`} />
-                </div>
-
-                <div className='privacy-dropdown__option__content'>
-                  <strong>{item.text}</strong>
-                  {item.meta}
-                </div>
-              </div>
-            )}
-          </div>
-        )}
-      </Motion>
-    );
-  }
-
-}
-
-@injectIntl
-export default class PrivacyDropdown extends React.PureComponent {
-
-  static propTypes = {
-    isUserTouching: PropTypes.func,
-    isModalOpen: PropTypes.bool.isRequired,
-    onModalOpen: PropTypes.func,
-    onModalClose: PropTypes.func,
-    value: PropTypes.string.isRequired,
-    onChange: PropTypes.func.isRequired,
-    intl: PropTypes.object.isRequired,
-  };
-
-  state = {
-    open: false,
-  };
-
-  handleToggle = () => {
-    if (this.props.isUserTouching()) {
-      if (this.state.open) {
-        this.props.onModalClose();
-      } else {
-        this.props.onModalOpen({
-          actions: this.options.map(option => ({ ...option, active: option.value === this.props.value })),
-          onClick: this.handleModalActionClick,
-        });
-      }
-    } else {
-      this.setState({ open: !this.state.open });
-    }
-  }
-
-  handleModalActionClick = (e) => {
-    e.preventDefault();
-
-    const { value } = this.options[e.currentTarget.getAttribute('data-index')];
-
-    this.props.onModalClose();
-    this.props.onChange(value);
-  }
-
-  handleKeyDown = e => {
-    switch(e.key) {
-    case 'Enter':
-      this.handleToggle();
-      break;
-    case 'Escape':
-      this.handleClose();
-      break;
-    }
-  }
-
-  handleClose = () => {
-    this.setState({ open: false });
-  }
-
-  handleChange = value => {
-    this.props.onChange(value);
-  }
-
-  componentWillMount () {
-    const { intl: { formatMessage } } = this.props;
-
-    this.options = [
-      { icon: 'globe', value: 'public', text: formatMessage(messages.public_short), meta: formatMessage(messages.public_long) },
-      { icon: 'unlock-alt', value: 'unlisted', text: formatMessage(messages.unlisted_short), meta: formatMessage(messages.unlisted_long) },
-      { icon: 'lock', value: 'private', text: formatMessage(messages.private_short), meta: formatMessage(messages.private_long) },
-      { icon: 'envelope', value: 'direct', text: formatMessage(messages.direct_short), meta: formatMessage(messages.direct_long) },
-    ];
-  }
-
-  render () {
-    const { value, intl } = this.props;
-    const { open } = this.state;
-
-    const valueOption = this.options.find(item => item.value === value);
-
-    return (
-      <div className={classNames('privacy-dropdown', { active: open })} onKeyDown={this.handleKeyDown}>
-        <div className={classNames('privacy-dropdown__value', { active: this.options.indexOf(valueOption) === 0 })}>
-          <IconButton
-            className='privacy-dropdown__value-icon'
-            icon={valueOption.icon}
-            title={intl.formatMessage(messages.change_privacy)}
-            size={18}
-            expanded={open}
-            active={open}
-            inverted
-            onClick={this.handleToggle}
-            style={{ height: null, lineHeight: '27px' }}
-          />
-        </div>
-
-        <Overlay show={open} placement='bottom' target={this}>
-          <PrivacyDropdownMenu
-            items={this.options}
-            value={value}
-            onClose={this.handleClose}
-            onChange={this.handleChange}
-          />
-        </Overlay>
-      </div>
-    );
-  }
-
-}
diff --git a/app/javascript/flavours/glitch/features/compose/components/reply_indicator.js b/app/javascript/flavours/glitch/features/compose/components/reply_indicator.js
deleted file mode 100644
index 3048d591b..000000000
--- a/app/javascript/flavours/glitch/features/compose/components/reply_indicator.js
+++ /dev/null
@@ -1,67 +0,0 @@
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import Avatar from 'flavours/glitch/components/avatar';
-import IconButton from 'flavours/glitch/components/icon_button';
-import DisplayName from 'flavours/glitch/components/display_name';
-import { defineMessages, injectIntl } from 'react-intl';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-import { isRtl } from 'flavours/glitch/util/rtl';
-
-const messages = defineMessages({
-  cancel: { id: 'reply_indicator.cancel', defaultMessage: 'Cancel' },
-});
-
-@injectIntl
-export default class ReplyIndicator extends ImmutablePureComponent {
-
-  static contextTypes = {
-    router: PropTypes.object,
-  };
-
-  static propTypes = {
-    status: ImmutablePropTypes.map,
-    onCancel: PropTypes.func.isRequired,
-    intl: PropTypes.object.isRequired,
-  };
-
-  handleClick = () => {
-    this.props.onCancel();
-  }
-
-  handleAccountClick = (e) => {
-    if (e.button === 0) {
-      e.preventDefault();
-      this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
-    }
-  }
-
-  render () {
-    const { status, intl } = this.props;
-
-    if (!status) {
-      return null;
-    }
-
-    const content = { __html: status.get('contentHtml') };
-    const style   = {
-      direction: isRtl(status.get('search_index')) ? 'rtl' : 'ltr',
-    };
-
-    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 account={status.get('account')} size={24} /></div>
-            <DisplayName account={status.get('account')} />
-          </a>
-        </div>
-
-        <div className='reply-indicator__content' style={style} dangerouslySetInnerHTML={content} />
-      </div>
-    );
-  }
-
-}
diff --git a/app/javascript/flavours/glitch/features/compose/components/search.js b/app/javascript/flavours/glitch/features/compose/components/search.js
deleted file mode 100644
index 1ce66b19d..000000000
--- a/app/javascript/flavours/glitch/features/compose/components/search.js
+++ /dev/null
@@ -1,129 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import Overlay from 'react-overlays/lib/Overlay';
-import Motion from 'flavours/glitch/util/optional_motion';
-import spring from 'react-motion/lib/spring';
-
-const messages = defineMessages({
-  placeholder: { id: 'search.placeholder', defaultMessage: 'Search' },
-});
-
-class SearchPopout extends React.PureComponent {
-
-  static propTypes = {
-    style: PropTypes.object,
-  };
-
-  render () {
-    const { style } = this.props;
-
-    return (
-      <div style={{ ...style, position: 'absolute', width: 285 }}>
-        <Motion defaultStyle={{ opacity: 0, scaleX: 0.85, scaleY: 0.75 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}>
-          {({ opacity, scaleX, scaleY }) => (
-            <div className='search-popout' style={{ opacity: opacity, transform: `scale(${scaleX}, ${scaleY})` }}>
-              <h4><FormattedMessage id='search_popout.search_format' defaultMessage='Advanced search format' /></h4>
-
-              <ul>
-                <li><em>#example</em> <FormattedMessage id='search_popout.tips.hashtag' defaultMessage='hashtag' /></li>
-                <li><em>@username@domain</em> <FormattedMessage id='search_popout.tips.user' defaultMessage='user' /></li>
-                <li><em>URL</em> <FormattedMessage id='search_popout.tips.user' defaultMessage='user' /></li>
-                <li><em>URL</em> <FormattedMessage id='search_popout.tips.status' defaultMessage='status' /></li>
-              </ul>
-
-              <FormattedMessage id='search_popout.tips.text' defaultMessage='Simple text returns matching display names, usernames and hashtags' />
-            </div>
-          )}
-        </Motion>
-      </div>
-    );
-  }
-
-}
-
-@injectIntl
-export default class Search extends React.PureComponent {
-
-  static 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,
-  };
-
-  state = {
-    expanded: false,
-  };
-
-  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();
-    } else if (e.key === 'Escape') {
-      document.querySelector('.ui').parentElement.focus();
-    }
-  }
-
-  noop () {
-
-  }
-
-  handleFocus = () => {
-    this.setState({ expanded: true });
-    this.props.onShow();
-  }
-
-  handleBlur = () => {
-    this.setState({ expanded: false });
-  }
-
-  render () {
-    const { intl, value, submitted } = this.props;
-    const { expanded } = this.state;
-    const hasValue = value.length > 0 || submitted;
-
-    return (
-      <div className='search'>
-        <label>
-          <span style={{ display: 'none' }}>{intl.formatMessage(messages.placeholder)}</span>
-          <input
-            className='search__input'
-            type='text'
-            placeholder={intl.formatMessage(messages.placeholder)}
-            value={value}
-            onChange={this.handleChange}
-            onKeyUp={this.handleKeyDown}
-            onFocus={this.handleFocus}
-            onBlur={this.handleBlur}
-          />
-        </label>
-
-        <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>
-
-        <Overlay show={expanded && !hasValue} placement='bottom' target={this}>
-          <SearchPopout />
-        </Overlay>
-      </div>
-    );
-  }
-
-}
diff --git a/app/javascript/flavours/glitch/features/compose/components/search_results.js b/app/javascript/flavours/glitch/features/compose/components/search_results.js
deleted file mode 100644
index 2a4818d4e..000000000
--- a/app/javascript/flavours/glitch/features/compose/components/search_results.js
+++ /dev/null
@@ -1,65 +0,0 @@
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import { FormattedMessage } from 'react-intl';
-import AccountContainer from 'flavours/glitch/containers/account_container';
-import StatusContainer from 'flavours/glitch/containers/status_container';
-import { Link } from 'react-router-dom';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-
-export default class SearchResults extends ImmutablePureComponent {
-
-  static propTypes = {
-    results: ImmutablePropTypes.map.isRequired,
-  };
-
-  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 key={hashtag} 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>
-    );
-  }
-
-}
diff --git a/app/javascript/flavours/glitch/features/compose/components/text_icon_button.js b/app/javascript/flavours/glitch/features/compose/components/text_icon_button.js
deleted file mode 100644
index 9c8ffab1f..000000000
--- a/app/javascript/flavours/glitch/features/compose/components/text_icon_button.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-
-export default class TextIconButton extends React.PureComponent {
-
-  static propTypes = {
-    label: PropTypes.string.isRequired,
-    title: PropTypes.string,
-    active: PropTypes.bool,
-    onClick: PropTypes.func.isRequired,
-    ariaControls: PropTypes.string,
-  };
-
-  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>
-    );
-  }
-
-}
diff --git a/app/javascript/flavours/glitch/features/compose/components/upload.js b/app/javascript/flavours/glitch/features/compose/components/upload.js
deleted file mode 100644
index a1fc93234..000000000
--- a/app/javascript/flavours/glitch/features/compose/components/upload.js
+++ /dev/null
@@ -1,96 +0,0 @@
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import PropTypes from 'prop-types';
-import IconButton from 'flavours/glitch/components/icon_button';
-import Motion from 'flavours/glitch/util/optional_motion';
-import spring from 'react-motion/lib/spring';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-import { defineMessages, injectIntl } from 'react-intl';
-import classNames from 'classnames';
-
-const messages = defineMessages({
-  undo: { id: 'upload_form.undo', defaultMessage: 'Undo' },
-  description: { id: 'upload_form.description', defaultMessage: 'Describe for the visually impaired' },
-});
-
-@injectIntl
-export default class Upload extends ImmutablePureComponent {
-
-  static propTypes = {
-    media: ImmutablePropTypes.map.isRequired,
-    intl: PropTypes.object.isRequired,
-    onUndo: PropTypes.func.isRequired,
-    onDescriptionChange: PropTypes.func.isRequired,
-  };
-
-  state = {
-    hovered: false,
-    focused: false,
-    dirtyDescription: null,
-  };
-
-  handleUndoClick = () => {
-    this.props.onUndo(this.props.media.get('id'));
-  }
-
-  handleInputChange = e => {
-    this.setState({ dirtyDescription: e.target.value });
-  }
-
-  handleMouseEnter = () => {
-    this.setState({ hovered: true });
-  }
-
-  handleMouseLeave = () => {
-    this.setState({ hovered: false });
-  }
-
-  handleInputFocus = () => {
-    this.setState({ focused: true });
-  }
-
-  handleInputBlur = () => {
-    const { dirtyDescription } = this.state;
-
-    this.setState({ focused: false, dirtyDescription: null });
-
-    if (dirtyDescription !== null) {
-      this.props.onDescriptionChange(this.props.media.get('id'), dirtyDescription);
-    }
-  }
-
-  render () {
-    const { intl, media } = this.props;
-    const active          = this.state.hovered || this.state.focused;
-    const description     = this.state.dirtyDescription || media.get('description') || '';
-
-    return (
-      <div className='compose-form__upload' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
-        <Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12 }) }}>
-          {({ scale }) => (
-            <div className='compose-form__upload-thumbnail' style={{ transform: `scale(${scale})`, backgroundImage: `url(${media.get('preview_url')})` }}>
-              <IconButton icon='times' title={intl.formatMessage(messages.undo)} size={36} onClick={this.handleUndoClick} />
-
-              <div className={classNames('compose-form__upload-description', { active })}>
-                <label>
-                  <span style={{ display: 'none' }}>{intl.formatMessage(messages.description)}</span>
-
-                  <input
-                    placeholder={intl.formatMessage(messages.description)}
-                    type='text'
-                    value={description}
-                    maxLength={420}
-                    onFocus={this.handleInputFocus}
-                    onChange={this.handleInputChange}
-                    onBlur={this.handleInputBlur}
-                  />
-                </label>
-              </div>
-            </div>
-          )}
-        </Motion>
-      </div>
-    );
-  }
-
-}
diff --git a/app/javascript/flavours/glitch/features/compose/components/upload_button.js b/app/javascript/flavours/glitch/features/compose/components/upload_button.js
deleted file mode 100644
index f06167a2a..000000000
--- a/app/javascript/flavours/glitch/features/compose/components/upload_button.js
+++ /dev/null
@@ -1,77 +0,0 @@
-import React from 'react';
-import IconButton from 'flavours/glitch/components/icon_button';
-import PropTypes from 'prop-types';
-import { defineMessages, injectIntl } from 'react-intl';
-import { connect } from 'react-redux';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-
-const messages = defineMessages({
-  upload: { id: 'upload_button.label', defaultMessage: 'Add media' },
-});
-
-const makeMapStateToProps = () => {
-  const mapStateToProps = state => ({
-    acceptContentTypes: state.getIn(['media_attachments', 'accept_content_types']),
-  });
-
-  return mapStateToProps;
-};
-
-const iconStyle = {
-  height: null,
-  lineHeight: '27px',
-};
-
-@connect(makeMapStateToProps)
-@injectIntl
-export default class UploadButton extends ImmutablePureComponent {
-
-  static propTypes = {
-    disabled: PropTypes.bool,
-    onSelectFile: PropTypes.func.isRequired,
-    style: PropTypes.object,
-    resetFileKey: PropTypes.number,
-    acceptContentTypes: ImmutablePropTypes.listOf(PropTypes.string).isRequired,
-    intl: PropTypes.object.isRequired,
-  };
-
-  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, acceptContentTypes } = 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} />
-        <label>
-          <span style={{ display: 'none' }}>{intl.formatMessage(messages.upload)}</span>
-          <input
-            key={resetFileKey}
-            ref={this.setRef}
-            type='file'
-            multiple={false}
-            accept={acceptContentTypes.toArray().join(',')}
-            onChange={this.handleChange}
-            disabled={disabled}
-            style={{ display: 'none' }}
-          />
-        </label>
-      </div>
-    );
-  }
-
-}
diff --git a/app/javascript/flavours/glitch/features/compose/components/upload_form.js b/app/javascript/flavours/glitch/features/compose/components/upload_form.js
deleted file mode 100644
index b7f112205..000000000
--- a/app/javascript/flavours/glitch/features/compose/components/upload_form.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import UploadProgressContainer from '../containers/upload_progress_container';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-import UploadContainer from '../containers/upload_container';
-
-export default class UploadForm extends ImmutablePureComponent {
-
-  static propTypes = {
-    mediaIds: ImmutablePropTypes.list.isRequired,
-  };
-
-  render () {
-    const { mediaIds } = this.props;
-
-    return (
-      <div className='compose-form__upload-wrapper'>
-        <UploadProgressContainer />
-
-        <div className='compose-form__uploads-wrapper'>
-          {mediaIds.map(id => (
-            <UploadContainer id={id} key={id} />
-          ))}
-        </div>
-      </div>
-    );
-  }
-
-}
diff --git a/app/javascript/flavours/glitch/features/compose/components/upload_progress.js b/app/javascript/flavours/glitch/features/compose/components/upload_progress.js
deleted file mode 100644
index 2a3b8ceb4..000000000
--- a/app/javascript/flavours/glitch/features/compose/components/upload_progress.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import Motion from 'flavours/glitch/util/optional_motion';
-import spring from 'react-motion/lib/spring';
-import { FormattedMessage } from 'react-intl';
-
-export default class UploadProgress extends React.PureComponent {
-
-  static propTypes = {
-    active: PropTypes.bool,
-    progress: PropTypes.number,
-  };
-
-  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>
-    );
-  }
-
-}
diff --git a/app/javascript/flavours/glitch/features/compose/components/warning.js b/app/javascript/flavours/glitch/features/compose/components/warning.js
deleted file mode 100644
index 4962e76c8..000000000
--- a/app/javascript/flavours/glitch/features/compose/components/warning.js
+++ /dev/null
@@ -1,26 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import Motion from 'flavours/glitch/util/optional_motion';
-import spring from 'react-motion/lib/spring';
-
-export default class Warning extends React.PureComponent {
-
-  static propTypes = {
-    message: PropTypes.node.isRequired,
-  };
-
-  render () {
-    const { message } = this.props;
-
-    return (
-      <Motion defaultStyle={{ opacity: 0, scaleX: 0.85, scaleY: 0.75 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}>
-        {({ opacity, scaleX, scaleY }) => (
-          <div className='compose-form__warning' style={{ opacity: opacity, transform: `scale(${scaleX}, ${scaleY})` }}>
-            {message}
-          </div>
-        )}
-      </Motion>
-    );
-  }
-
-}
diff --git a/app/javascript/flavours/glitch/features/compose/containers/advanced_options_container.js b/app/javascript/flavours/glitch/features/compose/containers/advanced_options_container.js
deleted file mode 100644
index da381568b..000000000
--- a/app/javascript/flavours/glitch/features/compose/containers/advanced_options_container.js
+++ /dev/null
@@ -1,20 +0,0 @@
-//  Package imports.
-import { connect } from 'react-redux';
-
-//  Our imports.
-import { toggleComposeAdvancedOption } from 'flavours/glitch/actions/compose';
-import ComposeAdvancedOptions from '../components/advanced_options';
-
-const mapStateToProps = state => ({
-  values: state.getIn(['compose', 'advanced_options']),
-});
-
-const mapDispatchToProps = dispatch => ({
-
-  onChange (option) {
-    dispatch(toggleComposeAdvancedOption(option));
-  },
-
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(ComposeAdvancedOptions);
diff --git a/app/javascript/flavours/glitch/features/compose/containers/autosuggest_account_container.js b/app/javascript/flavours/glitch/features/compose/containers/autosuggest_account_container.js
deleted file mode 100644
index 0e1c328fe..000000000
--- a/app/javascript/flavours/glitch/features/compose/containers/autosuggest_account_container.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import { connect } from 'react-redux';
-import AutosuggestAccount from '../components/autosuggest_account';
-import { makeGetAccount } from 'flavours/glitch/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/javascript/flavours/glitch/features/compose/containers/compose_form_container.js b/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js
deleted file mode 100644
index e2e93e44b..000000000
--- a/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js
+++ /dev/null
@@ -1,71 +0,0 @@
-import { connect } from 'react-redux';
-import ComposeForm from '../components/compose_form';
-import { changeComposeVisibility, uploadCompose } from 'flavours/glitch/actions/compose';
-import {
-  changeCompose,
-  submitCompose,
-  clearComposeSuggestions,
-  fetchComposeSuggestions,
-  selectComposeSuggestion,
-  changeComposeSpoilerText,
-  insertEmojiCompose,
-} from 'flavours/glitch/actions/compose';
-
-const mapStateToProps = state => ({
-  text: state.getIn(['compose', 'text']),
-  suggestion_token: state.getIn(['compose', 'suggestion_token']),
-  suggestions: state.getIn(['compose', 'suggestions']),
-  advanced_options: state.getIn(['compose', 'advanced_options']),
-  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']),
-  showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']),
-  settings: state.get('local_settings'),
-  filesAttached: state.getIn(['compose', 'media_attachments']).size > 0,
-});
-
-const mapDispatchToProps = (dispatch) => ({
-
-  onChange (text) {
-    dispatch(changeCompose(text));
-  },
-
-  onPrivacyChange (value) {
-    dispatch(changeComposeVisibility(value));
-  },
-
-  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/javascript/flavours/glitch/features/compose/containers/emoji_picker_dropdown_container.js b/app/javascript/flavours/glitch/features/compose/containers/emoji_picker_dropdown_container.js
deleted file mode 100644
index ba85edd87..000000000
--- a/app/javascript/flavours/glitch/features/compose/containers/emoji_picker_dropdown_container.js
+++ /dev/null
@@ -1,82 +0,0 @@
-import { connect } from 'react-redux';
-import EmojiPickerDropdown from '../components/emoji_picker_dropdown';
-import { changeSetting } from 'flavours/glitch/actions/settings';
-import { createSelector } from 'reselect';
-import { Map as ImmutableMap } from 'immutable';
-import { useEmoji } from 'flavours/glitch/actions/emojis';
-
-const perLine = 8;
-const lines   = 2;
-
-const DEFAULTS = [
-  '+1',
-  'grinning',
-  'kissing_heart',
-  'heart_eyes',
-  'laughing',
-  'stuck_out_tongue_winking_eye',
-  'sweat_smile',
-  'joy',
-  'yum',
-  'disappointed',
-  'thinking_face',
-  'weary',
-  'sob',
-  'sunglasses',
-  'heart',
-  'ok_hand',
-];
-
-const getFrequentlyUsedEmojis = createSelector([
-  state => state.getIn(['settings', 'frequentlyUsedEmojis'], ImmutableMap()),
-], emojiCounters => {
-  let emojis = emojiCounters
-    .keySeq()
-    .sort((a, b) => emojiCounters.get(a) - emojiCounters.get(b))
-    .reverse()
-    .slice(0, perLine * lines)
-    .toArray();
-
-  if (emojis.length < DEFAULTS.length) {
-    emojis = emojis.concat(DEFAULTS.slice(0, DEFAULTS.length - emojis.length));
-  }
-
-  return emojis;
-});
-
-const getCustomEmojis = createSelector([
-  state => state.get('custom_emojis'),
-], emojis => emojis.filter(e => e.get('visible_in_picker')).sort((a, b) => {
-  const aShort = a.get('shortcode').toLowerCase();
-  const bShort = b.get('shortcode').toLowerCase();
-
-  if (aShort < bShort) {
-    return -1;
-  } else if (aShort > bShort ) {
-    return 1;
-  } else {
-    return 0;
-  }
-}));
-
-const mapStateToProps = state => ({
-  custom_emojis: getCustomEmojis(state),
-  skinTone: state.getIn(['settings', 'skinTone']),
-  frequentlyUsedEmojis: getFrequentlyUsedEmojis(state),
-});
-
-const mapDispatchToProps = (dispatch, { onPickEmoji }) => ({
-  onSkinTone: skinTone => {
-    dispatch(changeSetting(['skinTone'], skinTone));
-  },
-
-  onPickEmoji: emoji => {
-    dispatch(useEmoji(emoji));
-
-    if (onPickEmoji) {
-      onPickEmoji(emoji);
-    }
-  },
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(EmojiPickerDropdown);
diff --git a/app/javascript/flavours/glitch/features/compose/containers/navigation_container.js b/app/javascript/flavours/glitch/features/compose/containers/navigation_container.js
deleted file mode 100644
index eb630ffbb..000000000
--- a/app/javascript/flavours/glitch/features/compose/containers/navigation_container.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import { connect }   from 'react-redux';
-import NavigationBar from '../components/navigation_bar';
-import { me } from 'flavours/glitch/util/initial_state';
-
-const mapStateToProps = state => {
-  return {
-    account: state.getIn(['accounts', me]),
-  };
-};
-
-export default connect(mapStateToProps)(NavigationBar);
diff --git a/app/javascript/flavours/glitch/features/compose/containers/privacy_dropdown_container.js b/app/javascript/flavours/glitch/features/compose/containers/privacy_dropdown_container.js
deleted file mode 100644
index cb94fcc80..000000000
--- a/app/javascript/flavours/glitch/features/compose/containers/privacy_dropdown_container.js
+++ /dev/null
@@ -1,24 +0,0 @@
-import { connect } from 'react-redux';
-import PrivacyDropdown from '../components/privacy_dropdown';
-import { changeComposeVisibility } from 'flavours/glitch/actions/compose';
-import { openModal, closeModal } from 'flavours/glitch/actions/modal';
-import { isUserTouching } from 'flavours/glitch/util/is_mobile';
-
-const mapStateToProps = state => ({
-  isModalOpen: state.get('modal').modalType === 'ACTIONS',
-  value: state.getIn(['compose', 'privacy']),
-});
-
-const mapDispatchToProps = dispatch => ({
-
-  onChange (value) {
-    dispatch(changeComposeVisibility(value));
-  },
-
-  isUserTouching,
-  onModalOpen: props => dispatch(openModal('ACTIONS', props)),
-  onModalClose: () => dispatch(closeModal()),
-
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(PrivacyDropdown);
diff --git a/app/javascript/flavours/glitch/features/compose/containers/reply_indicator_container.js b/app/javascript/flavours/glitch/features/compose/containers/reply_indicator_container.js
deleted file mode 100644
index a7c82d135..000000000
--- a/app/javascript/flavours/glitch/features/compose/containers/reply_indicator_container.js
+++ /dev/null
@@ -1,24 +0,0 @@
-import { connect } from 'react-redux';
-import { cancelReplyCompose } from 'flavours/glitch/actions/compose';
-import { makeGetStatus } from 'flavours/glitch/selectors';
-import ReplyIndicator from '../components/reply_indicator';
-
-const makeMapStateToProps = () => {
-  const getStatus = makeGetStatus();
-
-  const mapStateToProps = state => ({
-    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/javascript/flavours/glitch/features/compose/containers/search_container.js b/app/javascript/flavours/glitch/features/compose/containers/search_container.js
deleted file mode 100644
index 8f4bfcf08..000000000
--- a/app/javascript/flavours/glitch/features/compose/containers/search_container.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import { connect } from 'react-redux';
-import {
-  changeSearch,
-  clearSearch,
-  submitSearch,
-  showSearch,
-} from 'flavours/glitch/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/javascript/flavours/glitch/features/compose/containers/search_results_container.js b/app/javascript/flavours/glitch/features/compose/containers/search_results_container.js
deleted file mode 100644
index 16d95d417..000000000
--- a/app/javascript/flavours/glitch/features/compose/containers/search_results_container.js
+++ /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/javascript/flavours/glitch/features/compose/containers/sensitive_button_container.js b/app/javascript/flavours/glitch/features/compose/containers/sensitive_button_container.js
deleted file mode 100644
index cf6706c0e..000000000
--- a/app/javascript/flavours/glitch/features/compose/containers/sensitive_button_container.js
+++ /dev/null
@@ -1,71 +0,0 @@
-import React from 'react';
-import { connect } from 'react-redux';
-import PropTypes from 'prop-types';
-import classNames from 'classnames';
-import IconButton from 'flavours/glitch/components/icon_button';
-import { changeComposeSensitivity } from 'flavours/glitch/actions/compose';
-import Motion from 'flavours/glitch/util/optional_motion';
-import spring from 'react-motion/lib/spring';
-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']),
-  disabled: state.getIn(['compose', 'spoiler']),
-});
-
-const mapDispatchToProps = dispatch => ({
-
-  onClick () {
-    dispatch(changeComposeSensitivity());
-  },
-
-});
-
-class SensitiveButton extends React.PureComponent {
-
-  static propTypes = {
-    visible: PropTypes.bool,
-    active: PropTypes.bool,
-    disabled: PropTypes.bool,
-    onClick: PropTypes.func.isRequired,
-    intl: PropTypes.object.isRequired,
-  };
-
-  render () {
-    const { visible, active, disabled, onClick, intl } = this.props;
-
-    return (
-      <Motion defaultStyle={{ scale: 0.87 }} style={{ scale: spring(visible ? 1 : 0.87, { stiffness: 200, damping: 3 }) }}>
-        {({ scale }) => {
-          const icon = active ? 'eye-slash' : 'eye';
-          const className = classNames('compose-form__sensitive-button', {
-            'compose-form__sensitive-button--visible': visible,
-          });
-          return (
-            <div className={className} style={{ transform: `scale(${scale})` }}>
-              <IconButton
-                className='compose-form__sensitive-button__icon'
-                title={intl.formatMessage(messages.title)}
-                icon={icon}
-                onClick={onClick}
-                size={18}
-                active={active}
-                disabled={disabled}
-                style={{ lineHeight: null, height: null }}
-                inverted
-              />
-            </div>
-          );
-        }}
-      </Motion>
-    );
-  }
-
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(SensitiveButton));
diff --git a/app/javascript/flavours/glitch/features/compose/containers/spoiler_button_container.js b/app/javascript/flavours/glitch/features/compose/containers/spoiler_button_container.js
deleted file mode 100644
index d7b4246bc..000000000
--- a/app/javascript/flavours/glitch/features/compose/containers/spoiler_button_container.js
+++ /dev/null
@@ -1,25 +0,0 @@
-import { connect } from 'react-redux';
-import TextIconButton from '../components/text_icon_button';
-import { changeComposeSpoilerness } from 'flavours/glitch/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/javascript/flavours/glitch/features/compose/containers/upload_button_container.js b/app/javascript/flavours/glitch/features/compose/containers/upload_button_container.js
deleted file mode 100644
index 4c1cb49e9..000000000
--- a/app/javascript/flavours/glitch/features/compose/containers/upload_button_container.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import { connect } from 'react-redux';
-import UploadButton from '../components/upload_button';
-import { uploadCompose } from 'flavours/glitch/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/javascript/flavours/glitch/features/compose/containers/upload_container.js b/app/javascript/flavours/glitch/features/compose/containers/upload_container.js
deleted file mode 100644
index 368038425..000000000
--- a/app/javascript/flavours/glitch/features/compose/containers/upload_container.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import { connect } from 'react-redux';
-import Upload from '../components/upload';
-import { undoUploadCompose, changeUploadCompose } from 'flavours/glitch/actions/compose';
-
-const mapStateToProps = (state, { id }) => ({
-  media: state.getIn(['compose', 'media_attachments']).find(item => item.get('id') === id),
-});
-
-const mapDispatchToProps = dispatch => ({
-
-  onUndo: id => {
-    dispatch(undoUploadCompose(id));
-  },
-
-  onDescriptionChange: (id, description) => {
-    dispatch(changeUploadCompose(id, description));
-  },
-
-});
-
-export default connect(mapStateToProps, mapDispatchToProps)(Upload);
diff --git a/app/javascript/flavours/glitch/features/compose/containers/upload_form_container.js b/app/javascript/flavours/glitch/features/compose/containers/upload_form_container.js
deleted file mode 100644
index a6798bf51..000000000
--- a/app/javascript/flavours/glitch/features/compose/containers/upload_form_container.js
+++ /dev/null
@@ -1,8 +0,0 @@
-import { connect } from 'react-redux';
-import UploadForm from '../components/upload_form';
-
-const mapStateToProps = state => ({
-  mediaIds: state.getIn(['compose', 'media_attachments']).map(item => item.get('id')),
-});
-
-export default connect(mapStateToProps)(UploadForm);
diff --git a/app/javascript/flavours/glitch/features/compose/containers/upload_progress_container.js b/app/javascript/flavours/glitch/features/compose/containers/upload_progress_container.js
deleted file mode 100644
index 0cfee96da..000000000
--- a/app/javascript/flavours/glitch/features/compose/containers/upload_progress_container.js
+++ /dev/null
@@ -1,9 +0,0 @@
-import { connect } from 'react-redux';
-import UploadProgress from '../components/upload_progress';
-
-const mapStateToProps = state => ({
-  active: state.getIn(['compose', 'is_uploading']),
-  progress: state.getIn(['compose', 'progress']),
-});
-
-export default connect(mapStateToProps)(UploadProgress);
diff --git a/app/javascript/flavours/glitch/features/compose/containers/warning_container.js b/app/javascript/flavours/glitch/features/compose/containers/warning_container.js
deleted file mode 100644
index f20c75b91..000000000
--- a/app/javascript/flavours/glitch/features/compose/containers/warning_container.js
+++ /dev/null
@@ -1,24 +0,0 @@
-import React from 'react';
-import { connect } from 'react-redux';
-import Warning from '../components/warning';
-import PropTypes from 'prop-types';
-import { FormattedMessage } from 'react-intl';
-import { me } from 'flavours/glitch/util/initial_state';
-
-const mapStateToProps = state => ({
-  needsLockWarning: state.getIn(['compose', 'privacy']) === 'private' && !state.getIn(['accounts', me, 'locked']),
-});
-
-const WarningWrapper = ({ needsLockWarning }) => {
-  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> }} />} />;
-  }
-
-  return null;
-};
-
-WarningWrapper.propTypes = {
-  needsLockWarning: PropTypes.bool,
-};
-
-export default connect(mapStateToProps)(WarningWrapper);
diff --git a/app/javascript/flavours/glitch/features/compose/index.js b/app/javascript/flavours/glitch/features/compose/index.js
deleted file mode 100644
index 63c9500b1..000000000
--- a/app/javascript/flavours/glitch/features/compose/index.js
+++ /dev/null
@@ -1,126 +0,0 @@
-import React from 'react';
-import ComposeFormContainer from './containers/compose_form_container';
-import NavigationContainer from './containers/navigation_container';
-import PropTypes from 'prop-types';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import { connect } from 'react-redux';
-import { mountCompose, unmountCompose } from 'flavours/glitch/actions/compose';
-import { openModal } from 'flavours/glitch/actions/modal';
-import { changeLocalSetting } from 'flavours/glitch/actions/local_settings';
-import { Link } from 'react-router-dom';
-import { injectIntl, defineMessages } from 'react-intl';
-import SearchContainer from './containers/search_container';
-import Motion from 'flavours/glitch/util/optional_motion';
-import spring from 'react-motion/lib/spring';
-import SearchResultsContainer from './containers/search_results_container';
-import { changeComposing } from 'flavours/glitch/actions/compose';
-
-const messages = defineMessages({
-  start: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
-  home_timeline: { id: 'tabs_bar.home', defaultMessage: 'Home' },
-  notifications: { id: 'tabs_bar.notifications', defaultMessage: 'Notifications' },
-  public: { id: 'navigation_bar.public_timeline', defaultMessage: 'Federated timeline' },
-  community: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' },
-  settings: { id: 'navigation_bar.app_settings', defaultMessage: 'App settings' },
-  logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
-});
-
-const mapStateToProps = state => ({
-  columns: state.getIn(['settings', 'columns']),
-  showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']),
-});
-
-@connect(mapStateToProps)
-@injectIntl
-export default class Compose extends React.PureComponent {
-
-  static propTypes = {
-    dispatch: PropTypes.func.isRequired,
-    columns: ImmutablePropTypes.list.isRequired,
-    multiColumn: PropTypes.bool,
-    showSearch: PropTypes.bool,
-    intl: PropTypes.object.isRequired,
-  };
-
-  componentDidMount () {
-    this.props.dispatch(mountCompose());
-  }
-
-  componentWillUnmount () {
-    this.props.dispatch(unmountCompose());
-  }
-
-  onLayoutClick = (e) => {
-    const layout = e.currentTarget.getAttribute('data-mastodon-layout');
-    this.props.dispatch(changeLocalSetting(['layout'], layout));
-    e.preventDefault();
-  }
-
-  openSettings = () => {
-    this.props.dispatch(openModal('SETTINGS', {}));
-  }
-
-  onFocus = () => {
-    this.props.dispatch(changeComposing(true));
-  }
-
-  onBlur = () => {
-    this.props.dispatch(changeComposing(false));
-  }
-
-  render () {
-    const { multiColumn, showSearch, intl } = this.props;
-
-    let header = '';
-
-    if (multiColumn) {
-      const { columns } = this.props;
-      header = (
-        <nav className='drawer__header'>
-          <Link to='/getting-started' className='drawer__tab' title={intl.formatMessage(messages.start)} aria-label={intl.formatMessage(messages.start)}><i role='img' className='fa fa-fw fa-asterisk' /></Link>
-          {!columns.some(column => column.get('id') === 'HOME') && (
-            <Link to='/timelines/home' className='drawer__tab' title={intl.formatMessage(messages.home_timeline)} aria-label={intl.formatMessage(messages.home_timeline)}><i role='img' className='fa fa-fw fa-home' /></Link>
-          )}
-          {!columns.some(column => column.get('id') === 'NOTIFICATIONS') && (
-            <Link to='/notifications' className='drawer__tab' title={intl.formatMessage(messages.notifications)} aria-label={intl.formatMessage(messages.notifications)}><i role='img' className='fa fa-fw fa-bell' /></Link>
-          )}
-          {!columns.some(column => column.get('id') === 'COMMUNITY') && (
-            <Link to='/timelines/public/local' className='drawer__tab' title={intl.formatMessage(messages.community)} aria-label={intl.formatMessage(messages.community)}><i role='img' className='fa fa-fw fa-users' /></Link>
-          )}
-          {!columns.some(column => column.get('id') === 'PUBLIC') && (
-            <Link to='/timelines/public' className='drawer__tab' title={intl.formatMessage(messages.public)} aria-label={intl.formatMessage(messages.public)}><i role='img' className='fa fa-fw fa-globe' /></Link>
-          )}
-          <a onClick={this.openSettings} role='button' tabIndex='0' className='drawer__tab' title={intl.formatMessage(messages.settings)} aria-label={intl.formatMessage(messages.settings)}><i role='img' className='fa fa-fw fa-cogs' /></a>
-          <a href='/auth/sign_out' className='drawer__tab' data-method='delete' title={intl.formatMessage(messages.logout)} aria-label={intl.formatMessage(messages.logout)}><i role='img' className='fa fa-fw fa-sign-out' /></a>
-        </nav>
-      );
-    }
-
-
-
-    return (
-      <div className='drawer'>
-        {header}
-
-        <SearchContainer />
-
-        <div className='drawer__pager'>
-          <div className='drawer__inner scrollable optionally-scrollable' onFocus={this.onFocus}>
-            <NavigationContainer onClose={this.onBlur} />
-            <ComposeFormContainer />
-          </div>
-
-          <Motion defaultStyle={{ x: -100 }} style={{ x: spring(showSearch ? 0 : -100, { stiffness: 210, damping: 20 }) }}>
-            {({ x }) =>
-              <div className='drawer__inner darker scrollable optionally-scrollable' style={{ transform: `translateX(${x}%)`, visibility: x === -100 ? 'hidden' : 'visible' }}>
-                <SearchResultsContainer />
-              </div>
-            }
-          </Motion>
-        </div>
-
-      </div>
-    );
-  }
-
-}