Enable the message right-click context menu in the browser (#8336)
* Enable the message right-click context menu in the browser Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> * Move `getSelectedText()` to `strings.ts` Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> * Move `canCancel()` to `EventUtils.ts` Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
This commit is contained in:
parent
1afecc474f
commit
11e0a3a8fa
5 changed files with 32 additions and 15 deletions
|
@ -37,7 +37,7 @@ import { ReadPinsEventId } from "../right_panel/types";
|
||||||
import { Action } from "../../../dispatcher/actions";
|
import { Action } from "../../../dispatcher/actions";
|
||||||
import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks';
|
import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks';
|
||||||
import { ButtonEvent } from '../elements/AccessibleButton';
|
import { ButtonEvent } from '../elements/AccessibleButton';
|
||||||
import { copyPlaintext } from '../../../utils/strings';
|
import { copyPlaintext, getSelectedText } from '../../../utils/strings';
|
||||||
import ContextMenu, { toRightOf } from '../../structures/ContextMenu';
|
import ContextMenu, { toRightOf } from '../../structures/ContextMenu';
|
||||||
import ReactionPicker from '../emojipicker/ReactionPicker';
|
import ReactionPicker from '../emojipicker/ReactionPicker';
|
||||||
import ViewSource from '../../structures/ViewSource';
|
import ViewSource from '../../structures/ViewSource';
|
||||||
|
@ -54,10 +54,6 @@ import { OpenForwardDialogPayload } from "../../../dispatcher/payloads/OpenForwa
|
||||||
import { OpenReportEventDialogPayload } from "../../../dispatcher/payloads/OpenReportEventDialogPayload";
|
import { OpenReportEventDialogPayload } from "../../../dispatcher/payloads/OpenReportEventDialogPayload";
|
||||||
import { createMapSiteLink } from '../../../utils/location';
|
import { createMapSiteLink } from '../../../utils/location';
|
||||||
|
|
||||||
export function canCancel(status: EventStatus): boolean {
|
|
||||||
return status === EventStatus.QUEUED || status === EventStatus.NOT_SENT || status === EventStatus.ENCRYPTING;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IEventTileOps {
|
export interface IEventTileOps {
|
||||||
isWidgetHidden(): boolean;
|
isWidgetHidden(): boolean;
|
||||||
unhideWidget(): void;
|
unhideWidget(): void;
|
||||||
|
@ -263,7 +259,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
||||||
};
|
};
|
||||||
|
|
||||||
private onCopyClick = (): void => {
|
private onCopyClick = (): void => {
|
||||||
copyPlaintext(this.getSelectedText());
|
copyPlaintext(getSelectedText());
|
||||||
this.closeMenu();
|
this.closeMenu();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -310,10 +306,6 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private getSelectedText(): string {
|
|
||||||
return window.getSelection().toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private getPermalink(): string {
|
private getPermalink(): string {
|
||||||
if (!this.props.permalinkCreator) return;
|
if (!this.props.permalinkCreator) return;
|
||||||
return this.props.permalinkCreator.forEvent(this.props.mxEvent.getId());
|
return this.props.permalinkCreator.forEvent(this.props.mxEvent.getId());
|
||||||
|
@ -539,7 +531,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rightClick && this.getSelectedText()) {
|
if (rightClick && getSelectedText()) {
|
||||||
copyButton = (
|
copyButton = (
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
iconClassName="mx_MessageContextMenu_iconCopy"
|
iconClassName="mx_MessageContextMenu_iconCopy"
|
||||||
|
|
|
@ -26,11 +26,11 @@ import type { Relations } from 'matrix-js-sdk/src/models/relations';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import dis from '../../../dispatcher/dispatcher';
|
import dis from '../../../dispatcher/dispatcher';
|
||||||
import ContextMenu, { aboveLeftOf, ContextMenuTooltipButton, useContextMenu } from '../../structures/ContextMenu';
|
import ContextMenu, { aboveLeftOf, ContextMenuTooltipButton, useContextMenu } from '../../structures/ContextMenu';
|
||||||
import { isContentActionable, canEditContent, editEvent } from '../../../utils/EventUtils';
|
import { isContentActionable, canEditContent, editEvent, canCancel } from '../../../utils/EventUtils';
|
||||||
import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext";
|
import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext";
|
||||||
import Toolbar from "../../../accessibility/Toolbar";
|
import Toolbar from "../../../accessibility/Toolbar";
|
||||||
import { RovingAccessibleTooltipButton, useRovingTabIndex } from "../../../accessibility/RovingTabIndex";
|
import { RovingAccessibleTooltipButton, useRovingTabIndex } from "../../../accessibility/RovingTabIndex";
|
||||||
import MessageContextMenu, { canCancel } from "../context_menus/MessageContextMenu";
|
import MessageContextMenu from "../context_menus/MessageContextMenu";
|
||||||
import Resend from "../../../Resend";
|
import Resend from "../../../Resend";
|
||||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||||
import { MediaEventHelper } from "../../../utils/MediaEventHelper";
|
import { MediaEventHelper } from "../../../utils/MediaEventHelper";
|
||||||
|
|
|
@ -70,7 +70,7 @@ import { RoomNotificationStateStore } from '../../../stores/notifications/RoomNo
|
||||||
import { NotificationStateEvents } from '../../../stores/notifications/NotificationState';
|
import { NotificationStateEvents } from '../../../stores/notifications/NotificationState';
|
||||||
import { NotificationColor } from '../../../stores/notifications/NotificationColor';
|
import { NotificationColor } from '../../../stores/notifications/NotificationColor';
|
||||||
import AccessibleButton, { ButtonEvent } from '../elements/AccessibleButton';
|
import AccessibleButton, { ButtonEvent } from '../elements/AccessibleButton';
|
||||||
import { copyPlaintext } from '../../../utils/strings';
|
import { copyPlaintext, getSelectedText } from '../../../utils/strings';
|
||||||
import { DecryptionFailureTracker } from '../../../DecryptionFailureTracker';
|
import { DecryptionFailureTracker } from '../../../DecryptionFailureTracker';
|
||||||
import RedactedBody from '../messages/RedactedBody';
|
import RedactedBody from '../messages/RedactedBody';
|
||||||
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
||||||
|
@ -947,13 +947,26 @@ export class UnwrappedEventTile extends React.Component<IProps, IState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
private showContextMenu(ev: React.MouseEvent, showPermalink?: boolean): void {
|
private showContextMenu(ev: React.MouseEvent, showPermalink?: boolean): void {
|
||||||
|
// Return if message right-click context menu isn't enabled
|
||||||
if (!SettingsStore.getValue("feature_message_right_click_context_menu")) return;
|
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;
|
||||||
|
|
||||||
// There is no way to copy non-PNG images into clipboard, so we can't
|
// 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
|
// have our own handling for copying images, so we leave it to the
|
||||||
// Electron layer (webcontents-handler.ts)
|
// Electron layer (webcontents-handler.ts)
|
||||||
if (ev.target instanceof HTMLImageElement) return;
|
if (ev.target instanceof HTMLImageElement) return;
|
||||||
if (!PlatformPeg.get().allowOverridingNativeContextMenus()) return;
|
|
||||||
|
// We don't want to show the menu when editing a message
|
||||||
if (this.props.editState) return;
|
if (this.props.editState) return;
|
||||||
|
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
this.setState({
|
this.setState({
|
||||||
|
|
|
@ -258,3 +258,7 @@ export function editEvent(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function canCancel(status: EventStatus): boolean {
|
||||||
|
return status === EventStatus.QUEUED || status === EventStatus.NOT_SENT || status === EventStatus.ENCRYPTING;
|
||||||
|
}
|
||||||
|
|
|
@ -84,3 +84,11 @@ const collator = new Intl.Collator();
|
||||||
export function compare(a: string, b: string): number {
|
export function compare(a: string, b: string): number {
|
||||||
return collator.compare(a, b);
|
return collator.compare(a, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns text which has been selected by the user
|
||||||
|
* @returns the selected text
|
||||||
|
*/
|
||||||
|
export function getSelectedText(): string {
|
||||||
|
return window.getSelection().toString();
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue