Fixed an issue where shortcuts are not working as expected on multiple keyboard layouts. #9157

pull/9177/head
Aditya Toshniwal 2025-09-17 11:34:15 +05:30 committed by GitHub
parent e5590797c0
commit 7ff3c4bbeb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 34 additions and 76 deletions

View File

@ -37,4 +37,5 @@ Bug fixes
| `Issue #9095 <https://github.com/pgadmin-org/pgadmin4/issues/9095>`_ - Fixed an issue where pgAdmin config migration was failing while upgrading to v9.7.
| `Issue #9114 <https://github.com/pgadmin-org/pgadmin4/issues/9114>`_ - Fixed Cross-Origin Opener Policy (COOP) vulnerability in the OAuth 2.0 authentication flow (CVE-2025-9636).
| `Issue #9116 <https://github.com/pgadmin-org/pgadmin4/issues/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 <https://github.com/pgadmin-org/pgadmin4/issues/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 #9125 <https://github.com/pgadmin-org/pgadmin4/issues/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 <https://github.com/pgadmin-org/pgadmin4/issues/9157>`_ - Fixed an issue where shortcuts are not working as expected on multiple keyboard layouts.

View File

@ -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",

View File

@ -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,

View File

@ -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);
};

View File

@ -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 });

View File

@ -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]);

View File

@ -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,
};

View File

@ -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'){

View File

@ -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,

View File

@ -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