diff --git a/packages/nicolium/src/locales/en.json b/packages/nicolium/src/locales/en.json index c04f17633..dd5bf1b81 100644 --- a/packages/nicolium/src/locales/en.json +++ b/packages/nicolium/src/locales/en.json @@ -1282,6 +1282,7 @@ "join_event.request_success": "Requested to join the event", "join_event.success": "Joined the event", "join_event.title": "Join event", + "keyboard_shortcuts.action": "Action", "keyboard_shortcuts.back": "to navigate back", "keyboard_shortcuts.blocked": "to open blocked users list", "keyboard_shortcuts.boost": "to repost", @@ -1293,6 +1294,17 @@ "keyboard_shortcuts.heading": "Keyboard shortcuts", "keyboard_shortcuts.home": "to open home timeline", "keyboard_shortcuts.hotkey": "Hotkey", + "keyboard_shortcuts.joiners.or": "or", + "keyboard_shortcuts.joiners.plus": "plus", + "keyboard_shortcuts.joiners.then": "then", + "keyboard_shortcuts.key_names.alt": "Alt", + "keyboard_shortcuts.key_names.backspace": "Backspace", + "keyboard_shortcuts.key_names.down": "Arrow down", + "keyboard_shortcuts.key_names.enter": "Enter", + "keyboard_shortcuts.key_names.esc": "Escape", + "keyboard_shortcuts.key_names.question_mark": "Question mark", + "keyboard_shortcuts.key_names.slash": "Slash", + "keyboard_shortcuts.key_names.up": "Arrow up", "keyboard_shortcuts.legend": "to display this legend", "keyboard_shortcuts.mention": "to mention author", "keyboard_shortcuts.muted": "to open muted users list", diff --git a/packages/nicolium/src/modals/hotkeys-modal.tsx b/packages/nicolium/src/modals/hotkeys-modal.tsx index 72b3277a3..d6f9d297e 100644 --- a/packages/nicolium/src/modals/hotkeys-modal.tsx +++ b/packages/nicolium/src/modals/hotkeys-modal.tsx @@ -1,6 +1,6 @@ import clsx from 'clsx'; import React from 'react'; -import { FormattedMessage } from 'react-intl'; +import { defineMessages, FormattedMessage, type MessageDescriptor, useIntl } from 'react-intl'; import Modal from '@/components/ui/modal'; import { useFeatures } from '@/hooks/use-features'; @@ -8,12 +8,88 @@ import { useLoggedIn } from '@/hooks/use-logged-in'; import type { BaseModalProps } from '@/features/ui/components/modal-root'; +const messages = defineMessages({ + keyNameSlash: { id: 'keyboard_shortcuts.key_names.slash', defaultMessage: 'Slash' }, + keyNameQuestionMark: { + id: 'keyboard_shortcuts.key_names.question_mark', + defaultMessage: 'Question mark', + }, + keyNameAlt: { id: 'keyboard_shortcuts.key_names.alt', defaultMessage: 'Alt' }, + keyNameBackspace: { id: 'keyboard_shortcuts.key_names.backspace', defaultMessage: 'Backspace' }, + keyNameDown: { id: 'keyboard_shortcuts.key_names.down', defaultMessage: 'Arrow down' }, + keyNameEnter: { id: 'keyboard_shortcuts.key_names.enter', defaultMessage: 'Enter' }, + keyNameEsc: { id: 'keyboard_shortcuts.key_names.esc', defaultMessage: 'Escape' }, + keyNameUp: { id: 'keyboard_shortcuts.key_names.up', defaultMessage: 'Arrow up' }, + joinerOr: { id: 'keyboard_shortcuts.joiners.or', defaultMessage: 'or' }, + joinerPlus: { id: 'keyboard_shortcuts.joiners.plus', defaultMessage: 'plus' }, + joinerThen: { id: 'keyboard_shortcuts.joiners.then', defaultMessage: 'then' }, +}); + const Hotkey: React.FC<{ children: React.ReactNode }> = ({ children }) => ( {children} ); +const spokenKeyNames: Record = { + '/': messages.keyNameSlash, + '?': messages.keyNameQuestionMark, + alt: messages.keyNameAlt, + backspace: messages.keyNameBackspace, + down: messages.keyNameDown, + enter: messages.keyNameEnter, + esc: messages.keyNameEsc, + up: messages.keyNameUp, +}; + +const getSpokenKeyName = (keyName: string) => { + if (spokenKeyNames[keyName]) return spokenKeyNames[keyName]; + if (/^[a-z]$/i.test(keyName)) return keyName.toUpperCase(); + return keyName; +}; + +type KeyJoiner = 'or' | 'plus' | 'then'; + +const visualJoiners: Record = { + or: ', ', + plus: ' + ', + then: ' + ', +}; + +const spokenJoiners: Record = { + or: messages.joinerOr, + plus: messages.joinerPlus, + then: messages.joinerThen, +}; + +const HotkeyBinding: React.FC<{ keys: string[]; joiner?: KeyJoiner }> = ({ + keys, + joiner = 'or', +}) => { + const intl = useIntl(); + + const spokenBinding = keys + .map((keyName) => { + const spokenKey = getSpokenKeyName(keyName); + return typeof spokenKey === 'string' ? spokenKey : intl.formatMessage(spokenKey); + }) + .join(` ${intl.formatMessage(spokenJoiners[joiner])} `); + + return ( + + + {spokenBinding} + + ); +}; + const TableCell: React.FC<{ className?: string; children: React.ReactNode }> = ({ className, children, @@ -41,17 +117,17 @@ const HotkeysModal: React.FC = ({ onClose }) => { const hotkeys = [ isLoggedIn && { - key: r, + key: , label: , }, isLoggedIn && { - key: m, + key: , label: ( ), }, { - key: p, + key: , label: ( = ({ onClose }) => { ), }, isLoggedIn && { - key: f, + key: , label: , }, isLoggedIn && features.emojiReacts && { - key: e, + key: , label: , }, isLoggedIn && { - key: b, + key: , label: , }, { - key: ( - <> - enter, o - - ), + key: , label: , }, { - key: a, + key: , label: , }, features.spoilers && { - key: x, + key: , label: ( = ({ onClose }) => { ), }, features.spoilers && { - key: h, + key: , label: ( = ({ onClose }) => { ), }, { - key: ( - <> - up, k - - ), + key: , label: ( ), }, { - key: ( - <> - down, j - - ), + key: , label: ( ), }, isLoggedIn && { - key: n, + key: , label: ( = ({ onClose }) => { ), }, isLoggedIn && { - key: ( - <> - alt + n - - ), + key: , label: , }, { - key: backspace, + key: , label: , }, isLoggedIn && { - key: ( - <> - s, / - - ), + key: , label: document.querySelector('#search') ? ( ) : ( @@ -159,7 +215,7 @@ const HotkeysModal: React.FC = ({ onClose }) => { ), }, { - key: esc, + key: , label: ( = ({ onClose }) => { ), }, isLoggedIn && { - key: ( - <> - g + h - - ), + key: , label: ( ), }, isLoggedIn && { - key: ( - <> - g + n - - ), + key: , label: ( = ({ onClose }) => { ), }, isLoggedIn && { - key: ( - <> - g + f - - ), + key: , label: ( ), }, isLoggedIn && { - key: ( - <> - g + u - - ), + key: , label: ( = ({ onClose }) => { ), }, isLoggedIn && { - key: ( - <> - g + b - - ), + key: , label: ( = ({ onClose }) => { ), }, isLoggedIn && { - key: ( - <> - g + m - - ), + key: , label: ( ), }, isLoggedIn && features.followRequests && { - key: ( - <> - g + r - - ), + key: , label: ( = ({ onClose }) => { ), }, { - key: ?, + key: , label: ( ), @@ -291,6 +319,9 @@ const HotkeysModal: React.FC = ({ onClose }) => { + + +