From 5e6793d9ac05ed24122302a54651ea1360d9cb3b Mon Sep 17 00:00:00 2001 From: Michael Weimann Date: Sun, 21 Feb 2021 18:02:34 +0100 Subject: [PATCH 01/74] migrate the message context menu to IconizedContextMenu Signed-off-by: Michael Weimann --- res/css/_components.scss | 1 - .../context_menus/_MessageContextMenu.scss | 30 --- .../views/context_menus/MessageContextMenu.js | 216 +++++++++++------- .../views/messages/MessageActionBar.js | 17 +- 4 files changed, 145 insertions(+), 119 deletions(-) delete mode 100644 res/css/views/context_menus/_MessageContextMenu.scss diff --git a/res/css/_components.scss b/res/css/_components.scss index 006bac09c97..a2ab59e3249 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -53,7 +53,6 @@ @import "./views/avatars/_WidgetAvatar.scss"; @import "./views/context_menus/_CallContextMenu.scss"; @import "./views/context_menus/_IconizedContextMenu.scss"; -@import "./views/context_menus/_MessageContextMenu.scss"; @import "./views/context_menus/_StatusMessageContextMenu.scss"; @import "./views/context_menus/_TagTileContextMenu.scss"; @import "./views/dialogs/_AddressPickerDialog.scss"; diff --git a/res/css/views/context_menus/_MessageContextMenu.scss b/res/css/views/context_menus/_MessageContextMenu.scss deleted file mode 100644 index 2ecb93e734a..00000000000 --- a/res/css/views/context_menus/_MessageContextMenu.scss +++ /dev/null @@ -1,30 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -.mx_MessageContextMenu { - padding: 6px; -} - -.mx_MessageContextMenu_field { - display: block; - padding: 3px 6px 3px 6px; - cursor: pointer; - white-space: nowrap; -} - -.mx_MessageContextMenu_field.mx_MessageContextMenu_fieldSet { - font-weight: bold; -} diff --git a/src/components/views/context_menus/MessageContextMenu.js b/src/components/views/context_menus/MessageContextMenu.js index 6b871e4f24a..428050f2f40 100644 --- a/src/components/views/context_menus/MessageContextMenu.js +++ b/src/components/views/context_menus/MessageContextMenu.js @@ -24,14 +24,14 @@ import {EventStatus} from 'matrix-js-sdk'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; import dis from '../../../dispatcher/dispatcher'; import * as sdk from '../../../index'; -import { _t } from '../../../languageHandler'; +import {_t} from '../../../languageHandler'; import Modal from '../../../Modal'; import Resend from '../../../Resend'; import SettingsStore from '../../../settings/SettingsStore'; -import { isUrlPermitted } from '../../../HtmlUtils'; -import { isContentActionable } from '../../../utils/EventUtils'; -import {MenuItem} from "../../structures/ContextMenu"; +import {isUrlPermitted} from '../../../HtmlUtils'; +import {isContentActionable} from '../../../utils/EventUtils'; import {EventType} from "matrix-js-sdk/src/@types/event"; +import IconizedContextMenu, {IconizedContextMenuOption, IconizedContextMenuOptionList} from "./IconizedContextMenu"; function canCancel(eventStatus) { return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT; @@ -314,95 +314,107 @@ export default class MessageContextMenu extends React.Component { let externalURLButton; let quoteButton; let collapseReplyThread; + let optionLists = []; // status is SENT before remote-echo, null after const isSent = !eventStatus || eventStatus === EventStatus.SENT; if (!mxEvent.isRedacted()) { if (eventStatus === EventStatus.NOT_SENT) { resendButton = ( - - { _t('Resend') } - + ); } if (editStatus === EventStatus.NOT_SENT) { resendEditButton = ( - - { _t('Resend edit') } - + ); } if (unsentReactionsCount !== 0) { resendReactionsButton = ( - - { _t('Resend %(unsentCount)s reaction(s)', {unsentCount: unsentReactionsCount}) } - + ); } } if (redactStatus === EventStatus.NOT_SENT) { resendRedactionButton = ( - - { _t('Resend removal') } - + ); } if (isSent && this.state.canRedact) { redactButton = ( - - { _t('Remove') } - + ); } if (allowCancel) { cancelButton = ( - - { _t('Cancel Sending') } - + ); } if (isContentActionable(mxEvent)) { forwardButton = ( - - { _t('Forward Message') } - + ); if (this.state.canPin) { pinButton = ( - - { this._isPinned() ? _t('Unpin Message') : _t('Pin Message') } - + ); } } const viewSourceButton = ( - - { _t('View Source') } - + ); if (mxEvent.getType() !== mxEvent.getWireType()) { viewClearSourceButton = ( - - { _t('View Decrypted Source') } - + ); } if (this.props.eventTileOps) { if (this.props.eventTileOps.isWidgetHidden()) { unhidePreviewButton = ( - - { _t('Unhide Preview') } - + ); } } @@ -413,82 +425,128 @@ export default class MessageContextMenu extends React.Component { } // 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 = ( - - { mxEvent.isRedacted() || mxEvent.getType() !== 'm.room.message' - ? _t('Share Permalink') : _t('Share Message') } - + /> ); if (this.props.eventTileOps) { // this event is rendered using TextualBody quoteButton = ( - - { _t('Quote') } - + ); } // Bridges can provide a 'external_url' to link back to the source. if ( - typeof(mxEvent.event.content.external_url) === "string" && + typeof (mxEvent.event.content.external_url) === "string" && isUrlPermitted(mxEvent.event.content.external_url) ) { externalURLButton = ( - - { _t('Source URL') } - - ); + /> + ); } if (this.props.collapseReplyThread) { collapseReplyThread = ( - - { _t('Collapse Reply Thread') } - + ); } let reportEventButton; if (mxEvent.getSender() !== me) { reportEventButton = ( - - { _t('Report Content') } - + ); } + if (viewSourceButton || viewClearSourceButton) { + optionLists.push(( + + {viewSourceButton} + {viewClearSourceButton} + + )) + } + + if (resendButton || resendEditButton || resendReactionsButton || resendRedactionButton) { + optionLists.push(( + + {resendButton} + {resendEditButton} + {resendReactionsButton} + {resendRedactionButton} + + )) + } + + if (redactButton || cancelButton) { + optionLists.push(( + + {redactButton} + {cancelButton} + + )) + } + + if (externalURLButton || permalinkButton) { + optionLists.push(( + + {externalURLButton} + {permalinkButton} + + )) + } + + if (pinButton || unhidePreviewButton || reportEventButton) { + optionLists.push(( + + {pinButton} + {unhidePreviewButton} + {reportEventButton} + + )) + } + + if (forwardButton || quoteButton || collapseReplyThread) { + optionLists.push(( + + {forwardButton} + {collapseReplyThread} + {quoteButton} + + )) + } + return ( -
- { resendButton } - { resendEditButton } - { resendReactionsButton } - { resendRedactionButton } - { redactButton } - { cancelButton } - { forwardButton } - { pinButton } - { viewSourceButton } - { viewClearSourceButton } - { unhidePreviewButton } - { permalinkButton } - { quoteButton } - { externalURLButton } - { collapseReplyThread } - { reportEventButton } -
+ + {optionLists} + ); } } diff --git a/src/components/views/messages/MessageActionBar.js b/src/components/views/messages/MessageActionBar.js index c94f296eac9..016fda3d8b8 100644 --- a/src/components/views/messages/MessageActionBar.js +++ b/src/components/views/messages/MessageActionBar.js @@ -44,15 +44,14 @@ const OptionsButton = ({mxEvent, getTile, getReplyThread, permalinkCreator, onFo const replyThread = getReplyThread && getReplyThread(); const buttonRect = button.current.getBoundingClientRect(); - contextMenu = - - ; + contextMenu = ; } return From c2b31d198d1449dc654a09f9951f54556087d8e1 Mon Sep 17 00:00:00 2001 From: Michael Weimann Date: Sun, 21 Feb 2021 18:02:34 +0100 Subject: [PATCH 02/74] migrate the message context menu to IconizedContextMenu Signed-off-by: Michael Weimann --- res/css/_components.scss | 1 - .../context_menus/_MessageContextMenu.scss | 30 --- .../views/context_menus/MessageContextMenu.js | 216 +++++++++++------- .../views/messages/MessageActionBar.js | 17 +- 4 files changed, 145 insertions(+), 119 deletions(-) delete mode 100644 res/css/views/context_menus/_MessageContextMenu.scss diff --git a/res/css/_components.scss b/res/css/_components.scss index 006bac09c97..a2ab59e3249 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -53,7 +53,6 @@ @import "./views/avatars/_WidgetAvatar.scss"; @import "./views/context_menus/_CallContextMenu.scss"; @import "./views/context_menus/_IconizedContextMenu.scss"; -@import "./views/context_menus/_MessageContextMenu.scss"; @import "./views/context_menus/_StatusMessageContextMenu.scss"; @import "./views/context_menus/_TagTileContextMenu.scss"; @import "./views/dialogs/_AddressPickerDialog.scss"; diff --git a/res/css/views/context_menus/_MessageContextMenu.scss b/res/css/views/context_menus/_MessageContextMenu.scss deleted file mode 100644 index 2ecb93e734a..00000000000 --- a/res/css/views/context_menus/_MessageContextMenu.scss +++ /dev/null @@ -1,30 +0,0 @@ -/* -Copyright 2015, 2016 OpenMarket Ltd - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -.mx_MessageContextMenu { - padding: 6px; -} - -.mx_MessageContextMenu_field { - display: block; - padding: 3px 6px 3px 6px; - cursor: pointer; - white-space: nowrap; -} - -.mx_MessageContextMenu_field.mx_MessageContextMenu_fieldSet { - font-weight: bold; -} diff --git a/src/components/views/context_menus/MessageContextMenu.js b/src/components/views/context_menus/MessageContextMenu.js index 6b871e4f24a..532f49573b7 100644 --- a/src/components/views/context_menus/MessageContextMenu.js +++ b/src/components/views/context_menus/MessageContextMenu.js @@ -24,14 +24,14 @@ import {EventStatus} from 'matrix-js-sdk'; import {MatrixClientPeg} from '../../../MatrixClientPeg'; import dis from '../../../dispatcher/dispatcher'; import * as sdk from '../../../index'; -import { _t } from '../../../languageHandler'; +import {_t} from '../../../languageHandler'; import Modal from '../../../Modal'; import Resend from '../../../Resend'; import SettingsStore from '../../../settings/SettingsStore'; -import { isUrlPermitted } from '../../../HtmlUtils'; -import { isContentActionable } from '../../../utils/EventUtils'; -import {MenuItem} from "../../structures/ContextMenu"; +import {isUrlPermitted} from '../../../HtmlUtils'; +import {isContentActionable} from '../../../utils/EventUtils'; import {EventType} from "matrix-js-sdk/src/@types/event"; +import IconizedContextMenu, {IconizedContextMenuOption, IconizedContextMenuOptionList} from "./IconizedContextMenu"; function canCancel(eventStatus) { return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT; @@ -314,95 +314,107 @@ export default class MessageContextMenu extends React.Component { let externalURLButton; let quoteButton; let collapseReplyThread; + const optionLists = []; // status is SENT before remote-echo, null after const isSent = !eventStatus || eventStatus === EventStatus.SENT; if (!mxEvent.isRedacted()) { if (eventStatus === EventStatus.NOT_SENT) { resendButton = ( - - { _t('Resend') } - + ); } if (editStatus === EventStatus.NOT_SENT) { resendEditButton = ( - - { _t('Resend edit') } - + ); } if (unsentReactionsCount !== 0) { resendReactionsButton = ( - - { _t('Resend %(unsentCount)s reaction(s)', {unsentCount: unsentReactionsCount}) } - + ); } } if (redactStatus === EventStatus.NOT_SENT) { resendRedactionButton = ( - - { _t('Resend removal') } - + ); } if (isSent && this.state.canRedact) { redactButton = ( - - { _t('Remove') } - + ); } if (allowCancel) { cancelButton = ( - - { _t('Cancel Sending') } - + ); } if (isContentActionable(mxEvent)) { forwardButton = ( - - { _t('Forward Message') } - + ); if (this.state.canPin) { pinButton = ( - - { this._isPinned() ? _t('Unpin Message') : _t('Pin Message') } - + ); } } const viewSourceButton = ( - - { _t('View Source') } - + ); if (mxEvent.getType() !== mxEvent.getWireType()) { viewClearSourceButton = ( - - { _t('View Decrypted Source') } - + ); } if (this.props.eventTileOps) { if (this.props.eventTileOps.isWidgetHidden()) { unhidePreviewButton = ( - - { _t('Unhide Preview') } - + ); } } @@ -413,82 +425,128 @@ export default class MessageContextMenu extends React.Component { } // 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 = ( - - { mxEvent.isRedacted() || mxEvent.getType() !== 'm.room.message' - ? _t('Share Permalink') : _t('Share Message') } - + /> ); if (this.props.eventTileOps) { // this event is rendered using TextualBody quoteButton = ( - - { _t('Quote') } - + ); } // Bridges can provide a 'external_url' to link back to the source. if ( - typeof(mxEvent.event.content.external_url) === "string" && + typeof (mxEvent.event.content.external_url) === "string" && isUrlPermitted(mxEvent.event.content.external_url) ) { externalURLButton = ( - - { _t('Source URL') } - - ); + /> + ); } if (this.props.collapseReplyThread) { collapseReplyThread = ( - - { _t('Collapse Reply Thread') } - + ); } let reportEventButton; if (mxEvent.getSender() !== me) { reportEventButton = ( - - { _t('Report Content') } - + ); } + if (viewSourceButton || viewClearSourceButton) { + optionLists.push(( + + {viewSourceButton} + {viewClearSourceButton} + + )); + } + + if (resendButton || resendEditButton || resendReactionsButton || resendRedactionButton) { + optionLists.push(( + + {resendButton} + {resendEditButton} + {resendReactionsButton} + {resendRedactionButton} + + )); + } + + if (redactButton || cancelButton) { + optionLists.push(( + + {redactButton} + {cancelButton} + + )); + } + + if (externalURLButton || permalinkButton) { + optionLists.push(( + + {externalURLButton} + {permalinkButton} + + )); + } + + if (pinButton || unhidePreviewButton || reportEventButton) { + optionLists.push(( + + {pinButton} + {unhidePreviewButton} + {reportEventButton} + + )); + } + + if (forwardButton || quoteButton || collapseReplyThread) { + optionLists.push(( + + {forwardButton} + {collapseReplyThread} + {quoteButton} + + )); + } + return ( -
- { resendButton } - { resendEditButton } - { resendReactionsButton } - { resendRedactionButton } - { redactButton } - { cancelButton } - { forwardButton } - { pinButton } - { viewSourceButton } - { viewClearSourceButton } - { unhidePreviewButton } - { permalinkButton } - { quoteButton } - { externalURLButton } - { collapseReplyThread } - { reportEventButton } -
+ + {optionLists} + ); } } diff --git a/src/components/views/messages/MessageActionBar.js b/src/components/views/messages/MessageActionBar.js index c94f296eac9..016fda3d8b8 100644 --- a/src/components/views/messages/MessageActionBar.js +++ b/src/components/views/messages/MessageActionBar.js @@ -44,15 +44,14 @@ const OptionsButton = ({mxEvent, getTile, getReplyThread, permalinkCreator, onFo const replyThread = getReplyThread && getReplyThread(); const buttonRect = button.current.getBoundingClientRect(); - contextMenu = - - ; + contextMenu = ; } return From ee2d800ad5c3874fe1bd7fd47e6e7e646eabdd09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 21 Feb 2021 20:46:43 +0100 Subject: [PATCH 03/74] Added right-click menu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/EventTile.js | 60 ++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 5 deletions(-) diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 210f966d1e4..365b65f42b8 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -39,6 +39,8 @@ import {WidgetType} from "../../../widgets/WidgetType"; import RoomAvatar from "../avatars/RoomAvatar"; import {WIDGET_LAYOUT_EVENT_TYPE} from "../../../stores/widgets/WidgetLayoutStore"; import {objectHasDiff} from "../../../utils/objects"; +import MessageContextMenu from "../context_menus/MessageContextMenu"; +import {aboveLeftOf} from '../../structures/ContextMenu'; const eventTileTypes = { 'm.room.message': 'messages.MessageEvent', @@ -257,6 +259,8 @@ export default class EventTile extends React.Component { previouslyRequestedKeys: false, // The Relations model from the JS SDK for reactions to `mxEvent` reactions: this.getReactions(), + // Context menu position + contextMenuPosition: null, }; // don't do RR animations until we are mounted @@ -635,6 +639,48 @@ export default class EventTile extends React.Component { }); }; + renderMenu() { + let contextMenu = null; + if (this.state.contextMenuPosition) { + const tile = this.getTile && this.getTile(); + const replyThread = this.getReplyThread && this.getReplyThread(); + const collapseReplyThread = replyThread && replyThread.canCollapse() ? replyThread.collapse : undefined; + + contextMenu = ( + + ); + } + + return ( + + { contextMenu } + + ); + } + + onContextMenu = (ev) => { + ev.preventDefault(); + ev.stopPropagation(); + this.setState({ + contextMenuPosition: { + right: ev.clientX, + top: ev.clientY, + bottom: ev.clientY, + }, + }); + }; + + onCloseMenu = () => { + this.setState({contextMenuPosition: null}); + } + render() { const MessageTimestamp = sdk.getComponent('messages.MessageTimestamp'); const SenderProfile = sdk.getComponent('messages.SenderProfile'); @@ -856,7 +902,8 @@ export default class EventTile extends React.Component { case 'notif': { const room = this.context.getRoom(this.props.mxEvent.getRoomId()); return ( -
+
+ {this.renderMenu()}
@@ -883,7 +930,8 @@ export default class EventTile extends React.Component { } case 'file_grid': { return ( -
+
+ {this.renderMenu()}
+
+ {this.renderMenu()} { ircTimestamp } { avatar } { sender } @@ -950,14 +999,15 @@ 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 ( -
+
+ {this.renderMenu()} { ircTimestamp }
{ readAvatars }
{ sender } { ircPadlock } -
+
{ groupTimestamp } { groupPadlock } { thread } From e548ddaa7a886bed23528a9cb3c49163a207fd45 Mon Sep 17 00:00:00 2001 From: Michael Weimann Date: Sun, 21 Feb 2021 21:55:00 +0100 Subject: [PATCH 04/74] add message context menu group keys Signed-off-by: Michael Weimann --- .../views/context_menus/MessageContextMenu.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/views/context_menus/MessageContextMenu.js b/src/components/views/context_menus/MessageContextMenu.js index 532f49573b7..fc3f234a3f1 100644 --- a/src/components/views/context_menus/MessageContextMenu.js +++ b/src/components/views/context_menus/MessageContextMenu.js @@ -483,7 +483,7 @@ export default class MessageContextMenu extends React.Component { if (viewSourceButton || viewClearSourceButton) { optionLists.push(( - + {viewSourceButton} {viewClearSourceButton} @@ -492,7 +492,7 @@ export default class MessageContextMenu extends React.Component { if (resendButton || resendEditButton || resendReactionsButton || resendRedactionButton) { optionLists.push(( - + {resendButton} {resendEditButton} {resendReactionsButton} @@ -503,7 +503,7 @@ export default class MessageContextMenu extends React.Component { if (redactButton || cancelButton) { optionLists.push(( - + {redactButton} {cancelButton} @@ -512,7 +512,7 @@ export default class MessageContextMenu extends React.Component { if (externalURLButton || permalinkButton) { optionLists.push(( - + {externalURLButton} {permalinkButton} @@ -521,7 +521,7 @@ export default class MessageContextMenu extends React.Component { if (pinButton || unhidePreviewButton || reportEventButton) { optionLists.push(( - + {pinButton} {unhidePreviewButton} {reportEventButton} @@ -531,7 +531,7 @@ export default class MessageContextMenu extends React.Component { if (forwardButton || quoteButton || collapseReplyThread) { optionLists.push(( - + {forwardButton} {collapseReplyThread} {quoteButton} From b1bc966388dd56abce243788cdb00727b0002483 Mon Sep 17 00:00:00 2001 From: Michael Weimann Date: Tue, 23 Feb 2021 18:49:11 +0100 Subject: [PATCH 05/74] add message context menu icons Signed-off-by: Michael Weimann --- res/css/_components.scss | 1 + .../context_menus/_MessageContextMenu.scss | 71 +++++++++++++++++++ res/img/element-icons/message/chevron-up.svg | 1 + .../element-icons/message/corner-up-right.svg | 1 + res/img/element-icons/message/link.svg | 1 + res/img/element-icons/message/repeat.svg | 1 + res/img/element-icons/message/share.svg | 1 + .../views/context_menus/MessageContextMenu.js | 16 +++++ 8 files changed, 93 insertions(+) create mode 100644 res/css/views/context_menus/_MessageContextMenu.scss create mode 100644 res/img/element-icons/message/chevron-up.svg create mode 100644 res/img/element-icons/message/corner-up-right.svg create mode 100644 res/img/element-icons/message/link.svg create mode 100644 res/img/element-icons/message/repeat.svg create mode 100644 res/img/element-icons/message/share.svg diff --git a/res/css/_components.scss b/res/css/_components.scss index a2ab59e3249..006bac09c97 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -53,6 +53,7 @@ @import "./views/avatars/_WidgetAvatar.scss"; @import "./views/context_menus/_CallContextMenu.scss"; @import "./views/context_menus/_IconizedContextMenu.scss"; +@import "./views/context_menus/_MessageContextMenu.scss"; @import "./views/context_menus/_StatusMessageContextMenu.scss"; @import "./views/context_menus/_TagTileContextMenu.scss"; @import "./views/dialogs/_AddressPickerDialog.scss"; diff --git a/res/css/views/context_menus/_MessageContextMenu.scss b/res/css/views/context_menus/_MessageContextMenu.scss new file mode 100644 index 00000000000..d4f5913064f --- /dev/null +++ b/res/css/views/context_menus/_MessageContextMenu.scss @@ -0,0 +1,71 @@ +.mx_MessageContextMenu { + + .mx_IconizedContextMenu_icon { + width: 16px; + height: 16px; + display: block; + + &::before { + content: ''; + width: 16px; + height: 16px; + display: block; + mask-position: center; + mask-size: contain; + mask-repeat: no-repeat; + background: $primary-fg-color; + } + } + + .mx_MessageContextMenu_iconCollapse::before { + mask-image: url('$(res)/img/element-icons/message/chevron-up.svg'); + } + + .mx_MessageContextMenu_iconReport::before { + mask-image: url('$(res)/img/feather-customised/warning-triangle.svg'); + } + + .mx_MessageContextMenu_iconLink::before { + mask-image: url('$(res)/img/element-icons/message/link.svg'); + } + + .mx_MessageContextMenu_iconPermalink::before { + mask-image: url('$(res)/img/element-icons/message/share.svg'); + } + + .mx_MessageContextMenu_iconUnhidePreview::before { + mask-image: url('$(res)/img/feather-customised/eye.svg'); + } + + .mx_MessageContextMenu_iconForward::before { + mask-image: url('$(res)/img/element-icons/message/corner-up-right.svg'); + } + + .mx_MessageContextMenu_iconCancel::before { + mask-image: url('$(res)/img/cancel.svg'); + } + + .mx_MessageContextMenu_iconRedact::before { + mask-image: url('$(res)/img/feather-customised/trash.custom.svg'); + } + + .mx_MessageContextMenu_iconResend::before { + mask-image: url('$(res)/img/element-icons/message/repeat.svg'); + } + + .mx_MessageContextMenu_iconSource::before { + mask-image: url('$(res)/img/element-icons/room/format-bar/code.svg'); + } + + .mx_MessageContextMenu_iconQuote::before { + mask-image: url('$(res)/img/format/quote.svg'); + } + + .mx_MessageContextMenu_iconPin::before { + mask-image: url('$(res)/img/element-icons/room/pin-upright.svg'); + } + + .mx_MessageContextMenu_iconUnpin::before { + mask-image: url('$(res)/img/element-icons/room/pin.svg'); + } +} diff --git a/res/img/element-icons/message/chevron-up.svg b/res/img/element-icons/message/chevron-up.svg new file mode 100644 index 00000000000..4eb5ecc33e3 --- /dev/null +++ b/res/img/element-icons/message/chevron-up.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/res/img/element-icons/message/corner-up-right.svg b/res/img/element-icons/message/corner-up-right.svg new file mode 100644 index 00000000000..0b8f961b7b5 --- /dev/null +++ b/res/img/element-icons/message/corner-up-right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/res/img/element-icons/message/link.svg b/res/img/element-icons/message/link.svg new file mode 100644 index 00000000000..c89dd41c233 --- /dev/null +++ b/res/img/element-icons/message/link.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/res/img/element-icons/message/repeat.svg b/res/img/element-icons/message/repeat.svg new file mode 100644 index 00000000000..c7657b08ed3 --- /dev/null +++ b/res/img/element-icons/message/repeat.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/res/img/element-icons/message/share.svg b/res/img/element-icons/message/share.svg new file mode 100644 index 00000000000..df38c14d630 --- /dev/null +++ b/res/img/element-icons/message/share.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/views/context_menus/MessageContextMenu.js b/src/components/views/context_menus/MessageContextMenu.js index fc3f234a3f1..e7fdf7be745 100644 --- a/src/components/views/context_menus/MessageContextMenu.js +++ b/src/components/views/context_menus/MessageContextMenu.js @@ -322,6 +322,7 @@ export default class MessageContextMenu extends React.Component { if (eventStatus === EventStatus.NOT_SENT) { resendButton = ( @@ -331,6 +332,7 @@ export default class MessageContextMenu extends React.Component { if (editStatus === EventStatus.NOT_SENT) { resendEditButton = ( @@ -340,6 +342,7 @@ export default class MessageContextMenu extends React.Component { if (unsentReactionsCount !== 0) { resendReactionsButton = ( @@ -350,6 +353,7 @@ export default class MessageContextMenu extends React.Component { if (redactStatus === EventStatus.NOT_SENT) { resendRedactionButton = ( @@ -359,6 +363,7 @@ export default class MessageContextMenu extends React.Component { if (isSent && this.state.canRedact) { redactButton = ( @@ -368,6 +373,7 @@ export default class MessageContextMenu extends React.Component { if (allowCancel) { cancelButton = ( @@ -377,6 +383,7 @@ export default class MessageContextMenu extends React.Component { if (isContentActionable(mxEvent)) { forwardButton = ( @@ -385,6 +392,7 @@ export default class MessageContextMenu extends React.Component { if (this.state.canPin) { pinButton = ( @@ -394,6 +402,7 @@ export default class MessageContextMenu extends React.Component { const viewSourceButton = ( @@ -402,6 +411,7 @@ export default class MessageContextMenu extends React.Component { if (mxEvent.getType() !== mxEvent.getWireType()) { viewClearSourceButton = ( @@ -412,6 +422,7 @@ export default class MessageContextMenu extends React.Component { if (this.props.eventTileOps.isWidgetHidden()) { unhidePreviewButton = ( @@ -426,6 +437,7 @@ export default class MessageContextMenu extends React.Component { // 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 = ( @@ -452,6 +465,7 @@ export default class MessageContextMenu extends React.Component { ) { externalURLButton = ( @@ -475,6 +490,7 @@ export default class MessageContextMenu extends React.Component { if (mxEvent.getSender() !== me) { reportEventButton = ( From 53a83676affcc555ce5ef2a8d1483279f2ea5b65 Mon Sep 17 00:00:00 2001 From: Michael Weimann Date: Tue, 23 Feb 2021 19:06:01 +0100 Subject: [PATCH 06/74] add _MessageContextMenu.scss license header Signed-off-by: Michael Weimann --- .../views/context_menus/_MessageContextMenu.scss | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/res/css/views/context_menus/_MessageContextMenu.scss b/res/css/views/context_menus/_MessageContextMenu.scss index d4f5913064f..fdce390b178 100644 --- a/res/css/views/context_menus/_MessageContextMenu.scss +++ b/res/css/views/context_menus/_MessageContextMenu.scss @@ -1,3 +1,19 @@ +/* +Copyright 2021 Michael Weimann + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + .mx_MessageContextMenu { .mx_IconizedContextMenu_icon { From b0a32851a8ee8354e9ae5fa1548893528247443a Mon Sep 17 00:00:00 2001 From: Michael Weimann Date: Thu, 24 Jun 2021 22:06:12 +0200 Subject: [PATCH 07/74] use null vars for context menu lists --- .../views/context_menus/MessageContextMenu.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/components/views/context_menus/MessageContextMenu.js b/src/components/views/context_menus/MessageContextMenu.js index b61d8b50f01..1d871b484d7 100644 --- a/src/components/views/context_menus/MessageContextMenu.js +++ b/src/components/views/context_menus/MessageContextMenu.js @@ -257,7 +257,7 @@ export default class MessageContextMenu extends React.Component { let externalURLButton; let quoteButton; let collapseReplyThread; - const optionLists = []; + let redactItemList; // status is SENT before remote-echo, null after const isSent = !eventStatus || eventStatus === EventStatus.SENT; @@ -388,7 +388,7 @@ export default class MessageContextMenu extends React.Component { ); } - optionLists.push(( + const commonItemsList = ( { quoteButton } { forwardButton } @@ -401,14 +401,14 @@ export default class MessageContextMenu extends React.Component { { resendReactionsButton } { collapseReplyThread } - )); + ); if (redactButton) { - optionLists.push(( + redactItemList = ( { redactButton } - )); + ); } return ( @@ -417,7 +417,8 @@ export default class MessageContextMenu extends React.Component { className="mx_MessageContextMenu" compact={true} > - { optionLists } + { commonItemsList } + { redactItemList } ); } From dcd938c4455061a33292655c01027758c53bc37c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 25 Jun 2021 14:19:17 +0200 Subject: [PATCH 08/74] Add allowOverridingNativeContextMenus() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/BasePlatform.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/BasePlatform.ts b/src/BasePlatform.ts index 5483ea68742..c223f23d27f 100644 --- a/src/BasePlatform.ts +++ b/src/BasePlatform.ts @@ -139,6 +139,14 @@ export default abstract class BasePlatform { return false; } + + /** + * Returns true if platform allows overriding native context menus + */ + public allowOverridingNativeContextMenus(): boolean { + return true; // TODO: Change to false before merge! True for live preview + } + /** * Returns true if the platform supports displaying * notifications, otherwise false. From 8cbf690969b6a245e1a396a57eef9798528ba137 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 25 Jun 2021 14:23:47 +0200 Subject: [PATCH 09/74] Use allowOverridingNativeContextMenus() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/EventTile.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 33d2dfb446d..fee35970737 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -49,6 +49,7 @@ import { StaticNotificationState } from "../../../stores/notifications/StaticNot import NotificationBadge from "./NotificationBadge"; import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInsertPayload"; import { Action } from '../../../dispatcher/actions'; +import PlatformPeg from '../../../PlatformPeg'; const eventTileTypes = { [EventType.RoomMessage]: 'messages.MessageEvent', @@ -867,7 +868,7 @@ export default class EventTile extends React.Component { } private onContextMenu = (ev: React.MouseEvent): void => { - console.log("LOG clicked"); + if (!PlatformPeg.get().allowOverridingNativeContextMenus()) return; ev.preventDefault(); ev.stopPropagation(); this.setState({ From 5bbe6ce2ff0af2f8d15bbac161a155c8b70d57f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 25 Jun 2021 14:23:59 +0200 Subject: [PATCH 10/74] Fix types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/structures/ContextMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/ContextMenu.tsx b/src/components/structures/ContextMenu.tsx index 9d8665c176e..f691d710ad3 100644 --- a/src/components/structures/ContextMenu.tsx +++ b/src/components/structures/ContextMenu.tsx @@ -404,7 +404,7 @@ export const toRightOf = (elementRect: Pick // Placement method for to position context menu right-aligned and flowing to the left of elementRect, // and either above or below: wherever there is more space (maybe this should be aboveOrBelowLeftOf?) -export const aboveLeftOf = (elementRect: DOMRect, chevronFace = ChevronFace.None, vPadding = 0) => { +export const aboveLeftOf = (elementRect: Partial, chevronFace = ChevronFace.None, vPadding = 0) => { const menuOptions: IPosition & { chevronFace: ChevronFace } = { chevronFace }; const buttonRight = elementRect.right + window.pageXOffset; From c1f7ff407661481260fba4bf59fd7d9ef64c1247 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 25 Jun 2021 14:51:42 +0200 Subject: [PATCH 11/74] Fix types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/EventTile.tsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index fee35970737..a3c17bfe0a5 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -314,7 +314,7 @@ export default class EventTile extends React.Component { private isListeningForReceipts: boolean; private ref: React.RefObject; private tile = React.createRef(); - private replyThread = React.createRef(); + private replyThread = React.createRef(); static defaultProps = { // no-op function because onHeightChanged is optional yet some sub-components assume its existence @@ -816,7 +816,7 @@ export default class EventTile extends React.Component { }); }; - getTile = () => this.tile.current; + getTile = (): any => this.tile.current; getReplyThread = () => this.replyThread.current; @@ -844,16 +844,17 @@ export default class EventTile extends React.Component { private renderContextMenu(): React.ReactFragment { let contextMenu = null; if (this.state.contextMenuPosition) { - const tile = this.getTile && this.getTile(); - const replyThread = this.getReplyThread && this.getReplyThread(); - const collapseReplyThread = replyThread && replyThread.canCollapse() ? replyThread.collapse : undefined; + const tile = this.getTile(); + const replyThread = this.getReplyThread(); + const eventTileOps = tile?.getEventTileOps ? tile.getEventTileOps() : undefined; + const collapseReplyThread = replyThread?.canCollapse() ? replyThread.collapse : undefined; contextMenu = ( From 16447d63f7d3280f152473756d0378accc452c9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 29 Jun 2021 14:43:15 +0200 Subject: [PATCH 12/74] Remove mistaken line MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/EventTile.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index dbc28db42de..c4734b48468 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -1118,7 +1118,6 @@ export default class EventTile extends React.Component { "aria-live": ariaLive, "aria-atomic": true, "data-scroll-tokens": scrollToken, - "onContextMenu": this.onContextMenu, }, [
From 9487a7270c6c9d57cd1c702a082d4de6f7d44141 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 29 Jun 2021 14:44:56 +0200 Subject: [PATCH 13/74] Fix styling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/EventTile.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index c4734b48468..84ae9c38013 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -882,7 +882,7 @@ export default class EventTile extends React.Component { }; private onCloseMenu = (): void => { - this.setState({contextMenuPosition: null}); + this.setState({ contextMenuPosition: null }); } render() { From 5ff18795018e545322c3208a7536555b78e82fff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 30 Jun 2021 08:08:19 +0200 Subject: [PATCH 14/74] InputHTMLAttributes -> AllHTMLAttributes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/AccessibleButton.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/elements/AccessibleButton.tsx b/src/components/views/elements/AccessibleButton.tsx index 05bcca24b22..2ed7df75d1d 100644 --- a/src/components/views/elements/AccessibleButton.tsx +++ b/src/components/views/elements/AccessibleButton.tsx @@ -27,7 +27,7 @@ export type ButtonEvent = React.MouseEvent | React.KeyboardEvent { +interface IProps extends React.AllHTMLAttributes { inputRef?: React.Ref; element?: string; // The kind of button, similar to how Bootstrap works. @@ -42,7 +42,7 @@ interface IProps extends React.InputHTMLAttributes { onClick(e?: ButtonEvent): void; } -interface IAccessibleButtonProps extends React.InputHTMLAttributes { +interface IAccessibleButtonProps extends React.AllHTMLAttributes { ref?: React.Ref; } From 98033855fe3e5710e66fa49e0c2361238b5a0eed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 30 Jun 2021 14:19:59 +0200 Subject: [PATCH 15/74] Convert to TS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- ...eContextMenu.js => MessageContextMenu.tsx} | 97 ++++++++++--------- 1 file changed, 52 insertions(+), 45 deletions(-) rename src/components/views/context_menus/{MessageContextMenu.js => MessageContextMenu.tsx} (87%) diff --git a/src/components/views/context_menus/MessageContextMenu.js b/src/components/views/context_menus/MessageContextMenu.tsx similarity index 87% rename from src/components/views/context_menus/MessageContextMenu.js rename to src/components/views/context_menus/MessageContextMenu.tsx index 5d1aad0600f..b969fecd78c 100644 --- a/src/components/views/context_menus/MessageContextMenu.js +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -16,9 +16,7 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; -import { EventStatus } from 'matrix-js-sdk/src/models/event'; - +import { EventStatus, MatrixEvent } from 'matrix-js-sdk/src/models/event'; import { MatrixClientPeg } from '../../../MatrixClientPeg'; import dis from '../../../dispatcher/dispatcher'; import * as sdk from '../../../index'; @@ -34,48 +32,61 @@ import { replaceableComponent } from "../../../utils/replaceableComponent"; import { ReadPinsEventId } from "../right_panel/PinnedMessagesCard"; import ForwardDialog from "../dialogs/ForwardDialog"; import { Action } from "../../../dispatcher/actions"; +import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks'; +import { ButtonEvent } from '../elements/AccessibleButton'; export function canCancel(eventStatus) { return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT; } -@replaceableComponent("views.context_menus.MessageContextMenu") -export default class MessageContextMenu extends React.Component { - static propTypes = { - /* the MatrixEvent associated with the context menu */ - mxEvent: PropTypes.object.isRequired, +interface IProps { + /* the MatrixEvent associated with the context menu */ + mxEvent: MatrixEvent; /* an optional EventTileOps implementation that can be used to unhide preview widgets */ - eventTileOps: PropTypes.object, + eventTileOps; /* an optional function to be called when the user clicks collapse thread, if not provided hide button */ - collapseReplyThread: PropTypes.func, + collapseReplyThread?(); /* callback called when the menu is dismissed */ - onFinished: PropTypes.func, + onFinished(); /* if the menu is inside a dialog, we sometimes need to close that dialog after click (forwarding) */ - onCloseDialog: PropTypes.func, - }; + onCloseDialog?(); - state = { - canRedact: false, - canPin: false, - }; + permalinkCreator: RoomPermalinkCreator; +} + +interface IState { + canRedact: boolean; + canPin: boolean; +} + +@replaceableComponent("views.context_menus.MessageContextMenu") +export default class MessageContextMenu extends React.Component { + constructor(props: IProps) { + super(props); + + this.state = { + canRedact: false, + canPin: false, + }; + } componentDidMount() { - MatrixClientPeg.get().on('RoomMember.powerLevel', this._checkPermissions); - this._checkPermissions(); + MatrixClientPeg.get().on('RoomMember.powerLevel', this.checkPermissions); + this.checkPermissions(); } componentWillUnmount() { const cli = MatrixClientPeg.get(); if (cli) { - cli.removeListener('RoomMember.powerLevel', this._checkPermissions); + cli.removeListener('RoomMember.powerLevel', this.checkPermissions); } } - _checkPermissions = () => { + private checkPermissions = (): void => { const cli = MatrixClientPeg.get(); const room = cli.getRoom(this.props.mxEvent.getRoomId()); @@ -90,10 +101,10 @@ export default class MessageContextMenu extends React.Component { // HACK: Intentionally say we can't pin if the user doesn't want to use the functionality if (!SettingsStore.getValue("feature_pinning")) canPin = false; - this.setState({canRedact, canPin}); + this.setState({ canRedact, canPin }); }; - _isPinned() { + private isPinned(): boolean { const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId()); const pinnedEvent = room.currentState.getStateEvents(EventType.RoomPinnedEvents, ''); if (!pinnedEvent) return false; @@ -101,14 +112,14 @@ export default class MessageContextMenu extends React.Component { return content.pinned && Array.isArray(content.pinned) && content.pinned.includes(this.props.mxEvent.getId()); } - onResendReactionsClick = () => { - for (const reaction of this._getUnsentReactions()) { + private onResendReactionsClick = (): void => { + for (const reaction of this.getUnsentReactions()) { Resend.resend(reaction); } this.closeMenu(); }; - onReportEventClick = () => { + private onReportEventClick = (): void => { const ReportEventDialog = sdk.getComponent("dialogs.ReportEventDialog"); Modal.createTrackedDialog('Report Event', '', ReportEventDialog, { mxEvent: this.props.mxEvent, @@ -116,7 +127,7 @@ export default class MessageContextMenu extends React.Component { this.closeMenu(); }; - onViewSourceClick = () => { + private onViewSourceClick = (): void => { const ViewSource = sdk.getComponent('structures.ViewSource'); Modal.createTrackedDialog('View Event Source', '', ViewSource, { mxEvent: this.props.mxEvent, @@ -124,7 +135,7 @@ export default class MessageContextMenu extends React.Component { this.closeMenu(); }; - onRedactClick = () => { + private onRedactClick = (): void => { const ConfirmRedactDialog = sdk.getComponent("dialogs.ConfirmRedactDialog"); Modal.createTrackedDialog('Confirm Redact Dialog', '', ConfirmRedactDialog, { onFinished: async (proceed, reason) => { @@ -158,7 +169,7 @@ export default class MessageContextMenu extends React.Component { this.closeMenu(); }; - onForwardClick = () => { + private onForwardClick = (): void => { Modal.createTrackedDialog('Forward Message', '', ForwardDialog, { matrixClient: MatrixClientPeg.get(), event: this.props.mxEvent, @@ -167,12 +178,12 @@ export default class MessageContextMenu extends React.Component { this.closeMenu(); }; - onPinClick = () => { + private onPinClick = (): void => { const cli = MatrixClientPeg.get(); const room = cli.getRoom(this.props.mxEvent.getRoomId()); const eventId = this.props.mxEvent.getId(); - const pinnedIds = room?.currentState?.getStateEvents(EventType.RoomPinnedEvents, "")?.pinned || []; + const pinnedIds = room?.currentState?.getStateEvents(EventType.RoomPinnedEvents, "")?.getContent().pinned || []; if (pinnedIds.includes(eventId)) { pinnedIds.splice(pinnedIds.indexOf(eventId), 1); } else { @@ -188,18 +199,18 @@ export default class MessageContextMenu extends React.Component { this.closeMenu(); }; - closeMenu = () => { + private closeMenu = (): void => { if (this.props.onFinished) this.props.onFinished(); }; - onUnhidePreviewClick = () => { + private onUnhidePreviewClick = (): void => { if (this.props.eventTileOps) { this.props.eventTileOps.unhideWidget(); } this.closeMenu(); }; - onQuoteClick = () => { + private onQuoteClick = (): void => { dis.dispatch({ action: Action.ComposerInsert, event: this.props.mxEvent, @@ -207,7 +218,7 @@ export default class MessageContextMenu extends React.Component { this.closeMenu(); }; - onPermalinkClick = (e: Event) => { + private onPermalinkClick = (e: ButtonEvent): void => { e.preventDefault(); const ShareDialog = sdk.getComponent("dialogs.ShareDialog"); Modal.createTrackedDialog('share room message dialog', '', ShareDialog, { @@ -217,12 +228,12 @@ export default class MessageContextMenu extends React.Component { this.closeMenu(); }; - onCollapseReplyThreadClick = () => { + private onCollapseReplyThreadClick = (): void => { this.props.collapseReplyThread(); this.closeMenu(); }; - _getReactions(filter) { + private getReactions(filter: (event: MatrixEvent) => boolean): Array { const cli = MatrixClientPeg.get(); const room = cli.getRoom(this.props.mxEvent.getRoomId()); const eventId = this.props.mxEvent.getId(); @@ -235,12 +246,8 @@ export default class MessageContextMenu extends React.Component { }); } - _getPendingReactions() { - return this._getReactions(e => canCancel(e.status)); - } - - _getUnsentReactions() { - return this._getReactions(e => e.status === EventStatus.NOT_SENT); + private getUnsentReactions(): Array { + return this.getReactions(e => e.status === EventStatus.NOT_SENT); } render() { @@ -248,7 +255,7 @@ export default class MessageContextMenu extends React.Component { const me = cli.getUserId(); const mxEvent = this.props.mxEvent; const eventStatus = mxEvent.status; - const unsentReactionsCount = this._getUnsentReactions().length; + const unsentReactionsCount = this.getUnsentReactions().length; let resendReactionsButton; let redactButton; let forwardButton; @@ -296,7 +303,7 @@ export default class MessageContextMenu extends React.Component { pinButton = ( ); From 41214d064ac6563192a1103a58fd0ee78a249c4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 30 Jun 2021 14:45:49 +0200 Subject: [PATCH 16/74] Add some types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../context_menus/MessageContextMenu.tsx | 27 ++++++++----------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index ffafa313659..058351ab3f0 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -40,22 +40,17 @@ export function canCancel(eventStatus) { } interface IProps { - /* the MatrixEvent associated with the context menu */ - mxEvent: MatrixEvent; - - /* an optional EventTileOps implementation that can be used to unhide preview widgets */ - eventTileOps; - - /* an optional function to be called when the user clicks collapse thread, if not provided hide button */ - collapseReplyThread?(); - - /* callback called when the menu is dismissed */ - onFinished(); - - /* if the menu is inside a dialog, we sometimes need to close that dialog after click (forwarding) */ - onCloseDialog?(); - - permalinkCreator: RoomPermalinkCreator; + // The MatrixEvent associated with the context menu */ + mxEvent: MatrixEvent; + // An optional EventTileOps implementation that can be used to unhide preview widgets */ + eventTileOps?; // TODO: Add type when TextualBody is TSified + // An optional function to be called when the user clicks collapse thread, if not provided hide button + collapseReplyThread?(): void; + // Callback called when the menu is dismissed + onFinished(): void; + // If the menu is inside a dialog, we sometimes need to close that dialog after click (forwarding) + onCloseDialog?(): void; + permalinkCreator: RoomPermalinkCreator; } interface IState { From a30921911f7047be6d112148f237d7589800eb6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 30 Jun 2021 15:13:56 +0200 Subject: [PATCH 17/74] Make onClick optional MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/AccessibleButton.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/views/elements/AccessibleButton.tsx b/src/components/views/elements/AccessibleButton.tsx index e4914436156..70b4493ef3a 100644 --- a/src/components/views/elements/AccessibleButton.tsx +++ b/src/components/views/elements/AccessibleButton.tsx @@ -39,7 +39,8 @@ interface IProps extends React.AllHTMLAttributes { tabIndex?: number; disabled?: boolean; className?: string; - onClick(e?: ButtonEvent): void; + // Optional in case we need to use onMouseDown + onClick?(e?: ButtonEvent): void; } interface IAccessibleButtonProps extends React.AllHTMLAttributes { From 2d7457512f82739bb49f72484c88ee34cdb085df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 30 Jun 2021 15:15:01 +0200 Subject: [PATCH 18/74] Add rightClick prop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/context_menus/MessageContextMenu.tsx | 1 + src/components/views/rooms/EventTile.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index 058351ab3f0..ecdd5ef71a1 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -51,6 +51,7 @@ interface IProps { // If the menu is inside a dialog, we sometimes need to close that dialog after click (forwarding) onCloseDialog?(): void; permalinkCreator: RoomPermalinkCreator; + rightClick?: boolean; } interface IState { diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 1064aa0978d..5caecbfd345 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -863,6 +863,7 @@ export default class EventTile extends React.Component { eventTileOps={eventTileOps} collapseReplyThread={collapseReplyThread} onFinished={this.onCloseMenu} + rightClick={true} /> ); } From d64b4f9b3b35279dff1f79410cc8e4947fa055d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 30 Jun 2021 15:16:35 +0200 Subject: [PATCH 19/74] Add copy button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../context_menus/_MessageContextMenu.scss | 4 +++ .../context_menus/MessageContextMenu.tsx | 28 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/res/css/views/context_menus/_MessageContextMenu.scss b/res/css/views/context_menus/_MessageContextMenu.scss index 338841cce43..c6a925f6957 100644 --- a/res/css/views/context_menus/_MessageContextMenu.scss +++ b/res/css/views/context_menus/_MessageContextMenu.scss @@ -81,4 +81,8 @@ limitations under the License. .mx_MessageContextMenu_iconUnpin::before { mask-image: url('$(res)/img/element-icons/room/pin.svg'); } + + .mx_MessageContextMenu_iconCopy::before { + mask-image: url($copy-button-url); + } } diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index ecdd5ef71a1..8b23f9016d2 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -34,6 +34,7 @@ import ForwardDialog from "../dialogs/ForwardDialog"; import { Action } from "../../../dispatcher/actions"; import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks'; import { ButtonEvent } from '../elements/AccessibleButton'; +import { copyPlaintext } from '../../../utils/strings'; export function canCancel(eventStatus) { return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT; @@ -229,6 +230,11 @@ export default class MessageContextMenu extends React.Component this.closeMenu(); }; + private onCopyClick = (): void => { + copyPlaintext(this.getSelectedText()); + this.closeMenu(); + }; + private getReactions(filter: (event: MatrixEvent) => boolean): Array { const cli = MatrixClientPeg.get(); const room = cli.getRoom(this.props.mxEvent.getRoomId()); @@ -246,6 +252,10 @@ export default class MessageContextMenu extends React.Component return this.getReactions(e => e.status === EventStatus.NOT_SENT); } + private getSelectedText(): string { + return window.getSelection().toString(); + } + render() { const cli = MatrixClientPeg.get(); const me = cli.getUserId(); @@ -391,6 +401,23 @@ export default class MessageContextMenu extends React.Component ); } + let copyButton; + if (this.props.rightClick && this.getSelectedText()) { + copyButton = ( + + ); + } + + const nativeItemsList = ( + + { copyButton } + + ) + const commonItemsList = ( { quoteButton } @@ -420,6 +447,7 @@ export default class MessageContextMenu extends React.Component className="mx_MessageContextMenu" compact={true} > + { nativeItemsList } { commonItemsList } { redactItemList } From ef61c53528e3314c186421fb51310633e6d4fe0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 30 Jun 2021 15:26:02 +0200 Subject: [PATCH 20/74] What about upgrading deps after the eslint migration, Simon? MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/BasePlatform.ts | 1 - src/components/views/context_menus/MessageContextMenu.tsx | 2 +- src/components/views/rooms/EventTile.tsx | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/BasePlatform.ts b/src/BasePlatform.ts index 0f3346312f9..da0f53650ea 100644 --- a/src/BasePlatform.ts +++ b/src/BasePlatform.ts @@ -139,7 +139,6 @@ export default abstract class BasePlatform { return false; } - /** * Returns true if platform allows overriding native context menus */ diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index 8b23f9016d2..632356e4014 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -416,7 +416,7 @@ export default class MessageContextMenu extends React.Component { copyButton } - ) + ); const commonItemsList = ( diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 5caecbfd345..251ee18d6ea 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -890,7 +890,7 @@ export default class EventTile extends React.Component { private onCloseMenu = (): void => { this.setState({ contextMenuPosition: null }); - } + }; render() { const MessageTimestamp = sdk.getComponent('messages.MessageTimestamp'); From f2bb287a38056d954ab66ff6ed3e838ea3afa9b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 30 Jun 2021 15:41:59 +0200 Subject: [PATCH 21/74] Add edit button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../context_menus/_MessageContextMenu.scss | 4 +++ .../context_menus/MessageContextMenu.tsx | 26 ++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/res/css/views/context_menus/_MessageContextMenu.scss b/res/css/views/context_menus/_MessageContextMenu.scss index c6a925f6957..ce2827a1acc 100644 --- a/res/css/views/context_menus/_MessageContextMenu.scss +++ b/res/css/views/context_menus/_MessageContextMenu.scss @@ -85,4 +85,8 @@ limitations under the License. .mx_MessageContextMenu_iconCopy::before { mask-image: url($copy-button-url); } + + .mx_MessageContextMenu_iconEdit::before { + mask-image: url('$(res)/img/element-icons/room/message-bar/edit.svg'); + } } diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index 632356e4014..bba47a4d4d4 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -25,7 +25,7 @@ import Modal from '../../../Modal'; import Resend from '../../../Resend'; import SettingsStore from '../../../settings/SettingsStore'; import { isUrlPermitted } from '../../../HtmlUtils'; -import { isContentActionable } from '../../../utils/EventUtils'; +import { canEditContent, isContentActionable } from '../../../utils/EventUtils'; import IconizedContextMenu, { IconizedContextMenuOption, IconizedContextMenuOptionList } from './IconizedContextMenu'; import { EventType } from "matrix-js-sdk/src/@types/event"; import { replaceableComponent } from "../../../utils/replaceableComponent"; @@ -235,6 +235,13 @@ export default class MessageContextMenu extends React.Component this.closeMenu(); }; + private onEditClick = (): void => { + dis.dispatch({ + action: 'edit_event', + event: this.props.mxEvent, + }); + this.closeMenu(); + }; private getReactions(filter: (event: MatrixEvent) => boolean): Array { const cli = MatrixClientPeg.get(); const room = cli.getRoom(this.props.mxEvent.getRoomId()); @@ -412,12 +419,28 @@ export default class MessageContextMenu extends React.Component ); } + let editButton; + if (canEditContent(this.props.mxEvent)) { + editButton = ( + + ); + } const nativeItemsList = ( { copyButton } ); + const quickItemsList = ( + + { editButton } + + ); + const commonItemsList = ( { quoteButton } @@ -448,6 +471,7 @@ export default class MessageContextMenu extends React.Component compact={true} > { nativeItemsList } + { quickItemsList } { commonItemsList } { redactItemList } From 76942c60ca79501fa57e4bee8ecb89d39c60cbc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 30 Jun 2021 15:44:52 +0200 Subject: [PATCH 22/74] fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/context_menus/MessageContextMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index bba47a4d4d4..68cabe27ce3 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -420,7 +420,7 @@ export default class MessageContextMenu extends React.Component } let editButton; - if (canEditContent(this.props.mxEvent)) { + if (this.props.rightClick && canEditContent(this.props.mxEvent)) { editButton = ( Date: Wed, 30 Jun 2021 15:52:18 +0200 Subject: [PATCH 23/74] Add reply button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../context_menus/_MessageContextMenu.scss | 4 +++ .../context_menus/MessageContextMenu.tsx | 26 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/res/css/views/context_menus/_MessageContextMenu.scss b/res/css/views/context_menus/_MessageContextMenu.scss index ce2827a1acc..0b8caa522c7 100644 --- a/res/css/views/context_menus/_MessageContextMenu.scss +++ b/res/css/views/context_menus/_MessageContextMenu.scss @@ -89,4 +89,8 @@ limitations under the License. .mx_MessageContextMenu_iconEdit::before { mask-image: url('$(res)/img/element-icons/room/message-bar/edit.svg'); } + + .mx_MessageContextMenu_iconReply::before { + mask-image: url('$(res)/img/element-icons/room/message-bar/reply.svg'); + } } diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index 68cabe27ce3..0bb17e3a534 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -35,6 +35,7 @@ import { Action } from "../../../dispatcher/actions"; import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks'; import { ButtonEvent } from '../elements/AccessibleButton'; import { copyPlaintext } from '../../../utils/strings'; +import RoomContext from '../../../contexts/RoomContext'; export function canCancel(eventStatus) { return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT; @@ -62,6 +63,8 @@ interface IState { @replaceableComponent("views.context_menus.MessageContextMenu") export default class MessageContextMenu extends React.Component { + static contextType = RoomContext; + constructor(props: IProps) { super(props); @@ -242,6 +245,15 @@ export default class MessageContextMenu extends React.Component }); this.closeMenu(); }; + + private onReplyClick = (): void => { + dis.dispatch({ + action: 'reply_to_event', + event: this.props.mxEvent, + }); + this.closeMenu(); + }; + private getReactions(filter: (event: MatrixEvent) => boolean): Array { const cli = MatrixClientPeg.get(); const room = cli.getRoom(this.props.mxEvent.getRoomId()); @@ -264,6 +276,7 @@ export default class MessageContextMenu extends React.Component } render() { + // TODO: Cleanup me up const cli = MatrixClientPeg.get(); const me = cli.getUserId(); const mxEvent = this.props.mxEvent; @@ -429,6 +442,18 @@ export default class MessageContextMenu extends React.Component /> ); } + + let replyButton; + if (this.props.rightClick && isContentActionable(this.props.mxEvent) && this.context.canReply) { + replyButton = ( + + ); + } + const nativeItemsList = ( { copyButton } @@ -438,6 +463,7 @@ export default class MessageContextMenu extends React.Component const quickItemsList = ( { editButton } + { replyButton } ); From 97e65affbc192d9b87653b27b6ba9af147170991 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 30 Jun 2021 17:39:52 +0200 Subject: [PATCH 24/74] Add react button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../context_menus/_MessageContextMenu.scss | 4 + .../context_menus/MessageContextMenu.tsx | 73 ++++++++++++++++--- src/components/views/rooms/EventTile.tsx | 1 + 3 files changed, 67 insertions(+), 11 deletions(-) diff --git a/res/css/views/context_menus/_MessageContextMenu.scss b/res/css/views/context_menus/_MessageContextMenu.scss index 0b8caa522c7..d9d15fb91c4 100644 --- a/res/css/views/context_menus/_MessageContextMenu.scss +++ b/res/css/views/context_menus/_MessageContextMenu.scss @@ -93,4 +93,8 @@ limitations under the License. .mx_MessageContextMenu_iconReply::before { mask-image: url('$(res)/img/element-icons/room/message-bar/reply.svg'); } + + .mx_MessageContextMenu_iconReact::before { + mask-image: url('$(res)/img/element-icons/room/message-bar/emoji.svg'); + } } diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index 0bb17e3a534..f67a45e8a9e 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React from 'react'; +import React, { createRef } from 'react'; import { EventStatus, MatrixEvent } from 'matrix-js-sdk/src/models/event'; import { MatrixClientPeg } from '../../../MatrixClientPeg'; import dis from '../../../dispatcher/dispatcher'; @@ -36,6 +36,9 @@ import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks'; import { ButtonEvent } from '../elements/AccessibleButton'; import { copyPlaintext } from '../../../utils/strings'; import RoomContext from '../../../contexts/RoomContext'; +import { toRightOf, ContextMenu } from '../../structures/ContextMenu'; +import ReactionPicker from '../emojipicker/ReactionPicker'; +import { Relations } from 'matrix-js-sdk/src/models/relations'; export function canCancel(eventStatus) { return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT; @@ -54,16 +57,20 @@ interface IProps { onCloseDialog?(): void; permalinkCreator: RoomPermalinkCreator; rightClick?: boolean; + // The Relations model from the JS SDK for reactions to `mxEvent` + reactions?: Relations; } interface IState { canRedact: boolean; canPin: boolean; + reactionPickerDisplayed: boolean; } @replaceableComponent("views.context_menus.MessageContextMenu") export default class MessageContextMenu extends React.Component { static contextType = RoomContext; + private reactButtonRef = createRef(); // XXX Ref to a functional component constructor(props: IProps) { super(props); @@ -71,6 +78,7 @@ export default class MessageContextMenu extends React.Component this.state = { canRedact: false, canPin: false, + reactionPickerDisplayed: false, }; } @@ -254,6 +262,15 @@ export default class MessageContextMenu extends React.Component this.closeMenu(); }; + private onReactClick = (): void => { + this.setState({ reactionPickerDisplayed: true }); + }; + + private onCloseReactionPicker = (): void => { + this.setState({ reactionPickerDisplayed: false }); + this.closeMenu(); + }; + private getReactions(filter: (event: MatrixEvent) => boolean): Array { const cli = MatrixClientPeg.get(); const room = cli.getRoom(this.props.mxEvent.getRoomId()); @@ -454,6 +471,18 @@ export default class MessageContextMenu extends React.Component ); } + let reactButton; + if (this.props.rightClick && isContentActionable(this.props.mxEvent) && this.context.canReact) { + reactButton = ( + + ); + } + const nativeItemsList = ( { copyButton } @@ -464,6 +493,7 @@ export default class MessageContextMenu extends React.Component { editButton } { replyButton } + { reactButton } ); @@ -490,17 +520,38 @@ export default class MessageContextMenu extends React.Component ); } + let reactionPicker; + if (this.state.reactionPickerDisplayed) { + const buttonRect = (this.reactButtonRef.current as HTMLElement)?.getBoundingClientRect(); + reactionPicker = ( + + + + ); + } + return ( - - { nativeItemsList } - { quickItemsList } - { commonItemsList } - { redactItemList } - + + + { nativeItemsList } + { quickItemsList } + { commonItemsList } + { redactItemList } + + { reactionPicker } + ); } } diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 251ee18d6ea..e78d0cec0e6 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -864,6 +864,7 @@ export default class EventTile extends React.Component { collapseReplyThread={collapseReplyThread} onFinished={this.onCloseMenu} rightClick={true} + reactions={this.state.reactions} /> ); } From ee30d2c47727be2a61d2f8387e89186fdb5ccb56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 30 Jun 2021 17:49:56 +0200 Subject: [PATCH 25/74] Cleanup render() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../context_menus/MessageContextMenu.tsx | 61 +++++++++++-------- 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index f67a45e8a9e..90677d070d5 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -299,6 +299,9 @@ export default class MessageContextMenu extends React.Component const mxEvent = this.props.mxEvent; const eventStatus = mxEvent.status; const unsentReactionsCount = this.getUnsentReactions().length; + const contentActionable = isContentActionable(this.props.mxEvent); + const rightClick = this.props.rightClick; + const context = this.context; let resendReactionsButton; let redactButton; let forwardButton; @@ -308,6 +311,15 @@ export default class MessageContextMenu extends React.Component let quoteButton; let collapseReplyThread; let redactItemList; + let reportEventButton; + let permalink; + let copyButton; + let editButton; + let replyButton; + let reactButton; + let reactionPicker; + let quickItemsList; + let nativeItemsList; // status is SENT before remote-echo, null after const isSent = !eventStatus || eventStatus === EventStatus.SENT; @@ -333,7 +345,7 @@ export default class MessageContextMenu extends React.Component ); } - if (isContentActionable(mxEvent)) { + if (contentActionable) { forwardButton = ( } } - let permalink; if (this.props.permalinkCreator) { - permalink = this.props.permalinkCreator.forEvent(this.props.mxEvent.getId()); + permalink = this.props.permalinkCreator.forEvent(mxEvent.getId()); } // 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 = ( @@ -427,7 +438,6 @@ export default class MessageContextMenu extends React.Component ); } - let reportEventButton; if (mxEvent.getSender() !== me) { reportEventButton = ( ); } - let copyButton; - if (this.props.rightClick && this.getSelectedText()) { + if (rightClick && this.getSelectedText()) { copyButton = ( ); } - let editButton; - if (this.props.rightClick && canEditContent(this.props.mxEvent)) { + if (rightClick && canEditContent(mxEvent)) { editButton = ( ); } - let replyButton; - if (this.props.rightClick && isContentActionable(this.props.mxEvent) && this.context.canReply) { + if (rightClick && contentActionable && context.canReply) { replyButton = ( ); } - let reactButton; - if (this.props.rightClick && isContentActionable(this.props.mxEvent) && this.context.canReact) { + if (rightClick && contentActionable && context.canReact) { reactButton = ( ); } - const nativeItemsList = ( - - { copyButton } - - ); + if (copyButton) { + nativeItemsList = ( + + { copyButton } + + ); + } - const quickItemsList = ( - - { editButton } - { replyButton } - { reactButton } - - ); + if (editButton || replyButton || reactButton) { + quickItemsList = ( + + { editButton } + { replyButton } + { reactButton } + + ); + } const commonItemsList = ( @@ -520,7 +530,6 @@ export default class MessageContextMenu extends React.Component ); } - let reactionPicker; if (this.state.reactionPickerDisplayed) { const buttonRect = (this.reactButtonRef.current as HTMLElement)?.getBoundingClientRect(); reactionPicker = ( @@ -530,7 +539,7 @@ export default class MessageContextMenu extends React.Component managed={false} > From 9161c61dba7036d0f3b46ae1c9e43af82ba149a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 30 Jun 2021 18:04:56 +0200 Subject: [PATCH 26/74] Fix comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/context_menus/MessageContextMenu.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index 90677d070d5..08a80e4765d 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -45,9 +45,9 @@ export function canCancel(eventStatus) { } interface IProps { - // The MatrixEvent associated with the context menu */ + // The MatrixEvent associated with the context menu mxEvent: MatrixEvent; - // An optional EventTileOps implementation that can be used to unhide preview widgets */ + // An optional EventTileOps implementation that can be used to unhide preview widgets eventTileOps?; // TODO: Add type when TextualBody is TSified // An optional function to be called when the user clicks collapse thread, if not provided hide button collapseReplyThread?(): void; @@ -55,7 +55,9 @@ interface IProps { onFinished(): void; // If the menu is inside a dialog, we sometimes need to close that dialog after click (forwarding) onCloseDialog?(): void; + // The RoomPermalinkCreator permalinkCreator: RoomPermalinkCreator; + // True if the menu is being used as a right click menu rightClick?: boolean; // The Relations model from the JS SDK for reactions to `mxEvent` reactions?: Relations; From 9390e15e8b7f67cf989e2f2f9deccc8375088ee2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 1 Jul 2021 08:29:29 +0200 Subject: [PATCH 27/74] Add save button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../context_menus/_MessageContextMenu.scss | 4 ++ .../context_menus/MessageContextMenu.tsx | 52 ++++++++++++++++++- .../models/IMediaEventContent.ts | 1 + 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/res/css/views/context_menus/_MessageContextMenu.scss b/res/css/views/context_menus/_MessageContextMenu.scss index d9d15fb91c4..bbf5d70e57f 100644 --- a/res/css/views/context_menus/_MessageContextMenu.scss +++ b/res/css/views/context_menus/_MessageContextMenu.scss @@ -97,4 +97,8 @@ limitations under the License. .mx_MessageContextMenu_iconReact::before { mask-image: url('$(res)/img/element-icons/room/message-bar/emoji.svg'); } + + .mx_MessageContextMenu_iconSave::before { + mask-image: url('$(res)/img/image-view/download.svg'); + } } diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index 08a80e4765d..362db83eefc 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -27,7 +27,7 @@ import SettingsStore from '../../../settings/SettingsStore'; import { isUrlPermitted } from '../../../HtmlUtils'; import { canEditContent, isContentActionable } from '../../../utils/EventUtils'; import IconizedContextMenu, { IconizedContextMenuOption, IconizedContextMenuOptionList } from './IconizedContextMenu'; -import { EventType } from "matrix-js-sdk/src/@types/event"; +import { EventType, MsgType } from "matrix-js-sdk/src/@types/event"; import { replaceableComponent } from "../../../utils/replaceableComponent"; import { ReadPinsEventId } from "../right_panel/PinnedMessagesCard"; import ForwardDialog from "../dialogs/ForwardDialog"; @@ -39,6 +39,16 @@ import RoomContext from '../../../contexts/RoomContext'; import { toRightOf, ContextMenu } from '../../structures/ContextMenu'; import ReactionPicker from '../emojipicker/ReactionPicker'; import { Relations } from 'matrix-js-sdk/src/models/relations'; +import { IMediaEventContent } from '../../../customisations/models/IMediaEventContent'; +import { mediaFromContent } from '../../../customisations/Media'; +import { decryptFile } from '../../../utils/DecryptFile'; + +const DOWNLOADABLE_MESSAGE_TYPES = [ + MsgType.Audio, + MsgType.File, + MsgType.Image, + MsgType.Video, +]; export function canCancel(eventStatus) { return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT; @@ -73,6 +83,7 @@ interface IState { export default class MessageContextMenu extends React.Component { static contextType = RoomContext; private reactButtonRef = createRef(); // XXX Ref to a functional component + private decryptedUrl?: string; constructor(props: IProps) { super(props); @@ -248,6 +259,29 @@ export default class MessageContextMenu extends React.Component this.closeMenu(); }; + private onSaveClick = async (): Promise => { + const content = this.props.mxEvent.getContent() as IMediaEventContent; + const media = mediaFromContent(this.props.mxEvent.getContent()); + + if (content.file !== undefined && !this.decryptedUrl) { + try { + const blob = await decryptFile(content.file); + this.decryptedUrl = URL.createObjectURL(blob); + } catch (error) { + console.warn("Unable to decrypt attachment: ", error); + return; + } + } + + const anchor = document.createElement("a"); + anchor.href = this.decryptedUrl || media.srcHttp; + anchor.download = content.body?.length > 0 ? content.body : _t('Attachment'); + anchor.target = "_blank"; + anchor.rel = "noreferrer noopener"; + anchor.click(); + this.closeMenu(); + }; + private onEditClick = (): void => { dis.dispatch({ action: 'edit_event', @@ -299,11 +333,13 @@ export default class MessageContextMenu extends React.Component const cli = MatrixClientPeg.get(); const me = cli.getUserId(); const mxEvent = this.props.mxEvent; + const messageType = mxEvent.getContent().msgtype; const eventStatus = mxEvent.status; const unsentReactionsCount = this.getUnsentReactions().length; const contentActionable = isContentActionable(this.props.mxEvent); const rightClick = this.props.rightClick; const context = this.context; + let resendReactionsButton; let redactButton; let forwardButton; @@ -316,6 +352,7 @@ export default class MessageContextMenu extends React.Component let reportEventButton; let permalink; let copyButton; + let saveButton; let editButton; let replyButton; let reactButton; @@ -460,6 +497,16 @@ export default class MessageContextMenu extends React.Component ); } + if (rightClick && DOWNLOADABLE_MESSAGE_TYPES.includes(messageType as MsgType)) { + saveButton = ( + + ); + } + if (rightClick && canEditContent(mxEvent)) { editButton = ( ); } - if (copyButton) { + if (copyButton || saveButton) { nativeItemsList = ( { copyButton } + { saveButton } ); } diff --git a/src/customisations/models/IMediaEventContent.ts b/src/customisations/models/IMediaEventContent.ts index fb05d76a4d5..a540638d9ee 100644 --- a/src/customisations/models/IMediaEventContent.ts +++ b/src/customisations/models/IMediaEventContent.ts @@ -38,6 +38,7 @@ export interface IMediaEventContent { thumbnail_url?: string; // eslint-disable-line camelcase thumbnail_file?: IEncryptedFile; // eslint-disable-line camelcase }; + body?: string; } export interface IPreparedMedia extends IMediaObject { From 09f06923938b9c0e4a0b73616ab9795d447e4bbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 1 Jul 2021 09:30:30 +0200 Subject: [PATCH 28/74] Don't show context menu if editing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/EventTile.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index e78d0cec0e6..b6e6a3d0345 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -878,6 +878,7 @@ export default class EventTile extends React.Component { private onContextMenu = (ev: React.MouseEvent): void => { if (!PlatformPeg.get().allowOverridingNativeContextMenus()) return; + if (this.props.editState) return; ev.preventDefault(); ev.stopPropagation(); this.setState({ From 757957e4f7b9b135de50e85320d4fb6dfe3bd25c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 1 Jul 2021 10:07:16 +0200 Subject: [PATCH 29/74] Add special handling for click a timestamp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../context_menus/MessageContextMenu.tsx | 58 ++++++++++++++----- src/components/views/rooms/EventTile.tsx | 51 +++++++++++----- src/i18n/strings/en_EN.json | 1 + 3 files changed, 81 insertions(+), 29 deletions(-) diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index 362db83eefc..054a9a9f425 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -71,6 +71,8 @@ interface IProps { rightClick?: boolean; // The Relations model from the JS SDK for reactions to `mxEvent` reactions?: Relations; + // A permalink to the event + showPermalink?: boolean; } interface IState { @@ -249,6 +251,12 @@ export default class MessageContextMenu extends React.Component this.closeMenu(); }; + private onCopyPermalinkClick = (e: ButtonEvent): void => { + e.preventDefault(); // So that we don't open the permalink + copyPlaintext(this.getPermalink()); + this.closeMenu(); + }; + private onCollapseReplyThreadClick = (): void => { this.props.collapseReplyThread(); this.closeMenu(); @@ -328,6 +336,13 @@ export default class MessageContextMenu extends React.Component return window.getSelection().toString(); } + + private getPermalink(): string { + if (!this.props.permalinkCreator) return null; + // 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) + return this.props.permalinkCreator.forEvent(this.props.mxEvent.getId()); + } + render() { // TODO: Cleanup me up const cli = MatrixClientPeg.get(); @@ -339,6 +354,7 @@ export default class MessageContextMenu extends React.Component const contentActionable = isContentActionable(this.props.mxEvent); const rightClick = this.props.rightClick; const context = this.context; + const permalink = this.getPermalink(); let resendReactionsButton; let redactButton; @@ -350,7 +366,6 @@ export default class MessageContextMenu extends React.Component let collapseReplyThread; let redactItemList; let reportEventButton; - let permalink; let copyButton; let saveButton; let editButton; @@ -359,6 +374,7 @@ export default class MessageContextMenu extends React.Component let reactionPicker; let quickItemsList; let nativeItemsList; + let permalinkButton; // status is SENT before remote-echo, null after const isSent = !eventStatus || eventStatus === EventStatus.SENT; @@ -424,21 +440,33 @@ export default class MessageContextMenu extends React.Component } } - if (this.props.permalinkCreator) { - permalink = this.props.permalinkCreator.forEvent(mxEvent.getId()); + if (rightClick && permalink) { + if (this.props.showPermalink) { + permalinkButton = ( + + ); + } else { + permalinkButton = ( + + ); + } } - // 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 = ( - - ); if (this.props.eventTileOps) { // this event is rendered using TextualBody quoteButton = ( diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index b6e6a3d0345..78a8b8aacc2 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -312,7 +312,10 @@ interface IState { hover: boolean; // Position of the context menu - contextMenuPosition: Partial; + contextMenu: { + position: Partial, + showPermalink?: boolean, + }; } @replaceableComponent("views.rooms.EventTile") @@ -347,7 +350,7 @@ export default class EventTile extends React.Component { // The Relations model from the JS SDK for reactions to `mxEvent` reactions: this.getReactions(), // Context menu position - contextMenuPosition: null, + contextMenu: null, hover: false, }; @@ -849,7 +852,7 @@ export default class EventTile extends React.Component { private renderContextMenu(): React.ReactFragment { let contextMenu = null; - if (this.state.contextMenuPosition) { + if (this.state.contextMenu) { const tile = this.getTile(); const replyThread = this.getReplyThread(); const eventTileOps = tile?.getEventTileOps ? tile.getEventTileOps() : undefined; @@ -857,7 +860,7 @@ export default class EventTile extends React.Component { contextMenu = ( { onFinished={this.onCloseMenu} rightClick={true} reactions={this.state.reactions} + showPermalink={this.state.contextMenu.showPermalink} /> ); } @@ -877,21 +881,32 @@ export default class EventTile extends React.Component { } private onContextMenu = (ev: React.MouseEvent): void => { - if (!PlatformPeg.get().allowOverridingNativeContextMenus()) return; - if (this.props.editState) return; + this.showContextMenu(ev); + }; + + private onTimestampContextMenu = (ev: React.MouseEvent): void => { + this.showContextMenu(ev, true); + }; + + private showContextMenu(ev: React.MouseEvent, showPermalink?: boolean): void { ev.preventDefault(); ev.stopPropagation(); + if (!PlatformPeg.get().allowOverridingNativeContextMenus()) return; + if (this.props.editState) return; this.setState({ - contextMenuPosition: { - right: ev.clientX, - top: ev.clientY, - bottom: ev.clientY, + contextMenu: { + position: { + right: ev.clientX, + top: ev.clientY, + bottom: ev.clientY, + }, + showPermalink: showPermalink, }, }); - }; + } private onCloseMenu = (): void => { - this.setState({ contextMenuPosition: null }); + this.setState({ contextMenu: null }); }; render() { @@ -1103,6 +1118,7 @@ export default class EventTile extends React.Component { href={permalink} onClick={this.onPermalinkClicked} aria-label={formatTime(new Date(this.props.mxEvent.getTs()), this.props.isTwelveHour)} + onContextMenu={this.onTimestampContextMenu} > { timestamp } ; @@ -1136,7 +1152,11 @@ export default class EventTile extends React.Component {
,
{ avatar } - + { sender } { timestamp } @@ -1177,7 +1197,10 @@ export default class EventTile extends React.Component { href={permalink} onClick={this.onPermalinkClicked} > -
+
{ sender } { timestamp }
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index f0599c7e49d..110f1827504 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2530,6 +2530,7 @@ "Forward": "Forward", "View source": "View source", "Show preview": "Show preview", + "Copy link": "Copy link", "Source URL": "Source URL", "Collapse reply thread": "Collapse reply thread", "Report": "Report", From cb00f02f277d03ffa51f99c563acda709cc4486f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 1 Jul 2021 15:34:12 +0200 Subject: [PATCH 30/74] Fix double empty line MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/context_menus/MessageContextMenu.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index 054a9a9f425..7ce0be84472 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -336,7 +336,6 @@ export default class MessageContextMenu extends React.Component return window.getSelection().toString(); } - private getPermalink(): string { if (!this.props.permalinkCreator) return null; // 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) From 8c15721eb7f6974faa97da82c1c9c146141d9548 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 1 Jul 2021 15:50:11 +0200 Subject: [PATCH 31/74] Don't show context menu for images MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/EventTile.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 78a8b8aacc2..a6a1e38d8cf 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -889,6 +889,10 @@ export default class EventTile extends React.Component { }; private showContextMenu(ev: React.MouseEvent, showPermalink?: boolean): void { + // There is no way to copy non-PNG images into clipboard, so we can't + // have our own handling for copying images, so we leave it to the + // Electron layer (webcontents-handler.ts) + if (ev.target instanceof HTMLImageElement) return; ev.preventDefault(); ev.stopPropagation(); if (!PlatformPeg.get().allowOverridingNativeContextMenus()) return; From 6754bdacb1970f7c8ee1b8258ebbfebee9342dcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 1 Jul 2021 15:50:24 +0200 Subject: [PATCH 32/74] Cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/context_menus/MessageContextMenu.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index 7ce0be84472..a3ae0e9e059 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -343,7 +343,6 @@ export default class MessageContextMenu extends React.Component } render() { - // TODO: Cleanup me up const cli = MatrixClientPeg.get(); const me = cli.getUserId(); const mxEvent = this.props.mxEvent; From 57a17dca1fe4760da888f84e67df2de3ec947804 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 1 Jul 2021 15:53:37 +0200 Subject: [PATCH 33/74] Fix order MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/EventTile.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index a6a1e38d8cf..6bcb0873d9f 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -893,10 +893,10 @@ export default class EventTile extends React.Component { // have our own handling for copying images, so we leave it to the // Electron layer (webcontents-handler.ts) if (ev.target instanceof HTMLImageElement) return; - ev.preventDefault(); - ev.stopPropagation(); if (!PlatformPeg.get().allowOverridingNativeContextMenus()) return; if (this.props.editState) return; + ev.preventDefault(); + ev.stopPropagation(); this.setState({ contextMenu: { position: { From 51e21023797c551b34e39f8c2e8099a639d4b375 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 2 Jul 2021 08:17:23 +0200 Subject: [PATCH 34/74] Keep action bar shown when right-clicking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/EventTile.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 6bcb0873d9f..7959acee837 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -906,11 +906,15 @@ export default class EventTile extends React.Component { }, showPermalink: showPermalink, }, + actionBarFocused: true, }); } private onCloseMenu = (): void => { - this.setState({ contextMenu: null }); + this.setState({ + contextMenu: null, + actionBarFocused: false, + }); }; render() { From 42a74281d862776ac16599a28de795b4a87871ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 2 Jul 2021 08:21:24 +0200 Subject: [PATCH 35/74] Highlight event tile when right-clicking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/EventTile.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 7959acee837..9894ae09269 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -978,7 +978,7 @@ export default class EventTile extends React.Component { // Note: we keep the `sending` state class for tests, not for our styles mx_EventTile_sending: !isEditing && isSending, mx_EventTile_highlight: this.props.tileShape === 'notif' ? false : this.shouldHighlight(), - mx_EventTile_selected: this.props.isSelectedEvent, + mx_EventTile_selected: this.props.isSelectedEvent || this.state.contextMenu, mx_EventTile_continuation: this.props.tileShape ? '' : this.props.continuation, mx_EventTile_last: this.props.last, mx_EventTile_lastInSection: this.props.lastInSection, @@ -1074,8 +1074,16 @@ export default class EventTile extends React.Component { onFocusChange={this.onActionBarFocusChange} /> : undefined; - const showTimestamp = this.props.mxEvent.getTs() && - (this.props.alwaysShowTimestamps || this.props.last || this.state.hover || this.state.actionBarFocused); + const showTimestamp = ( + this.props.mxEvent.getTs() && + ( + this.props.alwaysShowTimestamps || + this.props.last || + this.state.hover || + this.state.actionBarFocused || + this.state.contextMenu + ) + ); const timestamp = showTimestamp ? : null; From e442926d942d7aa8380b4e3f9baa57706b077651 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 20 Jul 2021 13:56:53 +0200 Subject: [PATCH 36/74] Delint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/customisations/models/IMediaEventContent.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/customisations/models/IMediaEventContent.ts b/src/customisations/models/IMediaEventContent.ts index 52728b7c84f..62dfe4ee19a 100644 --- a/src/customisations/models/IMediaEventContent.ts +++ b/src/customisations/models/IMediaEventContent.ts @@ -43,7 +43,6 @@ export interface IMediaEventContent { h?: number; size?: number; }; - body?: string; } export interface IPreparedMedia extends IMediaObject { From 8d2ddeeeb300e8b077602c6774c2c6da346779c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 20 Jul 2021 14:02:02 +0200 Subject: [PATCH 37/74] Pointless change so that I can re-run the CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/context_menus/MessageContextMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index 6ba371c81e3..d52338890d9 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -88,8 +88,8 @@ interface IState { @replaceableComponent("views.context_menus.MessageContextMenu") export default class MessageContextMenu extends React.Component { static contextType = RoomContext; - private reactButtonRef = createRef(); // XXX Ref to a functional component private decryptedUrl?: string; + private reactButtonRef = createRef(); // XXX Ref to a functional component constructor(props: IProps) { super(props); From b59ac5053e2894e956f63c41af4a4f5afb77c87a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 1 Aug 2021 13:22:31 +0200 Subject: [PATCH 38/74] Remove dowload button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Because we don't use this menu when clicking on images Signed-off-by: Šimon Brandner --- .../context_menus/_MessageContextMenu.scss | 4 -- .../context_menus/MessageContextMenu.tsx | 47 +------------------ 2 files changed, 1 insertion(+), 50 deletions(-) diff --git a/res/css/views/context_menus/_MessageContextMenu.scss b/res/css/views/context_menus/_MessageContextMenu.scss index bbf5d70e57f..d9d15fb91c4 100644 --- a/res/css/views/context_menus/_MessageContextMenu.scss +++ b/res/css/views/context_menus/_MessageContextMenu.scss @@ -97,8 +97,4 @@ limitations under the License. .mx_MessageContextMenu_iconReact::before { mask-image: url('$(res)/img/element-icons/room/message-bar/emoji.svg'); } - - .mx_MessageContextMenu_iconSave::before { - mask-image: url('$(res)/img/image-view/download.svg'); - } } diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index d52338890d9..2e77e0fc17a 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -38,22 +38,12 @@ import RoomContext from '../../../contexts/RoomContext'; import { toRightOf, ContextMenu } from '../../structures/ContextMenu'; import ReactionPicker from '../emojipicker/ReactionPicker'; import { Relations } from 'matrix-js-sdk/src/models/relations'; -import { IMediaEventContent } from '../../../customisations/models/IMediaEventContent'; -import { mediaFromContent } from '../../../customisations/Media'; -import { decryptFile } from '../../../utils/DecryptFile'; import ReportEventDialog from '../dialogs/ReportEventDialog'; import ViewSource from '../../structures/ViewSource'; import ConfirmRedactDialog from '../dialogs/ConfirmRedactDialog'; import ErrorDialog from '../dialogs/ErrorDialog'; import ShareDialog from '../dialogs/ShareDialog'; -const DOWNLOADABLE_MESSAGE_TYPES = [ - MsgType.Audio, - MsgType.File, - MsgType.Image, - MsgType.Video, -]; - export function canCancel(eventStatus) { return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT; } @@ -264,29 +254,6 @@ export default class MessageContextMenu extends React.Component this.closeMenu(); }; - private onSaveClick = async (): Promise => { - const content = this.props.mxEvent.getContent() as IMediaEventContent; - const media = mediaFromContent(this.props.mxEvent.getContent()); - - if (content.file !== undefined && !this.decryptedUrl) { - try { - const blob = await decryptFile(content.file); - this.decryptedUrl = URL.createObjectURL(blob); - } catch (error) { - console.warn("Unable to decrypt attachment: ", error); - return; - } - } - - const anchor = document.createElement("a"); - anchor.href = this.decryptedUrl || media.srcHttp; - anchor.download = content.body?.length > 0 ? content.body : _t('Attachment'); - anchor.target = "_blank"; - anchor.rel = "noreferrer noopener"; - anchor.click(); - this.closeMenu(); - }; - private onEditClick = (): void => { dis.dispatch({ action: 'edit_event', @@ -359,7 +326,6 @@ export default class MessageContextMenu extends React.Component let redactItemList; let reportEventButton; let copyButton; - let saveButton; let editButton; let replyButton; let reactButton; @@ -522,16 +488,6 @@ export default class MessageContextMenu extends React.Component ); } - if (rightClick && DOWNLOADABLE_MESSAGE_TYPES.includes(messageType as MsgType)) { - saveButton = ( - - ); - } - if (rightClick && canEditContent(mxEvent)) { editButton = ( ); } - if (copyButton || saveButton) { + if (copyButton) { nativeItemsList = ( { copyButton } - { saveButton } ); } From ba7fcea9ec6e4c490fd84be36fa3cd0ba3472705 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 1 Aug 2021 13:31:00 +0200 Subject: [PATCH 39/74] Be more clear for non-bools MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/EventTile.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index a241b14b270..93d8b82bfd6 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -1061,7 +1061,7 @@ export default class EventTile extends React.Component { this.props.last || this.state.hover || this.state.actionBarFocused || - this.state.contextMenu + Boolean(this.state.contextMenu) ) ); const timestamp = showTimestamp ? From cbb67d377b0e0899466d0a1d5d262d009c19a5f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 1 Aug 2021 17:33:53 +0200 Subject: [PATCH 40/74] Use triggerOnMouse down prop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../views/context_menus/MessageContextMenu.tsx | 3 ++- src/components/views/elements/AccessibleButton.tsx | 13 +++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index 965a49b387e..d703552edc2 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -492,7 +492,8 @@ export default class MessageContextMenu extends React.Component ); } diff --git a/src/components/views/elements/AccessibleButton.tsx b/src/components/views/elements/AccessibleButton.tsx index 0c87fa8a9fa..311d3e083c8 100644 --- a/src/components/views/elements/AccessibleButton.tsx +++ b/src/components/views/elements/AccessibleButton.tsx @@ -39,11 +39,11 @@ interface IProps extends React.AllHTMLAttributes { tabIndex?: number; disabled?: boolean; className?: string; - // Optional in case we need to use onMouseDown - onClick?(e?: ButtonEvent): void; + triggerOnMouseDown?: boolean; + onClick(e?: ButtonEvent): void; } -interface IAccessibleButtonProps extends React.AllHTMLAttributes { +interface IAccessibleButtonProps extends React.InputHTMLAttributes { ref?: React.Ref; } @@ -65,11 +65,16 @@ export default function AccessibleButton({ className, onKeyDown, onKeyUp, + triggerOnMouseDown, ...restProps }: IProps) { const newProps: IAccessibleButtonProps = restProps; if (!disabled) { - newProps.onClick = onClick; + if (triggerOnMouseDown) { + newProps.onMouseDown = onClick; + } else { + newProps.onClick = onClick; + } // We need to consume enter onKeyDown and space onKeyUp // otherwise we are risking also activating other keyboard focusable elements // that might receive focus as a result of the AccessibleButtonClick action From 9db902c55d0167930381e460d75ae5390e2e9eaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 1 Aug 2021 17:43:25 +0200 Subject: [PATCH 41/74] Remove a comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/context_menus/MessageContextMenu.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index d703552edc2..577b9fdd6a5 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -308,7 +308,6 @@ export default class MessageContextMenu extends React.Component private getPermalink(): string { if (!this.props.permalinkCreator) return null; - // 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) return this.props.permalinkCreator.forEvent(this.props.mxEvent.getId()); } From 1567831d4a13ef67e155a814e42a160e598b2be4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 1 Aug 2021 17:50:14 +0200 Subject: [PATCH 42/74] Remove unused var MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/context_menus/MessageContextMenu.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index 577b9fdd6a5..87a698deb60 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -315,7 +315,6 @@ export default class MessageContextMenu extends React.Component const cli = MatrixClientPeg.get(); const me = cli.getUserId(); const mxEvent = this.props.mxEvent; - const messageType = mxEvent.getContent().msgtype; const eventStatus = mxEvent.status; const unsentReactionsCount = this.getUnsentReactions().length; const contentActionable = isContentActionable(this.props.mxEvent); From a86484ed5812dcf3bd36f551183f0d5bf6f63346 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 1 Aug 2021 18:42:48 +0200 Subject: [PATCH 43/74] Remove unnecessary import MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/context_menus/MessageContextMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index 87a698deb60..5e08d683c67 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -26,7 +26,7 @@ import SettingsStore from '../../../settings/SettingsStore'; import { isUrlPermitted } from '../../../HtmlUtils'; import { canEditContent, isContentActionable } from '../../../utils/EventUtils'; import IconizedContextMenu, { IconizedContextMenuOption, IconizedContextMenuOptionList } from './IconizedContextMenu'; -import { EventType, MsgType, RelationType } from "matrix-js-sdk/src/@types/event"; +import { EventType, RelationType } from "matrix-js-sdk/src/@types/event"; import { replaceableComponent } from "../../../utils/replaceableComponent"; import { ReadPinsEventId } from "../right_panel/PinnedMessagesCard"; import ForwardDialog from "../dialogs/ForwardDialog"; From f4ee7e51418b2847d90536521f196fa17a7d92e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 1 Aug 2021 19:08:16 +0200 Subject: [PATCH 44/74] Add some missing types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/EventTile.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index c9a68607950..bb76959e59e 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -36,7 +36,7 @@ import { E2E_STATE } from "./E2EIcon"; import { toRem } from "../../../utils/units"; import { WidgetType } from "../../../widgets/WidgetType"; import RoomAvatar from "../avatars/RoomAvatar"; -import MessageContextMenu from "../context_menus/MessageContextMenu"; +import MessageContextMenu, { IEventTileOps } from "../context_menus/MessageContextMenu"; import { aboveLeftOf } from '../../structures/ContextMenu'; import { WIDGET_LAYOUT_EVENT_TYPE } from "../../../stores/widgets/WidgetLayoutStore"; import { objectHasDiff } from "../../../utils/objects"; @@ -58,6 +58,7 @@ import ReadReceiptMarker from "./ReadReceiptMarker"; import MessageActionBar from "../messages/MessageActionBar"; import ReactionsRow from '../messages/ReactionsRow'; import { getEventDisplayInfo } from '../../../utils/EventUtils'; +import TextualBody from "../messages/TextualBody"; const eventTileTypes = { [EventType.RoomMessage]: 'messages.MessageEvent', @@ -194,6 +195,10 @@ export enum TileShape { Pinned = "pinned", } +export interface IEventTileType extends React.Component { + getEventTileOps?(): IEventTileOps; +} + interface IProps { // the MatrixEvent to show mxEvent: MatrixEvent; @@ -330,7 +335,7 @@ interface IState { export default class EventTile extends React.Component { private suppressReadReceiptAnimation: boolean; private isListeningForReceipts: boolean; - private tile = React.createRef(); + private tile = React.createRef(); private replyThread = React.createRef(); public readonly ref = createRef(); @@ -840,9 +845,9 @@ export default class EventTile extends React.Component { }); }; - getTile = (): any => this.tile.current; + getTile = (): IEventTileType => this.tile.current; - getReplyThread = () => this.replyThread.current; + getReplyThread = (): ReplyThread => this.replyThread.current; getReactions = () => { if ( From 03422bae3847043651899e5f8ad76bb75e7122e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 1 Aug 2021 19:08:47 +0200 Subject: [PATCH 45/74] Add missing type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/context_menus/MessageContextMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index 5e08d683c67..abd0f14052a 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -61,7 +61,7 @@ interface IProps { // The MatrixEvent associated with the context menu mxEvent: MatrixEvent; // An optional EventTileOps implementation that can be used to unhide preview widgets - eventTileOps?; // TODO: Add type when TextualBody is TSified + eventTileOps?: IEventTileOps; // An optional function to be called when the user clicks collapse thread, if not provided hide button collapseReplyThread?(): void; // Callback called when the menu is dismissed From 3baa634337296f60e0f54f4327fe935cacc8858c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 1 Aug 2021 19:16:31 +0200 Subject: [PATCH 46/74] Remove unused import MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/EventTile.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index bb76959e59e..f12f89b2a8f 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -58,7 +58,6 @@ import ReadReceiptMarker from "./ReadReceiptMarker"; import MessageActionBar from "../messages/MessageActionBar"; import ReactionsRow from '../messages/ReactionsRow'; import { getEventDisplayInfo } from '../../../utils/EventUtils'; -import TextualBody from "../messages/TextualBody"; const eventTileTypes = { [EventType.RoomMessage]: 'messages.MessageEvent', From 50b84b9c5e01de19c908936766abcba556e3357e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 1 Aug 2021 19:23:45 +0200 Subject: [PATCH 47/74] Add a missing type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/context_menus/MessageContextMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index abd0f14052a..699b21f5a0b 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -44,7 +44,7 @@ import ConfirmRedactDialog from '../dialogs/ConfirmRedactDialog'; import ErrorDialog from '../dialogs/ErrorDialog'; import ShareDialog from '../dialogs/ShareDialog'; -export function canCancel(eventStatus) { +export function canCancel(eventStatus): boolean { return eventStatus === EventStatus.QUEUED || eventStatus === EventStatus.NOT_SENT; } From b594e687000453f216d87c8c6192095640c14990 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 17 Oct 2021 13:34:09 +0200 Subject: [PATCH 48/74] Fix types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/elements/AccessibleTooltipButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/elements/AccessibleTooltipButton.tsx b/src/components/views/elements/AccessibleTooltipButton.tsx index d2a4801a2dd..a1ae13981ca 100644 --- a/src/components/views/elements/AccessibleTooltipButton.tsx +++ b/src/components/views/elements/AccessibleTooltipButton.tsx @@ -25,7 +25,7 @@ import { replaceableComponent } from "../../../utils/replaceableComponent"; interface ITooltipProps extends React.ComponentProps { title: string; tooltip?: React.ReactNode; - label?: React.ReactNode; + label?: string; tooltipClassName?: string; forceHide?: boolean; yOffset?: number; From 13aa030667342c9b4ab3c052880e2a9da9319a7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 18 Oct 2021 20:09:34 +0200 Subject: [PATCH 49/74] Fix types/naming MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/EventTile.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 0c66b2bd51a..b6515084553 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -975,9 +975,9 @@ export default class EventTile extends React.Component { let contextMenu = null; if (this.state.contextMenu) { const tile = this.getTile(); - const replyThread = this.getReplyChain(); + const replyChain = this.getReplyChain(); const eventTileOps = tile?.getEventTileOps ? tile.getEventTileOps() : undefined; - const collapseReplyThread = replyThread?.canCollapse() ? replyThread.collapse : undefined; + const collapseReplyChain = replyChain?.canCollapse() ? replyChain.collapse : undefined; contextMenu = ( { mxEvent={this.props.mxEvent} permalinkCreator={this.props.permalinkCreator} eventTileOps={eventTileOps} - collapseReplyThread={collapseReplyThread} + collapseReplyChain={collapseReplyChain} onFinished={this.onCloseMenu} rightClick={true} reactions={this.state.reactions} From bbf4cab7aed924bc42e0073657f37b40cf27dc62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 18 Oct 2021 20:14:44 +0200 Subject: [PATCH 50/74] Add missing current MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/EventTile.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index b6515084553..c9a6185c254 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -948,7 +948,7 @@ export default class EventTile extends React.Component { getTile = (): IEventTileType => this.tile.current; - getReplyChain = (): ReplyChain => this.replyChain; + getReplyChain = (): ReplyChain => this.replyChain.current; getReactions = () => { if ( From 8a3c02d354dd99b5fc2bf95c7420eb2d1b6c941d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 22 Oct 2021 17:19:10 +0200 Subject: [PATCH 51/74] Remove unused var MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/context_menus/MessageContextMenu.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index 76000b06124..9de9025858a 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -88,7 +88,6 @@ interface IState { @replaceableComponent("views.context_menus.MessageContextMenu") export default class MessageContextMenu extends React.Component { static contextType = RoomContext; - private decryptedUrl?: string; private reactButtonRef = createRef(); // XXX Ref to a functional component constructor(props: IProps) { From cf350e48374884005785443e36d2957eb255b603 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 22 Oct 2021 17:22:56 +0200 Subject: [PATCH 52/74] Fix editing and replying MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/context_menus/MessageContextMenu.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index 9de9025858a..5b733433a74 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -242,8 +242,9 @@ export default class MessageContextMenu extends React.Component private onEditClick = (): void => { dis.dispatch({ - action: 'edit_event', + action: Action.EditEvent, event: this.props.mxEvent, + timelineRenderingType: this.context.timelineRenderingType, }); this.closeMenu(); }; @@ -252,6 +253,7 @@ export default class MessageContextMenu extends React.Component dis.dispatch({ action: 'reply_to_event', event: this.props.mxEvent, + context: this.context.timelineRenderingType, }); this.closeMenu(); }; From fbed31398b6eb30b2d3d41375bde85e27969b71e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 26 Nov 2021 15:30:36 +0100 Subject: [PATCH 53/74] i18n MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/i18n/strings/en_EN.json | 1 - 1 file changed, 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index f301ae41600..b91650172e4 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2766,7 +2766,6 @@ "Forget": "Forget", "Leave": "Leave", "Mentions only": "Mentions only", - "Copy link": "Copy link", "See room timeline (devtools)": "See room timeline (devtools)", "Add space": "Add space", "Manage & explore rooms": "Manage & explore rooms", From a732ab408b01087fc7a459723f683f3010d75fa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 29 Nov 2021 19:23:32 +0100 Subject: [PATCH 54/74] Fix import MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/context_menus/MessageContextMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index 2f2f40a27f8..b670996e8a6 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -34,7 +34,7 @@ import { Action } from "../../../dispatcher/actions"; import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks'; import { ButtonEvent } from '../elements/AccessibleButton'; import { copyPlaintext } from '../../../utils/strings'; -import { toRightOf, ContextMenu } from '../../structures/ContextMenu'; +import ContextMenu, { toRightOf } from '../../structures/ContextMenu'; import ReactionPicker from '../emojipicker/ReactionPicker'; import { Relations } from 'matrix-js-sdk/src/models/relations'; import ReportEventDialog from '../dialogs/ReportEventDialog'; From 3faf382eded33631548bcba0a1a9c8de94741b2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 4 Feb 2022 13:53:35 +0100 Subject: [PATCH 55/74] Support right-click context menu for threads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/EventTile.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 6754fc22aa6..d9fee72b5d8 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -1554,7 +1554,8 @@ export default class EventTile extends React.Component { { sender }
, -
+
+ { this.renderContextMenu() } { replyChain } Date: Tue, 8 Feb 2022 12:09:55 +0100 Subject: [PATCH 56/74] Make button order match `MessageActionBar` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/context_menus/MessageContextMenu.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index 303719875cc..a5f2fb83a01 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -1,6 +1,7 @@ /* Copyright 2019 Michael Telatynski <7t3chguy@gmail.com> Copyright 2015 - 2021 The Matrix.org Foundation C.I.C. +Copyright 2021 - 2022 Šimon Brandner Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -599,9 +600,9 @@ export default class MessageContextMenu extends React.Component if (editButton || replyButton || reactButton) { quickItemsList = ( - { editButton } - { replyButton } { reactButton } + { replyButton } + { editButton } ); } From 7eb9cc8030ca34cdd6e801688001cffc6db31ae3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 8 Feb 2022 12:12:52 +0100 Subject: [PATCH 57/74] Fix missing permalink button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/context_menus/MessageContextMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index a5f2fb83a01..3c1b93e6d15 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -457,7 +457,7 @@ export default class MessageContextMenu extends React.Component } } - if (rightClick && permalink) { + if (rightClick || permalink) { if (this.props.showPermalink) { permalinkButton = ( Date: Tue, 8 Feb 2022 19:32:51 +0100 Subject: [PATCH 58/74] Remove useless part of if statement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/context_menus/MessageContextMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index 3c1b93e6d15..f5a2140ee0f 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -457,7 +457,7 @@ export default class MessageContextMenu extends React.Component } } - if (rightClick || permalink) { + if (permalink) { if (this.props.showPermalink) { permalinkButton = ( Date: Wed, 9 Feb 2022 20:46:07 +0100 Subject: [PATCH 59/74] Some small refactoring for consistency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../context_menus/MessageContextMenu.tsx | 170 +++++++++--------- 1 file changed, 80 insertions(+), 90 deletions(-) diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index f5a2140ee0f..04d0b0e9187 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -117,12 +117,12 @@ export default class MessageContextMenu extends React.Component }; } - componentDidMount() { + public componentDidMount(): void { MatrixClientPeg.get().on('RoomMember.powerLevel', this.checkPermissions); this.checkPermissions(); } - componentWillUnmount() { + public componentWillUnmount(): void { const cli = MatrixClientPeg.get(); if (cli) { cli.removeListener('RoomMember.powerLevel', this.checkPermissions); @@ -330,7 +330,7 @@ export default class MessageContextMenu extends React.Component return this.getReactions(e => e.status === EventStatus.NOT_SENT); } - private viewInRoom = () => { + private viewInRoom = (): void => { dis.dispatch({ action: Action.ViewRoom, event_id: this.props.mxEvent.getId(), @@ -340,7 +340,7 @@ export default class MessageContextMenu extends React.Component this.closeMenu(); }; - render() { + public render(): JSX.Element { const cli = MatrixClientPeg.get(); const me = cli.getUserId(); const mxEvent = this.props.mxEvent; @@ -348,8 +348,18 @@ export default class MessageContextMenu extends React.Component const unsentReactionsCount = this.getUnsentReactions().length; const contentActionable = isContentActionable(this.props.mxEvent); const rightClick = this.props.rightClick; - const context = this.context; const permalink = this.getPermalink(); + // status is SENT before remote-echo, null after + const isSent = !eventStatus || eventStatus === EventStatus.SENT; + const { timelineRenderingType, canReact, canReply } = this.context; + const isThread = ( + timelineRenderingType === TimelineRenderingType.Thread || + timelineRenderingType === TimelineRenderingType.ThreadsList + ); + const isThreadRootEvent = isThread && this.props.mxEvent?.getThread()?.rootEvent === this.props.mxEvent; + const isMainSplitTimelineShown = !WidgetLayoutStore.instance.hasMaximisedWidget( + MatrixClientPeg.get().getRoom(mxEvent.getRoomId()), + ); let openInMapSiteButton: JSX.Element; let endPollButton: JSX.Element; @@ -371,19 +381,16 @@ export default class MessageContextMenu extends React.Component let nativeItemsList: JSX.Element; let permalinkButton: JSX.Element; let collapseReplyChain: JSX.Element; + let viewInRoomButton: JSX.Element; - // status is SENT before remote-echo, null after - const isSent = !eventStatus || eventStatus === EventStatus.SENT; - if (!mxEvent.isRedacted()) { - if (unsentReactionsCount !== 0) { - resendReactionsButton = ( - - ); - } + if (!mxEvent.isRedacted() && unsentReactionsCount !== 0) { + resendReactionsButton = ( + + ); } if (isSent && this.state.canRedact) { @@ -415,26 +422,24 @@ export default class MessageContextMenu extends React.Component ); } - if (contentActionable) { - if (canForward(mxEvent)) { - forwardButton = ( - - ); - } - - if (this.state.canPin) { - pinButton = ( - - ); - } + if (contentActionable && canForward(mxEvent)) { + forwardButton = ( + + ); + } + + if (contentActionable && this.state.canPin) { + pinButton = ( + + ); } const viewSourceButton = ( @@ -445,44 +450,32 @@ export default class MessageContextMenu extends React.Component /> ); - if (this.props.eventTileOps) { - if (this.props.eventTileOps.isWidgetHidden()) { - unhidePreviewButton = ( - - ); - } + if (this.props.eventTileOps && this.props.eventTileOps.isWidgetHidden()) { + unhidePreviewButton = ( + + ); } if (permalink) { - if (this.props.showPermalink) { - permalinkButton = ( - - ); - } else { - permalinkButton = ( - - ); - } + const showPermalink = this.props.showPermalink; + permalinkButton = ( + + ); } if (this.canEndPoll(mxEvent)) { @@ -506,7 +499,8 @@ export default class MessageContextMenu extends React.Component } // Bridges can provide a 'external_url' to link back to the source. - if (typeof (mxEvent.getContent().external_url) === "string" && + if ( + typeof (mxEvent.getContent().external_url) === "string" && isUrlPermitted(mxEvent.getContent().external_url) ) { externalURLButton = ( @@ -568,7 +562,7 @@ export default class MessageContextMenu extends React.Component ); } - if (rightClick && contentActionable && context.canReply) { + if (rightClick && contentActionable && canReply) { replyButton = ( ); } - if (rightClick && contentActionable && context.canReact) { + if (rightClick && contentActionable && canReact) { reactButton = ( ); } + if (isThreadRootEvent && isMainSplitTimelineShown) { + viewInRoomButton = ( + + ); + } + if (copyButton) { nativeItemsList = ( @@ -607,23 +611,9 @@ export default class MessageContextMenu extends React.Component ); } - const { timelineRenderingType } = this.context; - const isThread = ( - timelineRenderingType === TimelineRenderingType.Thread || - timelineRenderingType === TimelineRenderingType.ThreadsList - ); - const isThreadRootEvent = isThread && this.props.mxEvent?.getThread()?.rootEvent === this.props.mxEvent; - - const isMainSplitTimelineShown = !WidgetLayoutStore.instance.hasMaximisedWidget( - MatrixClientPeg.get().getRoom(mxEvent.getRoomId()), - ); const commonItemsList = ( - { (isThreadRootEvent && isMainSplitTimelineShown) && } + { viewInRoomButton } { openInMapSiteButton } { endPollButton } { quoteButton } From f4eeb7db0048874017788c255d695b88eaff6777 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 9 Feb 2022 20:54:01 +0100 Subject: [PATCH 60/74] Some more refactoring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../views/context_menus/MessageContextMenu.tsx | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index 04d0b0e9187..67da2228a12 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -343,11 +343,10 @@ export default class MessageContextMenu extends React.Component public render(): JSX.Element { const cli = MatrixClientPeg.get(); const me = cli.getUserId(); - const mxEvent = this.props.mxEvent; + const { mxEvent, rightClick, showPermalink, eventTileOps, reactions } = this.props; const eventStatus = mxEvent.status; const unsentReactionsCount = this.getUnsentReactions().length; - const contentActionable = isContentActionable(this.props.mxEvent); - const rightClick = this.props.rightClick; + const contentActionable = isContentActionable(mxEvent); const permalink = this.getPermalink(); // status is SENT before remote-echo, null after const isSent = !eventStatus || eventStatus === EventStatus.SENT; @@ -356,7 +355,7 @@ export default class MessageContextMenu extends React.Component timelineRenderingType === TimelineRenderingType.Thread || timelineRenderingType === TimelineRenderingType.ThreadsList ); - const isThreadRootEvent = isThread && this.props.mxEvent?.getThread()?.rootEvent === this.props.mxEvent; + const isThreadRootEvent = isThread && mxEvent?.getThread()?.rootEvent === mxEvent; const isMainSplitTimelineShown = !WidgetLayoutStore.instance.hasMaximisedWidget( MatrixClientPeg.get().getRoom(mxEvent.getRoomId()), ); @@ -450,7 +449,7 @@ export default class MessageContextMenu extends React.Component /> ); - if (this.props.eventTileOps && this.props.eventTileOps.isWidgetHidden()) { + if (eventTileOps?.isWidgetHidden()) { unhidePreviewButton = ( } if (permalink) { - const showPermalink = this.props.showPermalink; permalinkButton = ( ); } - if (this.props.eventTileOps) { // this event is rendered using TextualBody + if (eventTileOps) { // this event is rendered using TextualBody quoteButton = ( ); } - if (this.props.collapseReplyChain) { + if (collapseReplyChain) { collapseReplyChain = ( ); From 9a6d22d4cda8147a717c4b566888ec3f6f84c0ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 18 Feb 2022 16:06:23 +0100 Subject: [PATCH 61/74] Fix `editEvent()` call MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/messages/MessageActionBar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/messages/MessageActionBar.tsx b/src/components/views/messages/MessageActionBar.tsx index 455788f3b18..83ba6acf98a 100644 --- a/src/components/views/messages/MessageActionBar.tsx +++ b/src/components/views/messages/MessageActionBar.tsx @@ -229,7 +229,7 @@ export default class MessageActionBar extends React.PureComponent { - editEvent(this.props.mxEvent, this.context.timelineRenderingType); + editEvent(this.props.mxEvent, this.context.timelineRenderingType, this.props.getRelationsForEvent); }; private readonly forbiddenThreadHeadMsgType = [ From 0521653075b1087612ad859ca5747bdaec44368b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 18 Feb 2022 16:06:57 +0100 Subject: [PATCH 62/74] Make editing polls work MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../views/context_menus/MessageContextMenu.tsx | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index 44e6c8ac777..729cf277507 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -30,7 +30,7 @@ import Modal from '../../../Modal'; import Resend from '../../../Resend'; import SettingsStore from '../../../settings/SettingsStore'; import { isUrlPermitted } from '../../../HtmlUtils'; -import { canEditContent, isContentActionable } from '../../../utils/EventUtils'; +import { canEditContent, editEvent, isContentActionable } from '../../../utils/EventUtils'; import IconizedContextMenu, { IconizedContextMenuOption, IconizedContextMenuOptionList } from './IconizedContextMenu'; import { replaceableComponent } from "../../../utils/replaceableComponent"; import { ReadPinsEventId } from "../right_panel/PinnedMessagesCard"; @@ -53,6 +53,7 @@ import EndPollDialog from '../dialogs/EndPollDialog'; import { isPollEnded } from '../messages/MPollBody'; import { createMapSiteLink } from "../messages/MLocationBody"; import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; +import { GetRelationsForEvent } from "../rooms/EventTile"; export function canCancel(status: EventStatus): boolean { return status === EventStatus.QUEUED || status === EventStatus.NOT_SENT || status === EventStatus.ENCRYPTING; @@ -88,11 +89,7 @@ interface IProps extends IPosition { // A permalink to the event showPermalink?: boolean; - getRelationsForEvent?: ( - eventId: string, - relationType: string, - eventType: string - ) => Relations; + getRelationsForEvent?: GetRelationsForEvent; } interface IState { @@ -272,11 +269,7 @@ export default class MessageContextMenu extends React.Component }; private onEditClick = (): void => { - dis.dispatch({ - action: Action.EditEvent, - event: this.props.mxEvent, - timelineRenderingType: this.context.timelineRenderingType, - }); + editEvent(this.props.mxEvent, this.context.timelineRenderingType, this.props.getRelationsForEvent); this.closeMenu(); }; From 815d3f678d3bd43acaacac9d041dbc6d84db00dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 25 Feb 2022 15:58:49 +0100 Subject: [PATCH 63/74] Fix collapse reply chain button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/context_menus/MessageContextMenu.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index a450a4459e0..5a9b85ca012 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -339,7 +339,7 @@ export default class MessageContextMenu extends React.Component public render(): JSX.Element { const cli = MatrixClientPeg.get(); const me = cli.getUserId(); - const { mxEvent, rightClick, showPermalink, eventTileOps, reactions } = this.props; + const { mxEvent, rightClick, showPermalink, eventTileOps, reactions, collapseReplyChain } = this.props; const eventStatus = mxEvent.status; const unsentReactionsCount = this.getUnsentReactions().length; const contentActionable = isContentActionable(mxEvent); @@ -375,7 +375,7 @@ export default class MessageContextMenu extends React.Component let quickItemsList: JSX.Element; let nativeItemsList: JSX.Element; let permalinkButton: JSX.Element; - let collapseReplyChain: JSX.Element; + let collapseReplyChainButton: JSX.Element; let viewInRoomButton: JSX.Element; if (!mxEvent.isRedacted() && unsentReactionsCount !== 0) { @@ -516,7 +516,7 @@ export default class MessageContextMenu extends React.Component } if (collapseReplyChain) { - collapseReplyChain = ( + collapseReplyChainButton = ( { unhidePreviewButton } { viewSourceButton } { resendReactionsButton } - { collapseReplyChain } + { collapseReplyChainButton } ); From 25701e7cd3d53a0c2b2043e3f2ddfc4afc4758a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 25 Feb 2022 16:04:26 +0100 Subject: [PATCH 64/74] Fix timelineRenderingType MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/EventTile.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 32cec8c66d7..d02ba9e70b9 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -1256,7 +1256,7 @@ export default class EventTile extends React.Component { mx_EventTile_12hr: this.props.isTwelveHour, // Note: we keep the `sending` state class for tests, not for our styles mx_EventTile_sending: !isEditing && isSending, - mx_EventTile_highlight: (this.props.timelineRenderingType === TimelineRenderingType.Notification + mx_EventTile_highlight: (this.context.timelineRenderingType === TimelineRenderingType.Notification ? false : this.shouldHighlight()), mx_EventTile_selected: this.props.isSelectedEvent || this.state.contextMenu, From 70147d9d917e4d58dfd7b19322c04fb457072ab9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 5 Mar 2022 16:13:37 +0100 Subject: [PATCH 65/74] Fix reply button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/context_menus/MessageContextMenu.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index 5a9b85ca012..acfca87187e 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -346,7 +346,7 @@ export default class MessageContextMenu extends React.Component const permalink = this.getPermalink(); // status is SENT before remote-echo, null after const isSent = !eventStatus || eventStatus === EventStatus.SENT; - const { timelineRenderingType, canReact, canReply } = this.context; + const { timelineRenderingType, canReact, canSendMessages } = this.context; const isThread = ( timelineRenderingType === TimelineRenderingType.Thread || timelineRenderingType === TimelineRenderingType.ThreadsList @@ -556,7 +556,7 @@ export default class MessageContextMenu extends React.Component ); } - if (rightClick && contentActionable && canReply) { + if (rightClick && contentActionable && canSendMessages) { replyButton = ( Date: Sat, 19 Mar 2022 17:17:28 +0100 Subject: [PATCH 66/74] Hide right-click context menu behind a labs flag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/EventTile.tsx | 1 + src/i18n/strings/en_EN.json | 1 + src/settings/Settings.tsx | 7 +++++++ 3 files changed, 9 insertions(+) diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index f5f2583aeb7..436dc21ecba 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -1091,6 +1091,7 @@ export class UnwrappedEventTile extends React.Component { }; private showContextMenu(ev: React.MouseEvent, showPermalink?: boolean): void { + if (!SettingsStore.getValue("feature_message_right_click_context_menu")) return; // There is no way to copy non-PNG images into clipboard, so we can't // have our own handling for copying images, so we leave it to the // Electron layer (webcontents-handler.ts) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 518d1ce7db1..85ffd7de10b 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -901,6 +901,7 @@ "Right panel stays open (defaults to room member list)": "Right panel stays open (defaults to room member list)", "Jump to date (adds /jumptodate and jump to date headers)": "Jump to date (adds /jumptodate and jump to date headers)", "Don't send read receipts": "Don't send read receipts", + "Right-click message context menu": "Right-click message context menu", "Location sharing - pin drop (under active development)": "Location sharing - pin drop (under active development)", "Location sharing - share your current location with live updates (under active development)": "Location sharing - share your current location with live updates (under active development)", "Font size": "Font size", diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index fc4c4a68ced..a1ba83b7c62 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -399,6 +399,13 @@ export const SETTINGS: {[setting: string]: ISetting} = { displayName: _td("Don't send read receipts"), default: false, }, + "feature_message_right_click_context_menu": { + isFeature: true, + supportedLevels: LEVELS_FEATURE, + labsGroup: LabGroup.Rooms, + displayName: _td("Right-click message context menu"), + default: false, + }, "feature_location_share_pin_drop": { isFeature: true, labsGroup: LabGroup.Messaging, From 01a3a1768bcd8ab7a4e2325820a12f7016b0b815 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 9 Apr 2022 08:19:03 +0200 Subject: [PATCH 67/74] Add missing return type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/structures/ContextMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/structures/ContextMenu.tsx b/src/components/structures/ContextMenu.tsx index 329d32230e6..170ded2602b 100644 --- a/src/components/structures/ContextMenu.tsx +++ b/src/components/structures/ContextMenu.tsx @@ -432,7 +432,7 @@ export const aboveLeftOf = ( elementRect: Partial, chevronFace = ChevronFace.None, vPadding = 0, -) => { +): AboveLeftOf => { const menuOptions: IPosition & { chevronFace: ChevronFace } = { chevronFace }; const buttonRight = elementRect.right + window.pageXOffset; From c5b535ffbd5ff805f35de10252f0d6630b720927 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 9 Apr 2022 08:20:07 +0200 Subject: [PATCH 68/74] Make `contextMene` optional MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/EventTile.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index cd8910529be..a0b2c178f9c 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -229,7 +229,7 @@ interface IState { hover: boolean; // Position of the context menu - contextMenu: { + contextMenu?: { position: Partial; showPermalink?: boolean; }; From 905766911d892cb6181c991653cd9ef7fe535a3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 9 Apr 2022 08:20:45 +0200 Subject: [PATCH 69/74] Move `renderContextMenu()` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/EventTile.tsx | 60 ++++++++++++------------ 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index a0b2c178f9c..0e3f098b4cf 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -938,36 +938,6 @@ export class UnwrappedEventTile extends React.Component { }); }; - private renderContextMenu(): React.ReactFragment { - let contextMenu = null; - if (this.state.contextMenu) { - const tile = this.getTile(); - const replyChain = this.getReplyChain(); - const eventTileOps = tile?.getEventTileOps ? tile.getEventTileOps() : undefined; - const collapseReplyChain = replyChain?.canCollapse() ? replyChain.collapse : undefined; - - contextMenu = ( - - ); - } - - return ( - - { contextMenu } - - ); - } - private onContextMenu = (ev: React.MouseEvent): void => { this.showContextMenu(ev); }; @@ -1024,6 +994,36 @@ export class UnwrappedEventTile extends React.Component { return false; } + private renderContextMenu(): React.ReactFragment { + let contextMenu = null; + if (this.state.contextMenu) { + const tile = this.getTile(); + const replyChain = this.getReplyChain(); + const eventTileOps = tile?.getEventTileOps ? tile.getEventTileOps() : undefined; + const collapseReplyChain = replyChain?.canCollapse() ? replyChain.collapse : undefined; + + contextMenu = ( + + ); + } + + return ( + + { contextMenu } + + ); + } + public render() { const msgtype = this.props.mxEvent.getContent().msgtype; const eventType = this.props.mxEvent.getType() as EventType; From 144677ccdef23a9fdf653c1f4b2a2136089a8ba6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 9 Apr 2022 08:23:59 +0200 Subject: [PATCH 70/74] Simplify `renderContextMenu()` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/EventTile.tsx | 41 ++++++++++-------------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 0e3f098b4cf..d2813f7583f 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -995,32 +995,25 @@ export class UnwrappedEventTile extends React.Component { } private renderContextMenu(): React.ReactFragment { - let contextMenu = null; - if (this.state.contextMenu) { - const tile = this.getTile(); - const replyChain = this.getReplyChain(); - const eventTileOps = tile?.getEventTileOps ? tile.getEventTileOps() : undefined; - const collapseReplyChain = replyChain?.canCollapse() ? replyChain.collapse : undefined; - - contextMenu = ( - - ); - } + if (!this.state.contextMenu) return null; + + const tile = this.getTile(); + const replyChain = this.getReplyChain(); + const eventTileOps = tile?.getEventTileOps ? tile.getEventTileOps() : undefined; + const collapseReplyChain = replyChain?.canCollapse() ? replyChain.collapse : undefined; return ( - - { contextMenu } - + ); } From cd44684630941d06a11c48b2d0ba5d7c5a203ba8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 9 Apr 2022 08:26:57 +0200 Subject: [PATCH 71/74] Improve `aboveLeftOf` typing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/structures/ContextMenu.tsx | 2 +- src/components/views/rooms/EventTile.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/structures/ContextMenu.tsx b/src/components/structures/ContextMenu.tsx index 170ded2602b..c84452a2646 100644 --- a/src/components/structures/ContextMenu.tsx +++ b/src/components/structures/ContextMenu.tsx @@ -429,7 +429,7 @@ export type AboveLeftOf = IPosition & { // Placement method for to position context menu right-aligned and flowing to the left of elementRect, // and either above or below: wherever there is more space (maybe this should be aboveOrBelowLeftOf?) export const aboveLeftOf = ( - elementRect: Partial, + elementRect: Pick, chevronFace = ChevronFace.None, vPadding = 0, ): AboveLeftOf => { diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index d2813f7583f..6d365c5ebdd 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -230,7 +230,7 @@ interface IState { // Position of the context menu contextMenu?: { - position: Partial; + position: Pick; showPermalink?: boolean; }; From 4f0b848fde8d2cc14ff912e609da72cc9760f86b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 9 Apr 2022 08:30:14 +0200 Subject: [PATCH 72/74] Use `InputHTMLAttributes` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/context_menus/MessageContextMenu.tsx | 4 ---- src/components/views/elements/AccessibleButton.tsx | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index 29be5726eb4..62fb5e11b41 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -462,10 +462,6 @@ export default class MessageContextMenu extends React.Component } onClick={showPermalink ? this.onCopyPermalinkClick : this.onPermalinkClick} label={showPermalink ? _t('Copy link') : _t('Share')} - element="a" - href={permalink} - target="_blank" - rel="noreferrer noopener" /> ); } diff --git a/src/components/views/elements/AccessibleButton.tsx b/src/components/views/elements/AccessibleButton.tsx index f10bbf1aeaf..3db6d0dfb05 100644 --- a/src/components/views/elements/AccessibleButton.tsx +++ b/src/components/views/elements/AccessibleButton.tsx @@ -41,7 +41,7 @@ type AccessibleButtonKind = | 'primary' * onClick: (required) Event handler for button activation. Should be * implemented exactly like a normal onClick handler. */ -interface IProps extends React.AllHTMLAttributes { +interface IProps extends React.InputHTMLAttributes { inputRef?: React.Ref; element?: keyof ReactHTML; // The kind of button, similar to how Bootstrap works. From 6a422621d9bfb2126a28af70624c877bf2d81ae8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sat, 9 Apr 2022 08:35:02 +0200 Subject: [PATCH 73/74] Disable message right-click context menu in browser (for now) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/BasePlatform.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BasePlatform.ts b/src/BasePlatform.ts index 342e0d74cef..b7f52d38952 100644 --- a/src/BasePlatform.ts +++ b/src/BasePlatform.ts @@ -149,7 +149,7 @@ export default abstract class BasePlatform { * Returns true if platform allows overriding native context menus */ public allowOverridingNativeContextMenus(): boolean { - return true; // TODO: Change to false before merge! True for live preview + return false; } /** From 91057a39883b4c7fe53af43e9e5df66e3a2397f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Fri, 15 Apr 2022 08:58:49 +0200 Subject: [PATCH 74/74] Give permalink button more props MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../views/context_menus/MessageContextMenu.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index 62fb5e11b41..1f7f6ac62c4 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -462,6 +462,16 @@ export default class MessageContextMenu extends React.Component } onClick={showPermalink ? this.onCopyPermalinkClick : this.onPermalinkClick} label={showPermalink ? _t('Copy link') : _t('Share')} + element="a" + { + // XXX: Typescript signature for AccessibleButton doesn't work properly for non-inputs like `a` + ...{ + + href: permalink, + target: "_blank", + rel: "noreferrer noopener", + } + } /> ); }