about summary refs log tree commit diff
path: root/app/javascript/flavours/glitch/features
diff options
context:
space:
mode:
Diffstat (limited to 'app/javascript/flavours/glitch/features')
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/dropdown.js8
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/options.js76
-rw-r--r--app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.js115
-rw-r--r--app/javascript/flavours/glitch/features/picture_in_picture/components/footer.js7
-rw-r--r--app/javascript/flavours/glitch/features/status/components/action_bar.js7
-rw-r--r--app/javascript/flavours/glitch/features/status/containers/detailed_status_container.js7
-rw-r--r--app/javascript/flavours/glitch/features/status/index.js9
-rw-r--r--app/javascript/flavours/glitch/features/ui/components/boost_modal.js37
8 files changed, 174 insertions, 92 deletions
diff --git a/app/javascript/flavours/glitch/features/compose/components/dropdown.js b/app/javascript/flavours/glitch/features/compose/components/dropdown.js
index 04ef3964b..abf7cbba1 100644
--- a/app/javascript/flavours/glitch/features/compose/components/dropdown.js
+++ b/app/javascript/flavours/glitch/features/compose/components/dropdown.js
@@ -31,6 +31,8 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
     title: PropTypes.string,
     value: PropTypes.string,
     onChange: PropTypes.func,
+    noModal: PropTypes.bool,
+    container: PropTypes.func,
   };
 
   state = {
@@ -42,10 +44,10 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
 
   //  Toggles opening and closing the dropdown.
   handleToggle = ({ target, type }) => {
-    const { onModalOpen } = this.props;
+    const { onModalOpen, noModal } = this.props;
     const { open } = this.state;
 
-    if (isUserTouching()) {
+    if (!noModal && isUserTouching()) {
       if (this.state.open) {
         this.props.onModalClose();
       } else {
@@ -183,6 +185,7 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
       items,
       onChange,
       value,
+      container,
     } = this.props;
     const { open, placement } = this.state;
     const computedClass = classNames('composer--options--dropdown', {
@@ -219,6 +222,7 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
           placement={placement}
           show={open}
           target={this}
+          container={container}
         >
           <DropdownMenu
             items={items}
diff --git a/app/javascript/flavours/glitch/features/compose/components/options.js b/app/javascript/flavours/glitch/features/compose/components/options.js
index 9e332aabd..f9212bbae 100644
--- a/app/javascript/flavours/glitch/features/compose/components/options.js
+++ b/app/javascript/flavours/glitch/features/compose/components/options.js
@@ -9,6 +9,7 @@ import spring from 'react-motion/lib/spring';
 import IconButton from 'flavours/glitch/components/icon_button';
 import TextIconButton from './text_icon_button';
 import Dropdown from './dropdown';
+import PrivacyDropdown from './privacy_dropdown';
 import ImmutablePureComponent from 'react-immutable-pure-component';
 
 //  Utils.
@@ -25,22 +26,10 @@ const messages = defineMessages({
     defaultMessage: 'Attach...',
     id: 'compose.attach',
   },
-  change_privacy: {
-    defaultMessage: 'Adjust status privacy',
-    id: 'privacy.change',
-  },
   content_type: {
     defaultMessage: 'Content type',
     id: 'content-type.change',
   },
-  direct_long: {
-    defaultMessage: 'Visible for mentioned users only',
-    id: 'privacy.direct.long',
-  },
-  direct_short: {
-    defaultMessage: 'Direct',
-    id: 'privacy.direct.short',
-  },
   doodle: {
     defaultMessage: 'Draw something',
     id: 'compose.attach.doodle',
@@ -65,22 +54,6 @@ const messages = defineMessages({
     defaultMessage: 'Plain text',
     id: 'compose.content-type.plain',
   },
-  private_long: {
-    defaultMessage: 'Visible for followers only',
-    id: 'privacy.private.long',
-  },
-  private_short: {
-    defaultMessage: 'Followers-only',
-    id: 'privacy.private.short',
-  },
-  public_long: {
-    defaultMessage: 'Visible for all, shown in public timelines',
-    id: 'privacy.public.long',
-  },
-  public_short: {
-    defaultMessage: 'Public',
-    id: 'privacy.public.short',
-  },
   spoiler: {
     defaultMessage: 'Hide text behind warning',
     id: 'compose_form.spoiler',
@@ -93,14 +66,6 @@ const messages = defineMessages({
     defaultMessage: 'Threaded mode',
     id: 'advanced_options.threaded_mode.short',
   },
-  unlisted_long: {
-    defaultMessage: 'Visible for all, but not in public timelines',
-    id: 'privacy.unlisted.long',
-  },
-  unlisted_short: {
-    defaultMessage: 'Unlisted',
-    id: 'privacy.unlisted.short',
-  },
   upload: {
     defaultMessage: 'Upload a file',
     id: 'compose.attach.upload',
@@ -201,35 +166,6 @@ class ComposerOptions extends ImmutablePureComponent {
       showContentTypeChoice,
     } = this.props;
 
-    //  We predefine our privacy items so that we can easily pick the
-    //  dropdown icon later.
-    const privacyItems = {
-      direct: {
-        icon: 'envelope',
-        meta: <FormattedMessage {...messages.direct_long} />,
-        name: 'direct',
-        text: <FormattedMessage {...messages.direct_short} />,
-      },
-      private: {
-        icon: 'lock',
-        meta: <FormattedMessage {...messages.private_long} />,
-        name: 'private',
-        text: <FormattedMessage {...messages.private_short} />,
-      },
-      public: {
-        icon: 'globe',
-        meta: <FormattedMessage {...messages.public_long} />,
-        name: 'public',
-        text: <FormattedMessage {...messages.public_short} />,
-      },
-      unlisted: {
-        icon: 'unlock',
-        meta: <FormattedMessage {...messages.unlisted_long} />,
-        name: 'unlisted',
-        text: <FormattedMessage {...messages.unlisted_short} />,
-      },
-    };
-
     const contentTypeItems = {
       plain: {
         icon: 'file-text',
@@ -297,19 +233,11 @@ class ComposerOptions extends ImmutablePureComponent {
           />
         )}
         <hr />
-        <Dropdown
+        <PrivacyDropdown
           disabled={disabled}
-          icon={(privacyItems[privacy] || {}).icon}
-          items={[
-            privacyItems.public,
-            privacyItems.unlisted,
-            privacyItems.private,
-            privacyItems.direct,
-          ]}
           onChange={onChangeVisibility}
           onModalClose={onModalClose}
           onModalOpen={onModalOpen}
-          title={intl.formatMessage(messages.change_privacy)}
           value={privacy}
         />
         {showContentTypeChoice && (
diff --git a/app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.js b/app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.js
new file mode 100644
index 000000000..39f7c7bd1
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.js
@@ -0,0 +1,115 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
+import Dropdown from './dropdown';
+
+const messages = defineMessages({
+  change_privacy: {
+    defaultMessage: 'Adjust status privacy',
+    id: 'privacy.change',
+  },
+  direct_long: {
+    defaultMessage: 'Visible for mentioned users only',
+    id: 'privacy.direct.long',
+  },
+  direct_short: {
+    defaultMessage: 'Direct',
+    id: 'privacy.direct.short',
+  },
+  private_long: {
+    defaultMessage: 'Visible for followers only',
+    id: 'privacy.private.long',
+  },
+  private_short: {
+    defaultMessage: 'Followers-only',
+    id: 'privacy.private.short',
+  },
+  public_long: {
+    defaultMessage: 'Visible for all, shown in public timelines',
+    id: 'privacy.public.long',
+  },
+  public_short: {
+    defaultMessage: 'Public',
+    id: 'privacy.public.short',
+  },
+  unlisted_long: {
+    defaultMessage: 'Visible for all, but not in public timelines',
+    id: 'privacy.unlisted.long',
+  },
+  unlisted_short: {
+    defaultMessage: 'Unlisted',
+    id: 'privacy.unlisted.short',
+  },
+});
+
+export default @injectIntl
+class PrivacyDropdown extends React.PureComponent {
+
+  static propTypes = {
+    isUserTouching: PropTypes.func,
+    onModalOpen: PropTypes.func,
+    onModalClose: PropTypes.func,
+    value: PropTypes.string.isRequired,
+    onChange: PropTypes.func.isRequired,
+    noDirect: PropTypes.bool,
+    noModal: PropTypes.bool,
+    container: PropTypes.func,
+    intl: PropTypes.object.isRequired,
+  };
+
+  render () {
+    const { value, onChange, onModalOpen, onModalClose, disabled, noDirect, noModal, container, intl } = this.props;
+
+    //  We predefine our privacy items so that we can easily pick the
+    //  dropdown icon later.
+    const privacyItems = {
+      direct: {
+        icon: 'envelope',
+        meta: <FormattedMessage {...messages.direct_long} />,
+        name: 'direct',
+        text: <FormattedMessage {...messages.direct_short} />,
+      },
+      private: {
+        icon: 'lock',
+        meta: <FormattedMessage {...messages.private_long} />,
+        name: 'private',
+        text: <FormattedMessage {...messages.private_short} />,
+      },
+      public: {
+        icon: 'globe',
+        meta: <FormattedMessage {...messages.public_long} />,
+        name: 'public',
+        text: <FormattedMessage {...messages.public_short} />,
+      },
+      unlisted: {
+        icon: 'unlock',
+        meta: <FormattedMessage {...messages.unlisted_long} />,
+        name: 'unlisted',
+        text: <FormattedMessage {...messages.unlisted_short} />,
+      },
+    };
+
+    const items = [privacyItems.public, privacyItems.unlisted, privacyItems.private];
+
+    if (!noDirect) {
+      items.push(privacyItems.direct);
+    }
+
+    return (
+      <Dropdown
+        disabled={disabled}
+        icon={(privacyItems[value] || {}).icon}
+        items={items}
+        onChange={onChange}
+        onModalClose={onModalClose}
+        onModalOpen={onModalOpen}
+        title={intl.formatMessage(messages.change_privacy)}
+        container={container}
+        noModal={noModal}
+        value={value}
+      />
+    );
+  }
+
+}
diff --git a/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.js b/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.js
index 2ddba140e..d8989ec61 100644
--- a/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.js
+++ b/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.js
@@ -11,6 +11,7 @@ import { replyCompose } from 'flavours/glitch/actions/compose';
 import { reblog, favourite, unreblog, unfavourite } from 'flavours/glitch/actions/interactions';
 import { makeGetStatus } from 'flavours/glitch/selectors';
 import { openModal } from 'flavours/glitch/actions/modal';
+import { initBoostModal } from 'flavours/glitch/actions/boosts';
 
 const messages = defineMessages({
   reply: { id: 'status.reply', defaultMessage: 'Reply' },
@@ -82,9 +83,9 @@ class Footer extends ImmutablePureComponent {
     }
   };
 
-  _performReblog = () => {
+  _performReblog = (privacy) => {
     const { dispatch, status } = this.props;
-    dispatch(reblog(status));
+    dispatch(reblog(status, privacy));
   }
 
   handleReblogClick = e => {
@@ -95,7 +96,7 @@ class Footer extends ImmutablePureComponent {
     } else if ((e && e.shiftKey) || !boostModal) {
       this._performReblog();
     } else {
-      dispatch(openModal('BOOST', { status, onReblog: this._performReblog }));
+      dispatch(initBoostModal({ status, onReblog: this._performReblog }));
     }
   };
 
diff --git a/app/javascript/flavours/glitch/features/status/components/action_bar.js b/app/javascript/flavours/glitch/features/status/components/action_bar.js
index 0f16d93fe..6ed5f3865 100644
--- a/app/javascript/flavours/glitch/features/status/components/action_bar.js
+++ b/app/javascript/flavours/glitch/features/status/components/action_bar.js
@@ -145,8 +145,9 @@ class ActionBar extends React.PureComponent {
   render () {
     const { status, intl } = this.props;
 
-    const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
+    const publicStatus       = ['public', 'unlisted'].includes(status.get('visibility'));
     const mutingConversation = status.get('muted');
+    const writtenByMe        = status.getIn(['account', 'id']) === me;
 
     let menu = [];
 
@@ -156,12 +157,12 @@ class ActionBar extends React.PureComponent {
       menu.push(null);
     }
 
-    if (me === status.getIn(['account', 'id'])) {
+    if (writtenByMe) {
       if (publicStatus) {
         menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick });
+        menu.push(null);
       }
 
-      menu.push(null);
       menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick });
       menu.push(null);
       menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick });
diff --git a/app/javascript/flavours/glitch/features/status/containers/detailed_status_container.js b/app/javascript/flavours/glitch/features/status/containers/detailed_status_container.js
index 9d11f37e0..40e186569 100644
--- a/app/javascript/flavours/glitch/features/status/containers/detailed_status_container.js
+++ b/app/javascript/flavours/glitch/features/status/containers/detailed_status_container.js
@@ -24,6 +24,7 @@ import {
 import { initMuteModal } from 'flavours/glitch/actions/mutes';
 import { initBlockModal } from 'flavours/glitch/actions/blocks';
 import { initReport } from 'flavours/glitch/actions/reports';
+import { initBoostModal } from 'flavours/glitch/actions/boosts';
 import { openModal } from 'flavours/glitch/actions/modal';
 import { defineMessages, injectIntl } from 'react-intl';
 import { boostModal, deleteModal } from 'flavours/glitch/util/initial_state';
@@ -67,8 +68,8 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
     });
   },
 
-  onModalReblog (status) {
-    dispatch(reblog(status));
+  onModalReblog (status, privacy) {
+    dispatch(reblog(status, privacy));
   },
 
   onReblog (status, e) {
@@ -78,7 +79,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
       if (e.shiftKey || !boostModal) {
         this.onModalReblog(status);
       } else {
-        dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog }));
+        dispatch(initBoostModal({ status, onReblog: this.onModalReblog }));
       }
     }
   },
diff --git a/app/javascript/flavours/glitch/features/status/index.js b/app/javascript/flavours/glitch/features/status/index.js
index b330adf3f..21e441407 100644
--- a/app/javascript/flavours/glitch/features/status/index.js
+++ b/app/javascript/flavours/glitch/features/status/index.js
@@ -30,6 +30,7 @@ import { muteStatus, unmuteStatus, deleteStatus } from 'flavours/glitch/actions/
 import { initMuteModal } from 'flavours/glitch/actions/mutes';
 import { initBlockModal } from 'flavours/glitch/actions/blocks';
 import { initReport } from 'flavours/glitch/actions/reports';
+import { initBoostModal } from 'flavours/glitch/actions/boosts';
 import { makeGetStatus } from 'flavours/glitch/selectors';
 import { ScrollContainer } from 'react-router-scroll-4';
 import ColumnBackButton from 'flavours/glitch/components/column_back_button';
@@ -262,13 +263,13 @@ class Status extends ImmutablePureComponent {
     }
   }
 
-  handleModalReblog = (status) => {
+  handleModalReblog = (status, privacy) => {
     const { dispatch } = this.props;
 
     if (status.get('reblogged')) {
       dispatch(unreblog(status));
     } else {
-      dispatch(reblog(status));
+      dispatch(reblog(status, privacy));
     }
   }
 
@@ -276,11 +277,11 @@ class Status extends ImmutablePureComponent {
     const { settings, dispatch } = this.props;
 
     if (settings.get('confirm_boost_missing_media_description') && status.get('media_attachments').some(item => !item.get('description')) && !status.get('reblogged')) {
-      dispatch(openModal('BOOST', { status, onReblog: this.handleModalReblog, missingMediaDescription: true }));
+      dispatch(initBoostModal({ status, onReblog: this.handleModalReblog, missingMediaDescription: true }));
     } else if ((e && e.shiftKey) || !boostModal) {
       this.handleModalReblog(status);
     } else {
-      dispatch(openModal('BOOST', { status, onReblog: this.handleModalReblog }));
+      dispatch(initBoostModal({ status, onReblog: this.handleModalReblog }));
     }
   }
 
diff --git a/app/javascript/flavours/glitch/features/ui/components/boost_modal.js b/app/javascript/flavours/glitch/features/ui/components/boost_modal.js
index 12ad426c8..c4af25599 100644
--- a/app/javascript/flavours/glitch/features/ui/components/boost_modal.js
+++ b/app/javascript/flavours/glitch/features/ui/components/boost_modal.js
@@ -1,4 +1,5 @@
 import React from 'react';
+import { connect } from 'react-redux';
 import ImmutablePropTypes from 'react-immutable-proptypes';
 import PropTypes from 'prop-types';
 import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
@@ -10,7 +11,9 @@ import DisplayName from 'flavours/glitch/components/display_name';
 import AttachmentList from 'flavours/glitch/components/attachment_list';
 import Icon from 'flavours/glitch/components/icon';
 import ImmutablePureComponent from 'react-immutable-pure-component';
+import PrivacyDropdown from 'flavours/glitch/features/compose/components/privacy_dropdown';
 import classNames from 'classnames';
+import { changeBoostPrivacy } from 'flavours/glitch/actions/boosts';
 
 const messages = defineMessages({
   cancel_reblog: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' },
@@ -21,7 +24,22 @@ const messages = defineMessages({
   direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' },
 });
 
-export default @injectIntl
+const mapStateToProps = state => {
+  return {
+    privacy: state.getIn(['boosts', 'new', 'privacy']),
+  };
+};
+
+const mapDispatchToProps = dispatch => {
+  return {
+    onChangeBoostPrivacy(value) {
+      dispatch(changeBoostPrivacy(value));
+    },
+  };
+};
+
+export default @connect(mapStateToProps, mapDispatchToProps)
+@injectIntl
 class BoostModal extends ImmutablePureComponent {
 
   static contextTypes = {
@@ -41,7 +59,7 @@ class BoostModal extends ImmutablePureComponent {
   }
 
   handleReblog = () => {
-    this.props.onReblog(this.props.status);
+    this.props.onReblog(this.props.status, this.props.privacy);
     this.props.onClose();
   }
 
@@ -55,12 +73,16 @@ class BoostModal extends ImmutablePureComponent {
     }
   }
 
+  _findContainer = () => {
+    return document.getElementsByClassName('modal-root__container')[0];
+  };
+
   setRef = (c) => {
     this.button = c;
   }
 
   render () {
-    const { status, missingMediaDescription, intl } = this.props;
+    const { status, missingMediaDescription, privacy, intl } = this.props;
     const buttonText = status.get('reblogged') ? messages.cancel_reblog : messages.reblog;
 
     const visibilityIconInfo = {
@@ -111,6 +133,15 @@ class BoostModal extends ImmutablePureComponent {
                 <FormattedMessage id='boost_modal.combo' defaultMessage='You can press {combo} to skip this next time' values={{ combo: <span>Shift + <Icon id='retweet' /></span> }} />
             }
           </div>
+
+          {status.get('visibility') !== 'private' && !status.get('reblogged') && (
+            <PrivacyDropdown
+              noDirect
+              value={privacy}
+              container={this._findContainer}
+              onChange={this.props.onChangeBoostPrivacy}
+            />
+          )}
           <Button text={intl.formatMessage(buttonText)} onClick={this.handleReblog} ref={this.setRef} />
         </div>
       </div>