mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
fix(editor): UI enhancements and fixes for expression inputs (#8996)
This commit is contained in:
@@ -12,3 +12,10 @@ window.ResizeObserver =
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
Element.prototype.scrollIntoView = vi.fn();
|
Element.prototype.scrollIntoView = vi.fn();
|
||||||
|
|
||||||
|
Range.prototype.getBoundingClientRect = vi.fn();
|
||||||
|
Range.prototype.getClientRects = vi.fn(() => ({
|
||||||
|
item: vi.fn(),
|
||||||
|
length: 0,
|
||||||
|
[Symbol.iterator]: vi.fn(),
|
||||||
|
}));
|
||||||
|
|||||||
@@ -169,7 +169,6 @@ const onBlur = (): void => {
|
|||||||
display-options
|
display-options
|
||||||
hide-label
|
hide-label
|
||||||
hide-hint
|
hide-hint
|
||||||
:rows="3"
|
|
||||||
:is-read-only="isReadOnly"
|
:is-read-only="isReadOnly"
|
||||||
:parameter="nameParameter"
|
:parameter="nameParameter"
|
||||||
:value="assignment.name"
|
:value="assignment.name"
|
||||||
@@ -196,7 +195,6 @@ const onBlur = (): void => {
|
|||||||
hide-label
|
hide-label
|
||||||
hide-issues
|
hide-issues
|
||||||
hide-hint
|
hide-hint
|
||||||
:rows="3"
|
|
||||||
is-assignment
|
is-assignment
|
||||||
:is-read-only="isReadOnly"
|
:is-read-only="isReadOnly"
|
||||||
:options-position="breakpoint === 'default' ? 'top' : 'bottom'"
|
:options-position="breakpoint === 'default' ? 'top' : 'bottom'"
|
||||||
|
|||||||
@@ -156,7 +156,6 @@ const onBlur = (): void => {
|
|||||||
hide-label
|
hide-label
|
||||||
hide-hint
|
hide-hint
|
||||||
hide-issues
|
hide-issues
|
||||||
:rows="3"
|
|
||||||
:is-read-only="readOnly"
|
:is-read-only="readOnly"
|
||||||
:parameter="leftParameter"
|
:parameter="leftParameter"
|
||||||
:value="condition.leftValue"
|
:value="condition.leftValue"
|
||||||
@@ -181,7 +180,6 @@ const onBlur = (): void => {
|
|||||||
hide-label
|
hide-label
|
||||||
hide-hint
|
hide-hint
|
||||||
hide-issues
|
hide-issues
|
||||||
:rows="3"
|
|
||||||
:is-read-only="readOnly"
|
:is-read-only="readOnly"
|
||||||
:options-position="breakpoint === 'default' ? 'top' : 'bottom'"
|
:options-position="breakpoint === 'default' ? 'top' : 'bottom'"
|
||||||
:parameter="rightParameter"
|
:parameter="rightParameter"
|
||||||
|
|||||||
@@ -45,21 +45,24 @@ const resolvedExpression = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const plaintextSegments = computed<Plaintext[]>(() => {
|
const plaintextSegments = computed<Plaintext[]>(() => {
|
||||||
if (props.segments.length === 0) {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
from: 0,
|
|
||||||
to: resolvedExpression.value.length - 1,
|
|
||||||
plaintext: resolvedExpression.value,
|
|
||||||
kind: 'plaintext',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return props.segments.filter((s): s is Plaintext => s.kind === 'plaintext');
|
return props.segments.filter((s): s is Plaintext => s.kind === 'plaintext');
|
||||||
});
|
});
|
||||||
|
|
||||||
const resolvedSegments = computed<Resolved[]>(() => {
|
const resolvedSegments = computed<Resolved[]>(() => {
|
||||||
|
if (props.segments.length === 0) {
|
||||||
|
const emptyExpression = resolvedExpression.value;
|
||||||
|
const emptySegment: Resolved = {
|
||||||
|
from: 0,
|
||||||
|
to: emptyExpression.length,
|
||||||
|
kind: 'resolvable',
|
||||||
|
error: null,
|
||||||
|
resolvable: '',
|
||||||
|
resolved: emptyExpression,
|
||||||
|
state: 'pending',
|
||||||
|
};
|
||||||
|
return [emptySegment];
|
||||||
|
}
|
||||||
|
|
||||||
let cursor = 0;
|
let cursor = 0;
|
||||||
|
|
||||||
return props.segments
|
return props.segments
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<n8n-input-label
|
<n8n-input-label
|
||||||
:class="$style.wrapper"
|
:class="[$style.wrapper, { [$style.tipVisible]: showDragnDropTip }]"
|
||||||
:label="hideLabel ? '' : i18n.nodeText().inputLabelDisplayName(parameter, path)"
|
:label="hideLabel ? '' : i18n.nodeText().inputLabelDisplayName(parameter, path)"
|
||||||
:tooltip-text="hideLabel ? '' : i18n.nodeText().inputLabelDescription(parameter, path)"
|
:tooltip-text="hideLabel ? '' : i18n.nodeText().inputLabelDescription(parameter, path)"
|
||||||
:show-tooltip="focused"
|
:show-tooltip="focused"
|
||||||
@@ -357,6 +357,11 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tipVisible {
|
||||||
|
--input-border-bottom-left-radius: 0;
|
||||||
|
--input-border-bottom-right-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.tip {
|
.tip {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ import { setActivePinia } from 'pinia';
|
|||||||
import { waitFor } from '@testing-library/vue';
|
import { waitFor } from '@testing-library/vue';
|
||||||
|
|
||||||
describe('ExpressionParameterInput', () => {
|
describe('ExpressionParameterInput', () => {
|
||||||
const originalRangeGetBoundingClientRect = Range.prototype.getBoundingClientRect;
|
|
||||||
const originalRangeGetClientRects = Range.prototype.getClientRects;
|
|
||||||
const renderComponent = createComponentRenderer(ExpressionEditorModalInput);
|
const renderComponent = createComponentRenderer(ExpressionEditorModalInput);
|
||||||
let pinia: TestingPinia;
|
let pinia: TestingPinia;
|
||||||
|
|
||||||
@@ -16,19 +14,6 @@ describe('ExpressionParameterInput', () => {
|
|||||||
setActivePinia(pinia);
|
setActivePinia(pinia);
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
Range.prototype.getBoundingClientRect = vi.fn();
|
|
||||||
Range.prototype.getClientRects = () => ({
|
|
||||||
item: vi.fn(),
|
|
||||||
length: 0,
|
|
||||||
[Symbol.iterator]: vi.fn(),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
Range.prototype.getBoundingClientRect = originalRangeGetBoundingClientRect;
|
|
||||||
Range.prototype.getClientRects = originalRangeGetClientRects;
|
|
||||||
});
|
|
||||||
test.each([
|
test.each([
|
||||||
['not be editable', 'readonly', true, ''],
|
['not be editable', 'readonly', true, ''],
|
||||||
['be editable', 'not readonly', false, 'test'],
|
['be editable', 'not readonly', false, 'test'],
|
||||||
|
|||||||
@@ -17,9 +17,6 @@ const DEFAULT_SETUP = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
describe('HtmlEditor.vue', () => {
|
describe('HtmlEditor.vue', () => {
|
||||||
const originalRangeGetBoundingClientRect = Range.prototype.getBoundingClientRect;
|
|
||||||
const originalRangeGetClientRects = Range.prototype.getClientRects;
|
|
||||||
|
|
||||||
const pinia = createTestingPinia({
|
const pinia = createTestingPinia({
|
||||||
initialState: {
|
initialState: {
|
||||||
[STORES.SETTINGS]: {
|
[STORES.SETTINGS]: {
|
||||||
@@ -29,20 +26,6 @@ describe('HtmlEditor.vue', () => {
|
|||||||
});
|
});
|
||||||
setActivePinia(pinia);
|
setActivePinia(pinia);
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
Range.prototype.getBoundingClientRect = vi.fn();
|
|
||||||
Range.prototype.getClientRects = () => ({
|
|
||||||
item: vi.fn(),
|
|
||||||
length: 0,
|
|
||||||
[Symbol.iterator]: vi.fn(),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
Range.prototype.getBoundingClientRect = originalRangeGetBoundingClientRect;
|
|
||||||
Range.prototype.getClientRects = originalRangeGetClientRects;
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -28,9 +28,6 @@ vi.mock('@/stores/n8nRoot.store', () => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
describe('useAutocompleteTelemetry', () => {
|
describe('useAutocompleteTelemetry', () => {
|
||||||
const originalRangeGetBoundingClientRect = Range.prototype.getBoundingClientRect;
|
|
||||||
const originalRangeGetClientRects = Range.prototype.getClientRects;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
setActivePinia(createTestingPinia());
|
setActivePinia(createTestingPinia());
|
||||||
});
|
});
|
||||||
@@ -39,20 +36,6 @@ describe('useAutocompleteTelemetry', () => {
|
|||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
Range.prototype.getBoundingClientRect = vi.fn();
|
|
||||||
Range.prototype.getClientRects = () => ({
|
|
||||||
item: vi.fn(),
|
|
||||||
length: 0,
|
|
||||||
[Symbol.iterator]: vi.fn(),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
Range.prototype.getBoundingClientRect = originalRangeGetBoundingClientRect;
|
|
||||||
Range.prototype.getClientRects = originalRangeGetClientRects;
|
|
||||||
});
|
|
||||||
|
|
||||||
const getEditor = (defaultDoc = '') => {
|
const getEditor = (defaultDoc = '') => {
|
||||||
const extensionCompartment = new Compartment();
|
const extensionCompartment = new Compartment();
|
||||||
const state = EditorState.create({
|
const state = EditorState.create({
|
||||||
|
|||||||
@@ -21,9 +21,6 @@ vi.mock('@/stores/ndv.store', () => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
describe('useExpressionEditor', () => {
|
describe('useExpressionEditor', () => {
|
||||||
const originalRangeGetBoundingClientRect = Range.prototype.getBoundingClientRect;
|
|
||||||
const originalRangeGetClientRects = Range.prototype.getClientRects;
|
|
||||||
|
|
||||||
const mockResolveExpression = () => {
|
const mockResolveExpression = () => {
|
||||||
const mock = vi.fn();
|
const mock = vi.fn();
|
||||||
vi.spyOn(workflowHelpers, 'useWorkflowHelpers').mockReturnValueOnce({
|
vi.spyOn(workflowHelpers, 'useWorkflowHelpers').mockReturnValueOnce({
|
||||||
@@ -42,20 +39,6 @@ describe('useExpressionEditor', () => {
|
|||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
Range.prototype.getBoundingClientRect = vi.fn();
|
|
||||||
Range.prototype.getClientRects = () => ({
|
|
||||||
item: vi.fn(),
|
|
||||||
length: 0,
|
|
||||||
[Symbol.iterator]: vi.fn(),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
Range.prototype.getBoundingClientRect = originalRangeGetBoundingClientRect;
|
|
||||||
Range.prototype.getClientRects = originalRangeGetClientRects;
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should create an editor', async () => {
|
test('should create an editor', async () => {
|
||||||
const root = ref<HTMLElement>();
|
const root = ref<HTMLElement>();
|
||||||
const { editor } = useExpressionEditor({
|
const { editor } = useExpressionEditor({
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ export const useExpressionEditor = ({
|
|||||||
if (editor.value) {
|
if (editor.value) {
|
||||||
editor.value.destroy();
|
editor.value.destroy();
|
||||||
}
|
}
|
||||||
editor.value = new EditorView({ parent, state });
|
editor.value = new EditorView({ parent, state, scrollTo: EditorView.scrollIntoView(0) });
|
||||||
debouncedUpdateSegments();
|
debouncedUpdateSegments();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -385,7 +385,8 @@ export const useExpressionEditor = ({
|
|||||||
function setCursorPosition(pos: number | 'lastExpression' | 'end'): void {
|
function setCursorPosition(pos: number | 'lastExpression' | 'end'): void {
|
||||||
if (pos === 'lastExpression') {
|
if (pos === 'lastExpression') {
|
||||||
const END_OF_EXPRESSION = ' }}';
|
const END_OF_EXPRESSION = ' }}';
|
||||||
pos = Math.max(readEditorValue().lastIndexOf(END_OF_EXPRESSION), 0);
|
const endOfLastExpression = readEditorValue().lastIndexOf(END_OF_EXPRESSION);
|
||||||
|
pos = endOfLastExpression !== -1 ? endOfLastExpression : editor.value?.state.doc.length ?? 0;
|
||||||
} else if (pos === 'end') {
|
} else if (pos === 'end') {
|
||||||
pos = editor.value?.state.doc.length ?? 0;
|
pos = editor.value?.state.doc.length ?? 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -382,7 +382,10 @@ function parseJson(value: string): unknown {
|
|||||||
try {
|
try {
|
||||||
return JSON.parse(value);
|
return JSON.parse(value);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return undefined;
|
if (value.includes("'")) {
|
||||||
|
throw new ExpressionExtensionError("Parsing failed. Check you're using double quotes");
|
||||||
|
}
|
||||||
|
throw new ExpressionExtensionError('Parsing failed');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -270,7 +270,13 @@ describe('Data Transformation Functions', () => {
|
|||||||
test1: 1,
|
test1: 1,
|
||||||
test2: '2',
|
test2: '2',
|
||||||
});
|
});
|
||||||
expect(evaluate('={{ "hi".parseJson() }}')).toBeUndefined();
|
});
|
||||||
|
|
||||||
|
test('.parseJson should throw on invalid JSON', () => {
|
||||||
|
expect(() => evaluate("={{ \"{'test1':1,'test2':'2'}\".parseJson() }}")).toThrowError(
|
||||||
|
"Parsing failed. Check you're using double quotes",
|
||||||
|
);
|
||||||
|
expect(() => evaluate('={{ "No JSON here".parseJson() }}')).toThrowError('Parsing failed');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('.toBoolean should work on a string', () => {
|
test('.toBoolean should work on a string', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user