fix(editor): Make sure code editors work correctly in fullscreen (#12597)
Some checks are pending
Test Master / install-and-build (push) Waiting to run
Test Master / Unit tests (18.x) (push) Blocked by required conditions
Test Master / Unit tests (20.x) (push) Blocked by required conditions
Test Master / Unit tests (22.4) (push) Blocked by required conditions
Test Master / Lint (push) Blocked by required conditions
Test Master / Notify Slack on failure (push) Blocked by required conditions

This commit is contained in:
Elias Meire 2025-01-14 18:16:41 +01:00 committed by GitHub
parent bfe3c5611a
commit aa1f3a7d98
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 98 additions and 12 deletions

View file

@ -1,6 +1,10 @@
import { setCredentialValues } from '../composables/modals/credential-modal';
import { clickCreateNewCredential } from '../composables/ndv';
import { MANUAL_TRIGGER_NODE_DISPLAY_NAME, NOTION_NODE_NAME } from '../constants';
import { clickCreateNewCredential, setParameterSelectByContent } from '../composables/ndv';
import {
EDIT_FIELDS_SET_NODE_NAME,
MANUAL_TRIGGER_NODE_DISPLAY_NAME,
NOTION_NODE_NAME,
} from '../constants';
import { NDV, WorkflowPage } from '../pages';
import { NodeCreator } from '../pages/features/node-creator';
@ -359,15 +363,71 @@ describe('NDV', () => {
ndv.getters.nodeExecuteButton().should('be.visible');
});
it('should allow editing code in fullscreen in the Code node', () => {
it('should allow editing code in fullscreen in the code editors', () => {
// Code (JavaScript)
workflowPage.actions.addInitialNodeToCanvas('Code', { keepNdvOpen: true });
ndv.actions.openCodeEditorFullscreen();
ndv.getters.codeEditorFullscreen().type('{selectall}').type('{backspace}').type('foo()');
ndv.getters.codeEditorFullscreen().should('contain.text', 'foo()');
cy.wait(200);
cy.wait(200); // allow change to emit before closing modal
ndv.getters.codeEditorDialog().find('.el-dialog__close').click();
ndv.getters.parameterInput('jsCode').get('.cm-content').should('contain.text', 'foo()');
ndv.actions.close();
// SQL
workflowPage.actions.addNodeToCanvas('Postgres', true, true, 'Execute a SQL query');
ndv.actions.openCodeEditorFullscreen();
ndv.getters
.codeEditorFullscreen()
.type('{selectall}')
.type('{backspace}')
.type('SELECT * FROM workflows');
ndv.getters.codeEditorFullscreen().should('contain.text', 'SELECT * FROM workflows');
cy.wait(200);
ndv.getters.codeEditorDialog().find('.el-dialog__close').click();
ndv.getters
.parameterInput('query')
.get('.cm-content')
.should('contain.text', 'SELECT * FROM workflows');
ndv.actions.close();
// HTML
workflowPage.actions.addNodeToCanvas('HTML', true, true, 'Generate HTML template');
ndv.actions.openCodeEditorFullscreen();
ndv.getters
.codeEditorFullscreen()
.type('{selectall}')
.type('{backspace}')
.type('<div>Hello World');
ndv.getters.codeEditorFullscreen().should('contain.text', '<div>Hello World</div>');
cy.wait(200);
ndv.getters.codeEditorDialog().find('.el-dialog__close').click();
ndv.getters
.parameterInput('html')
.get('.cm-content')
.should('contain.text', '<div>Hello World</div>');
ndv.actions.close();
// JSON
workflowPage.actions.addNodeToCanvas(EDIT_FIELDS_SET_NODE_NAME, true, true);
setParameterSelectByContent('mode', 'JSON');
ndv.actions.openCodeEditorFullscreen();
ndv.getters
.codeEditorFullscreen()
.type('{selectall}')
.type('{backspace}')
.type('{ "key": "value" }', { parseSpecialCharSequences: false });
ndv.getters.codeEditorFullscreen().should('contain.text', '{ "key": "value" }');
cy.wait(200);
ndv.getters.codeEditorDialog().find('.el-dialog__close').click();
ndv.getters
.parameterInput('jsonOutput')
.get('.cm-content')
.should('contain.text', '{ "key": "value" }');
});
it('should not retrieve remote options when a parameter value changes', () => {

View file

@ -83,6 +83,7 @@ const {
editor: editorRef,
segments,
readEditorValue,
isDirty,
} = useExpressionEditor({
editorRef: htmlEditor,
editorValue,
@ -230,6 +231,7 @@ onMounted(() => {
});
onBeforeUnmount(() => {
if (isDirty.value) emit('update:model-value', readEditorValue());
htmlEditorEventBus.off('format-html', formatHtml);
});
@ -246,7 +248,10 @@ async function onDrop(value: string, event: MouseEvent) {
<template #default="{ activeDrop, droppable }">
<div
ref="htmlEditor"
:class="{ [$style.activeDrop]: activeDrop, [$style.droppable]: droppable }"
:class="[
$style.fillHeight,
{ [$style.activeDrop]: activeDrop, [$style.droppable]: droppable },
]"
data-test-id="html-editor-container"
></div
></template>
@ -264,6 +269,10 @@ async function onDrop(value: string, event: MouseEvent) {
}
}
.fillHeight {
height: 100%;
}
.droppable {
:global(.cm-editor) {
border-color: var(--color-ndv-droppable-parameter);

View file

@ -17,7 +17,7 @@ import {
import { editorKeymap } from '@/plugins/codemirror/keymap';
import { n8nAutocompletion } from '@/plugins/codemirror/n8nLang';
import { computed, onMounted, ref, watch } from 'vue';
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';
import { codeEditorTheme } from '../CodeNodeEditor/theme';
import { mappingDropCursor } from '@/plugins/codemirror/dragAndDrop';
@ -36,6 +36,7 @@ const emit = defineEmits<{
const jsonEditorRef = ref<HTMLDivElement>();
const editor = ref<EditorView | null>(null);
const editorState = ref<EditorState | null>(null);
const isDirty = ref(false);
const extensions = computed(() => {
const extensionsToApply: Extension[] = [
@ -65,6 +66,7 @@ const extensions = computed(() => {
bracketMatching(),
mappingDropCursor(),
EditorView.updateListener.of((viewUpdate: ViewUpdate) => {
isDirty.value = true;
if (!viewUpdate.docChanged || !editor.value) return;
emit('update:modelValue', editor.value?.state.doc.toString());
}),
@ -77,6 +79,12 @@ onMounted(() => {
createEditor();
});
onBeforeUnmount(() => {
if (!editor.value) return;
if (isDirty.value) emit('update:modelValue', editor.value.state.doc.toString());
editor.value.destroy();
});
watch(
() => props.modelValue,
(newValue: string) => {

View file

@ -1108,7 +1108,7 @@ onUpdated(async () => {
>
<div class="ignore-key-press-canvas code-edit-dialog">
<CodeNodeEditor
v-if="editorType === 'codeNodeEditor'"
v-if="editorType === 'codeNodeEditor' && codeEditDialogVisible"
:id="parameterId"
:mode="codeEditorMode"
:model-value="modelValueString"
@ -1119,7 +1119,7 @@ onUpdated(async () => {
@update:model-value="valueChangedDebounced"
/>
<HtmlEditor
v-else-if="editorType === 'htmlEditor' && !codeEditDialogVisible"
v-else-if="editorType === 'htmlEditor' && codeEditDialogVisible"
:model-value="modelValueString"
:is-read-only="isReadOnly"
:rows="editorRows"
@ -1129,7 +1129,7 @@ onUpdated(async () => {
@update:model-value="valueChangedDebounced"
/>
<SqlEditor
v-else-if="editorType === 'sqlEditor' && !codeEditDialogVisible"
v-else-if="editorType === 'sqlEditor' && codeEditDialogVisible"
:model-value="modelValueString"
:dialect="getArgument('sqlDialect')"
:is-read-only="isReadOnly"
@ -1138,7 +1138,7 @@ onUpdated(async () => {
@update:model-value="valueChangedDebounced"
/>
<JsEditor
v-else-if="editorType === 'jsEditor' && !codeEditDialogVisible"
v-else-if="editorType === 'jsEditor' && codeEditDialogVisible"
:model-value="modelValueString"
:is-read-only="isReadOnly"
:rows="editorRows"
@ -1148,7 +1148,7 @@ onUpdated(async () => {
/>
<JsonEditor
v-else-if="parameter.type === 'json' && !codeEditDialogVisible"
v-else-if="parameter.type === 'json' && codeEditDialogVisible"
:model-value="modelValueString"
:is-read-only="isReadOnly"
:rows="editorRows"
@ -1256,7 +1256,7 @@ onUpdated(async () => {
</JsEditor>
<JsonEditor
v-else-if="parameter.type === 'json'"
v-else-if="parameter.type === 'json' && !codeEditDialogVisible"
:model-value="modelValueString"
:is-read-only="isReadOnly"
:rows="editorRows"

View file

@ -114,6 +114,7 @@ const {
segments: { all: segments },
readEditorValue,
hasFocus: editorHasFocus,
isDirty,
} = useExpressionEditor({
editorRef: sqlEditor,
editorValue,
@ -148,6 +149,7 @@ onMounted(() => {
});
onBeforeUnmount(() => {
if (isDirty.value) emit('update:model-value', readEditorValue());
codeNodeEditorEventBus.off('highlightLine', highlightLine);
});
@ -226,6 +228,10 @@ async function onDrop(value: string, event: MouseEvent) {
.sqlEditor {
position: relative;
height: 100%;
& > div {
height: 100%;
}
}
.codemirror {

View file

@ -70,6 +70,7 @@ export const useExpressionEditor = ({
const telemetryExtensions = ref<Compartment>(new Compartment());
const autocompleteStatus = ref<'pending' | 'active' | null>(null);
const dragging = ref(false);
const isDirty = ref(false);
const updateSegments = (): void => {
const state = editor.value?.state;
@ -156,6 +157,7 @@ export const useExpressionEditor = ({
const debouncedUpdateSegments = debounce(updateSegments, 200);
function onEditorUpdate(viewUpdate: ViewUpdate) {
isDirty.value = true;
autocompleteStatus.value = completionStatus(viewUpdate.view.state);
updateSelection(viewUpdate);
@ -463,5 +465,6 @@ export const useExpressionEditor = ({
select,
selectAll,
focus,
isDirty,
};
};