about summary refs log tree commit diff
path: root/app/javascript/flavours
diff options
context:
space:
mode:
authorThibaut Girka <thib@sitedethib.com>2019-04-20 21:28:03 +0200
committerThibG <thib@sitedethib.com>2019-04-22 20:15:47 +0200
commit1bc4b8a0a57a4046364f4afbb741f2d4e7d48dcb (patch)
treed692e04c561eabbe53135381e372accb8b3c3678 /app/javascript/flavours
parent281a82d8784fec7e79e309095cbe61428173b44f (diff)
features/composer/index.js → ComposeForm
Diffstat (limited to 'app/javascript/flavours')
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/compose_form.js (renamed from app/javascript/flavours/glitch/features/composer/index.js)363
-rw-r--r--app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js175
-rw-r--r--app/javascript/flavours/glitch/features/compose/index.js7
-rw-r--r--app/javascript/flavours/glitch/features/standalone/compose/index.js4
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js5
5 files changed, 273 insertions, 281 deletions
diff --git a/app/javascript/flavours/glitch/features/composer/index.js b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
index 9d2e0b3da..0f9b11fa3 100644
--- a/app/javascript/flavours/glitch/features/composer/index.js
+++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
@@ -1,57 +1,26 @@
-//  Package imports.
-import PropTypes from 'prop-types';
 import React from 'react';
+import PropTypes from 'prop-types';
 import ImmutablePropTypes from 'react-immutable-proptypes';
-import { defineMessages } from 'react-intl';
+import { defineMessages, injectIntl } from 'react-intl';
+import ImmutablePureComponent from 'react-immutable-pure-component';
 
 const APPROX_HASHTAG_RE = /(?:^|[^\/\)\w])#(\S+)/i;
 
-//  Actions.
-import {
-  cancelReplyCompose,
-  changeCompose,
-  changeComposeAdvancedOption,
-  changeComposeSensitivity,
-  changeComposeSpoilerText,
-  changeComposeSpoilerness,
-  changeComposeVisibility,
-  changeUploadCompose,
-  clearComposeSuggestions,
-  fetchComposeSuggestions,
-  insertEmojiCompose,
-  mountCompose,
-  selectComposeSuggestion,
-  submitCompose,
-  undoUploadCompose,
-  unmountCompose,
-  uploadCompose,
-} from 'flavours/glitch/actions/compose';
-import {
-  closeModal,
-  openModal,
-} from 'flavours/glitch/actions/modal';
-import { changeLocalSetting } from 'flavours/glitch/actions/local_settings';
-import { addPoll, removePoll } from 'flavours/glitch/actions/compose';
-
 //  Components.
-import ComposerOptions from './options';
-import ComposerPublisher from './publisher';
-import ComposerReply from './reply';
-import ComposerSpoiler from './spoiler';
-import ComposerTextarea from './textarea';
-import ComposerUploadForm from './upload_form';
-import ComposerPollForm from './poll_form';
-import ComposerWarning from './warning';
-import ComposerHashtagWarning from './hashtag_warning';
-import ComposerDirectWarning from './direct_warning';
+import ComposerOptions from '../../composer/options';
+import ComposerPublisher from '../../composer/publisher';
+import ComposerReply from '../../composer/reply';
+import ComposerSpoiler from '../../composer/spoiler';
+import ComposerTextarea from '../../composer/textarea';
+import ComposerUploadForm from '../../composer/upload_form';
+import ComposerPollForm from '../../composer/poll_form';
+import ComposerWarning from '../../composer/warning';
+import ComposerHashtagWarning from '../../composer/hashtag_warning';
+import ComposerDirectWarning from '../../composer/direct_warning';
 
 //  Utils.
 import { countableText } from 'flavours/glitch/util/counter';
-import { me } from 'flavours/glitch/util/initial_state';
 import { isMobile } from 'flavours/glitch/util/is_mobile';
-import { assignHandlers } from 'flavours/glitch/util/react_helpers';
-import { wrap } from 'flavours/glitch/util/redux_helpers';
-import { privacyPreference } from 'flavours/glitch/util/privacy_preference';
 
 const messages = defineMessages({
   missingDescriptionMessage: {  id: 'confirmations.missing_media_description.message',
@@ -60,163 +29,88 @@ const messages = defineMessages({
                                 defaultMessage: 'Send anyway' },
 });
 
-//  State mapping.
-function mapStateToProps (state) {
-  const spoilersAlwaysOn = state.getIn(['local_settings', 'always_show_spoilers_field']);
-  const inReplyTo = state.getIn(['compose', 'in_reply_to']);
-  const replyPrivacy = inReplyTo ? state.getIn(['statuses', inReplyTo, 'visibility']) : null;
-  const sideArmBasePrivacy = state.getIn(['local_settings', 'side_arm']);
-  const sideArmRestrictedPrivacy = replyPrivacy ? privacyPreference(replyPrivacy, sideArmBasePrivacy) : null;
-  let sideArmPrivacy = null;
-  switch (state.getIn(['local_settings', 'side_arm_reply_mode'])) {
-    case 'copy':
-      sideArmPrivacy = replyPrivacy;
-      break;
-    case 'restrict':
-      sideArmPrivacy = sideArmRestrictedPrivacy;
-      break;
-  }
-  sideArmPrivacy = sideArmPrivacy || sideArmBasePrivacy;
-  return {
-    acceptContentTypes: state.getIn(['media_attachments', 'accept_content_types']).toArray().join(','),
-    advancedOptions: state.getIn(['compose', 'advanced_options']),
-    amUnlocked: !state.getIn(['accounts', me, 'locked']),
-    focusDate: state.getIn(['compose', 'focusDate']),
-    caretPosition: state.getIn(['compose', 'caretPosition']),
-    isSubmitting: state.getIn(['compose', 'is_submitting']),
-    isChangingUpload: state.getIn(['compose', 'is_changing_upload']),
-    isUploading: state.getIn(['compose', 'is_uploading']),
-    layout: state.getIn(['local_settings', 'layout']),
-    media: state.getIn(['compose', 'media_attachments']),
-    preselectDate: state.getIn(['compose', 'preselectDate']),
-    privacy: state.getIn(['compose', 'privacy']),
-    progress: state.getIn(['compose', 'progress']),
-    inReplyTo: inReplyTo ? state.getIn(['statuses', inReplyTo]) : null,
-    replyAccount: inReplyTo ? state.getIn(['statuses', inReplyTo, 'account']) : null,
-    replyContent: inReplyTo ? state.getIn(['statuses', inReplyTo, 'contentHtml']) : null,
-    resetFileKey: state.getIn(['compose', 'resetFileKey']),
-    sideArm: sideArmPrivacy,
-    sensitive: state.getIn(['compose', 'sensitive']),
-    showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']),
-    spoiler: spoilersAlwaysOn || state.getIn(['compose', 'spoiler']),
-    spoilerText: state.getIn(['compose', 'spoiler_text']),
-    suggestionToken: state.getIn(['compose', 'suggestion_token']),
-    suggestions: state.getIn(['compose', 'suggestions']),
-    text: state.getIn(['compose', 'text']),
-    anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
-    poll: state.getIn(['compose', 'poll']),
-    spoilersAlwaysOn: spoilersAlwaysOn,
-    mediaDescriptionConfirmation: state.getIn(['local_settings', 'confirm_missing_media_description']),
-    preselectOnReply: state.getIn(['local_settings', 'preselect_on_reply']),
-  };
-};
+export default @injectIntl
+class ComposeForm extends ImmutablePureComponent {
 
-//  Dispatch mapping.
-const mapDispatchToProps = (dispatch, { intl }) => ({
-  onCancelReply() {
-    dispatch(cancelReplyCompose());
-  },
-  onChangeAdvancedOption(option, value) {
-    dispatch(changeComposeAdvancedOption(option, value));
-  },
-  onChangeDescription(id, description) {
-    dispatch(changeUploadCompose(id, { description }));
-  },
-  onChangeSensitivity() {
-    dispatch(changeComposeSensitivity());
-  },
-  onChangeSpoilerText(text) {
-    dispatch(changeComposeSpoilerText(text));
-  },
-  onChangeSpoilerness() {
-    dispatch(changeComposeSpoilerness());
-  },
-  onChangeText(text) {
-    dispatch(changeCompose(text));
-  },
-  onChangeVisibility(value) {
-    dispatch(changeComposeVisibility(value));
-  },
-  onTogglePoll() {
-    dispatch((_, getState) => {
-      if (getState().getIn(['compose', 'poll'])) {
-        dispatch(removePoll());
-      } else {
-        dispatch(addPoll());
-      }
-    });
-  },
-  onClearSuggestions() {
-    dispatch(clearComposeSuggestions());
-  },
-  onCloseModal() {
-    dispatch(closeModal());
-  },
-  onFetchSuggestions(token) {
-    dispatch(fetchComposeSuggestions(token));
-  },
-  onInsertEmoji(position, emoji) {
-    dispatch(insertEmojiCompose(position, emoji));
-  },
-  onMount() {
-    dispatch(mountCompose());
-  },
-  onOpenActionsModal(props) {
-    dispatch(openModal('ACTIONS', props));
-  },
-  onOpenDoodleModal() {
-    dispatch(openModal('DOODLE', { noEsc: true }));
-  },
-  onOpenFocalPointModal(id) {
-    dispatch(openModal('FOCAL_POINT', { id }));
-  },
-  onSelectSuggestion(position, token, suggestion) {
-    dispatch(selectComposeSuggestion(position, token, suggestion));
-  },
-  onMediaDescriptionConfirm(routerHistory) {
-    dispatch(openModal('CONFIRM', {
-      message: intl.formatMessage(messages.missingDescriptionMessage),
-      confirm: intl.formatMessage(messages.missingDescriptionConfirm),
-      onConfirm: () => dispatch(submitCompose(routerHistory)),
-      onDoNotAsk: () => dispatch(changeLocalSetting(['confirm_missing_media_description'], false)),
-    }));
-  },
-  onSubmit(routerHistory) {
-    dispatch(submitCompose(routerHistory));
-  },
-  onUndoUpload(id) {
-    dispatch(undoUploadCompose(id));
-  },
-  onUnmount() {
-    dispatch(unmountCompose());
-  },
-  onUpload(files) {
-    dispatch(uploadCompose(files));
-  },
-});
+  static contextTypes = {
+    router: PropTypes.object,
+  };
 
-//  Handlers.
-const handlers = {
+  static propTypes = {
+    intl: PropTypes.object.isRequired,
+
+    //  State props.
+    acceptContentTypes: PropTypes.string,
+    advancedOptions: ImmutablePropTypes.map,
+    amUnlocked: PropTypes.bool,
+    focusDate: PropTypes.instanceOf(Date),
+    caretPosition: PropTypes.number,
+    isSubmitting: PropTypes.bool,
+    isChangingUpload: PropTypes.bool,
+    isUploading: PropTypes.bool,
+    layout: PropTypes.string,
+    media: ImmutablePropTypes.list,
+    preselectDate: PropTypes.instanceOf(Date),
+    privacy: PropTypes.string,
+    progress: PropTypes.number,
+    inReplyTo: ImmutablePropTypes.map,
+    resetFileKey: PropTypes.number,
+    sideArm: PropTypes.string,
+    sensitive: PropTypes.bool,
+    showSearch: PropTypes.bool,
+    spoiler: PropTypes.bool,
+    spoilerText: PropTypes.string,
+    suggestionToken: PropTypes.string,
+    suggestions: ImmutablePropTypes.list,
+    text: PropTypes.string,
+    anyMedia: PropTypes.bool,
+    spoilersAlwaysOn: PropTypes.bool,
+    mediaDescriptionConfirmation: PropTypes.bool,
+    preselectOnReply: PropTypes.bool,
+
+    //  Dispatch props.
+    onCancelReply: PropTypes.func,
+    onChangeAdvancedOption: PropTypes.func,
+    onChangeDescription: PropTypes.func,
+    onChangeSensitivity: PropTypes.func,
+    onChangeSpoilerText: PropTypes.func,
+    onChangeSpoilerness: PropTypes.func,
+    onChangeText: PropTypes.func,
+    onChangeVisibility: PropTypes.func,
+    onClearSuggestions: PropTypes.func,
+    onCloseModal: PropTypes.func,
+    onFetchSuggestions: PropTypes.func,
+    onInsertEmoji: PropTypes.func,
+    onMount: PropTypes.func,
+    onOpenActionsModal: PropTypes.func,
+    onOpenDoodleModal: PropTypes.func,
+    onSelectSuggestion: PropTypes.func,
+    onSubmit: PropTypes.func,
+    onUndoUpload: PropTypes.func,
+    onUnmount: PropTypes.func,
+    onUpload: PropTypes.func,
+    onMediaDescriptionConfirm: PropTypes.func,
+  };
 
   //  Changes the text value of the spoiler.
-  handleChangeSpoiler ({ target: { value } }) {
+  handleChangeSpoiler = ({ target: { value } }) => {
     const { onChangeSpoilerText } = this.props;
     if (onChangeSpoilerText) {
       onChangeSpoilerText(value);
     }
-  },
+  }
 
   //  Inserts an emoji at the caret.
-  handleEmoji (data) {
+  handleEmoji = (data) => {
     const { textarea: { selectionStart } } = this;
     const { onInsertEmoji } = this.props;
     if (onInsertEmoji) {
       onInsertEmoji(selectionStart, data);
     }
-  },
+  }
 
   //  Handles the secondary submit button.
-  handleSecondarySubmit () {
+  handleSecondarySubmit = () => {
     const { handleSubmit } = this.handlers;
     const {
       onChangeVisibility,
@@ -226,18 +120,18 @@ const handlers = {
       onChangeVisibility(sideArm);
     }
     handleSubmit();
-  },
+  }
 
   //  Selects a suggestion from the autofill.
-  handleSelect (tokenStart, token, value) {
+  handleSelect = (tokenStart, token, value) => {
     const { onSelectSuggestion } = this.props;
     if (onSelectSuggestion) {
       onSelectSuggestion(tokenStart, token, value);
     }
-  },
+  }
 
   //  Submits the status.
-  handleSubmit () {
+  handleSubmit = () => {
     const { textarea: { value }, uploadForm } = this;
     const {
       onChangeText,
@@ -276,40 +170,26 @@ const handlers = {
     } else if (onSubmit) {
       onSubmit(this.context.router ? this.context.router.history : null);
     }
-  },
+  }
 
   //  Sets a reference to the upload form.
-  handleRefUploadForm (uploadFormComponent) {
+  handleRefUploadForm = (uploadFormComponent) => {
     this.uploadForm = uploadFormComponent;
-  },
+  }
 
   //  Sets a reference to the textarea.
-  handleRefTextarea (textareaComponent) {
+  handleRefTextarea = (textareaComponent) => {
     if (textareaComponent) {
       this.textarea = textareaComponent.textarea;
     }
-  },
+  }
 
   //  Sets a reference to the CW field.
-  handleRefSpoilerText (spoilerComponent) {
+  handleRefSpoilerText = (spoilerComponent) => {
     if (spoilerComponent) {
       this.spoilerText = spoilerComponent.spoilerText;
     }
   }
-};
-
-//  The component.
-class Composer extends React.Component {
-
-  //  Constructor.
-  constructor (props) {
-    super(props);
-    assignHandlers(this, handlers);
-
-    //  Instance variables.
-    this.textarea = null;
-    this.spoilerText = null;
-  }
 
   //  Tells our state the composer has been mounted.
   componentDidMount () {
@@ -394,7 +274,7 @@ class Composer extends React.Component {
       handleRefUploadForm,
       handleRefTextarea,
       handleRefSpoilerText,
-    } = this.handlers;
+    } = this;
     const {
       acceptContentTypes,
       advancedOptions,
@@ -533,68 +413,3 @@ class Composer extends React.Component {
   }
 
 }
-
-//  Props.
-Composer.propTypes = {
-  intl: PropTypes.object.isRequired,
-
-  //  State props.
-  acceptContentTypes: PropTypes.string,
-  advancedOptions: ImmutablePropTypes.map,
-  amUnlocked: PropTypes.bool,
-  focusDate: PropTypes.instanceOf(Date),
-  caretPosition: PropTypes.number,
-  isSubmitting: PropTypes.bool,
-  isChangingUpload: PropTypes.bool,
-  isUploading: PropTypes.bool,
-  layout: PropTypes.string,
-  media: ImmutablePropTypes.list,
-  preselectDate: PropTypes.instanceOf(Date),
-  privacy: PropTypes.string,
-  progress: PropTypes.number,
-  inReplyTo: ImmutablePropTypes.map,
-  resetFileKey: PropTypes.number,
-  sideArm: PropTypes.string,
-  sensitive: PropTypes.bool,
-  showSearch: PropTypes.bool,
-  spoiler: PropTypes.bool,
-  spoilerText: PropTypes.string,
-  suggestionToken: PropTypes.string,
-  suggestions: ImmutablePropTypes.list,
-  text: PropTypes.string,
-  anyMedia: PropTypes.bool,
-  spoilersAlwaysOn: PropTypes.bool,
-  mediaDescriptionConfirmation: PropTypes.bool,
-  preselectOnReply: PropTypes.bool,
-
-  //  Dispatch props.
-  onCancelReply: PropTypes.func,
-  onChangeAdvancedOption: PropTypes.func,
-  onChangeDescription: PropTypes.func,
-  onChangeSensitivity: PropTypes.func,
-  onChangeSpoilerText: PropTypes.func,
-  onChangeSpoilerness: PropTypes.func,
-  onChangeText: PropTypes.func,
-  onChangeVisibility: PropTypes.func,
-  onClearSuggestions: PropTypes.func,
-  onCloseModal: PropTypes.func,
-  onFetchSuggestions: PropTypes.func,
-  onInsertEmoji: PropTypes.func,
-  onMount: PropTypes.func,
-  onOpenActionsModal: PropTypes.func,
-  onOpenDoodleModal: PropTypes.func,
-  onSelectSuggestion: PropTypes.func,
-  onSubmit: PropTypes.func,
-  onUndoUpload: PropTypes.func,
-  onUnmount: PropTypes.func,
-  onUpload: PropTypes.func,
-  onMediaDescriptionConfirm: PropTypes.func,
-};
-
-Composer.contextTypes = {
-  router: PropTypes.object,
-};
-
-//  Connecting and export.
-export { Composer as WrappedComponent };
-export default wrap(Composer, mapStateToProps, mapDispatchToProps, true);
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
new file mode 100644
index 000000000..18fc31dce
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js
@@ -0,0 +1,175 @@
+import { connect } from 'react-redux';
+import ComposeForm from '../components/compose_form';
+import {
+  cancelReplyCompose,
+  changeCompose,
+  changeComposeAdvancedOption,
+  changeComposeSensitivity,
+  changeComposeSpoilerText,
+  changeComposeSpoilerness,
+  changeComposeVisibility,
+  changeUploadCompose,
+  clearComposeSuggestions,
+  fetchComposeSuggestions,
+  insertEmojiCompose,
+  mountCompose,
+  selectComposeSuggestion,
+  submitCompose,
+  undoUploadCompose,
+  unmountCompose,
+  uploadCompose,
+} from 'flavours/glitch/actions/compose';
+import {
+  closeModal,
+  openModal,
+} from 'flavours/glitch/actions/modal';
+import { changeLocalSetting } from 'flavours/glitch/actions/local_settings';
+import { addPoll, removePoll } from 'flavours/glitch/actions/compose';
+
+import { privacyPreference } from 'flavours/glitch/util/privacy_preference';
+import { me } from 'flavours/glitch/util/initial_state';
+
+const messages = defineMessages({
+  missingDescriptionMessage: {  id: 'confirmations.missing_media_description.message',
+                                defaultMessage: 'At least one media attachment is lacking a description. Consider describing all media attachments for the visually impaired before sending your toot.' },
+  missingDescriptionConfirm: {  id: 'confirmations.missing_media_description.confirm',
+                                defaultMessage: 'Send anyway' },
+});
+import { defineMessages } from 'react-intl';
+
+//  State mapping.
+function mapStateToProps (state) {
+  const spoilersAlwaysOn = state.getIn(['local_settings', 'always_show_spoilers_field']);
+  const inReplyTo = state.getIn(['compose', 'in_reply_to']);
+  const replyPrivacy = inReplyTo ? state.getIn(['statuses', inReplyTo, 'visibility']) : null;
+  const sideArmBasePrivacy = state.getIn(['local_settings', 'side_arm']);
+  const sideArmRestrictedPrivacy = replyPrivacy ? privacyPreference(replyPrivacy, sideArmBasePrivacy) : null;
+  let sideArmPrivacy = null;
+  switch (state.getIn(['local_settings', 'side_arm_reply_mode'])) {
+    case 'copy':
+      sideArmPrivacy = replyPrivacy;
+      break;
+    case 'restrict':
+      sideArmPrivacy = sideArmRestrictedPrivacy;
+      break;
+  }
+  sideArmPrivacy = sideArmPrivacy || sideArmBasePrivacy;
+  return {
+    acceptContentTypes: state.getIn(['media_attachments', 'accept_content_types']).toArray().join(','),
+    advancedOptions: state.getIn(['compose', 'advanced_options']),
+    amUnlocked: !state.getIn(['accounts', me, 'locked']),
+    focusDate: state.getIn(['compose', 'focusDate']),
+    caretPosition: state.getIn(['compose', 'caretPosition']),
+    isSubmitting: state.getIn(['compose', 'is_submitting']),
+    isChangingUpload: state.getIn(['compose', 'is_changing_upload']),
+    isUploading: state.getIn(['compose', 'is_uploading']),
+    layout: state.getIn(['local_settings', 'layout']),
+    media: state.getIn(['compose', 'media_attachments']),
+    preselectDate: state.getIn(['compose', 'preselectDate']),
+    privacy: state.getIn(['compose', 'privacy']),
+    progress: state.getIn(['compose', 'progress']),
+    inReplyTo: inReplyTo ? state.getIn(['statuses', inReplyTo]) : null,
+    replyAccount: inReplyTo ? state.getIn(['statuses', inReplyTo, 'account']) : null,
+    replyContent: inReplyTo ? state.getIn(['statuses', inReplyTo, 'contentHtml']) : null,
+    resetFileKey: state.getIn(['compose', 'resetFileKey']),
+    sideArm: sideArmPrivacy,
+    sensitive: state.getIn(['compose', 'sensitive']),
+    showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']),
+    spoiler: spoilersAlwaysOn || state.getIn(['compose', 'spoiler']),
+    spoilerText: state.getIn(['compose', 'spoiler_text']),
+    suggestionToken: state.getIn(['compose', 'suggestion_token']),
+    suggestions: state.getIn(['compose', 'suggestions']),
+    text: state.getIn(['compose', 'text']),
+    anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
+    poll: state.getIn(['compose', 'poll']),
+    spoilersAlwaysOn: spoilersAlwaysOn,
+    mediaDescriptionConfirmation: state.getIn(['local_settings', 'confirm_missing_media_description']),
+    preselectOnReply: state.getIn(['local_settings', 'preselect_on_reply']),
+  };
+};
+
+//  Dispatch mapping.
+const mapDispatchToProps = (dispatch, { intl }) => ({
+  onCancelReply() {
+    dispatch(cancelReplyCompose());
+  },
+  onChangeAdvancedOption(option, value) {
+    dispatch(changeComposeAdvancedOption(option, value));
+  },
+  onChangeDescription(id, description) {
+    dispatch(changeUploadCompose(id, { description }));
+  },
+  onChangeSensitivity() {
+    dispatch(changeComposeSensitivity());
+  },
+  onChangeSpoilerText(text) {
+    dispatch(changeComposeSpoilerText(text));
+  },
+  onChangeSpoilerness() {
+    dispatch(changeComposeSpoilerness());
+  },
+  onChangeText(text) {
+    dispatch(changeCompose(text));
+  },
+  onChangeVisibility(value) {
+    dispatch(changeComposeVisibility(value));
+  },
+  onTogglePoll() {
+    dispatch((_, getState) => {
+      if (getState().getIn(['compose', 'poll'])) {
+        dispatch(removePoll());
+      } else {
+        dispatch(addPoll());
+      }
+    });
+  },
+  onClearSuggestions() {
+    dispatch(clearComposeSuggestions());
+  },
+  onCloseModal() {
+    dispatch(closeModal());
+  },
+  onFetchSuggestions(token) {
+    dispatch(fetchComposeSuggestions(token));
+  },
+  onInsertEmoji(position, emoji) {
+    dispatch(insertEmojiCompose(position, emoji));
+  },
+  onMount() {
+    dispatch(mountCompose());
+  },
+  onOpenActionsModal(props) {
+    dispatch(openModal('ACTIONS', props));
+  },
+  onOpenDoodleModal() {
+    dispatch(openModal('DOODLE', { noEsc: true }));
+  },
+  onOpenFocalPointModal(id) {
+    dispatch(openModal('FOCAL_POINT', { id }));
+  },
+  onSelectSuggestion(position, token, suggestion) {
+    dispatch(selectComposeSuggestion(position, token, suggestion));
+  },
+  onMediaDescriptionConfirm(routerHistory) {
+    dispatch(openModal('CONFIRM', {
+      message: intl.formatMessage(messages.missingDescriptionMessage),
+      confirm: intl.formatMessage(messages.missingDescriptionConfirm),
+      onConfirm: () => dispatch(submitCompose(routerHistory)),
+      onDoNotAsk: () => dispatch(changeLocalSetting(['confirm_missing_media_description'], false)),
+    }));
+  },
+  onSubmit(routerHistory) {
+    dispatch(submitCompose(routerHistory));
+  },
+  onUndoUpload(id) {
+    dispatch(undoUploadCompose(id));
+  },
+  onUnmount() {
+    dispatch(unmountCompose());
+  },
+  onUpload(files) {
+    dispatch(uploadCompose(files));
+  },
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(ComposeForm);
diff --git a/app/javascript/flavours/glitch/features/compose/index.js b/app/javascript/flavours/glitch/features/compose/index.js
index 923d379ec..01e7d1906 100644
--- a/app/javascript/flavours/glitch/features/compose/index.js
+++ b/app/javascript/flavours/glitch/features/compose/index.js
@@ -10,7 +10,7 @@ import classNames from 'classnames';
 import { cycleElefriendCompose } from 'flavours/glitch/actions/compose';
 
 //  Components.
-import Composer from 'flavours/glitch/features/composer';
+import ComposeFormContainer from './containers/compose_form_container';
 import HeaderContainer from './containers/header_container';
 import SearchContainer from './containers/search_container';
 import SearchResultsContainer from './containers/search_results_container';
@@ -73,11 +73,13 @@ class Compose extends React.PureComponent {
     return (
       <div className={computedClass} role='region' aria-label={intl.formatMessage(messages.compose)}>
         {multiColumn && <HeaderContainer />}
+
         {(multiColumn || isSearchPage) && <SearchContainer />}
+
         <div className='drawer__pager'>
           {!isSearchPage && <div className='drawer__inner'>
             <NavigationContainer />
-            <Composer />
+            <ComposeFormContainer />
             {multiColumn && (
               <div className='drawer__inner__mastodon'>
                 {mascot ? <img alt='' draggable='false' src={mascot} /> : <button className='mastodon' onClick={onClickElefriend} />}
@@ -96,4 +98,5 @@ class Compose extends React.PureComponent {
       </div>
     );
   }
+
 }
diff --git a/app/javascript/flavours/glitch/features/standalone/compose/index.js b/app/javascript/flavours/glitch/features/standalone/compose/index.js
index a77b59448..b33c21cb5 100644
--- a/app/javascript/flavours/glitch/features/standalone/compose/index.js
+++ b/app/javascript/flavours/glitch/features/standalone/compose/index.js
@@ -1,5 +1,5 @@
 import React from 'react';
-import Composer from 'flavours/glitch/features/composer';
+import ComposeFormContainer from 'flavours/glitch/features/compose/containers/compose_form_container';
 import NotificationsContainer from 'flavours/glitch/features/ui/containers/notifications_container';
 import LoadingBarContainer from 'flavours/glitch/features/ui/containers/loading_bar_container';
 import ModalContainer from 'flavours/glitch/features/ui/containers/modal_container';
@@ -9,7 +9,7 @@ export default class Compose extends React.PureComponent {
   render () {
     return (
       <div>
-        <Composer />
+        <ComposeFormContainer />
         <NotificationsContainer />
         <ModalContainer />
         <LoadingBarContainer className='loading-bar' />
diff --git a/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js b/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js
index a8ac83366..f13e2e645 100644
--- a/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/onboarding_modal.js
@@ -6,7 +6,7 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 import ReactSwipeableViews from 'react-swipeable-views';
 import classNames from 'classnames';
 import Permalink from 'flavours/glitch/components/permalink';
-import { WrappedComponent as RawComposer } from 'flavours/glitch/features/composer';
+import { ComposeForm } from 'flavours/glitch/features/compose/components/compose_form';
 import DrawerAccount from 'flavours/glitch/features/compose/components/navigation_bar';
 import Search from 'flavours/glitch/features/compose/components/search';
 import ColumnHeader from './column_header';
@@ -45,8 +45,7 @@ const PageTwo = ({ intl, myAccount }) => (
     <div className='figure non-interactive'>
       <div className='pseudo-drawer'>
         <DrawerAccount account={myAccount} />
-        <RawComposer
-          intl={intl}
+        <ComposeForm
           privacy='public'
           text='Awoo! #introductions'
         />