diff --git a/docs/en_US/release_notes_9_8.rst b/docs/en_US/release_notes_9_8.rst index 36707a89b..ac5effad2 100644 --- a/docs/en_US/release_notes_9_8.rst +++ b/docs/en_US/release_notes_9_8.rst @@ -37,4 +37,5 @@ Bug fixes | `Issue #9095 `_ - Fixed an issue where pgAdmin config migration was failing while upgrading to v9.7. | `Issue #9114 `_ - Fixed Cross-Origin Opener Policy (COOP) vulnerability in the OAuth 2.0 authentication flow (CVE-2025-9636). | `Issue #9116 `_ - Fixed an issue where editor shortcuts fail when using Option key combinations on macOS, due to macOS treating Option+Key as a different key input. - | `Issue #9125 `_ - Fixed an issue where the pgAdmin configuration database wasn't being created on a fresh install when an external database was used for the configuration. \ No newline at end of file + | `Issue #9125 `_ - Fixed an issue where the pgAdmin configuration database wasn't being created on a fresh install when an external database was used for the configuration. + | `Issue #9157 `_ - Fixed an issue where shortcuts are not working as expected on multiple keyboard layouts. diff --git a/web/package.json b/web/package.json index e9ee6fea8..299c34cc3 100644 --- a/web/package.json +++ b/web/package.json @@ -77,6 +77,7 @@ "@date-io/core": "^3.0.0", "@date-io/date-fns": "3.x", "@emotion/sheet": "^1.0.1", + "@fluentui/keyboard-key": "^0.4.23", "@fortawesome/fontawesome-free": "latest", "@mui/icons-material": "^7.3.2", "@mui/material": "^7.3.2", diff --git a/web/pgadmin/browser/register_editor_preferences.py b/web/pgadmin/browser/register_editor_preferences.py index 3046e40fd..32a881ebf 100644 --- a/web/pgadmin/browser/register_editor_preferences.py +++ b/web/pgadmin/browser/register_editor_preferences.py @@ -52,13 +52,13 @@ def register_editor_preferences(self): gettext('Replace'), 'keyboardshortcut', { - 'alt': True, + 'alt': False, 'shift': False, 'control': True, 'ctrl_is_meta': True, 'key': { - 'key_code': 70, - 'char': 'f' + 'key_code': 82, + 'char': 'r' } }, category_label=PREF_LABEL_KEYBOARD_SHORTCUTS, diff --git a/web/pgadmin/static/js/components/KeyboardShortcuts.jsx b/web/pgadmin/static/js/components/KeyboardShortcuts.jsx index f8a268c7b..8815a1002 100644 --- a/web/pgadmin/static/js/components/KeyboardShortcuts.jsx +++ b/web/pgadmin/static/js/components/KeyboardShortcuts.jsx @@ -13,6 +13,7 @@ import { InputText, ToggleCheckButton } from './FormComponents'; import PropTypes from 'prop-types'; import { isMac } from '../keyboard_shortcuts'; import gettext from 'sources/gettext'; +import { getCode } from '@fluentui/keyboard-key'; export default function KeyboardShortcuts({ value, onChange, fields, name }) { const keyCid = `key-${name}`; @@ -29,7 +30,7 @@ export default function KeyboardShortcuts({ value, onChange, fields, name }) { } newVal.key = { char: _val, - key_code: e.keyCode + key_code: getCode(e), }; onChange(newVal); }; diff --git a/web/pgadmin/static/js/components/ReactCodeMirror/components/Editor.jsx b/web/pgadmin/static/js/components/ReactCodeMirror/components/Editor.jsx index 34d50084b..943640e41 100644 --- a/web/pgadmin/static/js/components/ReactCodeMirror/components/Editor.jsx +++ b/web/pgadmin/static/js/components/ReactCodeMirror/components/Editor.jsx @@ -102,7 +102,6 @@ function handlePaste(e) { checkTrojanSource(copiedText, true); } - function insertTabWithUnit({ state, dispatch }) { if (state.selection.ranges.some(r => !r.empty)) return indentMore({ state, dispatch }); diff --git a/web/pgadmin/static/js/custom_hooks.js b/web/pgadmin/static/js/custom_hooks.js index 34bace819..6492be9db 100644 --- a/web/pgadmin/static/js/custom_hooks.js +++ b/web/pgadmin/static/js/custom_hooks.js @@ -10,6 +10,7 @@ import React, {useRef, useEffect, useState, useCallback, useLayoutEffect} from ' import moment from 'moment'; import { isMac } from './keyboard_shortcuts'; import { getBrowser } from './utils'; +import { getCode } from '@fluentui/keyboard-key'; /* React hook for setInterval */ export function useInterval(callback, delay) { @@ -185,7 +186,7 @@ export function useKeyboardShortcuts(shortcuts, eleRef) { const matchFound = (shortcut, e)=>{ if(!shortcut) return false; - let keyCode = e.which || e.keyCode; + let keyCode = getCode(e); const ctrlKey = (isMac() && shortcut.ctrl_is_meta) ? e.metaKey : e.ctrlKey; return Boolean(shortcut.alt) == e.altKey && @@ -193,9 +194,10 @@ export function useKeyboardShortcuts(shortcuts, eleRef) { Boolean(shortcut.control) == ctrlKey && shortcut.key.key_code == keyCode; }; + useEffect(()=>{ let ele = eleRef.current ?? document; - const keydownCallback = (e)=>{ + const dispatch = (e)=>{ Promise.resolve(0).then(()=>{ let allListeners = _.filter(shortcutsRef.current, (s)=>matchFound(s.shortcut, e)); for(const {options} of allListeners) { @@ -209,9 +211,11 @@ export function useKeyboardShortcuts(shortcuts, eleRef) { } }); }; - ele.addEventListener('keydown', keydownCallback); + ele.addEventListener('keydown', dispatch); + ele.addEventListener('keyup', dispatch); return ()=>{ - ele.removeEventListener('keydown', keydownCallback); + ele.removeEventListener('keydown', dispatch); + ele.removeEventListener('keyup', dispatch); }; }, [eleRef.current]); diff --git a/web/pgadmin/static/js/keyboard_shortcuts.js b/web/pgadmin/static/js/keyboard_shortcuts.js index 849eeb2a9..d811e06f6 100644 --- a/web/pgadmin/static/js/keyboard_shortcuts.js +++ b/web/pgadmin/static/js/keyboard_shortcuts.js @@ -80,29 +80,8 @@ function shortcut_accesskey_title(title, shortcut) { } -function _stopEventPropagation(event) { - event.cancelBubble = true; - event.preventDefault(); - event.stopPropagation(); - event.stopImmediatePropagation(); -} - -/* Function use to validate shortcut keys */ -function validateShortcutKeys(user_defined_shortcut, event) { - if(!user_defined_shortcut) { - return false; - } - - let keyCode = event.which || event.keyCode; - return user_defined_shortcut.alt == event.altKey && - user_defined_shortcut.shift == event.shiftKey && - user_defined_shortcut.control == event.ctrlKey && - user_defined_shortcut.key.key_code == keyCode; -} - export { - validateShortcutKeys, - _stopEventPropagation, isMac, isKeyCtrlAlt, isKeyAltShift, isKeyCtrlShift, + isMac, isKeyCtrlAlt, isKeyAltShift, isKeyCtrlShift, isKeyCtrlAltShift, isAltShiftBoth, isCtrlShiftBoth, isCtrlAltBoth, shortcut_key, shortcut_title, shortcut_accesskey_title, }; diff --git a/web/pgadmin/static/js/utils.js b/web/pgadmin/static/js/utils.js index a0c55842e..66a8e0f68 100644 --- a/web/pgadmin/static/js/utils.js +++ b/web/pgadmin/static/js/utils.js @@ -17,8 +17,9 @@ import usePreferences from '../../preferences/static/js/store'; import pgAdmin from 'sources/pgadmin'; import { isMac } from './keyboard_shortcuts'; import { WORKSPACES } from '../../browser/static/js/constants'; +import { getCode } from '@fluentui/keyboard-key'; -export function parseShortcutValue(obj, useKeyboardCode=false) { +export function parseShortcutValue(obj, useCode=false) { let shortcut = ''; if (!obj){ return null; @@ -27,11 +28,11 @@ export function parseShortcutValue(obj, useKeyboardCode=false) { if (obj.shift) { shortcut += 'shift+'; } if (isMac() && obj.ctrl_is_meta) { shortcut += 'meta+'; } else if (obj.control) { shortcut += 'ctrl+'; } - shortcut += useKeyboardCode ? shortcutCharToCode(obj?.key.char) : obj?.key.char?.toLowerCase(); + shortcut += useCode ? obj?.key.key_code : obj?.key.char?.toLowerCase(); return shortcut; } -export function parseKeyEventValue(e, useKeyboardCode=false) { +export function parseKeyEventValue(e, useCode=false) { let shortcut = ''; if(!e) { return null; @@ -40,7 +41,7 @@ export function parseKeyEventValue(e, useKeyboardCode=false) { if (e.shiftKey) { shortcut += 'shift+'; } if (isMac() && e.metaKey) { shortcut += 'meta+'; } else if (e.ctrlKey) { shortcut += 'ctrl+'; } - shortcut += useKeyboardCode? e.code : e.key.toLowerCase(); + shortcut += useCode? getCode(e) : e.key.toLowerCase(); return shortcut; } @@ -49,43 +50,6 @@ export function isShortcutValue(obj) { return [obj.alt, obj.control, obj?.key, obj?.key?.char].every((k)=>!_.isUndefined(k)); } -// Map shortcut character to key code -export function shortcutCharToCode(char) { - const punctuationMap = { - '`': 'Backquote', - '-': 'Minus', - '=': 'Equal', - '[': 'BracketLeft', - ']': 'BracketRight', - '\\': 'Backslash', - ';': 'Semicolon', - '\'': 'Quote', - ',': 'Comma', - '.': 'Period', - '/': 'Slash', - ' ': 'Space', - }; - - const mappedCode = punctuationMap[char.toLowerCase()]; - if (mappedCode) { - return mappedCode; - } - - // Fallback for alphanumeric keys (A-Z, 0-9) - const isAlphanumeric = /^[a-z0-9]$/i.test(char); - if (isAlphanumeric) { - if (char.length === 1 && /[a-zA-Z]/.test(char)) { - return `Key${char.toUpperCase()}`; - } - if (char.length === 1 && /[0-9]/.test(char)) { - return `Digit${char}`; - } - } - - return char; -} - - export function getEnterKeyHandler(clickHandler) { return (e)=>{ if(e.code === 'Enter'){ diff --git a/web/pgadmin/tools/sqleditor/static/js/components/sections/Query.jsx b/web/pgadmin/tools/sqleditor/static/js/components/sections/Query.jsx index 3b26bdb56..1ebedab62 100644 --- a/web/pgadmin/tools/sqleditor/static/js/components/sections/Query.jsx +++ b/web/pgadmin/tools/sqleditor/static/js/components/sections/Query.jsx @@ -15,7 +15,7 @@ import { LayoutDockerContext, LAYOUT_EVENTS } from '../../../../../../static/js/ import ConfirmSaveContent from '../../../../../../static/js/Dialogs/ConfirmSaveContent'; import gettext from 'sources/gettext'; import { isMac } from '../../../../../../static/js/keyboard_shortcuts'; -import { checkTrojanSource, isShortcutValue, parseKeyEventValue, parseShortcutValue, shortcutCharToCode } from '../../../../../../static/js/utils'; +import { checkTrojanSource, isShortcutValue, parseKeyEventValue, parseShortcutValue } from '../../../../../../static/js/utils'; import { usePgAdmin } from '../../../../../../static/js/PgAdminProvider'; import ConfirmPromotionContent from '../dialogs/ConfirmPromotionContent'; import ConfirmExecuteQueryContent from '../dialogs/ConfirmExecuteQueryContent'; @@ -296,7 +296,6 @@ export default function Query({onTextSelect, setQtStatePartial}) { // this function creates a key object from the shortcut preference let key = { keyCode: pref.key.key_code, - code: shortcutCharToCode(pref.key.char), metaKey: false, ctrlKey: pref.control, shiftKey: pref.shift, diff --git a/web/yarn.lock b/web/yarn.lock index e0cf60fa7..2ecf16371 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -1967,6 +1967,15 @@ __metadata: languageName: node linkType: hard +"@fluentui/keyboard-key@npm:^0.4.23": + version: 0.4.23 + resolution: "@fluentui/keyboard-key@npm:0.4.23" + dependencies: + tslib: "npm:^2.1.0" + checksum: 10c0/f4c159f40632387e227efc2d7f1a0b4ec088da2c2cc669bc0598b4dd3c00953dbc554d665961fd57bbb9952a5081ab7f39b2c67d4cfc46d0a2a70d54fc60def2 + languageName: node + linkType: hard + "@fortawesome/fontawesome-common-types@npm:6.7.2": version: 6.7.2 resolution: "@fortawesome/fontawesome-common-types@npm:6.7.2" @@ -12861,6 +12870,7 @@ __metadata: "@emotion/sheet": "npm:^1.0.1" "@emotion/styled": "npm:^11.11.0" "@emotion/utils": "npm:^1.0.0" + "@fluentui/keyboard-key": "npm:^0.4.23" "@fortawesome/fontawesome-free": "npm:latest" "@mui/icons-material": "npm:^7.3.2" "@mui/material": "npm:^7.3.2" @@ -14351,7 +14361,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.0.3, tslib@npm:^2.4.0, tslib@npm:^2.7.0": +"tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.4.0, tslib@npm:^2.7.0": version: 2.8.1 resolution: "tslib@npm:2.8.1" checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62