Adds keyboard shortcut to open context menu on messages

This commit is contained in:
Josh Perez 2023-06-21 09:54:05 -07:00 committed by GitHub
parent 498205b964
commit 7247c2d674
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 114 additions and 14 deletions

View file

@ -2934,6 +2934,10 @@
"messageformat": "Mark selected text as a spoiler",
"description": "Description of command to bold text in composer"
},
"icu:Keyboard--open-context-menu": {
"messageformat": "Open context menu for selected message",
"description": "Shown in shortcuts guide"
},
"icu:FormatMenu--guide--bold": {
"messageformat": "Bold",
"description": "Shown when you hover over the bold button in the popup formatting menu"

View file

@ -24,6 +24,8 @@ type KeyType =
| 'tab'
| 'ctrl'
| 'F6'
| 'F10'
| 'F12'
| '↑'
| '↓'
| ','
@ -53,8 +55,17 @@ type KeyType =
type ShortcutType = {
id: string;
description: string;
keys: Array<Array<KeyType>>;
};
} & (
| {
keys: Array<Array<KeyType>>;
}
| {
keysByPlatform: {
macOS: Array<Array<KeyType>>;
other: Array<Array<KeyType>>;
};
}
);
function getNavigationShortcuts(i18n: LocalizerType): Array<ShortcutType> {
return [
@ -217,6 +228,14 @@ function getMessageShortcuts(i18n: LocalizerType): Array<ShortcutType> {
description: i18n('icu:Keyboard--forward-messages'),
keys: [['commandOrCtrl', 'shift', 'S']],
},
{
id: 'Keyboard--open-context-menu',
description: i18n('icu:Keyboard--open-context-menu'),
keysByPlatform: {
macOS: [['commandOrCtrl', 'F12']],
other: [['shift', 'F10']],
},
},
];
}
@ -439,13 +458,23 @@ function renderShortcut(
isMacOS: boolean,
i18n: LocalizerType
) {
let keysToRender: Array<Array<KeyType>> = [];
if ('keys' in shortcut) {
keysToRender = shortcut.keys;
} else if ('keysByPlatform' in shortcut) {
keysToRender = isMacOS
? shortcut.keysByPlatform.macOS
: shortcut.keysByPlatform.other;
}
return (
<div key={index} className="module-shortcut-guide__shortcut">
<div className="module-shortcut-guide__shortcut__description">
{shortcut.description}
</div>
<div className="module-shortcut-guide__shortcut__key-container">
{shortcut.keys.map(keys => (
{keysToRender.map(keys => (
<div
key={`${shortcut.description}--${keys.map(k => k).join('-')}`}
className="module-shortcut-guide__shortcut__key-inner-container"

View file

@ -26,7 +26,11 @@ import type {
import type { PushPanelForConversationActionType } from '../../state/ducks/conversations';
import { doesMessageBodyOverflow } from './MessageBodyReadMore';
import type { Props as ReactionPickerProps } from './ReactionPicker';
import { useToggleReactionPicker } from '../../hooks/useKeyboardShortcuts';
import {
useKeyboardShortcutsConditionally,
useOpenContextMenu,
useToggleReactionPicker,
} from '../../hooks/useKeyboardShortcuts';
import { PanelType } from '../../types/Panels';
import type { DeleteMessagesPropsType } from '../../state/ducks/globalModals';
@ -73,7 +77,9 @@ export type Props = PropsData &
};
type Trigger = {
handleContextClick: (event: React.MouseEvent<HTMLDivElement>) => void;
handleContextClick: (
event: React.MouseEvent<HTMLDivElement> | MouseEvent
) => void;
};
/**
@ -265,15 +271,21 @@ export function TimelineMessage(props: Props): JSX.Element {
handleReact || noop
);
useEffect(() => {
if (isTargeted) {
document.addEventListener('keydown', toggleReactionPickerKeyboard);
const handleOpenContextMenu = useCallback(() => {
if (!menuTriggerRef.current) {
return;
}
const event = new MouseEvent('click');
menuTriggerRef.current.handleContextClick(event);
}, []);
return () => {
document.removeEventListener('keydown', toggleReactionPickerKeyboard);
};
}, [isTargeted, toggleReactionPickerKeyboard]);
const openContextMenuKeyboard = useOpenContextMenu(handleOpenContextMenu);
useKeyboardShortcutsConditionally(
Boolean(isTargeted),
openContextMenuKeyboard,
toggleReactionPickerKeyboard
);
const renderMenu = useCallback(() => {
return (

View file

@ -234,6 +234,39 @@ export function useToggleReactionPicker(
);
}
export function useOpenContextMenu(
openContextMenu: () => unknown
): KeyboardShortcutHandlerType {
const hasOverlay = useHasAnyOverlay();
return useCallback(
ev => {
if (hasOverlay) {
return false;
}
const { shiftKey } = ev;
const key = KeyboardLayout.lookup(ev);
const isMacOS = get(window, 'platform') === 'darwin';
if (
(!isMacOS && shiftKey && key === 'F10') ||
(isMacOS && isCmdOrCtrl(ev) && key === 'F12')
) {
ev.preventDefault();
ev.stopPropagation();
openContextMenu();
return true;
}
return false;
},
[hasOverlay, openContextMenu]
);
}
export function useEditLastMessageSent(
maybeEditMessage: () => boolean
): KeyboardShortcutHandlerType {
@ -277,3 +310,23 @@ export function useKeyboardShortcuts(
};
}, [eventHandlers]);
}
export function useKeyboardShortcutsConditionally(
condition: boolean,
...eventHandlers: Array<KeyboardShortcutHandlerType>
): void {
useEffect(() => {
if (!condition) {
return;
}
function handleKeydown(ev: KeyboardEvent): void {
eventHandlers.some(eventHandler => eventHandler(ev));
}
document.addEventListener('keydown', handleKeydown);
return () => {
document.removeEventListener('keydown', handleKeydown);
};
}, [condition, eventHandlers]);
}

View file

@ -11,11 +11,13 @@ import { getQuotedMessageSelector } from '../state/selectors/composer';
import { removeLinkPreview } from './LinkPreview';
export function addGlobalKeyboardShortcuts(): void {
const isMacOS = window.platform === 'darwin';
document.addEventListener('keydown', event => {
const { ctrlKey, metaKey, shiftKey, altKey } = event;
const commandKey = window.platform === 'darwin' && metaKey;
const controlKey = window.platform !== 'darwin' && ctrlKey;
const commandKey = isMacOS && metaKey;
const controlKey = !isMacOS && ctrlKey;
const commandOrCtrl = commandKey || controlKey;
const state = window.reduxStore.getState();