mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-26 12:01:16 -08:00
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
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:
parent
bfe3c5611a
commit
aa1f3a7d98
|
@ -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', () => {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue