Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 78 additions & 1 deletion src/components/views/context_menus/MessageContextMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,12 @@ import Modal from '../../../Modal';
import Resend from '../../../Resend';
import SettingsStore from '../../../settings/SettingsStore';
import { isUrlPermitted } from '../../../HtmlUtils';
import { isContentActionable } from '../../../utils/EventUtils';
import { isContentActionable, canEditContent } from '../../../utils/EventUtils';
import {MenuItem} from "../../structures/ContextMenu";
import {EventType} from "matrix-js-sdk/src/@types/event";
import {useRovingTabIndex} from "../../../accessibility/RovingTabIndex";
import {aboveLeftOf, ContextMenu, useContextMenu} from '../../structures/ContextMenu';
import RoomContext from "../../../contexts/RoomContext";

function canCancel(eventStatus) {
return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT;
Expand All @@ -50,13 +53,18 @@ export default class MessageContextMenu extends React.Component {

/* callback called when the menu is dismissed */
onFinished: PropTypes.func,

/* if true show react button */
rightClick: PropTypes.bool,
};

state = {
canRedact: false,
canPin: false,
};

static contextType = RoomContext;

componentDidMount() {
MatrixClientPeg.get().on('RoomMember.powerLevel', this._checkPermissions);
this._checkPermissions();
Expand Down Expand Up @@ -288,6 +296,22 @@ export default class MessageContextMenu extends React.Component {
return this._getReactions(e => e.status === EventStatus.NOT_SENT);
}

onEditClick = (ev) => {
dis.dispatch({
action: 'edit_event',
event: this.props.mxEvent,
});
this.closeMenu();
};

onReplyClick = (ev) => {
dis.dispatch({
action: 'reply_to_event',
event: this.props.mxEvent,
});
this.closeMenu();
};

render() {
const cli = MatrixClientPeg.get();
const me = cli.getUserId();
Expand All @@ -314,6 +338,9 @@ export default class MessageContextMenu extends React.Component {
let externalURLButton;
let quoteButton;
let collapseReplyThread;
let reactButton;
let replyButton;
let editButton;

// status is SENT before remote-echo, null after
const isSent = !eventStatus || eventStatus === EventStatus.SENT;
Expand Down Expand Up @@ -470,8 +497,32 @@ export default class MessageContextMenu extends React.Component {
);
}

if (this.props.rightClick) {
if (isContentActionable(this.props.mxEvent)) {
if (this.context.canReact) {
reactButton = <ReactButton
mxEvent={this.props.mxEvent}
onCloseContextMenu={this.closeMenu}
/>;
}
if (this.context.canReply) {
replyButton = <MenuItem className="mx_MessageContextMenu_field" onClick={this.onReplyClick}>
{_t("Reply")}
</MenuItem>;
}
}
if (canEditContent(this.props.mxEvent)) {
editButton = <MenuItem className="mx_MessageContextMenu_field" onClick={this.onEditClick}>
{_t("Edit")}
</MenuItem>;
}
}

return (
<div className="mx_MessageContextMenu">
{ reactButton }
{ replyButton }
{ editButton }
{ resendButton }
{ resendEditButton }
{ resendReactionsButton }
Expand All @@ -492,3 +543,29 @@ export default class MessageContextMenu extends React.Component {
);
}
}

const ReactButton = ({mxEvent, onCloseContextMenu}) => {
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
const ref = useRovingTabIndex(button)[2];

const closeReactionPicker = () => {
closeMenu();
onCloseContextMenu();
};

let reactionPicker;
if (menuDisplayed) {
const buttonRect = button.current.getBoundingClientRect();
const ReactionPicker = sdk.getComponent('emojipicker.ReactionPicker');
reactionPicker = <ContextMenu {...aboveLeftOf(buttonRect)} onFinished={closeMenu} managed={false}>
<ReactionPicker mxEvent={mxEvent} onFinished={closeReactionPicker} />
</ContextMenu>;
}

return <React.Fragment>
<MenuItem className="mx_MessageContextMenu_field" onClick={openMenu} inputRef={ref}>
{_t("React")}
</MenuItem>
{ reactionPicker }
</React.Fragment>;
};
64 changes: 60 additions & 4 deletions src/components/views/rooms/EventTile.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {E2E_STATE} from "./E2EIcon";
import {toRem} from "../../../utils/units";
import {WidgetType} from "../../../widgets/WidgetType";
import RoomAvatar from "../avatars/RoomAvatar";
import {ContextMenu, ChevronFace} from '../../structures/ContextMenu';

const eventTileTypes = {
'm.room.message': 'messages.MessageEvent',
Expand Down Expand Up @@ -254,6 +255,8 @@ export default class EventTile extends React.Component {
previouslyRequestedKeys: false,
// The Relations model from the JS SDK for reactions to `mxEvent`
reactions: this.getReactions(),

contextMenuPosition: null,
};

// don't do RR animations until we are mounted
Expand Down Expand Up @@ -632,6 +635,56 @@ export default class EventTile extends React.Component {
});
};

renderMenu() {
let contextMenu = null;
if (this.state.contextMenuPosition) {
const MessageContextMenu = sdk.getComponent('context_menus.MessageContextMenu');

const tile = this.getTile && this.getTile();
const replyThread = this.getReplyThread && this.getReplyThread();
const collapseReplyThread = replyThread && replyThread.canCollapse() ? replyThread.collapse : undefined;

contextMenu = (
<ContextMenu
chevronFace={ChevronFace.None}
onFinished={this.onCloseMenu}
left={this.state.contextMenuPosition.left}
top={this.state.contextMenuPosition.top + this.state.contextMenuPosition.height}>
<MessageContextMenu
mxEvent={this.props.mxEvent}
permalinkCreator={this.props.permalinkCreator}
eventTileOps={tile && tile.getEventTileOps ? tile.getEventTileOps() : undefined}
collapseReplyThread={collapseReplyThread}
onFinished={this.onCloseMenu}
rightClick={true}
/>
</ContextMenu>
);
}

return (
<React.Fragment>
{ contextMenu }
</React.Fragment>
);
}

onContextMenu = (ev) => {
ev.preventDefault();
ev.stopPropagation();
this.setState({
contextMenuPosition: {
left: ev.clientX,
top: ev.clientY,
height: 0,
},
});
};

onCloseMenu = () => {
this.setState({contextMenuPosition: null});
}

render() {
const MessageTimestamp = sdk.getComponent('messages.MessageTimestamp');
const SenderProfile = sdk.getComponent('messages.SenderProfile');
Expand Down Expand Up @@ -853,6 +906,7 @@ export default class EventTile extends React.Component {
const room = this.context.getRoom(this.props.mxEvent.getRoomId());
return (
<div className={classes} aria-live={ariaLive} aria-atomic="true">
{this.renderMenu()}
<div className="mx_EventTile_roomName">
<RoomAvatar room={room} width={28} height={28} />
<a href={permalink} onClick={this.onPermalinkClicked}>
Expand All @@ -866,7 +920,7 @@ export default class EventTile extends React.Component {
{ timestamp }
</a>
</div>
<div className="mx_EventTile_line">
<div className="mx_EventTile_line" onContextMenu={this.onContextMenu} >
<EventTileType ref={this._tile}
mxEvent={this.props.mxEvent}
highlights={this.props.highlights}
Expand All @@ -880,7 +934,8 @@ export default class EventTile extends React.Component {
case 'file_grid': {
return (
<div className={classes} aria-live={ariaLive} aria-atomic="true">
<div className="mx_EventTile_line">
{this.renderMenu()}
<div className="mx_EventTile_line" onContextMenu={this.onContextMenu}>
<EventTileType ref={this._tile}
mxEvent={this.props.mxEvent}
highlights={this.props.highlights}
Expand Down Expand Up @@ -920,7 +975,7 @@ export default class EventTile extends React.Component {
{ avatar }
{ sender }
{ ircPadlock }
<div className="mx_EventTile_reply">
<div className="mx_EventTile_reply" onContextMenu={this.onContextMenu}>
{ groupTimestamp }
{ groupPadlock }
{ thread }
Expand All @@ -947,13 +1002,14 @@ export default class EventTile extends React.Component {
// tab-index=-1 to allow it to be focusable but do not add tab stop for it, primarily for screen readers
return (
<div className={classes} tabIndex={-1} aria-live={ariaLive} aria-atomic="true">
{this.renderMenu()}
{ ircTimestamp }
<div className="mx_EventTile_msgOption">
{ readAvatars }
</div>
{ sender }
{ ircPadlock }
<div className="mx_EventTile_line">
<div className="mx_EventTile_line" onContextMenu={this.onContextMenu}>
{ groupTimestamp }
{ groupPadlock }
{ thread }
Expand Down