Make the message context menu more accessible

Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
Michael Telatynski 2019-09-26 14:24:10 +01:00
parent 14e3cb8736
commit 8ec0ffea3a
3 changed files with 69 additions and 41 deletions

View file

@ -84,9 +84,9 @@ export class TabbedView extends React.Component {
const onClickHandler = () => this._setActiveTab(tab); const onClickHandler = () => this._setActiveTab(tab);
const onKeyDownHandler = (e) => { const onKeyDownHandler = (e) => {
if (e.keyCode === KeyCode.ENTER || e.keyCode === KeyCode.SPACE) {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
if (e.keyCode === KeyCode.ENTER || e.keyCode === KeyCode.SPACE) {
this._setActiveTab(tab); this._setActiveTab(tab);
} }
}; };

View file

@ -2,6 +2,7 @@
Copyright 2015, 2016 OpenMarket Ltd Copyright 2015, 2016 OpenMarket Ltd
Copyright 2018 New Vector Ltd Copyright 2018 New Vector Ltd
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com> Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -30,11 +31,28 @@ import Resend from '../../../Resend';
import SettingsStore from '../../../settings/SettingsStore'; import SettingsStore from '../../../settings/SettingsStore';
import { isUrlPermitted } from '../../../HtmlUtils'; import { isUrlPermitted } from '../../../HtmlUtils';
import { isContentActionable } from '../../../utils/EventUtils'; import { isContentActionable } from '../../../utils/EventUtils';
import {KeyCode} from "../../../Keyboard";
function canCancel(eventStatus) { function canCancel(eventStatus) {
return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT; return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT;
} }
const DropdownButton = ({children, onClick}) => {
const onKeyDown = (e) => {
if (e.keyCode === KeyCode.ENTER || e.keyCode === KeyCode.SPACE) {
e.stopPropagation();
e.preventDefault();
onClick();
}
};
return (
<div className="mx_MessageContextMenu_field" onClick={onClick} onKeyDown={onKeyDown} role="button" tabIndex={0}>
{ children }
</div>
);
};
module.exports = createReactClass({ module.exports = createReactClass({
displayName: 'MessageContextMenu', displayName: 'MessageContextMenu',
@ -319,89 +337,89 @@ module.exports = createReactClass({
if (!mxEvent.isRedacted()) { if (!mxEvent.isRedacted()) {
if (eventStatus === EventStatus.NOT_SENT) { if (eventStatus === EventStatus.NOT_SENT) {
resendButton = ( resendButton = (
<div className="mx_MessageContextMenu_field" onClick={this.onResendClick}> <DropdownButton onClick={this.onResendClick}>
{ _t('Resend') } { _t('Resend') }
</div> </DropdownButton>
); );
} }
if (editStatus === EventStatus.NOT_SENT) { if (editStatus === EventStatus.NOT_SENT) {
resendEditButton = ( resendEditButton = (
<div className="mx_MessageContextMenu_field" onClick={this.onResendEditClick}> <DropdownButton onClick={this.onResendEditClick}>
{ _t('Resend edit') } { _t('Resend edit') }
</div> </DropdownButton>
); );
} }
if (unsentReactionsCount !== 0) { if (unsentReactionsCount !== 0) {
resendReactionsButton = ( resendReactionsButton = (
<div className="mx_MessageContextMenu_field" onClick={this.onResendReactionsClick}> <DropdownButton onClick={this.onResendReactionsClick}>
{ _t('Resend %(unsentCount)s reaction(s)', {unsentCount: unsentReactionsCount}) } { _t('Resend %(unsentCount)s reaction(s)', {unsentCount: unsentReactionsCount}) }
</div> </DropdownButton>
); );
} }
} }
if (redactStatus === EventStatus.NOT_SENT) { if (redactStatus === EventStatus.NOT_SENT) {
resendRedactionButton = ( resendRedactionButton = (
<div className="mx_MessageContextMenu_field" onClick={this.onResendRedactionClick}> <DropdownButton onClick={this.onResendRedactionClick}>
{ _t('Resend removal') } { _t('Resend removal') }
</div> </DropdownButton>
); );
} }
if (isSent && this.state.canRedact) { if (isSent && this.state.canRedact) {
redactButton = ( redactButton = (
<div className="mx_MessageContextMenu_field" onClick={this.onRedactClick}> <DropdownButton onClick={this.onRedactClick}>
{ _t('Remove') } { _t('Remove') }
</div> </DropdownButton>
); );
} }
if (allowCancel) { if (allowCancel) {
cancelButton = ( cancelButton = (
<div className="mx_MessageContextMenu_field" onClick={this.onCancelSendClick}> <DropdownButton onClick={this.onCancelSendClick}>
{ _t('Cancel Sending') } { _t('Cancel Sending') }
</div> </DropdownButton>
); );
} }
if (isContentActionable(mxEvent)) { if (isContentActionable(mxEvent)) {
forwardButton = ( forwardButton = (
<div className="mx_MessageContextMenu_field" onClick={this.onForwardClick}> <DropdownButton onClick={this.onForwardClick}>
{ _t('Forward Message') } { _t('Forward Message') }
</div> </DropdownButton>
); );
if (this.state.canPin) { if (this.state.canPin) {
pinButton = ( pinButton = (
<div className="mx_MessageContextMenu_field" onClick={this.onPinClick}> <DropdownButton onClick={this.onPinClick}>
{ this._isPinned() ? _t('Unpin Message') : _t('Pin Message') } { this._isPinned() ? _t('Unpin Message') : _t('Pin Message') }
</div> </DropdownButton>
); );
} }
} }
const viewSourceButton = ( const viewSourceButton = (
<div className="mx_MessageContextMenu_field" onClick={this.onViewSourceClick}> <DropdownButton onClick={this.onViewSourceClick}>
{ _t('View Source') } { _t('View Source') }
</div> </DropdownButton>
); );
if (mxEvent.getType() !== mxEvent.getWireType()) { if (mxEvent.getType() !== mxEvent.getWireType()) {
viewClearSourceButton = ( viewClearSourceButton = (
<div className="mx_MessageContextMenu_field" onClick={this.onViewClearSourceClick}> <DropdownButton onClick={this.onViewClearSourceClick}>
{ _t('View Decrypted Source') } { _t('View Decrypted Source') }
</div> </DropdownButton>
); );
} }
if (this.props.eventTileOps) { if (this.props.eventTileOps) {
if (this.props.eventTileOps.isWidgetHidden()) { if (this.props.eventTileOps.isWidgetHidden()) {
unhidePreviewButton = ( unhidePreviewButton = (
<div className="mx_MessageContextMenu_field" onClick={this.onUnhidePreviewClick}> <DropdownButton onClick={this.onUnhidePreviewClick}>
{ _t('Unhide Preview') } { _t('Unhide Preview') }
</div> </DropdownButton>
); );
} }
} }
@ -412,20 +430,19 @@ module.exports = createReactClass({
} }
// XXX: if we use room ID, we should also include a server where the event can be found (other than in the domain of the event ID) // XXX: if we use room ID, we should also include a server where the event can be found (other than in the domain of the event ID)
const permalinkButton = ( const permalinkButton = (
<div className="mx_MessageContextMenu_field"> <DropdownButton>
<a href={permalink} <a href={permalink} target="_blank" rel="noopener" onClick={this.onPermalinkClick} tabIndex={-1}>
target="_blank" rel="noopener" onClick={this.onPermalinkClick}>
{ mxEvent.isRedacted() || mxEvent.getType() !== 'm.room.message' { mxEvent.isRedacted() || mxEvent.getType() !== 'm.room.message'
? _t('Share Permalink') : _t('Share Message') } ? _t('Share Permalink') : _t('Share Message') }
</a> </a>
</div> </DropdownButton>
); );
if (this.props.eventTileOps && this.props.eventTileOps.getInnerText) { if (this.props.eventTileOps && this.props.eventTileOps.getInnerText) {
quoteButton = ( quoteButton = (
<div className="mx_MessageContextMenu_field" onClick={this.onQuoteClick}> <DropdownButton onClick={this.onQuoteClick}>
{ _t('Quote') } { _t('Quote') }
</div> </DropdownButton>
); );
} }
@ -435,34 +452,43 @@ module.exports = createReactClass({
isUrlPermitted(mxEvent.event.content.external_url) isUrlPermitted(mxEvent.event.content.external_url)
) { ) {
externalURLButton = ( externalURLButton = (
<div className="mx_MessageContextMenu_field"> <DropdownButton>
<a href={mxEvent.event.content.external_url} <a
rel="noopener" target="_blank" onClick={this.closeMenu}>{ _t('Source URL') }</a> href={mxEvent.event.content.external_url}
</div> target="_blank"
rel="noopener"
onClick={this.closeMenu}
tabIndex={-1}
>
{ _t('Source URL') }
</a>
</DropdownButton>
); );
} }
if (this.props.collapseReplyThread) { if (this.props.collapseReplyThread) {
collapseReplyThread = ( collapseReplyThread = (
<div className="mx_MessageContextMenu_field" onClick={this.onCollapseReplyThreadClick}> <DropdownButton onClick={this.onCollapseReplyThreadClick}>
{ _t('Collapse Reply Thread') } { _t('Collapse Reply Thread') }
</div> </DropdownButton>
); );
} }
let e2eInfo; let e2eInfo;
if (this.props.e2eInfoCallback) { if (this.props.e2eInfoCallback) {
e2eInfo = <div className="mx_MessageContextMenu_field" onClick={this.e2eInfoClicked}> e2eInfo = (
<DropdownButton onClick={this.e2eInfoClicked}>
{ _t('End-to-end encryption information') } { _t('End-to-end encryption information') }
</div>; </DropdownButton>
);
} }
let reportEventButton; let reportEventButton;
if (mxEvent.getSender() !== me) { if (mxEvent.getSender() !== me) {
reportEventButton = ( reportEventButton = (
<div className="mx_MessageContextMenu_field" onClick={this.onReportEventClick}> <DropdownButton onClick={this.onReportEventClick}>
{ _t('Report Content') } { _t('Report Content') }
</div> </DropdownButton>
); );
} }

View file

@ -166,9 +166,11 @@ export default class MessageActionBar extends React.PureComponent {
{reactButton} {reactButton}
{replyButton} {replyButton}
{editButton} {editButton}
<span className="mx_MessageActionBar_maskButton mx_MessageActionBar_optionsButton" <span
className="mx_MessageActionBar_maskButton mx_MessageActionBar_optionsButton"
title={_t("Options")} title={_t("Options")}
onClick={this.onOptionsClick} onClick={this.onOptionsClick}
aria-haspopup={true}
/> />
</div>; </div>;
} }