// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. import PropTypes from 'prop-types'; import React, {Component} from 'react'; import {FormattedMessage} from 'react-intl'; import {OverlayTrigger, Tooltip} from 'react-bootstrap'; import Permissions from 'mattermost-redux/constants/permissions'; import {showGetPostLinkModal} from 'actions/global_actions.jsx'; import {Locations, ModalIdentifiers, UNSET_POST_EDIT_TIME_LIMIT} from 'utils/constants.jsx'; import DeletePostModal from 'components/delete_post_modal'; import DelayedAction from 'utils/delayed_action.jsx'; import * as PostUtils from 'utils/post_utils.jsx'; import * as Utils from 'utils/utils.jsx'; import ChannelPermissionGate from 'components/permissions_gates/channel_permission_gate'; import Pluggable from 'plugins/pluggable'; import Menu from 'components/widgets/menu/menu.jsx'; import MenuWrapper from 'components/widgets/menu/menu_wrapper.jsx'; import MenuItemAction from 'components/widgets/menu/menu_items/menu_item_action.jsx'; const MENU_BOTTOM_MARGIN = 80; export default class DotMenu extends Component { static propTypes = { post: PropTypes.object.isRequired, teamId: PropTypes.string, location: PropTypes.oneOf([Locations.CENTER, Locations.RHS_ROOT, Locations.RHS_COMMENT, Locations.SEARCH]).isRequired, commentCount: PropTypes.number, isFlagged: PropTypes.bool, handleCommentClick: PropTypes.func, handleDropdownOpened: PropTypes.func, handleAddReactionClick: PropTypes.func, isReadOnly: PropTypes.bool, pluginMenuItems: PropTypes.arrayOf(PropTypes.object), isLicensed: PropTypes.bool.isRequired, postEditTimeLimit: PropTypes.string.isRequired, enableEmojiPicker: PropTypes.bool.isRequired, actions: PropTypes.shape({ /** * Function flag the post */ flagPost: PropTypes.func.isRequired, /** * Function to unflag the post */ unflagPost: PropTypes.func.isRequired, /** * Function to set the editing post */ setEditingPost: PropTypes.func.isRequired, /** * Function to pin the post */ pinPost: PropTypes.func.isRequired, /** * Function to unpin the post */ unpinPost: PropTypes.func.isRequired, /** * Function to open a modal */ openModal: PropTypes.func.isRequired, }).isRequired, } static defaultProps = { post: {}, commentCount: 0, isFlagged: false, isReadOnly: false, pluginMenuItems: [], location: Locations.CENTER, } constructor(props) { super(props); this.editDisableAction = new DelayedAction(this.handleEditDisable); this.state = { openUp: false, }; this.buttonRef = React.createRef(); } disableCanEditPostByTime() { const {post, isLicensed, postEditTimeLimit} = this.props; const canEdit = PostUtils.canEditPost(post); if (canEdit && isLicensed) { if (String(postEditTimeLimit) !== String(UNSET_POST_EDIT_TIME_LIMIT)) { const milliseconds = 1000; const timeLeft = (post.create_at + (postEditTimeLimit * milliseconds)) - Utils.getTimestamp(); if (timeLeft > 0) { this.editDisableAction.fireAfter(timeLeft + milliseconds); } } } } componentDidMount() { this.disableCanEditPostByTime(); } static getDerivedStateFromProps(props) { return { canDelete: PostUtils.canDeletePost(props.post) && !props.isReadOnly, canEdit: PostUtils.canEditPost(props.post) && !props.isReadOnly, }; } componentWillUnmount() { this.editDisableAction.cancel(); } handleEditDisable = () => { this.setState({canEdit: false}); } handleFlagMenuItemActivated = () => { if (this.props.isFlagged) { this.props.actions.unflagPost(this.props.post.id); } else { this.props.actions.flagPost(this.props.post.id); } } // listen to clicks/taps on add reaction menu item and pass to parent handler handleAddReactionMenuItemActivated = (e) => { e.preventDefault(); // to be safe, make sure the handler function has been defined if (this.props.handleAddReactionClick) { this.props.handleAddReactionClick(); } } handlePermalinkMenuItemActivated = (e) => { e.preventDefault(); showGetPostLinkModal(this.props.post); } handlePinMenuItemActivated = () => { if (this.props.post.is_pinned) { this.props.actions.unpinPost(this.props.post.id); } else { this.props.actions.pinPost(this.props.post.id); } } handleDeleteMenuItemActivated = (e) => { e.preventDefault(); const deletePostModalData = { ModalId: ModalIdentifiers.DELETE_POST, dialogType: DeletePostModal, dialogProps: { post: this.props.post, commentCount: this.props.commentCount, isRHS: this.props.location === Locations.RHS_ROOT || this.props.location === Locations.RHS_COMMENT, }, }; this.props.actions.openModal(deletePostModalData); } handleEditMenuItemActivated = () => { this.props.actions.setEditingPost( this.props.post.id, this.props.commentCount, this.props.location === Locations.CENTER ? 'post_textbox' : 'reply_textbox', this.props.post.root_id ? Utils.localizeMessage('rhs_comment.comment', 'Comment') : Utils.localizeMessage('create_post.post', 'Post'), this.props.location === Locations.RHS_ROOT || this.props.location === Locations.RHS_COMMENT, ); } tooltip = ( ) refCallback = (ref) => { if (ref) { const rect = ref.rect(); const y = rect.y || rect.top; const height = rect.height; const windowHeight = window.innerHeight; if ((y + height) > (windowHeight - MENU_BOTTOM_MARGIN)) { this.setState({openUp: true}); } } } render() { const isSystemMessage = PostUtils.isSystemMessage(this.props.post); const isMobile = Utils.isMobile(); const pluginItems = this.props.pluginMenuItems. filter((item) => { return item.filter ? item.filter(this.props.post.id) : item; }). map((item) => { return ( { if (item.action) { item.action(this.props.post.id); } }} /> ); }); if (!this.state.canDelete && !this.state.canEdit && pluginItems.length === 0 && isSystemMessage) { return null; } return (