From 8abd6a0e219991b856ae606bc45f10d2c5739f43 Mon Sep 17 00:00:00 2001 From: Audric Ackermann Date: Mon, 26 Oct 2020 16:38:41 +1100 Subject: [PATCH] fix issue with contextmenu on scroll causing UI to break --- stylesheets/_session.scss | 2 +- ts/components/conversation/Message.tsx | 145 +++++++++--------- .../SessionConversationMessagesList.tsx | 1 + 3 files changed, 77 insertions(+), 71 deletions(-) diff --git a/stylesheets/_session.scss b/stylesheets/_session.scss index e1e7f040c..38569d474 100644 --- a/stylesheets/_session.scss +++ b/stylesheets/_session.scss @@ -756,7 +756,7 @@ label { .react-contexify { z-index: 3; min-width: 200px; - box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3), 0 0 0 1px #eee; + box-shadow: none; .react-contexify__item:not(.react-contexify__item--disabled):hover > .react-contexify__item__content { diff --git a/ts/components/conversation/Message.tsx b/ts/components/conversation/Message.tsx index d5c1d3294..84ce4361a 100644 --- a/ts/components/conversation/Message.tsx +++ b/ts/components/conversation/Message.tsx @@ -35,7 +35,8 @@ import { getIncrement } from '../../util/timer'; import { isFileDangerous } from '../../util/isFileDangerous'; import { SessionIcon, SessionIconSize, SessionIconType } from '../session/icon'; import _ from 'lodash'; -import { animation, Item, Menu, MenuProvider, theme } from 'react-contexify'; +import { animation, contextMenu, Item, Menu } from 'react-contexify'; +import uuid from 'uuid'; declare global { interface Window { @@ -130,25 +131,25 @@ const EXPIRATION_CHECK_MINIMUM = 2000; const EXPIRED_DELAY = 600; export class Message extends React.PureComponent { - public captureMenuTriggerBound: (trigger: any) => void; public handleImageErrorBound: () => void; - public menuTriggerRef: Trigger | undefined; public expirationCheckInterval: any; public expiredTimeout: any; + public ctxMenuID: string; public constructor(props: Props) { super(props); - this.captureMenuTriggerBound = this.captureMenuTrigger.bind(this); this.handleImageErrorBound = this.handleImageError.bind(this); this.onReplyPrivate = this.onReplyPrivate.bind(this); + this.handleContextMenu = this.handleContextMenu.bind(this); this.state = { expiring: false, expired: false, imageBroken: false, }; + this.ctxMenuID = uuid(); } public componentDidMount() { @@ -180,6 +181,7 @@ export class Message extends React.PureComponent { this.checkExpired(); } + public checkExpired() { const now = Date.now(); const { isExpired, expirationTimestamp, expirationLength } = this.props; @@ -783,16 +785,7 @@ export class Message extends React.PureComponent { ); } - public captureMenuTrigger(triggerRef: Trigger) { - this.menuTriggerRef = triggerRef; - } - public showMenu(event: React.MouseEvent) { - if (this.menuTriggerRef) { - this.menuTriggerRef.handleContextClick(event); - } - } - - public renderContextMenu(triggerId: string) { + public renderContextMenu() { const { attachments, onCopyText, @@ -843,7 +836,7 @@ export class Message extends React.PureComponent { return ( { } = this.props; const { expired, expiring } = this.state; - // The Date.now() is a workaround to be sure a single triggerID with this id exists - const rightClickTriggerId = id - ? String(`message-ctx-${id}-${Date.now()}`) - : String(`message-ctx-${authorPhoneNumber}-${timestamp}`); + if (expired) { return null; } @@ -997,18 +987,44 @@ export class Message extends React.PureComponent { divClasses.push('public-chat-message-wrapper'); } - const enableContextMenu = !isRss && !multiSelectMode && !isKickedFromGroup; - return ( -
- - {this.renderAvatar()} +
+ {this.renderAvatar()} +
{ + const selection = window.getSelection(); + // Text is being selected + if (selection && selection.type === 'Range') { + return; + } + + // User clicked on message body + const target = event.target as HTMLDivElement; + if (!multiSelectMode && target.className === 'text-selectable' || window.contextMenuShown) { + return; + } + + if (id) { + this.props.onSelectMessage(id); + } + }} + > + {this.renderError(isIncoming)} +
{ const selection = window.getSelection(); @@ -1019,7 +1035,7 @@ export class Message extends React.PureComponent { // User clicked on message body const target = event.target as HTMLDivElement; - if (!multiSelectMode && target.className === 'text-selectable') { + if (target.className === 'text-selectable' || window.contextMenuShown) { return; } @@ -1028,53 +1044,41 @@ export class Message extends React.PureComponent { } }} > - {this.renderError(isIncoming)} - -
{ - const selection = window.getSelection(); - // Text is being selected - if (selection && selection.type === 'Range') { - return; - } - - // User clicked on message body - const target = event.target as HTMLDivElement; - if (target.className === 'text-selectable') { - return; - } - - if (id) { - this.props.onSelectMessage(id); - } - }} - > - {this.renderAuthor()} - {this.renderQuote()} - {this.renderAttachment()} - {this.renderPreview()} - {this.renderEmbeddedContact()} - {this.renderText()} - {this.renderMetadata()} -
- {this.renderError(!isIncoming)} - {enableContextMenu - ? this.renderContextMenu(rightClickTriggerId) - : null} + {this.renderAuthor()} + {this.renderQuote()} + {this.renderAttachment()} + {this.renderPreview()} + {this.renderEmbeddedContact()} + {this.renderText()} + {this.renderMetadata()}
- + {this.renderError(!isIncoming)} + {this.renderContextMenu()} +
); } + private handleContextMenu(e: any) { + e.preventDefault(); + e.stopPropagation(); + const { + isRss, + multiSelectMode, + isKickedFromGroup, + } = this.props; + const enableContextMenu = !isRss && !multiSelectMode && !isKickedFromGroup; + + if (enableContextMenu) { + // Don't forget to pass the id and the event and voila! + contextMenu.hideAll(); + contextMenu.show({ + id: this.ctxMenuID, + event: e, + }); + } + } + private renderAuthor() { const { authorName, @@ -1114,6 +1118,7 @@ export class Message extends React.PureComponent { private onReplyPrivate(e: any) { e.event.stopPropagation(); + e.event.preventDefault(); if (this.props && this.props.onReply) { this.props.onReply(this.props.timestamp); } diff --git a/ts/components/session/conversation/SessionConversationMessagesList.tsx b/ts/components/session/conversation/SessionConversationMessagesList.tsx index 67d2f9de8..ee29bd734 100644 --- a/ts/components/session/conversation/SessionConversationMessagesList.tsx +++ b/ts/components/session/conversation/SessionConversationMessagesList.tsx @@ -292,6 +292,7 @@ export class SessionConversationMessagesList extends React.Component< if (!messageContainer) { return; } + contextMenu.hideAll(); const scrollTop = messageContainer.scrollTop; const scrollHeight = messageContainer.scrollHeight;