Commit ea1d4685 authored by Saturnino Abril's avatar Saturnino Abril Committed by Joram Wilander

create DotMenu components and add dotmenu IDs to posts at center and RHS (#6642)

parent 5e0aa96b
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import $ from 'jquery';
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import DotMenuFlag from './dot_menu_flag.jsx';
import DotMenuItem from './dot_menu_item.jsx';
import DotMenuEdit from './dot_menu_edit.jsx';
import * as Utils from 'utils/utils.jsx';
import * as PostUtils from 'utils/post_utils.jsx';
import Constants from 'utils/constants.jsx';
import DelayedAction from 'utils/delayed_action.jsx';
export default class DotMenu extends Component {
static propTypes = {
idPrefix: PropTypes.string.isRequired,
idCount: PropTypes.number,
post: PropTypes.object.isRequired,
commentCount: PropTypes.number,
isFlagged: PropTypes.bool,
handleCommentClick: PropTypes.func,
handleDropdownOpened: PropTypes.func
}
static defaultProps = {
idCount: -1,
post: {},
commentCount: 0,
isFlagged: false
}
constructor(props) {
super(props);
this.handleDropdownOpened = this.handleDropdownOpened.bind(this);
this.canDelete = false;
this.canEdit = false;
this.editDisableAction = new DelayedAction(this.handleEditDisable);
}
componentDidMount() {
$('#' + this.props.idPrefix + '_dropdown' + this.props.post.id).on('shown.bs.dropdown', this.handleDropdownOpened);
$('#' + this.props.idPrefix + '_dropdown' + this.props.post.id).on('hidden.bs.dropdown', () => this.props.handleDropdownOpened(false));
}
handleDropdownOpened() {
this.props.handleDropdownOpened(true);
const position = $('#post-list').height() - $(this.refs.dropdownToggle).offset().top;
const dropdown = $(this.refs.dropdown);
if (position < dropdown.height()) {
dropdown.addClass('bottom');
}
}
handleEditDisable() {
this.canEdit = false;
}
render() {
const isSystemMessage = PostUtils.isSystemMessage(this.props.post);
const isMobile = Utils.isMobile();
this.canDelete = PostUtils.canDeletePost(this.props.post);
this.canEdit = PostUtils.canEditPost(this.props.post, this.editDisableAction);
if (this.props.idPrefix === Constants.CENTER && (!isMobile && isSystemMessage && !this.canDelete && !this.canEdit)) {
return null;
}
if (this.props.idPrefix === Constants.RHS && (this.props.post.state === Constants.POST_FAILED || this.props.post.state === Constants.POST_LOADING)) {
return null;
}
let type = 'Post';
if (this.props.post.root_id && this.props.post.root_id.length > 0) {
type = 'Comment';
}
const idPrefix = this.props.idPrefix + 'DotMenu';
let dotMenuFlag = null;
if (isMobile) {
dotMenuFlag = (
<DotMenuFlag
idPrefix={idPrefix + 'Flag'}
idCount={this.props.idCount}
postId={this.props.post.id}
isFlagged={this.props.isFlagged}
/>
);
}
let dotMenuReply = null;
let dotMenuPermalink = null;
let dotMenuPin = null;
if (!isSystemMessage) {
if (this.props.idPrefix === Constants.CENTER) {
dotMenuReply = (
<DotMenuItem
idPrefix={idPrefix + 'Reply'}
idCount={this.props.idCount}
handleOnClick={this.props.handleCommentClick}
/>
);
}
dotMenuPermalink = (
<DotMenuItem
idPrefix={idPrefix + 'Permalink'}
idCount={this.props.idCount}
post={this.props.post}
/>
);
dotMenuPin = (
<DotMenuItem
idPrefix={idPrefix + 'Pin'}
idCount={this.props.idCount}
post={this.props.post}
/>
);
}
let dotMenuDelete = null;
if (this.canDelete) {
dotMenuDelete = (
<DotMenuItem
idPrefix={idPrefix + 'Delete'}
idCount={this.props.idCount}
post={this.props.post}
commentCount={type === 'Post' ? this.props.commentCount : 0}
/>
);
}
let dotMenuEdit = null;
if (this.canEdit) {
dotMenuEdit = (
<DotMenuEdit
idPrefix={idPrefix + 'Edit'}
idCount={this.props.idCount}
post={this.props.post}
type={type}
commentCount={type === 'Post' ? this.props.commentCount : 0}
/>
);
}
let dotMenuId = null;
if (this.props.idCount > -1) {
dotMenuId = Utils.createSafeId(idPrefix + this.props.idCount);
}
if (this.props.idPrefix === Constants.RHS_ROOT) {
dotMenuId = idPrefix;
}
return (
<div
id={dotMenuId}
className='dropdown'
ref='dotMenu'
>
<div
id={this.props.idPrefix + '_dropdown' + this.props.post.id}
>
<a
ref='dropdownToggle'
href='#'
className='dropdown-toggle post__dropdown theme'
type='button'
data-toggle='dropdown'
aria-expanded='false'
/>
<div className='dropdown-menu__content'>
<ul
ref='dropdown'
className='dropdown-menu'
role='menu'
>
{dotMenuReply}
{dotMenuFlag}
{dotMenuPermalink}
{dotMenuPin}
{dotMenuDelete}
{dotMenuEdit}
</ul>
</div>
</div>
</div>
);
}
}
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import {FormattedMessage} from 'react-intl';
import PropTypes from 'prop-types';
import * as Utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx';
export default function DotMenuEdit(props) {
let editId = null;
if (props.idCount > -1) {
editId = Utils.createSafeId(props.idPrefix + props.idCount);
}
if (props.idPrefix.indexOf(Constants.RHS_ROOT) === 0) {
editId = props.idPrefix;
}
return (
<li
id={Utils.createSafeId(editId)}
key={props.idPrefix}
role='presentation'
>
<a
href='#'
role='menuitem'
data-toggle='modal'
data-target='#edit_post'
data-refocusid={props.idPrefix.indexOf(Constants.CENTER) === 0 ? '#post_textbox' : '#reply_textbox'}
data-title={props.idPrefix.indexOf(Constants.CENTER) === 0 ? props.type : Utils.localizeMessage('rhs_comment.comment', 'Comment')}
data-message={props.post.message}
data-postid={props.post.id}
data-channelid={props.post.channel_id}
data-comments={props.commentCount}
>
<FormattedMessage
id='post_info.edit'
defaultMessage='Edit'
/>
</a>
</li>
);
}
DotMenuEdit.propTypes = {
idPrefix: PropTypes.string.isRequired,
idCount: PropTypes.number,
post: PropTypes.object,
type: PropTypes.string,
commentCount: PropTypes.number
};
DotMenuEdit.defaultProps = {
idCount: -1
};
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import {FormattedMessage} from 'react-intl';
import PropTypes from 'prop-types';
import {flagPost, unflagPost} from 'actions/post_actions.jsx';
import * as Utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx';
function formatMessage(isFlagged) {
return (
<FormattedMessage
id={isFlagged ? 'rhs_root.mobile.unflag' : 'rhs_root.mobile.flag'}
defaultMessage={isFlagged ? 'Unflag' : 'Flag'}
/>
);
}
export default function DotMenuFlag(props) {
function onFlagPost(e) {
e.preventDefault();
flagPost(props.postId);
}
function onUnflagPost(e) {
e.preventDefault();
unflagPost(props.postId);
}
const flagFunc = props.isFlagged ? onUnflagPost : onFlagPost;
let flagId = null;
if (props.idCount > -1) {
flagId = Utils.createSafeId(props.idPrefix + props.idCount);
}
if (props.idPrefix.indexOf(Constants.RHS_ROOT) === 0) {
flagId = props.idPrefix;
}
return (
<li
key={props.idPrefix}
role='presentation'
>
<a
id={flagId}
href='#'
onClick={flagFunc}
>
{formatMessage(props.isFlagged)}
</a>
</li>
);
}
DotMenuFlag.propTypes = {
idCount: PropTypes.number,
idPrefix: PropTypes.string.isRequired,
postId: PropTypes.string.isRequired,
isFlagged: PropTypes.bool.isRequired
};
DotMenuFlag.defaultProps = {
idCount: -1,
postId: '',
isFlagged: false
};
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import {FormattedMessage} from 'react-intl';
import PropTypes from 'prop-types';
import {unpinPost, pinPost} from 'actions/post_actions.jsx';
import {showGetPostLinkModal, showDeletePostModal} from 'actions/global_actions.jsx';
import * as Utils from 'utils/utils.jsx';
import Constants from 'utils/constants.jsx';
export default function DotMenuItem(props) {
function handlePermalink(e) {
e.preventDefault();
showGetPostLinkModal(props.post);
}
function handleUnpinPost(e) {
e.preventDefault();
unpinPost(props.post.channel_id, props.post.id);
}
function handlePinPost(e) {
e.preventDefault();
pinPost(props.post.channel_id, props.post.id);
}
function handleDeletePost(e) {
e.preventDefault();
showDeletePostModal(props.post, props.commentCount);
}
const attrib = {};
attrib.idPrefix = props.idPrefix;
attrib.class = '';
switch (props.idPrefix.substring((props.idPrefix.indexOf('DotMenu') + 7))) {
case 'Reply':
attrib.class = 'link__reply theme';
attrib.onClick = props.handleOnClick;
attrib.formattedMessageId = 'post_info.reply';
attrib.formattedDefaultMessage = 'Reply';
break;
case 'Permalink':
attrib.onClick = handlePermalink;
attrib.formattedMessageId = 'post_info.permalink';
attrib.formattedDefaultMessage = 'Permalink';
attrib.post = props.post;
break;
case 'Pin':
attrib.onClick = props.post.is_pinned ? handleUnpinPost : handlePinPost;
attrib.formattedMessageId = props.post.is_pinned ? 'post_info.unpin' : 'post_info.pin';
attrib.formattedDefaultMessage = props.post.is_pinned ? 'Un-pin from channel' : 'Pin from channel';
attrib.post = props.post;
break;
case 'Delete':
attrib.onClick = handleDeletePost;
attrib.formattedMessageId = 'post_info.del';
attrib.formattedDefaultMessage = 'Delete';
attrib.commentCount = props.commentCount;
break;
default:
}
let itemId = null;
if (props.idCount > -1) {
itemId = Utils.createSafeId(props.idPrefix + props.idCount);
}
if (attrib.idPrefix.indexOf(Constants.RHS_ROOT) === 0) {
itemId = attrib.idPrefix;
}
return (
<li
id={Utils.createSafeId(itemId)}
key={attrib.idPrefix}
role='presentation'
>
<a
href='#'
role='menuitem'
onClick={attrib.onClick}
>
<FormattedMessage
id={attrib.formattedMessageId}
defaultMessage={attrib.formattedDefaultMessage}
/>
</a>
</li>
);
}
DotMenuItem.propTypes = {
idPrefix: PropTypes.string.isRequired,
idCount: PropTypes.number,
post: PropTypes.object,
handleOnClick: PropTypes.func,
type: PropTypes.string,
commentCount: PropTypes.number
};
DotMenuItem.defaultProps = {
idPrefix: '',
idCount: -1
};
......@@ -7,8 +7,9 @@ import PendingPostOptions from 'components/post_view/components/pending_post_opt
import PostMessageContainer from 'components/post_view/components/post_message_container.jsx';
import ProfilePicture from 'components/profile_picture.jsx';
import ReactionListContainer from 'components/post_view/components/reaction_list_container.jsx';
import RhsDropdown from 'components/rhs_dropdown.jsx';
import PostFlagIcon from 'components/common/post_flag_icon.jsx';
import DotMenu from 'components/dot_menu/dot_menu.jsx';
import EmojiPickerOverlay from 'components/emoji_picker/emoji_picker_overlay.jsx';
import * as GlobalActions from 'actions/global_actions.jsx';
import {flagPost, unflagPost, pinPost, unpinPost, addReaction} from 'actions/post_actions.jsx';
......@@ -17,19 +18,13 @@ import TeamStore from 'stores/team_store.jsx';
import * as Utils from 'utils/utils.jsx';
import * as PostUtils from 'utils/post_utils.jsx';
import Constants from 'utils/constants.jsx';
import DelayedAction from 'utils/delayed_action.jsx';
import {FormattedMessage} from 'react-intl';
import EmojiPickerOverlay from 'components/emoji_picker/emoji_picker_overlay.jsx';
import loadingGif from 'images/load.gif';
import PropTypes from 'prop-types';
import React from 'react';
import PropTypes from 'prop-types';
import {FormattedMessage} from 'react-intl';
import {Link} from 'react-router/es6';
export default class RhsComment extends React.Component {
......@@ -45,10 +40,6 @@ export default class RhsComment extends React.Component {
this.reactEmojiClick = this.reactEmojiClick.bind(this);
this.handleDropdownOpened = this.handleDropdownOpened.bind(this);
this.canEdit = false;
this.canDelete = false;
this.editDisableAction = new DelayedAction(this.handleEditDisable);
this.state = {
currentTeamDisplayName: TeamStore.getCurrent().name,
width: '',
......@@ -75,10 +66,6 @@ export default class RhsComment extends React.Component {
GlobalActions.showGetPostLinkModal(this.props.post);
}
handleEditDisable() {
this.canEdit = false;
}
removePost() {
GlobalActions.emitRemovePost(this.props.post);
}
......@@ -160,173 +147,6 @@ export default class RhsComment extends React.Component {
unpinPost(this.props.post.channel_id, this.props.post.id);
}
createDropdown(isSystemMessage) {
const post = this.props.post;
if (post.state === Constants.POST_FAILED || post.state === Constants.POST_LOADING) {
return '';
}
this.canDelete = PostUtils.canDeletePost(post);
this.canEdit = PostUtils.canEditPost(post, this.editDisableAction);
var dropdownContents = [];
if (Utils.isMobile()) {
if (this.props.isFlagged) {
dropdownContents.push(
<li
key='mobileFlag'
role='presentation'
>
<a
href='#'
onClick={this.unflagPost}
>
<FormattedMessage
id='rhs_root.mobile.unflag'
defaultMessage='Unflag'
/>
</a>
</li>
);
} else {
dropdownContents.push(
<li
key='mobileFlag'
role='presentation'
>
<a
href='#'
onClick={this.flagPost}
>
<FormattedMessage
id='rhs_root.mobile.flag'
defaultMessage='Flag'
/>
</a>
</li>
);
}
}
if (!isSystemMessage) {
dropdownContents.push(
<li
key='rhs-root-permalink'
role='presentation'
>
<a
href='#'
onClick={this.handlePermalink}
>
<FormattedMessage
id='rhs_comment.permalink'
defaultMessage='Permalink'
/>
</a>
</li>
);
if (post.is_pinned) {
dropdownContents.push(
<li
key='rhs-comment-unpin'
role='presentation'
>
<a
href='#'
onClick={this.unpinPost}
>
<FormattedMessage
id='rhs_root.unpin'
defaultMessage='Un-pin from channel'
/>
</a>
</li>
);
} else {
dropdownContents.push(
<li
key='rhs-comment-pin'
role='presentation'
>
<a
href='#'
onClick={this.pinPost}
>
<FormattedMessage
id='rhs_root.pin'
defaultMessage='Pin to channel'
/>
</a>
</li>
);
}
}
if (this.canDelete) {
dropdownContents.push(
<li
role='presentation'
key='delete-button'
>
<a
href='#'
role='menuitem'
onClick={(e) => {
e.preventDefault();
GlobalActions.showDeletePostModal(post, 0);
}}
>
<FormattedMessage
id='rhs_comment.del'
defaultMessage='Delete'
/>
</a>
</li>
);
}
if (this.canEdit) {
dropdownContents.push(
<li
role='presentation'
key='edit-button'
className={this.canEdit ? '' : 'hide'}
>
<a
href='#'
role='menuitem'
data-toggle='modal'
data-target='#edit_post'
data-refocusid='#reply_textbox'
data-title={Utils.localizeMessage('rhs_comment.comment', 'Comment')}
data-message={post.message}
data-postid={post.id}
data-channelid={post.channel_id}
>
<FormattedMessage
id='rhs_comment.edit'
defaultMessage='Edit'
/>
</a>
</li>
);
}
if (dropdownContents.length === 0) {
return '';
}
return (
<RhsDropdown
dropdownContents={dropdownContents}
handleDropdownOpened={this.handleDropdownOpened}
/>
);
}
timeTag(post, timeOptions) {
return (
<time
......@@ -575,12 +395,22 @@ export default class RhsComment extends React.Component {
</div>
);
} else if (!isSystemMessage) {
const dotMenu = (
<DotMenu
idPrefix={Constants.RHS}
idCount={idCount}
post={this.props.post}
isFlagged={this.props.isFlagged}
handleDropdownOpened={this.handleDropdownOpened}
/>
);
options = (
<div
ref='dotMenu'
className='col col__reply'
>
{this.createDropdown(isSystemMessage)}
{dotMenu}
{react}
</div>
);
......
import PropTypes from 'prop-types';
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {Component} from 'react';
import {Dropdown} from 'react-bootstrap';
import RhsDropdownButton from 'components/rhs_dropdown_button.jsx';
import RhsDropdownMenu from 'components/rhs_dropdown_menu.jsx';
import * as Agent from 'utils/user_agent.jsx';
export default class RhsDropdown extends Component {
static propTypes = {
dropdownContents: PropTypes.array.isRequired,
handleDropdownOpened: PropTypes.func
}
constructor(props) {
super(props);
this.state = {
showDropdown: false
};
}
toggleDropdown = () => {
const showDropdown = !this.state.showDropdown;
if (this.props.handleDropdownOpened) {
this.props.handleDropdownOpened(showDropdown);
}
if (Agent.isMobile() || Agent.isMobileApp()) {
const scroll = document.querySelector('.scrollbar--view');