From b1daf3fec2f0459d088ed604c1413d08b13786c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 9 May 2022 08:25:14 +0200 Subject: [PATCH] Add a `Copy link` button to the right-click message context-menu labs feature (#8527) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Simplify `Share` button Signed-off-by: Šimon Brandner * Add proper `Copy link` button Signed-off-by: Šimon Brandner * i18n Signed-off-by: Šimon Brandner --- .../context_menus/MessageContextMenu.tsx | 52 ++++++++++++------- src/components/views/rooms/EventTile.tsx | 29 ++++++----- src/i18n/strings/en_EN.json | 2 +- 3 files changed, 48 insertions(+), 35 deletions(-) diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index cf61ee5bfd..86e9d9cc30 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -70,8 +70,8 @@ interface IProps extends IPosition { rightClick?: boolean; // The Relations model from the JS SDK for reactions to `mxEvent` reactions?: Relations; - // A permalink to the event - showPermalink?: boolean; + // A permalink to this event or an href of an anchor element the user has clicked + link?: string; getRelationsForEvent?: GetRelationsForEvent; } @@ -227,7 +227,7 @@ export default class MessageContextMenu extends React.Component this.closeMenu(); }; - private onPermalinkClick = (e: React.MouseEvent): void => { + private onShareClick = (e: React.MouseEvent): void => { e.preventDefault(); Modal.createTrackedDialog('share room message dialog', '', ShareDialog, { target: this.props.mxEvent, @@ -236,9 +236,9 @@ export default class MessageContextMenu extends React.Component this.closeMenu(); }; - private onCopyPermalinkClick = (e: ButtonEvent): void => { + private onCopyLinkClick = (e: ButtonEvent): void => { e.preventDefault(); // So that we don't open the permalink - copyPlaintext(this.getPermalink()); + copyPlaintext(this.props.link); this.closeMenu(); }; @@ -295,11 +295,6 @@ export default class MessageContextMenu extends React.Component }); } - private getPermalink(): string { - if (!this.props.permalinkCreator) return; - return this.props.permalinkCreator.forEvent(this.props.mxEvent.getId()); - } - private getUnsentReactions(): MatrixEvent[] { return this.getReactions(e => e.status === EventStatus.NOT_SENT); } @@ -318,11 +313,11 @@ 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, collapseReplyChain } = this.props; + const { mxEvent, rightClick, link, eventTileOps, reactions, collapseReplyChain } = this.props; const eventStatus = mxEvent.status; const unsentReactionsCount = this.getUnsentReactions().length; const contentActionable = isContentActionable(mxEvent); - const permalink = this.getPermalink(); + const permalink = this.props.permalinkCreator?.forEvent(this.props.mxEvent.getId()); // status is SENT before remote-echo, null after const isSent = !eventStatus || eventStatus === EventStatus.SENT; const { timelineRenderingType, canReact, canSendMessages } = this.context; @@ -420,17 +415,13 @@ export default class MessageContextMenu extends React.Component if (permalink) { permalinkButton = ( ); } + let copyLinkButton: JSX.Element; + if (link) { + copyLinkButton = ( + + ); + } + let copyButton: JSX.Element; if (rightClick && getSelectedText()) { copyButton = ( @@ -566,10 +577,11 @@ export default class MessageContextMenu extends React.Component } let nativeItemsList: JSX.Element; - if (copyButton) { + if (copyButton || copyLinkButton) { nativeItemsList = ( { copyButton } + { copyLinkButton } ); } diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index e8f0199c5f..e54c0900a5 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -234,7 +234,7 @@ interface IState { // Position of the context menu contextMenu?: { position: Pick; - showPermalink?: boolean; + link?: string; }; isQuoteExpanded?: boolean; @@ -842,26 +842,27 @@ export class UnwrappedEventTile extends React.Component { }; private onTimestampContextMenu = (ev: React.MouseEvent): void => { - this.showContextMenu(ev, true); + this.showContextMenu(ev, this.props.permalinkCreator?.forEvent(this.props.mxEvent.getId())); }; - private showContextMenu(ev: React.MouseEvent, showPermalink?: boolean): void { + private showContextMenu(ev: React.MouseEvent, permalink?: string): void { + const clickTarget = ev.target as HTMLElement; + // Return if message right-click context menu isn't enabled if (!SettingsStore.getValue("feature_message_right_click_context_menu")) return; - // Return if we're in a browser and click either an a tag or we have - // selected text, as in those cases we want to use the native browser - // menu - const clickTarget = ev.target as HTMLElement; - if ( - !PlatformPeg.get().allowOverridingNativeContextMenus() && - (clickTarget.tagName === "a" || clickTarget.closest("a") || getSelectedText()) - ) return; + // Try to find an anchor element + const anchorElement = (clickTarget instanceof HTMLAnchorElement) ? clickTarget : clickTarget.closest("a"); // 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; + if (clickTarget instanceof HTMLImageElement) return; + + // Return if we're in a browser and click either an a tag or we have + // selected text, as in those cases we want to use the native browser + // menu + if (!PlatformPeg.get().allowOverridingNativeContextMenus() && (getSelectedText() || anchorElement)) return; // We don't want to show the menu when editing a message if (this.props.editState) return; @@ -875,7 +876,7 @@ export class UnwrappedEventTile extends React.Component { top: ev.clientY, bottom: ev.clientY, }, - showPermalink: showPermalink, + link: anchorElement?.href || permalink, }, actionBarFocused: true, }); @@ -924,7 +925,7 @@ export class UnwrappedEventTile extends React.Component { onFinished={this.onCloseMenu} rightClick={true} reactions={this.state.reactions} - showPermalink={this.state.contextMenu.showPermalink} + link={this.state.contextMenu.link} /> ); } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 4c61d944e8..aa4dcd2cdd 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2921,10 +2921,10 @@ "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", + "Copy link": "Copy link", "Forget": "Forget", "Mentions only": "Mentions only", "See room timeline (devtools)": "See room timeline (devtools)",