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 (
+
+
+ {keys.map((keyName, idx) => (
+
+ {idx > 0 && visualJoiners[joiner]}
+ {keyName}
+
+ ))}
+
+ {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 }) => {
|
|
+
+
+ |