Adds keyboard shortcut to open context menu on messages
This commit is contained in:
parent
498205b964
commit
7247c2d674
5 changed files with 114 additions and 14 deletions
|
@ -2934,6 +2934,10 @@
|
||||||
"messageformat": "Mark selected text as a spoiler",
|
"messageformat": "Mark selected text as a spoiler",
|
||||||
"description": "Description of command to bold text in composer"
|
"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": {
|
"icu:FormatMenu--guide--bold": {
|
||||||
"messageformat": "Bold",
|
"messageformat": "Bold",
|
||||||
"description": "Shown when you hover over the bold button in the popup formatting menu"
|
"description": "Shown when you hover over the bold button in the popup formatting menu"
|
||||||
|
|
|
@ -24,6 +24,8 @@ type KeyType =
|
||||||
| 'tab'
|
| 'tab'
|
||||||
| 'ctrl'
|
| 'ctrl'
|
||||||
| 'F6'
|
| 'F6'
|
||||||
|
| 'F10'
|
||||||
|
| 'F12'
|
||||||
| '↑'
|
| '↑'
|
||||||
| '↓'
|
| '↓'
|
||||||
| ','
|
| ','
|
||||||
|
@ -53,8 +55,17 @@ type KeyType =
|
||||||
type ShortcutType = {
|
type ShortcutType = {
|
||||||
id: string;
|
id: string;
|
||||||
description: 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> {
|
function getNavigationShortcuts(i18n: LocalizerType): Array<ShortcutType> {
|
||||||
return [
|
return [
|
||||||
|
@ -217,6 +228,14 @@ function getMessageShortcuts(i18n: LocalizerType): Array<ShortcutType> {
|
||||||
description: i18n('icu:Keyboard--forward-messages'),
|
description: i18n('icu:Keyboard--forward-messages'),
|
||||||
keys: [['commandOrCtrl', 'shift', 'S']],
|
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,
|
isMacOS: boolean,
|
||||||
i18n: LocalizerType
|
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 (
|
return (
|
||||||
<div key={index} className="module-shortcut-guide__shortcut">
|
<div key={index} className="module-shortcut-guide__shortcut">
|
||||||
<div className="module-shortcut-guide__shortcut__description">
|
<div className="module-shortcut-guide__shortcut__description">
|
||||||
{shortcut.description}
|
{shortcut.description}
|
||||||
</div>
|
</div>
|
||||||
<div className="module-shortcut-guide__shortcut__key-container">
|
<div className="module-shortcut-guide__shortcut__key-container">
|
||||||
{shortcut.keys.map(keys => (
|
{keysToRender.map(keys => (
|
||||||
<div
|
<div
|
||||||
key={`${shortcut.description}--${keys.map(k => k).join('-')}`}
|
key={`${shortcut.description}--${keys.map(k => k).join('-')}`}
|
||||||
className="module-shortcut-guide__shortcut__key-inner-container"
|
className="module-shortcut-guide__shortcut__key-inner-container"
|
||||||
|
|
|
@ -26,7 +26,11 @@ import type {
|
||||||
import type { PushPanelForConversationActionType } from '../../state/ducks/conversations';
|
import type { PushPanelForConversationActionType } from '../../state/ducks/conversations';
|
||||||
import { doesMessageBodyOverflow } from './MessageBodyReadMore';
|
import { doesMessageBodyOverflow } from './MessageBodyReadMore';
|
||||||
import type { Props as ReactionPickerProps } from './ReactionPicker';
|
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 { PanelType } from '../../types/Panels';
|
||||||
import type { DeleteMessagesPropsType } from '../../state/ducks/globalModals';
|
import type { DeleteMessagesPropsType } from '../../state/ducks/globalModals';
|
||||||
|
|
||||||
|
@ -73,7 +77,9 @@ export type Props = PropsData &
|
||||||
};
|
};
|
||||||
|
|
||||||
type Trigger = {
|
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
|
handleReact || noop
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
const handleOpenContextMenu = useCallback(() => {
|
||||||
if (isTargeted) {
|
if (!menuTriggerRef.current) {
|
||||||
document.addEventListener('keydown', toggleReactionPickerKeyboard);
|
return;
|
||||||
}
|
}
|
||||||
|
const event = new MouseEvent('click');
|
||||||
|
menuTriggerRef.current.handleContextClick(event);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return () => {
|
const openContextMenuKeyboard = useOpenContextMenu(handleOpenContextMenu);
|
||||||
document.removeEventListener('keydown', toggleReactionPickerKeyboard);
|
|
||||||
};
|
useKeyboardShortcutsConditionally(
|
||||||
}, [isTargeted, toggleReactionPickerKeyboard]);
|
Boolean(isTargeted),
|
||||||
|
openContextMenuKeyboard,
|
||||||
|
toggleReactionPickerKeyboard
|
||||||
|
);
|
||||||
|
|
||||||
const renderMenu = useCallback(() => {
|
const renderMenu = useCallback(() => {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -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(
|
export function useEditLastMessageSent(
|
||||||
maybeEditMessage: () => boolean
|
maybeEditMessage: () => boolean
|
||||||
): KeyboardShortcutHandlerType {
|
): KeyboardShortcutHandlerType {
|
||||||
|
@ -277,3 +310,23 @@ export function useKeyboardShortcuts(
|
||||||
};
|
};
|
||||||
}, [eventHandlers]);
|
}, [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]);
|
||||||
|
}
|
||||||
|
|
|
@ -11,11 +11,13 @@ import { getQuotedMessageSelector } from '../state/selectors/composer';
|
||||||
import { removeLinkPreview } from './LinkPreview';
|
import { removeLinkPreview } from './LinkPreview';
|
||||||
|
|
||||||
export function addGlobalKeyboardShortcuts(): void {
|
export function addGlobalKeyboardShortcuts(): void {
|
||||||
|
const isMacOS = window.platform === 'darwin';
|
||||||
|
|
||||||
document.addEventListener('keydown', event => {
|
document.addEventListener('keydown', event => {
|
||||||
const { ctrlKey, metaKey, shiftKey, altKey } = event;
|
const { ctrlKey, metaKey, shiftKey, altKey } = event;
|
||||||
|
|
||||||
const commandKey = window.platform === 'darwin' && metaKey;
|
const commandKey = isMacOS && metaKey;
|
||||||
const controlKey = window.platform !== 'darwin' && ctrlKey;
|
const controlKey = !isMacOS && ctrlKey;
|
||||||
const commandOrCtrl = commandKey || controlKey;
|
const commandOrCtrl = commandKey || controlKey;
|
||||||
|
|
||||||
const state = window.reduxStore.getState();
|
const state = window.reduxStore.getState();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue