* 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
		
			
				
	
	
		
			197 lines
		
	
	
	
		
			7.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			197 lines
		
	
	
	
		
			7.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| import React from 'react';
 | |
| import ImmutablePropTypes from 'react-immutable-proptypes';
 | |
| import PropTypes from 'prop-types';
 | |
| import IconButton from './icon_button';
 | |
| import DropdownMenuContainer from '../containers/dropdown_menu_container';
 | |
| import { defineMessages, injectIntl } from 'react-intl';
 | |
| import ImmutablePureComponent from 'react-immutable-pure-component';
 | |
| import { me } from '../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}' },
 | |
|   mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
 | |
|   block: { id: 'account.block', defaultMessage: 'Block @{name}' },
 | |
|   reply: { id: 'status.reply', defaultMessage: 'Reply' },
 | |
|   share: { id: 'status.share', defaultMessage: 'Share' },
 | |
|   more: { id: 'status.more', defaultMessage: 'More' },
 | |
|   replyAll: { id: 'status.replyAll', defaultMessage: 'Reply to thread' },
 | |
|   reblog: { id: 'status.reblog', defaultMessage: 'Boost' },
 | |
|   cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' },
 | |
|   favourite: { id: 'status.favourite', defaultMessage: 'Favourite' },
 | |
|   open: { id: 'status.open', defaultMessage: 'Expand this status' },
 | |
|   report: { id: 'status.report', defaultMessage: 'Report @{name}' },
 | |
|   muteConversation: { id: 'status.mute_conversation', defaultMessage: 'Mute conversation' },
 | |
|   unmuteConversation: { id: 'status.unmute_conversation', defaultMessage: 'Unmute conversation' },
 | |
|   pin: { id: 'status.pin', defaultMessage: 'Pin on profile' },
 | |
|   unpin: { id: 'status.unpin', defaultMessage: 'Unpin from profile' },
 | |
|   embed: { id: 'status.embed', defaultMessage: 'Embed' },
 | |
| });
 | |
| 
 | |
| @injectIntl
 | |
| export default class StatusActionBar extends ImmutablePureComponent {
 | |
| 
 | |
|   static contextTypes = {
 | |
|     router: PropTypes.object,
 | |
|   };
 | |
| 
 | |
|   static propTypes = {
 | |
|     status: ImmutablePropTypes.map.isRequired,
 | |
|     onReply: PropTypes.func,
 | |
|     onFavourite: PropTypes.func,
 | |
|     onReblog: PropTypes.func,
 | |
|     onDelete: PropTypes.func,
 | |
|     onDirect: PropTypes.func,
 | |
|     onMention: PropTypes.func,
 | |
|     onMute: PropTypes.func,
 | |
|     onBlock: PropTypes.func,
 | |
|     onReport: PropTypes.func,
 | |
|     onEmbed: PropTypes.func,
 | |
|     onMuteConversation: PropTypes.func,
 | |
|     onPin: PropTypes.func,
 | |
|     withDismiss: PropTypes.bool,
 | |
|     intl: PropTypes.object.isRequired,
 | |
|   };
 | |
| 
 | |
|   // Avoid checking props that are functions (and whose equality will always
 | |
|   // evaluate to false. See react-immutable-pure-component for usage.
 | |
|   updateOnProps = [
 | |
|     'status',
 | |
|     'withDismiss',
 | |
|   ]
 | |
| 
 | |
|   handleReplyClick = () => {
 | |
|     this.props.onReply(this.props.status, this.context.router.history);
 | |
|   }
 | |
| 
 | |
|   handleShareClick = () => {
 | |
|     navigator.share({
 | |
|       text: this.props.status.get('search_index'),
 | |
|       url: this.props.status.get('url'),
 | |
|     }).catch((e) => {
 | |
|       if (e.name !== 'AbortError') console.error(e);
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   handleFavouriteClick = () => {
 | |
|     this.props.onFavourite(this.props.status);
 | |
|   }
 | |
| 
 | |
|   handleReblogClick = (e) => {
 | |
|     this.props.onReblog(this.props.status, e);
 | |
|   }
 | |
| 
 | |
|   handleDeleteClick = () => {
 | |
|     this.props.onDelete(this.props.status);
 | |
|   }
 | |
| 
 | |
|   handlePinClick = () => {
 | |
|     this.props.onPin(this.props.status);
 | |
|   }
 | |
| 
 | |
|   handleMentionClick = () => {
 | |
|     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'));
 | |
|   }
 | |
| 
 | |
|   handleBlockClick = () => {
 | |
|     this.props.onBlock(this.props.status.get('account'));
 | |
|   }
 | |
| 
 | |
|   handleOpen = () => {
 | |
|     this.context.router.history.push(`/statuses/${this.props.status.get('id')}`);
 | |
|   }
 | |
| 
 | |
|   handleEmbed = () => {
 | |
|     this.props.onEmbed(this.props.status);
 | |
|   }
 | |
| 
 | |
|   handleReport = () => {
 | |
|     this.props.onReport(this.props.status);
 | |
|   }
 | |
| 
 | |
|   handleConversationMuteClick = () => {
 | |
|     this.props.onMuteConversation(this.props.status);
 | |
|   }
 | |
| 
 | |
|   render () {
 | |
|     const { status, intl, withDismiss } = this.props;
 | |
| 
 | |
|     const mutingConversation = status.get('muted');
 | |
|     const anonymousAccess    = !me;
 | |
|     const publicStatus       = ['public', 'unlisted'].includes(status.get('visibility'));
 | |
| 
 | |
|     let menu = [];
 | |
|     let reblogIcon = 'retweet';
 | |
|     let replyIcon;
 | |
|     let replyTitle;
 | |
| 
 | |
|     menu.push({ text: intl.formatMessage(messages.open), action: this.handleOpen });
 | |
| 
 | |
|     if (publicStatus) {
 | |
|       menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
 | |
|     }
 | |
| 
 | |
|     menu.push(null);
 | |
| 
 | |
|     if (status.getIn(['account', 'id']) === me || withDismiss) {
 | |
|       menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick });
 | |
|       menu.push(null);
 | |
|     }
 | |
| 
 | |
|     if (status.getIn(['account', 'id']) === me) {
 | |
|       if (publicStatus) {
 | |
|         menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick });
 | |
|       }
 | |
| 
 | |
|       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 });
 | |
|       menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport });
 | |
|     }
 | |
| 
 | |
|     if (status.get('visibility') === 'direct') {
 | |
|       reblogIcon = 'envelope';
 | |
|     } else if (status.get('visibility') === 'private') {
 | |
|       reblogIcon = 'lock';
 | |
|     }
 | |
| 
 | |
|     if (status.get('in_reply_to_id', null) === null) {
 | |
|       replyIcon = 'reply';
 | |
|       replyTitle = intl.formatMessage(messages.reply);
 | |
|     } else {
 | |
|       replyIcon = 'reply-all';
 | |
|       replyTitle = intl.formatMessage(messages.replyAll);
 | |
|     }
 | |
| 
 | |
|     const shareButton = ('share' in navigator) && status.get('visibility') === 'public' && (
 | |
|       <IconButton className='status__action-bar-button' title={intl.formatMessage(messages.share)} icon='share-alt' onClick={this.handleShareClick} />
 | |
|     );
 | |
| 
 | |
|     return (
 | |
|       <div className='status__action-bar'>
 | |
|         <IconButton className='status__action-bar-button' disabled={anonymousAccess} title={replyTitle} icon={replyIcon} onClick={this.handleReplyClick} />
 | |
|         <IconButton className='status__action-bar-button' disabled={anonymousAccess || !publicStatus} active={status.get('reblogged')} pressed={status.get('reblogged')} title={!publicStatus ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} />
 | |
|         <IconButton className='status__action-bar-button star-icon' disabled={anonymousAccess} animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} />
 | |
|         {shareButton}
 | |
| 
 | |
|         <div className='status__action-bar-dropdown'>
 | |
|           <DropdownMenuContainer disabled={anonymousAccess} status={status} items={menu} icon='ellipsis-h' size={18} direction='right' title={intl.formatMessage(messages.more)} />
 | |
|         </div>
 | |
|       </div>
 | |
|     );
 | |
|   }
 | |
| 
 | |
| }
 |