diff options
Diffstat (limited to 'app/javascript/flavours/glitch')
14 files changed, 134 insertions, 25 deletions
diff --git a/app/javascript/flavours/glitch/actions/boosts.js b/app/javascript/flavours/glitch/actions/boosts.js new file mode 100644 index 000000000..6e14065d6 --- /dev/null +++ b/app/javascript/flavours/glitch/actions/boosts.js @@ -0,0 +1,29 @@ +import { openModal } from './modal'; + +export const BOOSTS_INIT_MODAL = 'BOOSTS_INIT_MODAL'; +export const BOOSTS_CHANGE_PRIVACY = 'BOOSTS_CHANGE_PRIVACY'; + +export function initBoostModal(props) { + return (dispatch, getState) => { + const default_privacy = getState().getIn(['compose', 'default_privacy']); + + const privacy = props.status.get('visibility') === 'private' ? 'private' : default_privacy; + + dispatch({ + type: BOOSTS_INIT_MODAL, + privacy + }); + + dispatch(openModal('BOOST', props)); + }; +} + + +export function changeBoostPrivacy(privacy) { + return dispatch => { + dispatch({ + type: BOOSTS_CHANGE_PRIVACY, + privacy, + }); + }; +} diff --git a/app/javascript/flavours/glitch/actions/interactions.js b/app/javascript/flavours/glitch/actions/interactions.js index 4407f8b6e..336c8fa90 100644 --- a/app/javascript/flavours/glitch/actions/interactions.js +++ b/app/javascript/flavours/glitch/actions/interactions.js @@ -41,11 +41,11 @@ export const UNBOOKMARK_REQUEST = 'UNBOOKMARKED_REQUEST'; export const UNBOOKMARK_SUCCESS = 'UNBOOKMARKED_SUCCESS'; export const UNBOOKMARK_FAIL = 'UNBOOKMARKED_FAIL'; -export function reblog(status) { +export function reblog(status, visibility) { return function (dispatch, getState) { dispatch(reblogRequest(status)); - api(getState).post(`/api/v1/statuses/${status.get('id')}/reblog`).then(function (response) { + api(getState).post(`/api/v1/statuses/${status.get('id')}/reblog`, { visibility }).then(function (response) { // The reblog API method returns a new status wrapped around the original. In this case we are only // interested in how the original is modified, hence passing it skipping the wrapper dispatch(importFetchedStatus(response.data.reblog)); diff --git a/app/javascript/flavours/glitch/components/dropdown_menu.js b/app/javascript/flavours/glitch/components/dropdown_menu.js index d1aba691c..023fecb9a 100644 --- a/app/javascript/flavours/glitch/components/dropdown_menu.js +++ b/app/javascript/flavours/glitch/components/dropdown_menu.js @@ -177,7 +177,6 @@ export default class Dropdown extends React.PureComponent { disabled: PropTypes.bool, status: ImmutablePropTypes.map, isUserTouching: PropTypes.func, - isModalOpen: PropTypes.bool.isRequired, onOpen: PropTypes.func.isRequired, onClose: PropTypes.func.isRequired, dropdownPlacement: PropTypes.string, diff --git a/app/javascript/flavours/glitch/containers/dropdown_menu_container.js b/app/javascript/flavours/glitch/containers/dropdown_menu_container.js index e60ee814d..1c0385b74 100644 --- a/app/javascript/flavours/glitch/containers/dropdown_menu_container.js +++ b/app/javascript/flavours/glitch/containers/dropdown_menu_container.js @@ -5,7 +5,6 @@ import DropdownMenu from 'flavours/glitch/components/dropdown_menu'; import { isUserTouching } from 'flavours/glitch/util/is_mobile'; const mapStateToProps = state => ({ - isModalOpen: state.get('modal').modalType === 'ACTIONS', dropdownPlacement: state.getIn(['dropdown_menu', 'placement']), openDropdownId: state.getIn(['dropdown_menu', 'openId']), openedViaKeyboard: state.getIn(['dropdown_menu', 'keyboard']), diff --git a/app/javascript/flavours/glitch/containers/status_container.js b/app/javascript/flavours/glitch/containers/status_container.js index 7782246a6..6461bf805 100644 --- a/app/javascript/flavours/glitch/containers/status_container.js +++ b/app/javascript/flavours/glitch/containers/status_container.js @@ -21,6 +21,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 { openModal } from 'flavours/glitch/actions/modal'; import { deployPictureInPicture } from 'flavours/glitch/actions/picture_in_picture'; import { changeLocalSetting } from 'flavours/glitch/actions/local_settings'; @@ -96,11 +97,11 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({ }); }, - onModalReblog (status) { + onModalReblog (status, privacy) { if (status.get('reblogged')) { dispatch(unreblog(status)); } else { - dispatch(reblog(status)); + dispatch(reblog(status, privacy)); } }, @@ -108,11 +109,11 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({ dispatch((_, getState) => { let state = getState(); if (state.getIn(['local_settings', 'confirm_boost_missing_media_description']) && status.get('media_attachments').some(item => !item.get('description')) && !status.get('reblogged')) { - dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog, missingMediaDescription: true })); + dispatch(initBoostModal({ status, onReblog: this.onModalReblog, missingMediaDescription: true })); } else 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/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/privacy_dropdown.js b/app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.js index ec8d1378e..39f7c7bd1 100644 --- a/app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.js +++ b/app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.js @@ -53,12 +53,13 @@ class PrivacyDropdown extends React.PureComponent { 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, intl } = this.props; + 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. @@ -89,7 +90,11 @@ class PrivacyDropdown extends React.PureComponent { }, }; - const items = [privacyItems.public, privacyItems.unlisted, privacyItems.private, privacyItems.direct]; + const items = [privacyItems.public, privacyItems.unlisted, privacyItems.private]; + + if (!noDirect) { + items.push(privacyItems.direct); + } return ( <Dropdown @@ -100,6 +105,8 @@ class PrivacyDropdown extends React.PureComponent { 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/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> diff --git a/app/javascript/flavours/glitch/reducers/boosts.js b/app/javascript/flavours/glitch/reducers/boosts.js new file mode 100644 index 000000000..3541ca0c2 --- /dev/null +++ b/app/javascript/flavours/glitch/reducers/boosts.js @@ -0,0 +1,25 @@ +import Immutable from 'immutable'; + +import { + BOOSTS_INIT_MODAL, + BOOSTS_CHANGE_PRIVACY, +} from 'flavours/glitch/actions/boosts'; + +const initialState = Immutable.Map({ + new: Immutable.Map({ + privacy: 'public', + }), +}); + +export default function mutes(state = initialState, action) { + switch (action.type) { + case BOOSTS_INIT_MODAL: + return state.withMutations((state) => { + state.setIn(['new', 'privacy'], action.privacy); + }); + case BOOSTS_CHANGE_PRIVACY: + return state.setIn(['new', 'privacy'], action.privacy); + default: + return state; + } +} diff --git a/app/javascript/flavours/glitch/reducers/index.js b/app/javascript/flavours/glitch/reducers/index.js index b1ddb769e..c452e834c 100644 --- a/app/javascript/flavours/glitch/reducers/index.js +++ b/app/javascript/flavours/glitch/reducers/index.js @@ -18,6 +18,7 @@ import status_lists from './status_lists'; import mutes from './mutes'; import blocks from './blocks'; import reports from './reports'; +import boosts from './boosts'; import contexts from './contexts'; import compose from './compose'; import search from './search'; @@ -61,6 +62,7 @@ const reducers = { mutes, blocks, reports, + boosts, contexts, compose, search, diff --git a/app/javascript/flavours/glitch/styles/components/modal.scss b/app/javascript/flavours/glitch/styles/components/modal.scss index 16c05bc3e..0bda305a8 100644 --- a/app/javascript/flavours/glitch/styles/components/modal.scss +++ b/app/javascript/flavours/glitch/styles/components/modal.scss @@ -1031,3 +1031,12 @@ } } } + +.modal-root__container .composer--options--dropdown { + flex-grow: 0; +} + +.modal-root__container .composer--options--dropdown--content { + pointer-events: auto; + z-index: 9999; +} |