fix(editor): Make keyboard shortcuts more strict; don't accept extra Ctrl/Alt/Shift keys (#8024)

## Summary
Keyboard shortcuts such as Shift+S get triggered even when other keys
(Ctrl/Alt) are pressed, creating conflicts with browser or OS shortcuts.

This PR makes keyboard shortcuts more strict: the exact combination
needs to be pressed, no extra keys allowed.
--> Ctrl+Shift+S will no longer trigger the Shift+S shortcut to add a
sticky note

## Related tickets and issues
https://n8nio.slack.com/archives/C035KBDA917/p1702545933647959
This commit is contained in:
Elias Meire 2023-12-20 12:06:49 +01:00 committed by GitHub
parent 81994ce13d
commit 8df49e134d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 35 additions and 25 deletions

View file

@ -1,3 +1,4 @@
import { META_KEY } from '../constants';
import { WorkflowPage as WorkflowPageClass } from '../pages/workflow'; import { WorkflowPage as WorkflowPageClass } from '../pages/workflow';
import { getPopper } from '../utils'; import { getPopper } from '../utils';
import { Interception } from 'cypress/types/net-stubbing'; import { Interception } from 'cypress/types/net-stubbing';
@ -35,6 +36,14 @@ describe('Canvas Actions', () => {
workflowPage.actions.addStickyFromContextMenu(); workflowPage.actions.addStickyFromContextMenu();
workflowPage.actions.hitAddStickyShortcut(); workflowPage.actions.hitAddStickyShortcut();
workflowPage.getters.stickies().should('have.length', 3);
// Should not add a sticky for ctrl+shift+s
cy.get('body')
.type(META_KEY, { delay: 500, release: false })
.type('{shift}', { release: false })
.type('s');
workflowPage.getters.stickies().should('have.length', 3); workflowPage.getters.stickies().should('have.length', 3);
workflowPage.getters workflowPage.getters
.stickies() .stickies()

View file

@ -1168,7 +1168,13 @@ export default defineComponent({
async keyDown(e: KeyboardEvent) { async keyDown(e: KeyboardEvent) {
this.contextMenu.close(); this.contextMenu.close();
if (e.key === 's' && this.isCtrlKeyPressed(e)) { const ctrlModifier = this.isCtrlKeyPressed(e) && !e.shiftKey && !e.altKey;
const shiftModifier = e.shiftKey && !e.altKey && !this.isCtrlKeyPressed(e);
const ctrlAltModifier = this.isCtrlKeyPressed(e) && e.altKey && !e.shiftKey;
const noModifierKeys = !this.isCtrlKeyPressed(e) && !e.shiftKey && !e.altKey;
const readOnly = this.isReadOnlyRoute || this.readOnlyEnv;
if (e.key === 's' && ctrlModifier && !readOnly) {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
@ -1201,7 +1207,7 @@ export default defineComponent({
return; return;
} }
if (e.key === 'Escape') { if (e.key === 'Escape' && noModifierKeys) {
this.createNodeActive = false; this.createNodeActive = false;
if (this.activeNode) { if (this.activeNode) {
void this.externalHooks.run('dataDisplay.nodeEditingFinished'); void this.externalHooks.run('dataDisplay.nodeEditingFinished');
@ -1220,42 +1226,37 @@ export default defineComponent({
.map((node) => node && this.workflowsStore.getNodeByName(node.name)) .map((node) => node && this.workflowsStore.getNodeByName(node.name))
.filter((node) => !!node) as INode[]; .filter((node) => !!node) as INode[];
if (e.key === 'd' && !this.isCtrlKeyPressed(e)) { if (e.key === 'd' && noModifierKeys && !readOnly) {
void this.callDebounced('toggleActivationNodes', { debounceTime: 350 }, selectedNodes); void this.callDebounced('toggleActivationNodes', { debounceTime: 350 }, selectedNodes);
} else if (e.key === 'd' && this.isCtrlKeyPressed(e)) { } else if (e.key === 'd' && ctrlModifier && !readOnly) {
if (selectedNodes.length > 0) { if (selectedNodes.length > 0) {
e.preventDefault(); e.preventDefault();
void this.duplicateNodes(selectedNodes); void this.duplicateNodes(selectedNodes);
} }
} else if (e.key === 'p' && !this.isCtrlKeyPressed(e)) { } else if (e.key === 'p' && noModifierKeys && !readOnly) {
if (selectedNodes.length > 0) { if (selectedNodes.length > 0) {
e.preventDefault(); e.preventDefault();
this.togglePinNodes(selectedNodes, 'keyboard-shortcut'); this.togglePinNodes(selectedNodes, 'keyboard-shortcut');
} }
} else if (e.key === 'Delete' || e.key === 'Backspace') { } else if ((e.key === 'Delete' || e.key === 'Backspace') && noModifierKeys && !readOnly) {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
void this.callDebounced('deleteNodes', { debounceTime: 500 }, selectedNodes); void this.callDebounced('deleteNodes', { debounceTime: 500 }, selectedNodes);
} else if (e.key === 'Tab') { } else if (e.key === 'Tab' && noModifierKeys && !readOnly) {
this.onToggleNodeCreator({ this.onToggleNodeCreator({
source: NODE_CREATOR_OPEN_SOURCES.TAB, source: NODE_CREATOR_OPEN_SOURCES.TAB,
createNodeActive: !this.createNodeActive && !this.isReadOnlyRoute && !this.readOnlyEnv, createNodeActive: !this.createNodeActive && !this.isReadOnlyRoute && !this.readOnlyEnv,
}); });
} else if ( } else if (e.key === 'Enter' && ctrlModifier && !readOnly) {
e.key === 'Enter' &&
this.isCtrlKeyPressed(e) &&
!this.isReadOnlyRoute &&
!this.readOnlyEnv
) {
void this.onRunWorkflow(); void this.onRunWorkflow();
} else if (e.key === 'S' && e.shiftKey && !this.isReadOnlyRoute && !this.readOnlyEnv) { } else if (e.key === 'S' && shiftModifier && !readOnly) {
void this.onAddNodes({ nodes: [{ type: STICKY_NODE_TYPE }], connections: [] }); void this.onAddNodes({ nodes: [{ type: STICKY_NODE_TYPE }], connections: [] });
} else if (e.key === this.controlKeyCode) { } else if (e.key === this.controlKeyCode) {
this.ctrlKeyPressed = true; this.ctrlKeyPressed = true;
} else if (e.key === ' ') { } else if (e.key === ' ') {
this.moveCanvasKeyPressed = true; this.moveCanvasKeyPressed = true;
} else if (e.key === 'F2' && !this.isReadOnlyRoute && !this.readOnlyEnv) { } else if (e.key === 'F2' && noModifierKeys && !readOnly) {
const lastSelectedNode = this.lastSelectedNode; const lastSelectedNode = this.lastSelectedNode;
if (lastSelectedNode !== null && lastSelectedNode.type !== STICKY_NODE_TYPE) { if (lastSelectedNode !== null && lastSelectedNode.type !== STICKY_NODE_TYPE) {
void this.callDebounced( void this.callDebounced(
@ -1264,21 +1265,21 @@ export default defineComponent({
lastSelectedNode.name, lastSelectedNode.name,
); );
} }
} else if (e.key === 'a' && this.isCtrlKeyPressed(e)) { } else if (e.key === 'a' && ctrlModifier) {
// Select all nodes // Select all nodes
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
void this.callDebounced('selectAllNodes', { debounceTime: 1000 }); void this.callDebounced('selectAllNodes', { debounceTime: 1000 });
} else if (e.key === 'c' && this.isCtrlKeyPressed(e)) { } else if (e.key === 'c' && ctrlModifier) {
void this.callDebounced('copyNodes', { debounceTime: 1000 }, selectedNodes); void this.callDebounced('copyNodes', { debounceTime: 1000 }, selectedNodes);
} else if (e.key === 'x' && this.isCtrlKeyPressed(e)) { } else if (e.key === 'x' && ctrlModifier && !readOnly) {
// Cut nodes // Cut nodes
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
void this.callDebounced('cutNodes', { debounceTime: 1000 }, selectedNodes); void this.callDebounced('cutNodes', { debounceTime: 1000 }, selectedNodes);
} else if (e.key === 'n' && this.isCtrlKeyPressed(e) && e.altKey) { } else if (e.key === 'n' && ctrlAltModifier) {
// Create a new workflow // Create a new workflow
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
@ -1296,7 +1297,7 @@ export default defineComponent({
title: this.$locale.baseText('nodeView.showMessage.keyDown.title'), title: this.$locale.baseText('nodeView.showMessage.keyDown.title'),
type: 'success', type: 'success',
}); });
} else if (e.key === 'Enter') { } else if (e.key === 'Enter' && noModifierKeys) {
// Activate the last selected node // Activate the last selected node
const lastSelectedNode = this.lastSelectedNode; const lastSelectedNode = this.lastSelectedNode;
@ -1309,13 +1310,13 @@ export default defineComponent({
} }
this.ndvStore.activeNodeName = lastSelectedNode.name; this.ndvStore.activeNodeName = lastSelectedNode.name;
} }
} else if (e.key === 'ArrowRight' && e.shiftKey) { } else if (e.key === 'ArrowRight' && shiftModifier) {
// Select all downstream nodes // Select all downstream nodes
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
void this.callDebounced('selectDownstreamNodes', { debounceTime: 1000 }); void this.callDebounced('selectDownstreamNodes', { debounceTime: 1000 });
} else if (e.key === 'ArrowRight') { } else if (e.key === 'ArrowRight' && noModifierKeys) {
// Set child node active // Set child node active
const lastSelectedNode = this.lastSelectedNode; const lastSelectedNode = this.lastSelectedNode;
if (lastSelectedNode === null) { if (lastSelectedNode === null) {
@ -1337,13 +1338,13 @@ export default defineComponent({
false, false,
true, true,
); );
} else if (e.key === 'ArrowLeft' && e.shiftKey) { } else if (e.key === 'ArrowLeft' && shiftModifier) {
// Select all downstream nodes // Select all downstream nodes
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
void this.callDebounced('selectUpstreamNodes', { debounceTime: 1000 }); void this.callDebounced('selectUpstreamNodes', { debounceTime: 1000 });
} else if (e.key === 'ArrowLeft') { } else if (e.key === 'ArrowLeft' && noModifierKeys) {
// Set parent node active // Set parent node active
const lastSelectedNode = this.lastSelectedNode; const lastSelectedNode = this.lastSelectedNode;
if (lastSelectedNode === null) { if (lastSelectedNode === null) {
@ -1369,7 +1370,7 @@ export default defineComponent({
false, false,
true, true,
); );
} else if (['ArrowUp', 'ArrowDown'].includes(e.key)) { } else if (['ArrowUp', 'ArrowDown'].includes(e.key) && noModifierKeys) {
// Set sibling node as active // Set sibling node as active
// Check first if it has a parent node // Check first if it has a parent node