Feature: Direct message from Statuses (#7089)

* Fix: Switching between composing direct message and mention from menus

Previously clicking "direct message" followed by "mention" resulted in the composed status staying as "direct", along with weird spacing of items in the text area. This attempts to fix that.

* Fix: Add missing proptype check for onMention in Status component

* Add the ability to send a direct message to a user from the menu on Statuses

* Add space between "Embed" and "Mention" on expanded statuses menu
This commit is contained in:
Emelia Smith 2018-04-09 17:09:11 +02:00 committed by Eugen Rochko
parent e057c0e525
commit 904a2479dd
8 changed files with 48 additions and 9 deletions

View File

@ -31,6 +31,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

@ -9,6 +9,7 @@ import { me } from '../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}' },
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}' },
@ -41,6 +42,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,
@ -92,6 +94,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'));
} }
@ -149,6 +155,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 '../selectors';
import { import {
replyCompose, replyCompose,
mentionCompose, mentionCompose,
directCompose,
} from '../actions/compose'; } from '../actions/compose';
import { import {
reblog, reblog,
@ -102,6 +103,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 '../../../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' },
@ -37,6 +38,7 @@ export default class ActionBar extends React.PureComponent {
onReblog: PropTypes.func.isRequired, onReblog: PropTypes.func.isRequired,
onFavourite: PropTypes.func.isRequired, onFavourite: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired, onDelete: PropTypes.func.isRequired,
onDirect: PropTypes.func.isRequired,
onMention: PropTypes.func.isRequired, onMention: PropTypes.func.isRequired,
onMute: PropTypes.func, onMute: PropTypes.func,
onMuteConversation: PropTypes.func, onMuteConversation: PropTypes.func,
@ -63,6 +65,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);
} }
@ -108,6 +114,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'])) {
@ -121,6 +128,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

@ -19,6 +19,7 @@ import {
import { import {
replyCompose, replyCompose,
mentionCompose, mentionCompose,
directCompose,
} from '../../actions/compose'; } from '../../actions/compose';
import { blockAccount } from '../../actions/accounts'; import { blockAccount } from '../../actions/accounts';
import { import {
@ -148,6 +149,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));
} }
@ -379,6 +384,7 @@ export default class Status extends ImmutablePureComponent {
onFavourite={this.handleFavouriteClick} onFavourite={this.handleFavouriteClick}
onReblog={this.handleReblogClick} onReblog={this.handleReblogClick}
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

@ -197,6 +197,10 @@
"defaultMessage": "Delete", "defaultMessage": "Delete",
"id": "status.delete" "id": "status.delete"
}, },
{
"defaultMessage": "Direct message @{name}",
"id": "status.direct"
},
{ {
"defaultMessage": "Mention @{name}", "defaultMessage": "Mention @{name}",
"id": "status.mention" "id": "status.mention"
@ -1370,6 +1374,10 @@
"defaultMessage": "Delete", "defaultMessage": "Delete",
"id": "status.delete" "id": "status.delete"
}, },
{
"defaultMessage": "Direct message @{name}",
"id": "status.direct"
},
{ {
"defaultMessage": "Mention @{name}", "defaultMessage": "Mention @{name}",
"id": "status.mention" "id": "status.mention"

View File

@ -240,6 +240,7 @@
"status.block": "Block @{name}", "status.block": "Block @{name}",
"status.cannot_reblog": "This post cannot be boosted", "status.cannot_reblog": "This post cannot be boosted",
"status.delete": "Delete", "status.delete": "Delete",
"status.direct": "Direct message @{name}",
"status.embed": "Embed", "status.embed": "Embed",
"status.favourite": "Favourite", "status.favourite": "Favourite",
"status.load_more": "Load more", "status.load_more": "Load more",

View File

@ -259,16 +259,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: case COMPOSE_DIRECT:
return state return state.withMutations(map => {
.update('text', text => `@${action.account.get('acct')} `) map.update('text', text => [text.trim(), `@${action.account.get('acct')} `].filter((str) => str.length !== 0).join(' '));
.set('privacy', 'direct') map.set('privacy', 'direct');
.set('focusDate', new Date()) map.set('focusDate', new Date());
.set('idempotencyKey', uuid()); 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: