diff --git a/app/javascript/mastodon/actions/boosts.js b/app/javascript/mastodon/actions/boosts.js
new file mode 100644
index 0000000000..6e14065d6f
--- /dev/null
+++ b/app/javascript/mastodon/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/mastodon/actions/interactions.js b/app/javascript/mastodon/actions/interactions.js
index 28c6b1a629..d60ccc1fb8 100644
--- a/app/javascript/mastodon/actions/interactions.js
+++ b/app/javascript/mastodon/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/mastodon/components/dropdown_menu.js b/app/javascript/mastodon/components/dropdown_menu.js
index c6b4b11873..7d0588901c 100644
--- a/app/javascript/mastodon/components/dropdown_menu.js
+++ b/app/javascript/mastodon/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/mastodon/containers/dropdown_menu_container.js b/app/javascript/mastodon/containers/dropdown_menu_container.js
index 6ec9bbffdf..c45bab40bb 100644
--- a/app/javascript/mastodon/containers/dropdown_menu_container.js
+++ b/app/javascript/mastodon/containers/dropdown_menu_container.js
@@ -6,7 +6,6 @@ import DropdownMenu from '../components/dropdown_menu';
import { isUserTouching } from '../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/mastodon/containers/status_container.js b/app/javascript/mastodon/containers/status_container.js
index d6bcb89734..9abdec138b 100644
--- a/app/javascript/mastodon/containers/status_container.js
+++ b/app/javascript/mastodon/containers/status_container.js
@@ -35,6 +35,7 @@ import {
} from '../actions/domain_blocks';
import { initMuteModal } from '../actions/mutes';
import { initBlockModal } from '../actions/blocks';
+import { initBoostModal } from '../actions/boosts';
import { initReport } from '../actions/reports';
import { openModal } from '../actions/modal';
import { deployPictureInPicture } from '../actions/picture_in_picture';
@@ -82,11 +83,11 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
});
},
- onModalReblog (status) {
+ onModalReblog (status, privacy) {
if (status.get('reblogged')) {
dispatch(unreblog(status));
} else {
- dispatch(reblog(status));
+ dispatch(reblog(status, privacy));
}
},
@@ -94,7 +95,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
if ((e && 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/mastodon/features/compose/components/privacy_dropdown.js b/app/javascript/mastodon/features/compose/components/privacy_dropdown.js
index 309f462903..936b14e9e6 100644
--- a/app/javascript/mastodon/features/compose/components/privacy_dropdown.js
+++ b/app/javascript/mastodon/features/compose/components/privacy_dropdown.js
@@ -127,7 +127,7 @@ class PrivacyDropdownMenu extends React.PureComponent {
// It should not be transformed when mounting because the resulting
// size will be used to determine the coordinate of the menu by
// react-overlays
-
+
{items.map(item => (
@@ -153,11 +153,12 @@ class PrivacyDropdown extends React.PureComponent {
static propTypes = {
isUserTouching: PropTypes.func,
- isModalOpen: PropTypes.bool.isRequired,
onModalOpen: PropTypes.func,
onModalClose: PropTypes.func,
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
+ noDirect: PropTpes.bool,
+ container: PropTypes.func,
intl: PropTypes.object.isRequired,
};
@@ -167,7 +168,7 @@ class PrivacyDropdown extends React.PureComponent {
};
handleToggle = ({ target }) => {
- if (this.props.isUserTouching()) {
+ if (this.props.isUserTouching && this.props.isUserTouching()) {
if (this.state.open) {
this.props.onModalClose();
} else {
@@ -236,12 +237,17 @@ class PrivacyDropdown extends React.PureComponent {
{ icon: 'globe', value: 'public', text: formatMessage(messages.public_short), meta: formatMessage(messages.public_long) },
{ icon: 'unlock', value: 'unlisted', text: formatMessage(messages.unlisted_short), meta: formatMessage(messages.unlisted_long) },
{ icon: 'lock', value: 'private', text: formatMessage(messages.private_short), meta: formatMessage(messages.private_long) },
- { icon: 'envelope', value: 'direct', text: formatMessage(messages.direct_short), meta: formatMessage(messages.direct_long) },
];
+
+ if (!this.props.noDirect) {
+ this.options.push(
+ { icon: 'envelope', value: 'direct', text: formatMessage(messages.direct_short), meta: formatMessage(messages.direct_long) },
+ );
+ }
}
render () {
- const { value, intl } = this.props;
+ const { value, container, intl } = this.props;
const { open, placement } = this.state;
const valueOption = this.options.find(item => item.value === value);
@@ -264,7 +270,7 @@ class PrivacyDropdown extends React.PureComponent {
/>
-
+
({
- isModalOpen: state.get('modal').modalType === 'ACTIONS',
value: state.getIn(['compose', 'privacy']),
});
diff --git a/app/javascript/mastodon/features/notifications/containers/notification_container.js b/app/javascript/mastodon/features/notifications/containers/notification_container.js
index 78576c760c..555d5e1b57 100644
--- a/app/javascript/mastodon/features/notifications/containers/notification_container.js
+++ b/app/javascript/mastodon/features/notifications/containers/notification_container.js
@@ -1,6 +1,7 @@
import { connect } from 'react-redux';
import { makeGetNotification, makeGetStatus } from '../../../selectors';
import Notification from '../components/notification';
+import { initBoostModal } from '../../../actions/boosts';
import { openModal } from '../../../actions/modal';
import { mentionCompose } from '../../../actions/compose';
import {
@@ -35,8 +36,8 @@ const mapDispatchToProps = dispatch => ({
dispatch(mentionCompose(account, router));
},
- onModalReblog (status) {
- dispatch(reblog(status));
+ onModalReblog (status, privacy) {
+ dispatch(reblog(status, privacy));
},
onReblog (status, e) {
@@ -46,7 +47,7 @@ const mapDispatchToProps = dispatch => ({
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/mastodon/features/picture_in_picture/components/footer.js b/app/javascript/mastodon/features/picture_in_picture/components/footer.js
index 1b1ec6d543..1ecb18bf82 100644
--- a/app/javascript/mastodon/features/picture_in_picture/components/footer.js
+++ b/app/javascript/mastodon/features/picture_in_picture/components/footer.js
@@ -10,6 +10,7 @@ import { defineMessages, injectIntl } from 'react-intl';
import { replyCompose } from 'mastodon/actions/compose';
import { reblog, favourite, unreblog, unfavourite } from 'mastodon/actions/interactions';
import { makeGetStatus } from 'mastodon/selectors';
+import { initBoostModal } from 'mastodon/actions/boosts';
import { openModal } from 'mastodon/actions/modal';
const messages = defineMessages({
@@ -89,9 +90,9 @@ class Footer extends ImmutablePureComponent {
}
};
- _performReblog = () => {
- const { dispatch, status } = this.props;
- dispatch(reblog(status));
+ _performReblog = (status, privacy) => {
+ const { dispatch } = this.props;
+ dispatch(reblog(status, privacy));
}
handleReblogClick = e => {
@@ -100,9 +101,9 @@ class Footer extends ImmutablePureComponent {
if (status.get('reblogged')) {
dispatch(unreblog(status));
} else if ((e && e.shiftKey) || !boostModal) {
- this._performReblog();
+ this._performReblog(status);
} else {
- dispatch(openModal('BOOST', { status, onReblog: this._performReblog }));
+ dispatch(initBoostModal({ status, onReblog: this._performReblog }));
}
};
diff --git a/app/javascript/mastodon/features/status/containers/detailed_status_container.js b/app/javascript/mastodon/features/status/containers/detailed_status_container.js
index 0ac4519c89..bfed166200 100644
--- a/app/javascript/mastodon/features/status/containers/detailed_status_container.js
+++ b/app/javascript/mastodon/features/status/containers/detailed_status_container.js
@@ -23,6 +23,7 @@ import {
} from '../../../actions/statuses';
import { initMuteModal } from '../../../actions/mutes';
import { initBlockModal } from '../../../actions/blocks';
+import { initBoostModal } from '../../../actions/boosts';
import { initReport } from '../../../actions/reports';
import { openModal } from '../../../actions/modal';
import { defineMessages, injectIntl } from 'react-intl';
@@ -68,8 +69,8 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
});
},
- onModalReblog (status) {
- dispatch(reblog(status));
+ onModalReblog (status, privacy) {
+ dispatch(reblog(status, privacy));
},
onReblog (status, e) {
@@ -79,7 +80,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/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js
index 09822f372a..df8362a1bc 100644
--- a/app/javascript/mastodon/features/status/index.js
+++ b/app/javascript/mastodon/features/status/index.js
@@ -42,6 +42,7 @@ import {
} from '../../actions/domain_blocks';
import { initMuteModal } from '../../actions/mutes';
import { initBlockModal } from '../../actions/blocks';
+import { initBoostModal } from '../../actions/boosts';
import { initReport } from '../../actions/reports';
import { makeGetStatus, makeGetPictureInPicture } from '../../selectors';
import { ScrollContainer } from 'react-router-scroll-4';
@@ -234,8 +235,8 @@ class Status extends ImmutablePureComponent {
}
}
- handleModalReblog = (status) => {
- this.props.dispatch(reblog(status));
+ handleModalReblog = (status, privacy) => {
+ this.props.dispatch(reblog(status, privacy));
}
handleReblogClick = (status, e) => {
@@ -245,7 +246,7 @@ class Status extends ImmutablePureComponent {
if ((e && e.shiftKey) || !boostModal) {
this.handleModalReblog(status);
} else {
- this.props.dispatch(openModal('BOOST', { status, onReblog: this.handleModalReblog }));
+ this.props.dispatch(initBoostModal({ status, onReblog: this.handleModalReblog }));
}
}
}
diff --git a/app/javascript/mastodon/features/ui/components/boost_modal.js b/app/javascript/mastodon/features/ui/components/boost_modal.js
index 963bb5dc4b..83229833b1 100644
--- a/app/javascript/mastodon/features/ui/components/boost_modal.js
+++ b/app/javascript/mastodon/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 '../../../components/display_name';
import ImmutablePureComponent from 'react-immutable-pure-component';
import Icon from 'mastodon/components/icon';
import AttachmentList from 'mastodon/components/attachment_list';
+import PrivacyDropdown from 'mastodon/features/compose/components/privacy_dropdown';
import classNames from 'classnames';
+import { changeBoostPrivacy } from 'mastodon/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 = {
@@ -32,6 +50,8 @@ class BoostModal extends ImmutablePureComponent {
status: ImmutablePropTypes.map.isRequired,
onReblog: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
+ onChangeBoostPrivacy: PropTypes.func.isRequired,
+ privacy: PropTypes.string.isRequired,
intl: PropTypes.object.isRequired,
};
@@ -40,7 +60,7 @@ class BoostModal extends ImmutablePureComponent {
}
handleReblog = () => {
- this.props.onReblog(this.props.status);
+ this.props.onReblog(this.props.status, this.props.privacy);
this.props.onClose();
}
@@ -52,12 +72,16 @@ class BoostModal extends ImmutablePureComponent {
}
}
+ _findContainer = () => {
+ return document.getElementsByClassName('modal-root__container')[0];
+ };
+
setRef = (c) => {
this.button = c;
}
render () {
- const { status, intl } = this.props;
+ const { status, privacy, intl } = this.props;
const buttonText = status.get('reblogged') ? messages.cancel_reblog : messages.reblog;
const visibilityIconInfo = {
@@ -102,6 +126,14 @@ class BoostModal extends ImmutablePureComponent {
Shift + }} />
+ {status.get('visibility') !== 'private' && !status.get('reblogged') && (
+
+ )}
diff --git a/app/javascript/mastodon/reducers/boosts.js b/app/javascript/mastodon/reducers/boosts.js
new file mode 100644
index 0000000000..d0d825057c
--- /dev/null
+++ b/app/javascript/mastodon/reducers/boosts.js
@@ -0,0 +1,25 @@
+import Immutable from 'immutable';
+
+import {
+ BOOSTS_INIT_MODAL,
+ BOOSTS_CHANGE_PRIVACY,
+} from 'mastodon/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/mastodon/reducers/index.js b/app/javascript/mastodon/reducers/index.js
index a8fb69c274..3b3c5ae299 100644
--- a/app/javascript/mastodon/reducers/index.js
+++ b/app/javascript/mastodon/reducers/index.js
@@ -16,6 +16,7 @@ import push_notifications from './push_notifications';
import status_lists from './status_lists';
import mutes from './mutes';
import blocks from './blocks';
+import boosts from './boosts';
import reports from './reports';
import contexts from './contexts';
import compose from './compose';
@@ -57,6 +58,7 @@ const reducers = {
push_notifications,
mutes,
blocks,
+ boosts,
reports,
contexts,
compose,
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 43b3407236..bdb7ce7682 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -4209,6 +4209,7 @@ a.status-card.compact:hover {
border-radius: 4px;
margin-left: 40px;
overflow: hidden;
+ z-index: 2;
&.top {
transform-origin: 50% 100%;
@@ -4219,6 +4220,15 @@ a.status-card.compact:hover {
}
}
+.modal-root__container .privacy-dropdown {
+ flex-grow: 0;
+}
+
+.modal-root__container .privacy-dropdown__dropdown {
+ pointer-events: auto;
+ z-index: 9999;
+}
+
.privacy-dropdown__option {
color: $inverted-text-color;
padding: 10px;