diff --git a/src/app.tsx b/src/app.tsx index e4b2f439..b82e1b4c 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -34,6 +34,7 @@ import { WithDialogs } from "dialogs"; import { useInit, usePageLocation } from "hooks"; import { superuser } from "superuser"; +import { testIsAppleDevice } from "./common.ts"; import { FilesBreadcrumbs } from "./files-breadcrumbs.tsx"; import { FilesFolderView } from "./files-folder-view.tsx"; import filetype_data from './filetype-data'; // eslint-disable-line import/extensions @@ -146,8 +147,10 @@ export const Application = () => { ); useInit(() => { + const isApple = testIsAppleDevice(); + const onKeyboardNav = (e: KeyboardEvent) => { - if (e.key === "L" && e.ctrlKey && !e.altKey) { + if (e.key === "L" && (isApple ? e.metaKey : e.ctrlKey) && !e.altKey) { e.preventDefault(); document.dispatchEvent(new Event("manual-change-dir")); } diff --git a/src/common.ts b/src/common.ts index 43fb12b7..bebb2347 100644 --- a/src/common.ts +++ b/src/common.ts @@ -51,3 +51,7 @@ export function * map_permissions(func: (value: number, label: string) => T) yield func(value, label); } } + +export function testIsAppleDevice() { + return /Mac|iPhone|iPad|iPod/.test(navigator.platform); +} diff --git a/src/dialogs/keyboardShortcutsHelp.tsx b/src/dialogs/keyboardShortcutsHelp.tsx index 8dbebe1b..b13ec8d8 100644 --- a/src/dialogs/keyboardShortcutsHelp.tsx +++ b/src/dialogs/keyboardShortcutsHelp.tsx @@ -11,9 +11,15 @@ import { Flex, } from "@patternfly/react-core/dist/esm/layouts/Flex"; import cockpit from 'cockpit'; import { DialogResult, Dialogs } from 'dialogs'; +import { testIsAppleDevice } from '../common.ts'; + const _ = cockpit.gettext; const KeyboardShortcutsHelp = ({ dialogResult } : { dialogResult: DialogResult }) => { + const isApple = testIsAppleDevice(); + const ctrlString = isApple ? "Command" : "Ctrl"; + const altString = isApple ? "Option" : "Alt"; + const footer = ( ); @@ -34,25 +40,25 @@ const KeyboardShortcutsHelp = ({ dialogResult } : { dialogResult: DialogResult = [ [ - Alt + {'\u{2191}'} + {altString} + {'\u{2191}'} , _("Go up a directory"), "go-up", ], [ - Alt + {'\u{2190}'} + {altString} + {'\u{2190}'} , _("Go back"), "go-back", ], [ - Alt + {'\u{2192}'} + {altString} + {'\u{2192}'} , _("Go forward"), "go-forward", ], [ - Alt + {'\u{2193}'} + {altString} + {'\u{2193}'} , _("Activate selected item, enter directory"), "activate", @@ -64,7 +70,7 @@ const KeyboardShortcutsHelp = ({ dialogResult } : { dialogResult: DialogResult - Ctrl + + {ctrlString} + Shift + L , @@ -87,19 +93,19 @@ const KeyboardShortcutsHelp = ({ dialogResult } : { dialogResult: DialogResult - Ctrl + C + {ctrlString} + C , _("Copy selected file or directory"), "copy", ], [ - Ctrl + V + {ctrlString} + V , _("Paste file or directory"), "paste", ], [ - Ctrl + A + {ctrlString} + A , _("Select all"), "select-all", diff --git a/src/files-card-body.tsx b/src/files-card-body.tsx index 1a2328de..1480c7dc 100644 --- a/src/files-card-body.tsx +++ b/src/files-card-body.tsx @@ -36,7 +36,7 @@ import { useDialogs } from "dialogs"; import * as timeformat from "timeformat"; import { FolderFileInfo, useFilesContext } from "./app.tsx"; -import { get_permissions } from "./common.ts"; +import { get_permissions, testIsAppleDevice } from "./common.ts"; import { confirm_delete } from "./dialogs/delete.tsx"; import { show_create_directory_dialog } from "./dialogs/mkdir.tsx"; import { show_rename_dialog } from "./dialogs/rename.tsx"; @@ -207,6 +207,7 @@ export const FilesCardBody = ({ useEffect(() => { let folderViewElem = null; + const isApple = testIsAppleDevice(); const resetSelected = (e: MouseEvent) => { if ((e.target instanceof HTMLElement)) { @@ -273,14 +274,16 @@ export const FilesCardBody = ({ } }; - const hasNoKeydownModifiers = (event: KeyboardEvent) => { - return !event.shiftKey && !event.ctrlKey && !event.altKey && !event.metaKey; + const hasNoKeydownModifiers = (event: KeyboardEvent, ctrlKey: boolean) => { + return !event.shiftKey && !ctrlKey && !event.altKey && !event.metaKey; }; const onKeyboardNav = (e: KeyboardEvent) => { + const ctrlKey = isApple ? e.metaKey : e.ctrlKey; + switch (e.key) { case "ArrowRight": - if (hasNoKeydownModifiers(e)) { + if (hasNoKeydownModifiers(e, ctrlKey)) { setSelected(_selected => { const firstSelectedName = _selected?.[0]?.name; const selectedIdx = sortedFiles?.findIndex(file => file.name === firstSelectedName); @@ -294,7 +297,7 @@ export const FilesCardBody = ({ break; case "ArrowLeft": - if (hasNoKeydownModifiers(e)) { + if (hasNoKeydownModifiers(e, ctrlKey)) { setSelected(_selected => { const firstSelectedName = _selected?.[0]?.name; const selectedIdx = sortedFiles?.findIndex(file => file.name === firstSelectedName); @@ -308,9 +311,9 @@ export const FilesCardBody = ({ break; case "ArrowUp": - if (e.altKey && !e.shiftKey && !e.ctrlKey) { + if (e.altKey && !e.shiftKey && !ctrlKey) { goUpOneDir(); - } else if (hasNoKeydownModifiers(e)) { + } else if (hasNoKeydownModifiers(e, ctrlKey)) { setSelected(_selected => { const firstSelectedName = _selected?.[0]?.name; const selectedIdx = sortedFiles?.findIndex(file => file.name === firstSelectedName); @@ -322,9 +325,9 @@ export const FilesCardBody = ({ break; case "ArrowDown": - if (e.altKey && !e.shiftKey && !e.ctrlKey && selected.length === 1) { + if (e.altKey && !e.shiftKey && !ctrlKey && selected.length === 1) { onDoubleClickNavigate(selected[0]); - } else if (hasNoKeydownModifiers(e)) { + } else if (hasNoKeydownModifiers(e, ctrlKey)) { setSelected(_selected => { const firstSelectedName = _selected?.[0]?.name; const selectedIdx = sortedFiles?.findIndex(file => file.name === firstSelectedName); @@ -336,26 +339,26 @@ export const FilesCardBody = ({ break; case "Enter": - if (hasNoKeydownModifiers(e) && selected.length === 1) { + if (hasNoKeydownModifiers(e, ctrlKey) && selected.length === 1) { onDoubleClickNavigate(selected[0]); } break; case "Delete": - if (hasNoKeydownModifiers(e) && selected.length !== 0) { + if (hasNoKeydownModifiers(e, ctrlKey) && selected.length !== 0) { confirm_delete(dialogs, path, selected, setSelected); } break; case "F2": - if (hasNoKeydownModifiers(e) && selected.length === 1) { + if (hasNoKeydownModifiers(e, ctrlKey) && selected.length === 1) { show_rename_dialog(dialogs, path, selected[0]); } break; case "a": // Keep standard text editing behavior by excluding input fields - if (e.ctrlKey && !e.shiftKey && !e.altKey && !(e.target instanceof HTMLInputElement)) { + if (ctrlKey && !e.shiftKey && !e.altKey && !(e.target instanceof HTMLInputElement)) { e.preventDefault(); setSelected(sortedFiles); } @@ -363,7 +366,7 @@ export const FilesCardBody = ({ case "c": // Keep standard text editing behavior by excluding input fields - if (e.ctrlKey && !e.shiftKey && !e.altKey && !(e.target instanceof HTMLInputElement)) { + if (ctrlKey && !e.shiftKey && !e.altKey && !(e.target instanceof HTMLInputElement)) { e.preventDefault(); setClipboard(selected.map(s => path + s.name)); } @@ -371,14 +374,14 @@ export const FilesCardBody = ({ case "v": // Keep standard text editing behavior by excluding input fields - if (e.ctrlKey && !e.shiftKey && !e.altKey && !(e.target instanceof HTMLInputElement)) { + if (ctrlKey && !e.shiftKey && !e.altKey && !(e.target instanceof HTMLInputElement)) { e.preventDefault(); pasteFromClipboard(clipboard, cwdInfo, path, addAlert); } break; case "N": - if (!e.ctrlKey && !e.altKey) { + if (!ctrlKey && !e.altKey) { e.preventDefault(); show_create_directory_dialog(dialogs, path); }