Merge pull request #405 from ThibG/glitch-soc/features/dm-from-menu

[Glitch] Feature: Direct message from menu
This commit is contained in:
ThibG 2018-05-16 20:17:08 +02:00 committed by GitHub
commit 80aad16e10
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 133 additions and 6 deletions

View File

@ -21,6 +21,7 @@ export const COMPOSE_SUBMIT_SUCCESS = 'COMPOSE_SUBMIT_SUCCESS';
export const COMPOSE_SUBMIT_FAIL = 'COMPOSE_SUBMIT_FAIL'; export const COMPOSE_SUBMIT_FAIL = 'COMPOSE_SUBMIT_FAIL';
export const COMPOSE_REPLY = 'COMPOSE_REPLY'; export const COMPOSE_REPLY = 'COMPOSE_REPLY';
export const COMPOSE_REPLY_CANCEL = 'COMPOSE_REPLY_CANCEL'; export const COMPOSE_REPLY_CANCEL = 'COMPOSE_REPLY_CANCEL';
export const COMPOSE_DIRECT = 'COMPOSE_DIRECT';
export const COMPOSE_MENTION = 'COMPOSE_MENTION'; export const COMPOSE_MENTION = 'COMPOSE_MENTION';
export const COMPOSE_RESET = 'COMPOSE_RESET'; export const COMPOSE_RESET = 'COMPOSE_RESET';
export const COMPOSE_UPLOAD_REQUEST = 'COMPOSE_UPLOAD_REQUEST'; 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() { export function submitCompose() {
return function (dispatch, getState) { return function (dispatch, getState) {
let status = getState().getIn(['compose', 'text'], ''); let status = getState().getIn(['compose', 'text'], '');

View File

@ -32,6 +32,8 @@ export default class Status extends ImmutablePureComponent {
onFavourite: PropTypes.func, onFavourite: PropTypes.func,
onReblog: PropTypes.func, onReblog: PropTypes.func,
onDelete: PropTypes.func, onDelete: PropTypes.func,
onDirect: PropTypes.func,
onMention: PropTypes.func,
onPin: PropTypes.func, onPin: PropTypes.func,
onOpenMedia: PropTypes.func, onOpenMedia: PropTypes.func,
onOpenVideo: PropTypes.func, onOpenVideo: PropTypes.func,

View File

@ -10,6 +10,7 @@ import RelativeTimestamp from './relative_timestamp';
const messages = defineMessages({ const messages = defineMessages({
delete: { id: 'status.delete', defaultMessage: 'Delete' }, delete: { id: 'status.delete', defaultMessage: 'Delete' },
direct: { id: 'status.direct', defaultMessage: 'Direct message @{name}' },
mention: { id: 'status.mention', defaultMessage: 'Mention @{name}' }, mention: { id: 'status.mention', defaultMessage: 'Mention @{name}' },
mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' }, mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
block: { id: 'account.block', defaultMessage: 'Block @{name}' }, block: { id: 'account.block', defaultMessage: 'Block @{name}' },
@ -44,6 +45,7 @@ export default class StatusActionBar extends ImmutablePureComponent {
onFavourite: PropTypes.func, onFavourite: PropTypes.func,
onReblog: PropTypes.func, onReblog: PropTypes.func,
onDelete: PropTypes.func, onDelete: PropTypes.func,
onDirect: PropTypes.func,
onMention: PropTypes.func, onMention: PropTypes.func,
onMute: PropTypes.func, onMute: PropTypes.func,
onBlock: 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); 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 = () => { handleMuteClick = () => {
this.props.onMute(this.props.status.get('account')); 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 }); menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick });
} else { } else {
menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick }); 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(null);
menu.push({ text: intl.formatMessage(messages.mute, { name: status.getIn(['account', 'username']) }), action: this.handleMuteClick }); 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 }); menu.push({ text: intl.formatMessage(messages.block, { name: status.getIn(['account', 'username']) }), action: this.handleBlockClick });

View File

@ -5,6 +5,7 @@ import { makeGetStatus } from 'flavours/glitch/selectors';
import { import {
replyCompose, replyCompose,
mentionCompose, mentionCompose,
directCompose,
} from 'flavours/glitch/actions/compose'; } from 'flavours/glitch/actions/compose';
import { import {
reblog, reblog,
@ -131,6 +132,10 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
} }
}, },
onDirect (account, router) {
dispatch(directCompose(account, router));
},
onMention (account, router) { onMention (account, router) {
dispatch(mentionCompose(account, router)); dispatch(mentionCompose(account, router));
}, },

View File

@ -8,6 +8,7 @@ import { me } from 'flavours/glitch/util/initial_state';
const messages = defineMessages({ const messages = defineMessages({
mention: { id: 'account.mention', defaultMessage: 'Mention @{name}' }, mention: { id: 'account.mention', defaultMessage: 'Mention @{name}' },
direct: { id: 'account.direct', defaultMessage: 'Direct message @{name}' },
edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' }, edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' }, unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
@ -32,6 +33,7 @@ export default class ActionBar extends React.PureComponent {
onFollow: PropTypes.func, onFollow: PropTypes.func,
onBlock: PropTypes.func.isRequired, onBlock: PropTypes.func.isRequired,
onMention: PropTypes.func.isRequired, onMention: PropTypes.func.isRequired,
onDirect: PropTypes.func.isRequired,
onReblogToggle: PropTypes.func.isRequired, onReblogToggle: PropTypes.func.isRequired,
onReport: PropTypes.func.isRequired, onReport: PropTypes.func.isRequired,
onMute: PropTypes.func.isRequired, onMute: PropTypes.func.isRequired,
@ -53,6 +55,7 @@ export default class ActionBar extends React.PureComponent {
let extraInfo = ''; let extraInfo = '';
menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention }); 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) { if ('share' in navigator) {
menu.push({ text: intl.formatMessage(messages.share, { name: account.get('username') }), action: this.handleShare }); menu.push({ text: intl.formatMessage(messages.share, { name: account.get('username') }), action: this.handleShare });

View File

@ -16,6 +16,7 @@ export default class Header extends ImmutablePureComponent {
onFollow: PropTypes.func.isRequired, onFollow: PropTypes.func.isRequired,
onBlock: PropTypes.func.isRequired, onBlock: PropTypes.func.isRequired,
onMention: PropTypes.func.isRequired, onMention: PropTypes.func.isRequired,
onDirect: PropTypes.func.isRequired,
onReblogToggle: PropTypes.func.isRequired, onReblogToggle: PropTypes.func.isRequired,
onReport: PropTypes.func.isRequired, onReport: PropTypes.func.isRequired,
onMute: 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); this.props.onMention(this.props.account, this.context.router.history);
} }
handleDirect = () => {
this.props.onDirect(this.props.account, this.context.router.history);
}
handleReport = () => { handleReport = () => {
this.props.onReport(this.props.account); this.props.onReport(this.props.account);
} }
@ -89,6 +94,7 @@ export default class Header extends ImmutablePureComponent {
account={account} account={account}
onBlock={this.handleBlock} onBlock={this.handleBlock}
onMention={this.handleMention} onMention={this.handleMention}
onDirect={this.handleDirect}
onReblogToggle={this.handleReblogToggle} onReblogToggle={this.handleReblogToggle}
onReport={this.handleReport} onReport={this.handleReport}
onMute={this.handleMute} onMute={this.handleMute}

View File

@ -9,7 +9,10 @@ import {
unblockAccount, unblockAccount,
unmuteAccount, unmuteAccount,
} from 'flavours/glitch/actions/accounts'; } 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 { initMuteModal } from 'flavours/glitch/actions/mutes';
import { initReport } from 'flavours/glitch/actions/reports'; import { initReport } from 'flavours/glitch/actions/reports';
import { openModal } from 'flavours/glitch/actions/modal'; import { openModal } from 'flavours/glitch/actions/modal';
@ -67,6 +70,14 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
dispatch(mentionCompose(account, router)); dispatch(mentionCompose(account, router));
}, },
onDirect (account, router) {
dispatch(directCompose(account, router));
},
onDirect (account, router) {
dispatch(directCompose(account, router));
},
onReblogToggle (account) { onReblogToggle (account) {
if (account.getIn(['relationship', 'showing_reblogs'])) { if (account.getIn(['relationship', 'showing_reblogs'])) {
dispatch(followAccount(account.get('id'), false)); dispatch(followAccount(account.get('id'), false));

View File

@ -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 = {};

View File

@ -39,6 +39,7 @@ import ComposerTextarea from './textarea';
import ComposerUploadForm from './upload_form'; import ComposerUploadForm from './upload_form';
import ComposerWarning from './warning'; import ComposerWarning from './warning';
import ComposerHashtagWarning from './hashtag_warning'; import ComposerHashtagWarning from './hashtag_warning';
import ComposerDirectWarning from './direct_warning';
// Utils. // Utils.
import { countableText } from 'flavours/glitch/util/counter'; import { countableText } from 'flavours/glitch/util/counter';
@ -326,6 +327,7 @@ class Composer extends React.Component {
onSubmit={handleSubmit} onSubmit={handleSubmit}
text={spoilerText} text={spoilerText}
/> />
{privacy === 'direct' ? <ComposerDirectWarning /> : null}
{privacy === 'private' && amUnlocked ? <ComposerWarning /> : null} {privacy === 'private' && amUnlocked ? <ComposerWarning /> : null}
{privacy !== 'public' && APPROX_HASHTAG_RE.test(text) ? <ComposerHashtagWarning /> : null} {privacy !== 'public' && APPROX_HASHTAG_RE.test(text) ? <ComposerHashtagWarning /> : null}
{replyContent ? ( {replyContent ? (

View File

@ -8,6 +8,7 @@ import { me } from 'flavours/glitch/util/initial_state';
const messages = defineMessages({ const messages = defineMessages({
delete: { id: 'status.delete', defaultMessage: 'Delete' }, delete: { id: 'status.delete', defaultMessage: 'Delete' },
direct: { id: 'status.direct', defaultMessage: 'Direct message @{name}' },
mention: { id: 'status.mention', defaultMessage: 'Mention @{name}' }, mention: { id: 'status.mention', defaultMessage: 'Mention @{name}' },
reply: { id: 'status.reply', defaultMessage: 'Reply' }, reply: { id: 'status.reply', defaultMessage: 'Reply' },
reblog: { id: 'status.reblog', defaultMessage: 'Boost' }, reblog: { id: 'status.reblog', defaultMessage: 'Boost' },
@ -43,6 +44,7 @@ export default class ActionBar extends React.PureComponent {
onMuteConversation: PropTypes.func, onMuteConversation: PropTypes.func,
onBlock: PropTypes.func, onBlock: PropTypes.func,
onDelete: PropTypes.func.isRequired, onDelete: PropTypes.func.isRequired,
onDirect: PropTypes.func.isRequired,
onMention: PropTypes.func.isRequired, onMention: PropTypes.func.isRequired,
onReport: PropTypes.func, onReport: PropTypes.func,
onPin: PropTypes.func, onPin: PropTypes.func,
@ -70,6 +72,10 @@ export default class ActionBar extends React.PureComponent {
this.props.onDelete(this.props.status); this.props.onDelete(this.props.status);
} }
handleDirectClick = () => {
this.props.onDirect(this.props.status.get('account'), this.context.router.history);
}
handleMentionClick = () => { handleMentionClick = () => {
this.props.onMention(this.props.status.get('account'), this.context.router.history); 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) { if (publicStatus) {
menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed }); menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
menu.push(null);
} }
if (me === status.getIn(['account', 'id'])) { 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 }); menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick });
} else { } else {
menu.push({ text: intl.formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick }); 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(null);
menu.push({ text: intl.formatMessage(messages.mute, { name: status.getIn(['account', 'username']) }), action: this.handleMuteClick }); 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 }); menu.push({ text: intl.formatMessage(messages.block, { name: status.getIn(['account', 'username']) }), action: this.handleBlockClick });

View File

@ -21,6 +21,7 @@ import {
import { import {
replyCompose, replyCompose,
mentionCompose, mentionCompose,
directCompose,
} from 'flavours/glitch/actions/compose'; } from 'flavours/glitch/actions/compose';
import { blockAccount } from 'flavours/glitch/actions/accounts'; import { blockAccount } from 'flavours/glitch/actions/accounts';
import { muteStatus, unmuteStatus, deleteStatus } from 'flavours/glitch/actions/statuses'; 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) => { handleMentionClick = (account, router) => {
this.props.dispatch(mentionCompose(account, router)); this.props.dispatch(mentionCompose(account, router));
} }
@ -399,6 +404,7 @@ export default class Status extends ImmutablePureComponent {
onReblog={this.handleReblogClick} onReblog={this.handleReblogClick}
onBookmark={this.handleBookmarkClick} onBookmark={this.handleBookmarkClick}
onDelete={this.handleDeleteClick} onDelete={this.handleDeleteClick}
onDirect={this.handleDirectClick}
onMention={this.handleMentionClick} onMention={this.handleMentionClick}
onMute={this.handleMuteClick} onMute={this.handleMuteClick}
onMuteConversation={this.handleConversationMuteClick} onMuteConversation={this.handleConversationMuteClick}

View File

@ -5,6 +5,7 @@ import {
COMPOSE_CYCLE_ELEFRIEND, COMPOSE_CYCLE_ELEFRIEND,
COMPOSE_REPLY, COMPOSE_REPLY,
COMPOSE_REPLY_CANCEL, COMPOSE_REPLY_CANCEL,
COMPOSE_DIRECT,
COMPOSE_MENTION, COMPOSE_MENTION,
COMPOSE_SUBMIT_REQUEST, COMPOSE_SUBMIT_REQUEST,
COMPOSE_SUBMIT_SUCCESS, COMPOSE_SUBMIT_SUCCESS,
@ -321,10 +322,18 @@ export default function compose(state = initialState, action) {
case COMPOSE_UPLOAD_PROGRESS: case COMPOSE_UPLOAD_PROGRESS:
return state.set('progress', Math.round((action.loaded / action.total) * 100)); return state.set('progress', Math.round((action.loaded / action.total) * 100));
case COMPOSE_MENTION: case COMPOSE_MENTION:
return state return state.withMutations(map => {
.update('text', text => `${text}@${action.account.get('acct')} `) map.update('text', text => [text.trim(), `@${action.account.get('acct')} `].filter((str) => str.length !== 0).join(' '));
.set('focusDate', new Date()) map.set('focusDate', new Date());
.set('idempotencyKey', uuid()); 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: case COMPOSE_SUGGESTIONS_CLEAR:
return state.update('suggestions', ImmutableList(), list => list.clear()).set('suggestion_token', null); return state.update('suggestions', ImmutableList(), list => list.clear()).set('suggestion_token', null);
case COMPOSE_SUGGESTIONS_READY: case COMPOSE_SUGGESTIONS_READY:

View File

@ -4,7 +4,11 @@ import {
SEARCH_FETCH_SUCCESS, SEARCH_FETCH_SUCCESS,
SEARCH_SHOW, SEARCH_SHOW,
} from 'flavours/glitch/actions/search'; } 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'; import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
const initialState = ImmutableMap({ const initialState = ImmutableMap({
@ -29,6 +33,7 @@ export default function search(state = initialState, action) {
return state.set('hidden', false); return state.set('hidden', false);
case COMPOSE_REPLY: case COMPOSE_REPLY:
case COMPOSE_MENTION: case COMPOSE_MENTION:
case COMPOSE_DIRECT:
return state.set('hidden', true); return state.set('hidden', true);
case SEARCH_FETCH_SUCCESS: case SEARCH_FETCH_SUCCESS:
return state.set('results', ImmutableMap({ return state.set('results', ImmutableMap({