mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
fix(editor): Fix keyboard shortcuts no longer working after editing sticky note (#13502)
This commit is contained in:
parent
819fc2da63
commit
ab41fc3fb5
|
@ -44,7 +44,8 @@ const emit = defineEmits<{
|
||||||
'update:modelValue': [elements: CanvasNode[]];
|
'update:modelValue': [elements: CanvasNode[]];
|
||||||
'update:node:position': [id: string, position: XYPosition];
|
'update:node:position': [id: string, position: XYPosition];
|
||||||
'update:nodes:position': [events: CanvasNodeMoveEvent[]];
|
'update:nodes:position': [events: CanvasNodeMoveEvent[]];
|
||||||
'update:node:active': [id: string];
|
'update:node:activated': [id: string];
|
||||||
|
'update:node:deactivated': [id: string];
|
||||||
'update:node:enabled': [id: string];
|
'update:node:enabled': [id: string];
|
||||||
'update:node:selected': [id?: string];
|
'update:node:selected': [id?: string];
|
||||||
'update:node:name': [id: string];
|
'update:node:name': [id: string];
|
||||||
|
@ -246,7 +247,7 @@ function selectUpstreamNodes(id: string) {
|
||||||
|
|
||||||
const keyMap = computed(() => ({
|
const keyMap = computed(() => ({
|
||||||
ctrl_c: emitWithSelectedNodes((ids) => emit('copy:nodes', ids)),
|
ctrl_c: emitWithSelectedNodes((ids) => emit('copy:nodes', ids)),
|
||||||
enter: emitWithLastSelectedNode((id) => onSetNodeActive(id)),
|
enter: emitWithLastSelectedNode((id) => onSetNodeActivated(id)),
|
||||||
ctrl_a: () => addSelectedNodes(graphNodes.value),
|
ctrl_a: () => addSelectedNodes(graphNodes.value),
|
||||||
// Support both key and code for zooming in and out
|
// Support both key and code for zooming in and out
|
||||||
'shift_+|+|=|shift_Equal|Equal': async () => await onZoomIn(),
|
'shift_+|+|=|shift_Equal|Equal': async () => await onZoomIn(),
|
||||||
|
@ -337,9 +338,13 @@ function onSelectionDragStop(event: NodeDragEvent) {
|
||||||
onUpdateNodesPosition(event.nodes.map(({ id, position }) => ({ id, position })));
|
onUpdateNodesPosition(event.nodes.map(({ id, position }) => ({ id, position })));
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSetNodeActive(id: string) {
|
function onSetNodeActivated(id: string) {
|
||||||
props.eventBus.emit('nodes:action', { ids: [id], action: 'update:node:active' });
|
props.eventBus.emit('nodes:action', { ids: [id], action: 'update:node:activated' });
|
||||||
emit('update:node:active', id);
|
emit('update:node:activated', id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSetNodeDeactivated(id: string) {
|
||||||
|
emit('update:node:deactivated', id);
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearSelectedNodes() {
|
function clearSelectedNodes() {
|
||||||
|
@ -607,7 +612,7 @@ function onContextMenuAction(action: ContextMenuAction, nodeIds: string[]) {
|
||||||
case 'toggle_activation':
|
case 'toggle_activation':
|
||||||
return emit('update:nodes:enabled', nodeIds);
|
return emit('update:nodes:enabled', nodeIds);
|
||||||
case 'open':
|
case 'open':
|
||||||
return onSetNodeActive(nodeIds[0]);
|
return onSetNodeActivated(nodeIds[0]);
|
||||||
case 'rename':
|
case 'rename':
|
||||||
return emit('update:node:name', nodeIds[0]);
|
return emit('update:node:name', nodeIds[0]);
|
||||||
case 'change_color':
|
case 'change_color':
|
||||||
|
@ -772,7 +777,8 @@ provide(CanvasKey, {
|
||||||
@run="onRunNode"
|
@run="onRunNode"
|
||||||
@select="onSelectNode"
|
@select="onSelectNode"
|
||||||
@toggle="onToggleNodeEnabled"
|
@toggle="onToggleNodeEnabled"
|
||||||
@activate="onSetNodeActive"
|
@activate="onSetNodeActivated"
|
||||||
|
@deactivate="onSetNodeDeactivated"
|
||||||
@open:contextmenu="onOpenNodeContextMenu"
|
@open:contextmenu="onOpenNodeContextMenu"
|
||||||
@update="onUpdateNodeParameters"
|
@update="onUpdateNodeParameters"
|
||||||
@update:inputs="onUpdateNodeInputs"
|
@update:inputs="onUpdateNodeInputs"
|
||||||
|
|
|
@ -59,6 +59,7 @@ const emit = defineEmits<{
|
||||||
select: [id: string, selected: boolean];
|
select: [id: string, selected: boolean];
|
||||||
toggle: [id: string];
|
toggle: [id: string];
|
||||||
activate: [id: string];
|
activate: [id: string];
|
||||||
|
deactivate: [id: string];
|
||||||
'open:contextmenu': [id: string, event: MouseEvent, source: 'node-button' | 'node-right-click'];
|
'open:contextmenu': [id: string, event: MouseEvent, source: 'node-button' | 'node-right-click'];
|
||||||
update: [id: string, parameters: Record<string, unknown>];
|
update: [id: string, parameters: Record<string, unknown>];
|
||||||
'update:inputs': [id: string];
|
'update:inputs': [id: string];
|
||||||
|
@ -258,6 +259,10 @@ function onActivate() {
|
||||||
emit('activate', props.id);
|
emit('activate', props.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onDeactivate() {
|
||||||
|
emit('deactivate', props.id);
|
||||||
|
}
|
||||||
|
|
||||||
function onOpenContextMenuFromToolbar(event: MouseEvent) {
|
function onOpenContextMenuFromToolbar(event: MouseEvent) {
|
||||||
emit('open:contextmenu', props.id, event, 'node-button');
|
emit('open:contextmenu', props.id, event, 'node-button');
|
||||||
}
|
}
|
||||||
|
@ -395,7 +400,8 @@ onBeforeUnmount(() => {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<CanvasNodeRenderer
|
<CanvasNodeRenderer
|
||||||
@dblclick.stop="onActivate"
|
@activate="onActivate"
|
||||||
|
@deactivate="onDeactivate"
|
||||||
@move="onMove"
|
@move="onMove"
|
||||||
@update="onUpdate"
|
@update="onUpdate"
|
||||||
@open:contextmenu="onOpenContextMenuFromNode"
|
@open:contextmenu="onOpenContextMenuFromNode"
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { createCanvasNodeProvide, createCanvasProvide } from '@/__tests__/data';
|
||||||
import { createTestingPinia } from '@pinia/testing';
|
import { createTestingPinia } from '@pinia/testing';
|
||||||
import { setActivePinia } from 'pinia';
|
import { setActivePinia } from 'pinia';
|
||||||
import { CanvasConnectionMode, CanvasNodeRenderType } from '@/types';
|
import { CanvasConnectionMode, CanvasNodeRenderType } from '@/types';
|
||||||
|
import { fireEvent } from '@testing-library/vue';
|
||||||
|
|
||||||
const renderComponent = createComponentRenderer(CanvasNodeDefault, {
|
const renderComponent = createComponentRenderer(CanvasNodeDefault, {
|
||||||
global: {
|
global: {
|
||||||
|
@ -333,4 +334,18 @@ describe('CanvasNodeDefault', () => {
|
||||||
expect(getByTestId('canvas-trigger-node')).toMatchSnapshot();
|
expect(getByTestId('canvas-trigger-node')).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should emit "activate" on double click', async () => {
|
||||||
|
const { getByText, emitted } = renderComponent({
|
||||||
|
global: {
|
||||||
|
provide: {
|
||||||
|
...createCanvasNodeProvide(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await fireEvent.dblClick(getByText('Test Node'));
|
||||||
|
|
||||||
|
expect(emitted()).toHaveProperty('activate');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,10 +12,12 @@ const i18n = useI18n();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
'open:contextmenu': [event: MouseEvent];
|
'open:contextmenu': [event: MouseEvent];
|
||||||
|
activate: [id: string];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { initialized, viewport } = useCanvas();
|
const { initialized, viewport } = useCanvas();
|
||||||
const {
|
const {
|
||||||
|
id,
|
||||||
label,
|
label,
|
||||||
subtitle,
|
subtitle,
|
||||||
inputs,
|
inputs,
|
||||||
|
@ -122,10 +124,20 @@ watch(viewport, () => {
|
||||||
function openContextMenu(event: MouseEvent) {
|
function openContextMenu(event: MouseEvent) {
|
||||||
emit('open:contextmenu', event);
|
emit('open:contextmenu', event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onActivate() {
|
||||||
|
emit('activate', id.value);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="classes" :style="styles" :data-test-id="dataTestId" @contextmenu="openContextMenu">
|
<div
|
||||||
|
:class="classes"
|
||||||
|
:style="styles"
|
||||||
|
:data-test-id="dataTestId"
|
||||||
|
@contextmenu="openContextMenu"
|
||||||
|
@dblclick.stop="onActivate"
|
||||||
|
>
|
||||||
<CanvasNodeTooltip v-if="renderOptions.tooltip" :visible="showTooltip" />
|
<CanvasNodeTooltip v-if="renderOptions.tooltip" :visible="showTooltip" />
|
||||||
<slot />
|
<slot />
|
||||||
<CanvasNodeStatusIcons v-if="!isDisabled" :class="$style.statusIcons" />
|
<CanvasNodeStatusIcons v-if="!isDisabled" :class="$style.statusIcons" />
|
||||||
|
|
|
@ -68,4 +68,48 @@ describe('CanvasNodeStickyNote', () => {
|
||||||
|
|
||||||
expect(getComputedStyle(stickyOptions).display).toBe('none');
|
expect(getComputedStyle(stickyOptions).display).toBe('none');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should emit "activate" on double click', async () => {
|
||||||
|
const { container, emitted } = renderComponent({
|
||||||
|
global: {
|
||||||
|
provide: {
|
||||||
|
...createCanvasNodeProvide({
|
||||||
|
id: 'sticky',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const sticky = container.querySelector('.sticky-textarea');
|
||||||
|
if (!sticky) throw new Error('Sticky not found');
|
||||||
|
|
||||||
|
await fireEvent.dblClick(sticky);
|
||||||
|
|
||||||
|
expect(emitted()).toHaveProperty('activate');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should emit "deactivate" on blur', async () => {
|
||||||
|
const { container, emitted } = renderComponent({
|
||||||
|
global: {
|
||||||
|
provide: {
|
||||||
|
...createCanvasNodeProvide({
|
||||||
|
id: 'sticky',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const sticky = container.querySelector('.sticky-textarea');
|
||||||
|
|
||||||
|
if (!sticky) throw new Error('Sticky not found');
|
||||||
|
|
||||||
|
await fireEvent.dblClick(sticky);
|
||||||
|
|
||||||
|
const stickyTextarea = container.querySelector('.sticky-textarea textarea');
|
||||||
|
if (!stickyTextarea) throw new Error('Textarea not found');
|
||||||
|
|
||||||
|
await fireEvent.blur(stickyTextarea);
|
||||||
|
|
||||||
|
expect(emitted()).toHaveProperty('deactivate');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -14,7 +14,8 @@ defineOptions({
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
update: [parameters: Record<string, unknown>];
|
update: [parameters: Record<string, unknown>];
|
||||||
move: [position: XYPosition];
|
move: [position: XYPosition];
|
||||||
dblclick: [event: MouseEvent];
|
activate: [id: string];
|
||||||
|
deactivate: [id: string];
|
||||||
'open:contextmenu': [event: MouseEvent];
|
'open:contextmenu': [event: MouseEvent];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
@ -58,16 +59,20 @@ function onInputChange(value: string) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function onEdit(edit: boolean) {
|
function onSetActive(value: boolean) {
|
||||||
isActive.value = edit;
|
if (isActive.value === value) return;
|
||||||
}
|
|
||||||
|
|
||||||
function onDoubleClick(event: MouseEvent) {
|
isActive.value = value;
|
||||||
emit('dblclick', event);
|
|
||||||
|
if (value) {
|
||||||
|
emit('activate', id.value);
|
||||||
|
} else {
|
||||||
|
emit('deactivate', id.value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onActivate() {
|
function onActivate() {
|
||||||
onEdit(true);
|
onSetActive(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -83,11 +88,11 @@ function openContextMenu(event: MouseEvent) {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
eventBus.value?.on('update:node:active', onActivate);
|
eventBus.value?.on('update:node:activated', onActivate);
|
||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
eventBus.value?.off('update:node:active', onActivate);
|
eventBus.value?.off('update:node:activated', onActivate);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
|
@ -110,8 +115,8 @@ onBeforeUnmount(() => {
|
||||||
:background-color="renderOptions.color"
|
:background-color="renderOptions.color"
|
||||||
:edit-mode="isActive"
|
:edit-mode="isActive"
|
||||||
:read-only="isReadOnly"
|
:read-only="isReadOnly"
|
||||||
@edit="onEdit"
|
@edit="onSetActive"
|
||||||
@dblclick="onDoubleClick"
|
@dblclick.stop="onActivate"
|
||||||
@update:model-value="onInputChange"
|
@update:model-value="onInputChange"
|
||||||
@contextmenu="openContextMenu"
|
@contextmenu="openContextMenu"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -395,6 +395,10 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
||||||
ndvStore.activeNodeName = name;
|
ndvStore.activeNodeName = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clearNodeActive() {
|
||||||
|
ndvStore.activeNodeName = null;
|
||||||
|
}
|
||||||
|
|
||||||
function setNodeParameters(id: string, parameters: Record<string, unknown>) {
|
function setNodeParameters(id: string, parameters: Record<string, unknown>) {
|
||||||
const node = workflowsStore.getNodeById(id);
|
const node = workflowsStore.getNodeById(id);
|
||||||
if (!node) {
|
if (!node) {
|
||||||
|
@ -1988,6 +1992,7 @@ export function useCanvasOperations({ router }: { router: ReturnType<typeof useR
|
||||||
revertUpdateNodePosition,
|
revertUpdateNodePosition,
|
||||||
setNodeActive,
|
setNodeActive,
|
||||||
setNodeActiveByName,
|
setNodeActiveByName,
|
||||||
|
clearNodeActive,
|
||||||
setNodeSelected,
|
setNodeSelected,
|
||||||
toggleNodesDisabled,
|
toggleNodesDisabled,
|
||||||
revertToggleNodeDisabled,
|
revertToggleNodeDisabled,
|
||||||
|
|
|
@ -143,7 +143,7 @@ export interface CanvasInjectionData {
|
||||||
|
|
||||||
export type CanvasNodeEventBusEvents = {
|
export type CanvasNodeEventBusEvents = {
|
||||||
'update:sticky:color': never;
|
'update:sticky:color': never;
|
||||||
'update:node:active': never;
|
'update:node:activated': never;
|
||||||
'update:node:class': { className: string; add?: boolean };
|
'update:node:class': { className: string; add?: boolean };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -199,6 +199,7 @@ const {
|
||||||
revalidateNodeInputConnections,
|
revalidateNodeInputConnections,
|
||||||
revalidateNodeOutputConnections,
|
revalidateNodeOutputConnections,
|
||||||
setNodeActiveByName,
|
setNodeActiveByName,
|
||||||
|
clearNodeActive,
|
||||||
addConnections,
|
addConnections,
|
||||||
importWorkflowData,
|
importWorkflowData,
|
||||||
fetchWorkflowDataFromUrl,
|
fetchWorkflowDataFromUrl,
|
||||||
|
@ -621,10 +622,14 @@ function onClickNode() {
|
||||||
closeNodeCreator();
|
closeNodeCreator();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSetNodeActive(id: string) {
|
function onSetNodeActivated(id: string) {
|
||||||
setNodeActive(id);
|
setNodeActive(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onSetNodeDeactivated() {
|
||||||
|
clearNodeActive();
|
||||||
|
}
|
||||||
|
|
||||||
function onSetNodeSelected(id?: string) {
|
function onSetNodeSelected(id?: string) {
|
||||||
closeNodeCreator();
|
closeNodeCreator();
|
||||||
setNodeSelected(id);
|
setNodeSelected(id);
|
||||||
|
@ -1728,7 +1733,8 @@ onBeforeUnmount(() => {
|
||||||
:key-bindings="keyBindingsEnabled"
|
:key-bindings="keyBindingsEnabled"
|
||||||
@update:nodes:position="onUpdateNodesPosition"
|
@update:nodes:position="onUpdateNodesPosition"
|
||||||
@update:node:position="onUpdateNodePosition"
|
@update:node:position="onUpdateNodePosition"
|
||||||
@update:node:active="onSetNodeActive"
|
@update:node:activated="onSetNodeActivated"
|
||||||
|
@update:node:deactivated="onSetNodeDeactivated"
|
||||||
@update:node:selected="onSetNodeSelected"
|
@update:node:selected="onSetNodeSelected"
|
||||||
@update:node:enabled="onToggleNodeDisabled"
|
@update:node:enabled="onToggleNodeDisabled"
|
||||||
@update:node:name="onOpenRenameNodeModal"
|
@update:node:name="onOpenRenameNodeModal"
|
||||||
|
|
Loading…
Reference in a new issue