diff options
13 files changed, 133 insertions, 6 deletions
diff --git a/app/javascript/flavours/glitch/actions/compose.js b/app/javascript/flavours/glitch/actions/compose.js index 8a6ca3699..39381faa8 100644 --- a/app/javascript/flavours/glitch/actions/compose.js +++ b/app/javascript/flavours/glitch/actions/compose.js @@ -21,6 +21,7 @@ export const COMPOSE_SUBMIT_SUCCESS = 'COMPOSE_SUBMIT_SUCCESS'; export const COMPOSE_SUBMIT_FAIL = 'COMPOSE_SUBMIT_FAIL'; export const COMPOSE_REPLY = 'COMPOSE_REPLY'; export const COMPOSE_REPLY_CANCEL = 'COMPOSE_REPLY_CANCEL'; +export const COMPOSE_DIRECT = 'COMPOSE_DIRECT'; export const COMPOSE_MENTION = 'COMPOSE_MENTION'; export const COMPOSE_RESET = 'COMPOSE_RESET'; export const COMPOSE_UPLOAD_REQUEST = 'COMPOSE_UPLOAD_REQUEST'; @@ -102,6 +103,19 @@ export function mentionCompose(account, router) { }; }; +export function directCompose(account, router) { + return (dispatch, getState) => { + dispatch({ + type: COMPOSE_DIRECT, + account: account, + }); + + if (!getState().getIn(['compose', 'mounted'])) { + router.push('/statuses/new'); + } + }; +}; + export function submitCompose() { return function (dispatch, getState) { let status = getState().getIn(['compose', 'text'], ''); diff --git a/app/javascript/flavours/glitch/components/status.js b/app/javascript/flavours/glitch/components/status.js index bff396f04..f929d17a6 100644 --- a/app/javascript/flavours/glitch/components/status.js +++ b/app/javascript/flavours/glitch/components/status.js @@ -32,6 +32,8 @@ export default class Status extends ImmutablePureComponent { onFavourite: PropTypes.func, onReblog: PropTypes.func, onDelete: PropTypes.func, + onDirect: PropTypes.func, + onMention: PropTypes.func, onPin: PropTypes.func, onOpenMedia: PropTypes.func, onOpenVideo: PropTypes.func, diff --git a/app/javascript/flavours/glitch/components/status_action_bar.js b/app/javascript/flavours/glitch/components/status_action_bar.js index da6e4e6ba..6ae4bc08d 100644 --- a/app/javascript/flavours/glitch/components/status_action_bar.js +++ b/app/javascript/flavours/glitch/components/status_action_bar.js @@ -10,6 +10,7 @@ import RelativeTimestamp from './relative_timestamp'; const messages = defineMessages({ delete: { id: 'status.delete', defaultMessage: 'Delete' }, + direct: { id: 'status.direct', defaultMessage: 'Direct message @{name}' }, mention: { id: 'status.mention', defaultMessage: 'Mention @{name}' }, mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' }, block: { id: 'account.block', defaultMessage: 'Block @{name}' }, @@ -44,6 +45,7 @@ export default class StatusActionBar extends ImmutablePureComponent { onFavourite: PropTypes.func, onReblog: PropTypes.func, onDelete: PropTypes.func, + onDirect: PropTypes.func, onMention: PropTypes.func, onMute: PropTypes.func, onBlock: PropTypes.func, @@ -98,6 +100,10 @@ export default class StatusActionBar extends ImmutablePureComponent { this.props.onMention(this.props.status.get('account'), this.context.router.history); } + handleDirectClick = () => { + this.props.onDirect(this.props.status.get('account'), this.context.router.history); + } + handleMuteClick = () => { this.props.onMute(this.props.status.get('account')); } @@ -157,6 +163,7 @@ export default class StatusActionBar extends ImmutablePureComponent { menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick }); } else { menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick }); + menu.push({ text: intl.formatMessage(messages.direct, { name: status.getIn(['account', 'username']) }), action: this.handleDirectClick }); menu.push(null); menu.push({ text: intl.formatMessage(messages.mute, { name: status.getIn(['account', 'username']) }), action: this.handleMuteClick }); menu.push({ text: intl.formatMessage(messages.block, { name: status.getIn(['account', 'username']) }), action: this.handleBlockClick }); diff --git a/app/javascript/flavours/glitch/containers/status_container.js b/app/javascript/flavours/glitch/containers/status_container.js index 3fc6a6a79..acec00e12 100644 --- a/app/javascript/flavours/glitch/containers/status_container.js +++ b/app/javascript/flavours/glitch/containers/status_container.js @@ -5,6 +5,7 @@ import { makeGetStatus } from 'flavours/glitch/selectors'; import { replyCompose, mentionCompose, + directCompose, } from 'flavours/glitch/actions/compose'; import { reblog, @@ -131,6 +132,10 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ } }, + onDirect (account, router) { + dispatch(directCompose(account, router)); + }, + onMention (account, router) { dispatch(mentionCompose(account, router)); }, diff --git a/app/javascript/flavours/glitch/features/account/components/action_bar.js b/app/javascript/flavours/glitch/features/account/components/action_bar.js index fb90722f3..8b95c08f2 100644 --- a/app/javascript/flavours/glitch/features/account/components/action_bar.js +++ b/app/javascript/flavours/glitch/features/account/components/action_bar.js @@ -8,6 +8,7 @@ import { me } from 'flavours/glitch/util/initial_state'; const messages = defineMessages({ mention: { id: 'account.mention', defaultMessage: 'Mention @{name}' }, + direct: { id: 'account.direct', defaultMessage: 'Direct message @{name}' }, edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' }, unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' }, unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, @@ -32,6 +33,7 @@ export default class ActionBar extends React.PureComponent { onFollow: PropTypes.func, onBlock: PropTypes.func.isRequired, onMention: PropTypes.func.isRequired, + onDirect: PropTypes.func.isRequired, onReblogToggle: PropTypes.func.isRequired, onReport: PropTypes.func.isRequired, onMute: PropTypes.func.isRequired, @@ -53,6 +55,7 @@ export default class ActionBar extends React.PureComponent { let extraInfo = ''; menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention }); + menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.props.onDirect }); if ('share' in navigator) { menu.push({ text: intl.formatMessage(messages.share, { name: account.get('username') }), action: this.handleShare }); diff --git a/app/javascript/flavours/glitch/features/account_timeline/components/header.js b/app/javascript/flavours/glitch/features/account_timeline/components/header.js index 39a1850d7..a1434b8dd 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/components/header.js +++ b/app/javascript/flavours/glitch/features/account_timeline/components/header.js @@ -16,6 +16,7 @@ export default class Header extends ImmutablePureComponent { onFollow: PropTypes.func.isRequired, onBlock: PropTypes.func.isRequired, onMention: PropTypes.func.isRequired, + onDirect: PropTypes.func.isRequired, onReblogToggle: PropTypes.func.isRequired, onReport: PropTypes.func.isRequired, onMute: PropTypes.func.isRequired, @@ -40,6 +41,10 @@ export default class Header extends ImmutablePureComponent { this.props.onMention(this.props.account, this.context.router.history); } + handleDirect = () => { + this.props.onDirect(this.props.account, this.context.router.history); + } + handleReport = () => { this.props.onReport(this.props.account); } @@ -89,6 +94,7 @@ export default class Header extends ImmutablePureComponent { account={account} onBlock={this.handleBlock} onMention={this.handleMention} + onDirect={this.handleDirect} onReblogToggle={this.handleReblogToggle} onReport={this.handleReport} onMute={this.handleMute} diff --git a/app/javascript/flavours/glitch/features/account_timeline/containers/header_container.js b/app/javascript/flavours/glitch/features/account_timeline/containers/header_container.js index 848119c63..fb0edfa88 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/containers/header_container.js +++ b/app/javascript/flavours/glitch/features/account_timeline/containers/header_container.js @@ -9,7 +9,10 @@ import { unblockAccount, unmuteAccount, } from 'flavours/glitch/actions/accounts'; -import { mentionCompose } from 'flavours/glitch/actions/compose'; +import { + mentionCompose, + directCompose +} from 'flavours/glitch/actions/compose'; import { initMuteModal } from 'flavours/glitch/actions/mutes'; import { initReport } from 'flavours/glitch/actions/reports'; import { openModal } from 'flavours/glitch/actions/modal'; @@ -67,6 +70,14 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ dispatch(mentionCompose(account, router)); }, + onDirect (account, router) { + dispatch(directCompose(account, router)); + }, + + onDirect (account, router) { + dispatch(directCompose(account, router)); + }, + onReblogToggle (account) { if (account.getIn(['relationship', 'showing_reblogs'])) { dispatch(followAccount(account.get('id'), false)); diff --git a/app/javascript/flavours/glitch/features/composer/direct_warning/index.js b/app/javascript/flavours/glitch/features/composer/direct_warning/index.js new file mode 100644 index 000000000..804c6538b --- /dev/null +++ b/app/javascript/flavours/glitch/features/composer/direct_warning/index.js @@ -0,0 +1,49 @@ +import React from 'react'; +import Motion from 'flavours/glitch/util/optional_motion'; +import spring from 'react-motion/lib/spring'; +import { defineMessages, FormattedMessage } from 'react-intl'; + +// This is the spring used with our motion. +const motionSpring = spring(1, { damping: 35, stiffness: 400 }); + +// Messages. +const messages = defineMessages({ + disclaimer: { + defaultMessage: 'This toot will only be sent to all the mentioned users. However, the operators of your instance and any receiving instances may see this message.', + id: 'compose_form.direct_message_warning', + }, +}); + +// The component. +export default function ComposerDirectWarning () { + return ( + <Motion + defaultStyle={{ + opacity: 0, + scaleX: 0.85, + scaleY: 0.75, + }} + style={{ + opacity: motionSpring, + scaleX: motionSpring, + scaleY: motionSpring, + }} + > + {({ opacity, scaleX, scaleY }) => ( + <div + className='composer--warning' + style={{ + opacity: opacity, + transform: `scale(${scaleX}, ${scaleY})`, + }} + > + <FormattedMessage + {...messages.disclaimer} + /> + </div> + )} + </Motion> + ); +} + +ComposerDirectWarning.propTypes = {}; diff --git a/app/javascript/flavours/glitch/features/composer/index.js b/app/javascript/flavours/glitch/features/composer/index.js index 3aa283628..de85340a3 100644 --- a/app/javascript/flavours/glitch/features/composer/index.js +++ b/app/javascript/flavours/glitch/features/composer/index.js @@ -39,6 +39,7 @@ import ComposerTextarea from './textarea'; import ComposerUploadForm from './upload_form'; import ComposerWarning from './warning'; import ComposerHashtagWarning from './hashtag_warning'; +import ComposerDirectWarning from './direct_warning'; // Utils. import { countableText } from 'flavours/glitch/util/counter'; @@ -326,6 +327,7 @@ class Composer extends React.Component { onSubmit={handleSubmit} text={spoilerText} /> + {privacy === 'direct' ? <ComposerDirectWarning /> : null} {privacy === 'private' && amUnlocked ? <ComposerWarning /> : null} {privacy !== 'public' && APPROX_HASHTAG_RE.test(text) ? <ComposerHashtagWarning /> : null} {replyContent ? ( 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 1ea0fa421..003f134a8 100644 --- a/app/javascript/flavours/glitch/features/status/components/action_bar.js +++ b/app/javascript/flavours/glitch/features/status/components/action_bar.js @@ -8,6 +8,7 @@ import { me } from 'flavours/glitch/util/initial_state'; const messages = defineMessages({ delete: { id: 'status.delete', defaultMessage: 'Delete' }, + direct: { id: 'status.direct', defaultMessage: 'Direct message @{name}' }, mention: { id: 'status.mention', defaultMessage: 'Mention @{name}' }, reply: { id: 'status.reply', defaultMessage: 'Reply' }, reblog: { id: 'status.reblog', defaultMessage: 'Boost' }, @@ -43,6 +44,7 @@ export default class ActionBar extends React.PureComponent { onMuteConversation: PropTypes.func, onBlock: PropTypes.func, onDelete: PropTypes.func.isRequired, + onDirect: PropTypes.func.isRequired, onMention: PropTypes.func.isRequired, onReport: PropTypes.func, onPin: PropTypes.func, @@ -70,6 +72,10 @@ export default class ActionBar extends React.PureComponent { this.props.onDelete(this.props.status); } + handleDirectClick = () => { + this.props.onDirect(this.props.status.get('account'), this.context.router.history); + } + handleMentionClick = () => { this.props.onMention(this.props.status.get('account'), this.context.router.history); } @@ -115,6 +121,7 @@ export default class ActionBar extends React.PureComponent { if (publicStatus) { menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed }); + menu.push(null); } if (me === status.getIn(['account', 'id'])) { @@ -128,6 +135,7 @@ export default class ActionBar extends React.PureComponent { menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick }); } else { menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick }); + menu.push({ text: intl.formatMessage(messages.direct, { name: status.getIn(['account', 'username']) }), action: this.handleDirectClick }); menu.push(null); menu.push({ text: intl.formatMessage(messages.mute, { name: status.getIn(['account', 'username']) }), action: this.handleMuteClick }); menu.push({ text: intl.formatMessage(messages.block, { name: status.getIn(['account', 'username']) }), action: this.handleBlockClick }); diff --git a/app/javascript/flavours/glitch/features/status/index.js b/app/javascript/flavours/glitch/features/status/index.js index 7e1658dbb..6c9da8e3e 100644 --- a/app/javascript/flavours/glitch/features/status/index.js +++ b/app/javascript/flavours/glitch/features/status/index.js @@ -21,6 +21,7 @@ import { import { replyCompose, mentionCompose, + directCompose, } from 'flavours/glitch/actions/compose'; import { blockAccount } from 'flavours/glitch/actions/accounts'; import { muteStatus, unmuteStatus, deleteStatus } from 'flavours/glitch/actions/statuses'; @@ -170,6 +171,10 @@ export default class Status extends ImmutablePureComponent { } } + handleDirectClick = (account, router) => { + this.props.dispatch(directCompose(account, router)); + } + handleMentionClick = (account, router) => { this.props.dispatch(mentionCompose(account, router)); } @@ -399,6 +404,7 @@ export default class Status extends ImmutablePureComponent { onReblog={this.handleReblogClick} onBookmark={this.handleBookmarkClick} onDelete={this.handleDeleteClick} + onDirect={this.handleDirectClick} onMention={this.handleMentionClick} onMute={this.handleMuteClick} onMuteConversation={this.handleConversationMuteClick} diff --git a/app/javascript/flavours/glitch/reducers/compose.js b/app/javascript/flavours/glitch/reducers/compose.js index 8973c7713..f79fa63d2 100644 --- a/app/javascript/flavours/glitch/reducers/compose.js +++ b/app/javascript/flavours/glitch/reducers/compose.js @@ -5,6 +5,7 @@ import { COMPOSE_CYCLE_ELEFRIEND, COMPOSE_REPLY, COMPOSE_REPLY_CANCEL, + COMPOSE_DIRECT, COMPOSE_MENTION, COMPOSE_SUBMIT_REQUEST, COMPOSE_SUBMIT_SUCCESS, @@ -321,10 +322,18 @@ export default function compose(state = initialState, action) { case COMPOSE_UPLOAD_PROGRESS: return state.set('progress', Math.round((action.loaded / action.total) * 100)); case COMPOSE_MENTION: - return state - .update('text', text => `${text}@${action.account.get('acct')} `) - .set('focusDate', new Date()) - .set('idempotencyKey', uuid()); + return state.withMutations(map => { + map.update('text', text => [text.trim(), `@${action.account.get('acct')} `].filter((str) => str.length !== 0).join(' ')); + map.set('focusDate', new Date()); + map.set('idempotencyKey', uuid()); + }); + case COMPOSE_DIRECT: + return state.withMutations(map => { + map.update('text', text => [text.trim(), `@${action.account.get('acct')} `].filter((str) => str.length !== 0).join(' ')); + map.set('privacy', 'direct'); + map.set('focusDate', new Date()); + map.set('idempotencyKey', uuid()); + }); case COMPOSE_SUGGESTIONS_CLEAR: return state.update('suggestions', ImmutableList(), list => list.clear()).set('suggestion_token', null); case COMPOSE_SUGGESTIONS_READY: diff --git a/app/javascript/flavours/glitch/reducers/search.js b/app/javascript/flavours/glitch/reducers/search.js index f9bf92098..dc6be97e2 100644 --- a/app/javascript/flavours/glitch/reducers/search.js +++ b/app/javascript/flavours/glitch/reducers/search.js @@ -4,7 +4,11 @@ import { SEARCH_FETCH_SUCCESS, SEARCH_SHOW, } from 'flavours/glitch/actions/search'; -import { COMPOSE_MENTION, COMPOSE_REPLY } from 'flavours/glitch/actions/compose'; +import { + COMPOSE_MENTION, + COMPOSE_REPLY, + COMPOSE_DIRECT, +} from 'flavours/glitch/actions/compose'; import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; const initialState = ImmutableMap({ @@ -29,6 +33,7 @@ export default function search(state = initialState, action) { return state.set('hidden', false); case COMPOSE_REPLY: case COMPOSE_MENTION: + case COMPOSE_DIRECT: return state.set('hidden', true); case SEARCH_FETCH_SUCCESS: return state.set('results', ImmutableMap({ |