about summary refs log tree commit diff
path: root/app/javascript/flavours/glitch/features/compose/components/options.jsx
diff options
context:
space:
mode:
Diffstat (limited to 'app/javascript/flavours/glitch/features/compose/components/options.jsx')
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/options.jsx323
1 files changed, 323 insertions, 0 deletions
diff --git a/app/javascript/flavours/glitch/features/compose/components/options.jsx b/app/javascript/flavours/glitch/features/compose/components/options.jsx
new file mode 100644
index 000000000..19ead2f21
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/components/options.jsx
@@ -0,0 +1,323 @@
+//  Package imports.
+import PropTypes from 'prop-types';
+import React from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { defineMessages, injectIntl } from 'react-intl';
+import spring from 'react-motion/lib/spring';
+import Toggle from 'react-toggle';
+import { connect } from 'react-redux';
+
+//  Components.
+import IconButton from 'flavours/glitch/components/icon_button';
+import TextIconButton from './text_icon_button';
+import DropdownContainer from '../containers/dropdown_container';
+import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
+import LanguageDropdown from '../containers/language_dropdown_container';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+
+//  Utils.
+import Motion from '../../ui/util/optional_motion';
+import { pollLimits } from 'flavours/glitch/initial_state';
+
+//  Messages.
+const messages = defineMessages({
+  advanced_options_icon_title: {
+    defaultMessage: 'Advanced options',
+    id: 'advanced_options.icon_title',
+  },
+  attach: {
+    defaultMessage: 'Attach...',
+    id: 'compose.attach',
+  },
+  content_type: {
+    defaultMessage: 'Content type',
+    id: 'content-type.change',
+  },
+  doodle: {
+    defaultMessage: 'Draw something',
+    id: 'compose.attach.doodle',
+  },
+  html: {
+    defaultMessage: 'HTML',
+    id: 'compose.content-type.html',
+  },
+  local_only_long: {
+    defaultMessage: 'Do not post to other instances',
+    id: 'advanced_options.local-only.long',
+  },
+  local_only_short: {
+    defaultMessage: 'Local-only',
+    id: 'advanced_options.local-only.short',
+  },
+  markdown: {
+    defaultMessage: 'Markdown',
+    id: 'compose.content-type.markdown',
+  },
+  plain: {
+    defaultMessage: 'Plain text',
+    id: 'compose.content-type.plain',
+  },
+  spoiler: {
+    defaultMessage: 'Hide text behind warning',
+    id: 'compose_form.spoiler',
+  },
+  threaded_mode_long: {
+    defaultMessage: 'Automatically opens a reply on posting',
+    id: 'advanced_options.threaded_mode.long',
+  },
+  threaded_mode_short: {
+    defaultMessage: 'Threaded mode',
+    id: 'advanced_options.threaded_mode.short',
+  },
+  upload: {
+    defaultMessage: 'Upload a file',
+    id: 'compose.attach.upload',
+  },
+  add_poll: {
+    defaultMessage: 'Add a poll',
+    id: 'poll_button.add_poll',
+  },
+  remove_poll: {
+    defaultMessage: 'Remove poll',
+    id: 'poll_button.remove_poll',
+  },
+});
+
+const mapStateToProps = (state, { name }) => ({
+  checked: state.getIn(['compose', 'advanced_options', name]),
+});
+
+class ToggleOptionImpl extends ImmutablePureComponent {
+
+  static propTypes = {
+    name: PropTypes.string.isRequired,
+    checked: PropTypes.bool,
+    onChangeAdvancedOption: PropTypes.func.isRequired,
+  };
+
+  handleChange = () => {
+    this.props.onChangeAdvancedOption(this.props.name);
+  };
+
+  render() {
+    const { meta, text, checked } = this.props;
+
+    return (
+      <React.Fragment>
+        <Toggle checked={checked} onChange={this.handleChange} />
+
+        <div className='privacy-dropdown__option__content'>
+          <strong>{text}</strong>
+          {meta}
+        </div>
+      </React.Fragment>
+    );
+  }
+
+}
+
+const ToggleOption = connect(mapStateToProps)(ToggleOptionImpl);
+
+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,
+    onChangeContentType: PropTypes.func,
+    onTogglePoll: PropTypes.func,
+    onDoodleOpen: PropTypes.func,
+    onToggleSpoiler: PropTypes.func,
+    onUpload: PropTypes.func,
+    contentType: PropTypes.string,
+    resetFileKey: PropTypes.number,
+    spoiler: PropTypes.bool,
+    showContentTypeChoice: PropTypes.bool,
+    isEditing: PropTypes.bool,
+  };
+
+  //  Handles file selection.
+  handleChangeFiles = ({ target: { files } }) => {
+    const { onUpload } = this.props;
+    if (files.length && onUpload) {
+      onUpload(files);
+    }
+  };
+
+  //  Handles attachment clicks.
+  handleClickAttach = (name) => {
+    const { fileElement } = this;
+    const { onDoodleOpen } = this.props;
+
+    //  We switch over the name of the option.
+    switch (name) {
+    case 'upload':
+      if (fileElement) {
+        fileElement.click();
+      }
+      return;
+    case 'doodle':
+      if (onDoodleOpen) {
+        onDoodleOpen();
+      }
+      return;
+    }
+  };
+
+  //  Handles a ref to the file input.
+  handleRefFileElement = (fileElement) => {
+    this.fileElement = fileElement;
+  };
+
+  renderToggleItemContents = (item) => {
+    const { onChangeAdvancedOption } = this.props;
+    const { name, meta, text } = item;
+
+    return <ToggleOption name={name} text={text} meta={meta} onChangeAdvancedOption={onChangeAdvancedOption} />;
+  };
+
+  //  Rendering.
+  render () {
+    const {
+      acceptContentTypes,
+      advancedOptions,
+      contentType,
+      disabled,
+      allowMedia,
+      hasMedia,
+      allowPoll,
+      hasPoll,
+      onChangeAdvancedOption,
+      onChangeContentType,
+      onTogglePoll,
+      onToggleSpoiler,
+      resetFileKey,
+      spoiler,
+      showContentTypeChoice,
+      isEditing,
+      intl: { formatMessage },
+    } = this.props;
+
+    const contentTypeItems = {
+      plain: {
+        icon: 'file-text',
+        name: 'text/plain',
+        text: formatMessage(messages.plain),
+      },
+      html: {
+        icon: 'code',
+        name: 'text/html',
+        text: formatMessage(messages.html),
+      },
+      markdown: {
+        icon: 'arrow-circle-down',
+        name: 'text/markdown',
+        text: formatMessage(messages.markdown),
+      },
+    };
+
+    //  The result.
+    return (
+      <div className='compose-form__buttons'>
+        <input
+          accept={acceptContentTypes}
+          disabled={disabled || !allowMedia}
+          key={resetFileKey}
+          onChange={this.handleChangeFiles}
+          ref={this.handleRefFileElement}
+          type='file'
+          multiple
+          style={{ display: 'none' }}
+        />
+        <DropdownContainer
+          disabled={disabled || !allowMedia}
+          icon='paperclip'
+          items={[
+            {
+              icon: 'cloud-upload',
+              name: 'upload',
+              text: formatMessage(messages.upload),
+            },
+            {
+              icon: 'paint-brush',
+              name: 'doodle',
+              text: formatMessage(messages.doodle),
+            },
+          ]}
+          onChange={this.handleClickAttach}
+          title={formatMessage(messages.attach)}
+        />
+        {!!pollLimits && (
+          <IconButton
+            active={hasPoll}
+            disabled={disabled || !allowPoll}
+            icon='tasks'
+            inverted
+            onClick={onTogglePoll}
+            size={18}
+            style={{
+              height: null,
+              lineHeight: null,
+            }}
+            title={formatMessage(hasPoll ? messages.remove_poll : messages.add_poll)}
+          />
+        )}
+        <hr />
+        <PrivacyDropdownContainer disabled={disabled || isEditing} />
+        {showContentTypeChoice && (
+          <DropdownContainer
+            disabled={disabled}
+            icon={(contentTypeItems[contentType.split('/')[1]] || {}).icon}
+            items={[
+              contentTypeItems.plain,
+              contentTypeItems.html,
+              contentTypeItems.markdown,
+            ]}
+            onChange={onChangeContentType}
+            title={formatMessage(messages.content_type)}
+            value={contentType}
+          />
+        )}
+        {onToggleSpoiler && (
+          <TextIconButton
+            active={spoiler}
+            ariaControls='glitch.composer.spoiler.input'
+            label='CW'
+            onClick={onToggleSpoiler}
+            title={formatMessage(messages.spoiler)}
+          />
+        )}
+        <LanguageDropdown />
+        <DropdownContainer
+          disabled={disabled || isEditing}
+          icon='ellipsis-h'
+          items={advancedOptions ? [
+            {
+              meta: formatMessage(messages.local_only_long),
+              name: 'do_not_federate',
+              text: formatMessage(messages.local_only_short),
+            },
+            {
+              meta: formatMessage(messages.threaded_mode_long),
+              name: 'threaded_mode',
+              text: formatMessage(messages.threaded_mode_short),
+            },
+          ] : null}
+          onChange={onChangeAdvancedOption}
+          renderItemContents={this.renderToggleItemContents}
+          title={formatMessage(messages.advanced_options_icon_title)}
+          closeOnChange={false}
+        />
+      </div>
+    );
+  }
+
+}
+
+export default injectIntl(ComposerOptions);