about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/compose_form.js5
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/dropdown.js (renamed from app/javascript/flavours/glitch/features/composer/options/dropdown/index.js)4
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js219
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/options.js (renamed from app/javascript/flavours/glitch/features/composer/options/index.js)99
-rw-r--r--app/javascript/flavours/glitch/features/composer/options/dropdown/content/index.js146
-rw-r--r--app/javascript/flavours/glitch/features/composer/options/dropdown/content/item/index.js129
6 files changed, 260 insertions, 342 deletions
diff --git a/app/javascript/flavours/glitch/features/compose/components/compose_form.js b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
index ae22f4d6d..a9be9c751 100644
--- a/app/javascript/flavours/glitch/features/compose/components/compose_form.js
+++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.js
@@ -5,7 +5,7 @@ import { defineMessages, injectIntl } from 'react-intl';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 
 //  Components.
-import ComposerOptions from '../../composer/options';
+import Options from './options';
 import ComposerPublisher from '../../composer/publisher';
 import TextareaIcons from './textarea_icons';
 import UploadFormContainer from '../containers/upload_form_container';
@@ -372,7 +372,7 @@ class ComposeForm extends ImmutablePureComponent {
           <PollFormContainer />
         </div>
 
-        <ComposerOptions
+        <Options
           acceptContentTypes={acceptContentTypes}
           advancedOptions={advancedOptions}
           disabled={isSubmitting}
@@ -382,7 +382,6 @@ class ComposeForm extends ImmutablePureComponent {
           hasMedia={media && !!media.size}
           allowPoll={!(media && !!media.size)}
           hasPoll={!!poll}
-          intl={intl}
           onChangeAdvancedOption={onChangeAdvancedOption}
           onChangeSensitivity={onChangeSensitivity}
           onChangeVisibility={onChangeVisibility}
diff --git a/app/javascript/flavours/glitch/features/composer/options/dropdown/index.js b/app/javascript/flavours/glitch/features/compose/components/dropdown.js
index 7817cc964..8d982208f 100644
--- a/app/javascript/flavours/glitch/features/composer/options/dropdown/index.js
+++ b/app/javascript/flavours/glitch/features/compose/components/dropdown.js
@@ -6,7 +6,7 @@ import Overlay from 'react-overlays/lib/Overlay';
 
 //  Components.
 import IconButton from 'flavours/glitch/components/icon_button';
-import ComposerOptionsDropdownContent from './content';
+import DropdownMenu from './dropdown_menu';
 
 //  Utils.
 import { isUserTouching } from 'flavours/glitch/util/is_mobile';
@@ -196,7 +196,7 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
           show={open}
           target={this}
         >
-          <ComposerOptionsDropdownContent
+          <DropdownMenu
             items={items}
             onChange={onChange}
             onClose={handleClose}
diff --git a/app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js b/app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js
new file mode 100644
index 000000000..b4e2ec07b
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js
@@ -0,0 +1,219 @@
+//  Package imports.
+import PropTypes from 'prop-types';
+import React from 'react';
+import spring from 'react-motion/lib/spring';
+import Toggle from 'react-toggle';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import classNames from 'classnames';
+
+//  Components.
+import Icon from 'flavours/glitch/components/icon';
+
+//  Utils.
+import { withPassive } from 'flavours/glitch/util/dom_helpers';
+import Motion from 'flavours/glitch/util/optional_motion';
+import { assignHandlers } from 'flavours/glitch/util/react_helpers';
+
+class ComposerOptionsDropdownContentItem extends ImmutablePureComponent {
+
+  static propTypes = {
+    active: PropTypes.bool,
+    name: PropTypes.string,
+    onChange: PropTypes.func,
+    onClose: PropTypes.func,
+    options: PropTypes.shape({
+      icon: PropTypes.string,
+      meta: PropTypes.node,
+      on: PropTypes.bool,
+      text: PropTypes.node,
+    }),
+  };
+
+  handleActivate = (e) => {
+    const {
+      name,
+      onChange,
+      onClose,
+      options: { on },
+    } = this.props;
+
+    //  If the escape key was pressed, we close the dropdown.
+    if (e.key === 'Escape' && onClose) {
+      onClose();
+
+    //  Otherwise, we both close the dropdown and change the value.
+    } else if (onChange && (!e.key || e.key === 'Enter')) {
+      e.preventDefault();  //  Prevents change in focus on click
+      if ((on === null || typeof on === 'undefined') && onClose) {
+        onClose();
+      }
+      onChange(name);
+    }
+  }
+
+  //  Rendering.
+  render () {
+    const {
+      active,
+      options: {
+        icon,
+        meta,
+        on,
+        text,
+      },
+    } = this.props;
+    const computedClass = classNames('composer--options--dropdown--content--item', {
+      active,
+      lengthy: meta,
+      'toggled-off': !on && on !== null && typeof on !== 'undefined',
+      'toggled-on': on,
+      'with-icon': icon,
+    });
+
+    let prefix = null;
+
+    if (on !== null && typeof on !== 'undefined') {
+      prefix = <Toggle checked={on} onChange={this.handleActivate} />;
+    } else if (icon) {
+      prefix = <Icon className='icon' fullwidth icon={icon} />
+    }
+
+    //  The result.
+    return (
+      <div
+        className={computedClass}
+        onClick={this.handleActivate}
+        onKeyDown={this.handleActivate}
+        role='button'
+        tabIndex='0'
+      >
+        {prefix}
+
+        <div className='content'>
+          <strong>{text}</strong>
+          {meta ? meta : nil}
+        </div>
+      </div>
+    );
+  }
+
+};
+
+//  The spring to use with our motion.
+const springMotion = spring(1, {
+  damping: 35,
+  stiffness: 400,
+});
+
+//  The component.
+export default class ComposerOptionsDropdownContent extends React.PureComponent {
+
+  static propTypes = {
+    items: PropTypes.arrayOf(PropTypes.shape({
+      icon: PropTypes.string,
+      meta: PropTypes.node,
+      name: PropTypes.string.isRequired,
+      on: PropTypes.bool,
+      text: PropTypes.node,
+    })),
+    onChange: PropTypes.func,
+    onClose: PropTypes.func,
+    style: PropTypes.object,
+    value: PropTypes.string,
+  };
+
+  static defaultProps = {
+    style: {},
+  };
+
+  state = {
+    mounted: false,
+  };
+
+  //  When the document is clicked elsewhere, we close the dropdown.
+  handleDocumentClick = ({ target }) => {
+    const { node } = this;
+    const { onClose } = this.props;
+    if (onClose && node && !node.contains(target)) {
+      onClose();
+    }
+  }
+
+  //  Stores our node in `this.node`.
+  handleRef = (node) => {
+    this.node = node;
+  }
+
+  //  On mounting, we add our listeners.
+  componentDidMount () {
+    document.addEventListener('click', this.handleDocumentClick, false);
+    document.addEventListener('touchend', this.handleDocumentClick, withPassive);
+    this.setState({ mounted: true });
+  }
+
+  //  On unmounting, we remove our listeners.
+  componentWillUnmount () {
+    document.removeEventListener('click', this.handleDocumentClick, false);
+    document.removeEventListener('touchend', this.handleDocumentClick, withPassive);
+  }
+
+  //  Rendering.
+  render () {
+    const { mounted } = this.state;
+    const {
+      items,
+      onChange,
+      onClose,
+      style,
+      value,
+    } = this.props;
+
+    //  The result.
+    return (
+      <Motion
+        defaultStyle={{
+          opacity: 0,
+          scaleX: 0.85,
+          scaleY: 0.75,
+        }}
+        style={{
+          opacity: springMotion,
+          scaleX: springMotion,
+          scaleY: springMotion,
+        }}
+      >
+        {({ opacity, scaleX, scaleY }) => (
+          // It should not be transformed when mounting because the resulting
+          // size will be used to determine the coordinate of the menu by
+          // react-overlays
+          <div
+            className='composer--options--dropdown--content'
+            ref={this.handleRef}
+            style={{
+              ...style,
+              opacity: opacity,
+              transform: mounted ? `scale(${scaleX}, ${scaleY})` : null,
+            }}
+          >
+            {items ? items.map(
+              ({
+                name,
+                ...rest
+              }) => (
+                <ComposerOptionsDropdownContentItem
+                  active={name === value}
+                  key={name}
+                  name={name}
+                  onChange={onChange}
+                  onClose={onClose}
+                  options={rest}
+                />
+              )
+            ) : null}
+          </div>
+        )}
+      </Motion>
+    );
+  }
+
+}
diff --git a/app/javascript/flavours/glitch/features/composer/options/index.js b/app/javascript/flavours/glitch/features/compose/components/options.js
index 7c7f01dc2..8a760bd15 100644
--- a/app/javascript/flavours/glitch/features/composer/options/index.js
+++ b/app/javascript/flavours/glitch/features/compose/components/options.js
@@ -2,23 +2,17 @@
 import PropTypes from 'prop-types';
 import React from 'react';
 import ImmutablePropTypes from 'react-immutable-proptypes';
-import {
-  FormattedMessage,
-  defineMessages,
-} from 'react-intl';
+import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
 import spring from 'react-motion/lib/spring';
 
 //  Components.
 import IconButton from 'flavours/glitch/components/icon_button';
 import TextIconButton from 'flavours/glitch/components/text_icon_button';
 import Dropdown from './dropdown';
+import ImmutablePureComponent from 'react-immutable-pure-component';
 
 //  Utils.
 import Motion from 'flavours/glitch/util/optional_motion';
-import {
-  assignHandlers,
-  hiddenComponent,
-} from 'flavours/glitch/util/react_helpers';
 import { pollLimits } from 'flavours/glitch/util/initial_state';
 
 //  Messages.
@@ -109,19 +103,43 @@ const messages = defineMessages({
   },
 });
 
-//  Handlers.
-const handlers = {
+export default @injectIntl
+class ComposerOptions extends ImmutablePureComponent {
+
+  static propTypes = {
+    acceptContentTypes: PropTypes.string,
+    advancedOptions: ImmutablePropTypes.map,
+    disabled: PropTypes.bool,
+    allowMedia: PropTypes.bool,
+    hasMedia: PropTypes.bool,
+    allowPoll: PropTypes.bool,
+    hasPoll: PropTypes.bool,
+    intl: PropTypes.object.isRequired,
+    onChangeAdvancedOption: PropTypes.func,
+    onChangeSensitivity: PropTypes.func,
+    onChangeVisibility: PropTypes.func,
+    onTogglePoll: PropTypes.func,
+    onDoodleOpen: PropTypes.func,
+    onModalClose: PropTypes.func,
+    onModalOpen: PropTypes.func,
+    onToggleSpoiler: PropTypes.func,
+    onUpload: PropTypes.func,
+    privacy: PropTypes.string,
+    resetFileKey: PropTypes.number,
+    sensitive: PropTypes.bool,
+    spoiler: PropTypes.bool,
+  };
 
   //  Handles file selection.
-  handleChangeFiles ({ target: { files } }) {
+  handleChangeFiles = ({ target: { files } }) => {
     const { onUpload } = this.props;
     if (files.length && onUpload) {
       onUpload(files);
     }
-  },
+  }
 
   //  Handles attachment clicks.
-  handleClickAttach (name) {
+  handleClickAttach = (name) => {
     const { fileElement } = this;
     const { onDoodleOpen } = this.props;
 
@@ -138,34 +156,16 @@ const handlers = {
       }
       return;
     }
-  },
+  }
 
   //  Handles a ref to the file input.
-  handleRefFileElement (fileElement) {
+  handleRefFileElement = (fileElement) => {
     this.fileElement = fileElement;
-  },
-};
-
-//  The component.
-export default class ComposerOptions extends React.PureComponent {
-
-  //  Constructor.
-  constructor (props) {
-    super(props);
-    assignHandlers(this, handlers);
-
-    //  Instance variables.
-    this.fileElement = null;
   }
 
   //  Rendering.
   render () {
     const {
-      handleChangeFiles,
-      handleClickAttach,
-      handleRefFileElement,
-    } = this.handlers;
-    const {
       acceptContentTypes,
       advancedOptions,
       disabled,
@@ -223,11 +223,11 @@ export default class ComposerOptions extends React.PureComponent {
           accept={acceptContentTypes}
           disabled={disabled || !allowMedia}
           key={resetFileKey}
-          onChange={handleChangeFiles}
-          ref={handleRefFileElement}
+          onChange={this.handleChangeFiles}
+          ref={this.handleRefFileElement}
           type='file'
           multiple
-          {...hiddenComponent}
+          style={{ display: 'none' }}
         />
         <Dropdown
           disabled={disabled || !allowMedia}
@@ -244,7 +244,7 @@ export default class ComposerOptions extends React.PureComponent {
               text: <FormattedMessage {...messages.doodle} />,
             },
           ]}
-          onChange={handleClickAttach}
+          onChange={this.handleClickAttach}
           onModalClose={onModalClose}
           onModalOpen={onModalOpen}
           title={intl.formatMessage(messages.attach)}
@@ -350,28 +350,3 @@ export default class ComposerOptions extends React.PureComponent {
   }
 
 }
-
-//  Props.
-ComposerOptions.propTypes = {
-  acceptContentTypes: PropTypes.string,
-  advancedOptions: ImmutablePropTypes.map,
-  disabled: PropTypes.bool,
-  allowMedia: PropTypes.bool,
-  hasMedia: PropTypes.bool,
-  allowPoll: PropTypes.bool,
-  hasPoll: PropTypes.bool,
-  intl: PropTypes.object.isRequired,
-  onChangeAdvancedOption: PropTypes.func,
-  onChangeSensitivity: PropTypes.func,
-  onChangeVisibility: PropTypes.func,
-  onTogglePoll: PropTypes.func,
-  onDoodleOpen: PropTypes.func,
-  onModalClose: PropTypes.func,
-  onModalOpen: PropTypes.func,
-  onToggleSpoiler: PropTypes.func,
-  onUpload: PropTypes.func,
-  privacy: PropTypes.string,
-  resetFileKey: PropTypes.number,
-  sensitive: PropTypes.bool,
-  spoiler: PropTypes.bool,
-};
diff --git a/app/javascript/flavours/glitch/features/composer/options/dropdown/content/index.js b/app/javascript/flavours/glitch/features/composer/options/dropdown/content/index.js
deleted file mode 100644
index b76410561..000000000
--- a/app/javascript/flavours/glitch/features/composer/options/dropdown/content/index.js
+++ /dev/null
@@ -1,146 +0,0 @@
-//  Package imports.
-import PropTypes from 'prop-types';
-import React from 'react';
-import spring from 'react-motion/lib/spring';
-
-//  Components.
-import ComposerOptionsDropdownContentItem from './item';
-
-//  Utils.
-import { withPassive } from 'flavours/glitch/util/dom_helpers';
-import Motion from 'flavours/glitch/util/optional_motion';
-import { assignHandlers } from 'flavours/glitch/util/react_helpers';
-
-//  Handlers.
-const handlers = {
-  //  When the document is clicked elsewhere, we close the dropdown.
-  handleDocumentClick ({ target }) {
-    const { node } = this;
-    const { onClose } = this.props;
-    if (onClose && node && !node.contains(target)) {
-      onClose();
-    }
-  },
-
-  //  Stores our node in `this.node`.
-  handleRef (node) {
-    this.node = node;
-  },
-};
-
-//  The spring to use with our motion.
-const springMotion = spring(1, {
-  damping: 35,
-  stiffness: 400,
-});
-
-//  The component.
-export default class ComposerOptionsDropdownContent extends React.PureComponent {
-
-  //  Constructor.
-  constructor (props) {
-    super(props);
-    assignHandlers(this, handlers);
-
-    //  Instance variables.
-    this.node = null;
-
-    this.state = {
-      mounted: false,
-    };
-  }
-
-  //  On mounting, we add our listeners.
-  componentDidMount () {
-    const { handleDocumentClick } = this.handlers;
-    document.addEventListener('click', handleDocumentClick, false);
-    document.addEventListener('touchend', handleDocumentClick, withPassive);
-    this.setState({ mounted: true });
-  }
-
-  //  On unmounting, we remove our listeners.
-  componentWillUnmount () {
-    const { handleDocumentClick } = this.handlers;
-    document.removeEventListener('click', handleDocumentClick, false);
-    document.removeEventListener('touchend', handleDocumentClick, withPassive);
-  }
-
-  //  Rendering.
-  render () {
-    const { mounted } = this.state;
-    const { handleRef } = this.handlers;
-    const {
-      items,
-      onChange,
-      onClose,
-      style,
-      value,
-    } = this.props;
-
-    //  The result.
-    return (
-      <Motion
-        defaultStyle={{
-          opacity: 0,
-          scaleX: 0.85,
-          scaleY: 0.75,
-        }}
-        style={{
-          opacity: springMotion,
-          scaleX: springMotion,
-          scaleY: springMotion,
-        }}
-      >
-        {({ opacity, scaleX, scaleY }) => (
-          // It should not be transformed when mounting because the resulting
-          // size will be used to determine the coordinate of the menu by
-          // react-overlays
-          <div
-            className='composer--options--dropdown--content'
-            ref={handleRef}
-            style={{
-              ...style,
-              opacity: opacity,
-              transform: mounted ? `scale(${scaleX}, ${scaleY})` : null,
-            }}
-          >
-            {items ? items.map(
-              ({
-                name,
-                ...rest
-              }) => (
-                <ComposerOptionsDropdownContentItem
-                  active={name === value}
-                  key={name}
-                  name={name}
-                  onChange={onChange}
-                  onClose={onClose}
-                  options={rest}
-                />
-              )
-            ) : null}
-          </div>
-        )}
-      </Motion>
-    );
-  }
-
-}
-
-//  Props.
-ComposerOptionsDropdownContent.propTypes = {
-  items: PropTypes.arrayOf(PropTypes.shape({
-    icon: PropTypes.string,
-    meta: PropTypes.node,
-    name: PropTypes.string.isRequired,
-    on: PropTypes.bool,
-    text: PropTypes.node,
-  })),
-  onChange: PropTypes.func,
-  onClose: PropTypes.func,
-  style: PropTypes.object,
-  value: PropTypes.string,
-};
-
-//  Default props.
-ComposerOptionsDropdownContent.defaultProps = { style: {} };
diff --git a/app/javascript/flavours/glitch/features/composer/options/dropdown/content/item/index.js b/app/javascript/flavours/glitch/features/composer/options/dropdown/content/item/index.js
deleted file mode 100644
index 68a52083f..000000000
--- a/app/javascript/flavours/glitch/features/composer/options/dropdown/content/item/index.js
+++ /dev/null
@@ -1,129 +0,0 @@
-//  Package imports.
-import classNames from 'classnames';
-import PropTypes from 'prop-types';
-import React from 'react';
-import Toggle from 'react-toggle';
-
-//  Components.
-import Icon from 'flavours/glitch/components/icon';
-
-//  Utils.
-import { assignHandlers } from 'flavours/glitch/util/react_helpers';
-
-//  Handlers.
-const handlers = {
-
-  //  This function activates the dropdown item.
-  handleActivate (e) {
-    const {
-      name,
-      onChange,
-      onClose,
-      options: { on },
-    } = this.props;
-
-    //  If the escape key was pressed, we close the dropdown.
-    if (e.key === 'Escape' && onClose) {
-      onClose();
-
-    //  Otherwise, we both close the dropdown and change the value.
-    } else if (onChange && (!e.key || e.key === 'Enter')) {
-      e.preventDefault();  //  Prevents change in focus on click
-      if ((on === null || typeof on === 'undefined') && onClose) {
-        onClose();
-      }
-      onChange(name);
-    }
-  },
-};
-
-//  The component.
-export default class ComposerOptionsDropdownContentItem extends React.PureComponent {
-
-  //  Constructor.
-  constructor (props) {
-    super(props);
-    assignHandlers(this, handlers);
-  }
-
-  //  Rendering.
-  render () {
-    const { handleActivate } = this.handlers;
-    const {
-      active,
-      options: {
-        icon,
-        meta,
-        on,
-        text,
-      },
-    } = this.props;
-    const computedClass = classNames('composer--options--dropdown--content--item', {
-      active,
-      lengthy: meta,
-      'toggled-off': !on && on !== null && typeof on !== 'undefined',
-      'toggled-on': on,
-      'with-icon': icon,
-    });
-
-    //  The result.
-    return (
-      <div
-        className={computedClass}
-        onClick={handleActivate}
-        onKeyDown={handleActivate}
-        role='button'
-        tabIndex='0'
-      >
-        {function () {
-
-          //  We render a `<Toggle>` if we were provided an `on`
-          //  property, and otherwise show an `<Icon>` if available.
-          switch (true) {
-          case on !== null && typeof on !== 'undefined':
-            return (
-              <Toggle
-                checked={on}
-                onChange={handleActivate}
-              />
-            );
-          case !!icon:
-            return (
-              <Icon
-                className='icon'
-                fullwidth
-                icon={icon}
-              />
-            );
-          default:
-            return null;
-          }
-        }()}
-        {meta ? (
-          <div className='content'>
-            <strong>{text}</strong>
-            {meta}
-          </div>
-        ) :
-          <div className='content'>
-            <strong>{text}</strong>
-          </div>}
-      </div>
-    );
-  }
-
-};
-
-//  Props.
-ComposerOptionsDropdownContentItem.propTypes = {
-  active: PropTypes.bool,
-  name: PropTypes.string,
-  onChange: PropTypes.func,
-  onClose: PropTypes.func,
-  options: PropTypes.shape({
-    icon: PropTypes.string,
-    meta: PropTypes.node,
-    on: PropTypes.bool,
-    text: PropTypes.node,
-  }),
-};