mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-11 04:47:29 -08:00
fix(editor): UI enhancements and fixes for expression inputs (#8996)
This commit is contained in:
parent
1cbd044e41
commit
8788e2a35b
|
@ -12,3 +12,10 @@ window.ResizeObserver =
|
|||
}));
|
||||
|
||||
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
|
||||
hide-label
|
||||
hide-hint
|
||||
:rows="3"
|
||||
:is-read-only="isReadOnly"
|
||||
:parameter="nameParameter"
|
||||
:value="assignment.name"
|
||||
|
@ -196,7 +195,6 @@ const onBlur = (): void => {
|
|||
hide-label
|
||||
hide-issues
|
||||
hide-hint
|
||||
:rows="3"
|
||||
is-assignment
|
||||
:is-read-only="isReadOnly"
|
||||
:options-position="breakpoint === 'default' ? 'top' : 'bottom'"
|
||||
|
|
|
@ -156,7 +156,6 @@ const onBlur = (): void => {
|
|||
hide-label
|
||||
hide-hint
|
||||
hide-issues
|
||||
:rows="3"
|
||||
:is-read-only="readOnly"
|
||||
:parameter="leftParameter"
|
||||
:value="condition.leftValue"
|
||||
|
@ -181,7 +180,6 @@ const onBlur = (): void => {
|
|||
hide-label
|
||||
hide-hint
|
||||
hide-issues
|
||||
:rows="3"
|
||||
:is-read-only="readOnly"
|
||||
:options-position="breakpoint === 'default' ? 'top' : 'bottom'"
|
||||
:parameter="rightParameter"
|
||||
|
|
|
@ -45,21 +45,24 @@ const resolvedExpression = computed(() => {
|
|||
});
|
||||
|
||||
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');
|
||||
});
|
||||
|
||||
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;
|
||||
|
||||
return props.segments
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<n8n-input-label
|
||||
:class="$style.wrapper"
|
||||
:class="[$style.wrapper, { [$style.tipVisible]: showDragnDropTip }]"
|
||||
:label="hideLabel ? '' : i18n.nodeText().inputLabelDisplayName(parameter, path)"
|
||||
:tooltip-text="hideLabel ? '' : i18n.nodeText().inputLabelDescription(parameter, path)"
|
||||
:show-tooltip="focused"
|
||||
|
@ -357,6 +357,11 @@ export default defineComponent({
|
|||
}
|
||||
}
|
||||
|
||||
.tipVisible {
|
||||
--input-border-bottom-left-radius: 0;
|
||||
--input-border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.tip {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
|
|
|
@ -6,8 +6,6 @@ import { setActivePinia } from 'pinia';
|
|||
import { waitFor } from '@testing-library/vue';
|
||||
|
||||
describe('ExpressionParameterInput', () => {
|
||||
const originalRangeGetBoundingClientRect = Range.prototype.getBoundingClientRect;
|
||||
const originalRangeGetClientRects = Range.prototype.getClientRects;
|
||||
const renderComponent = createComponentRenderer(ExpressionEditorModalInput);
|
||||
let pinia: TestingPinia;
|
||||
|
||||
|
@ -16,19 +14,6 @@ describe('ExpressionParameterInput', () => {
|
|||
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([
|
||||
['not be editable', 'readonly', true, ''],
|
||||
['be editable', 'not readonly', false, 'test'],
|
||||
|
|
|
@ -17,9 +17,6 @@ const DEFAULT_SETUP = {
|
|||
};
|
||||
|
||||
describe('HtmlEditor.vue', () => {
|
||||
const originalRangeGetBoundingClientRect = Range.prototype.getBoundingClientRect;
|
||||
const originalRangeGetClientRects = Range.prototype.getClientRects;
|
||||
|
||||
const pinia = createTestingPinia({
|
||||
initialState: {
|
||||
[STORES.SETTINGS]: {
|
||||
|
@ -29,20 +26,6 @@ describe('HtmlEditor.vue', () => {
|
|||
});
|
||||
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(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
|
|
@ -28,9 +28,6 @@ vi.mock('@/stores/n8nRoot.store', () => ({
|
|||
}));
|
||||
|
||||
describe('useAutocompleteTelemetry', () => {
|
||||
const originalRangeGetBoundingClientRect = Range.prototype.getBoundingClientRect;
|
||||
const originalRangeGetClientRects = Range.prototype.getClientRects;
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePinia(createTestingPinia());
|
||||
});
|
||||
|
@ -39,20 +36,6 @@ describe('useAutocompleteTelemetry', () => {
|
|||
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 extensionCompartment = new Compartment();
|
||||
const state = EditorState.create({
|
||||
|
|
|
@ -21,9 +21,6 @@ vi.mock('@/stores/ndv.store', () => ({
|
|||
}));
|
||||
|
||||
describe('useExpressionEditor', () => {
|
||||
const originalRangeGetBoundingClientRect = Range.prototype.getBoundingClientRect;
|
||||
const originalRangeGetClientRects = Range.prototype.getClientRects;
|
||||
|
||||
const mockResolveExpression = () => {
|
||||
const mock = vi.fn();
|
||||
vi.spyOn(workflowHelpers, 'useWorkflowHelpers').mockReturnValueOnce({
|
||||
|
@ -42,20 +39,6 @@ describe('useExpressionEditor', () => {
|
|||
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 () => {
|
||||
const root = ref<HTMLElement>();
|
||||
const { editor } = useExpressionEditor({
|
||||
|
|
|
@ -185,7 +185,7 @@ export const useExpressionEditor = ({
|
|||
if (editor.value) {
|
||||
editor.value.destroy();
|
||||
}
|
||||
editor.value = new EditorView({ parent, state });
|
||||
editor.value = new EditorView({ parent, state, scrollTo: EditorView.scrollIntoView(0) });
|
||||
debouncedUpdateSegments();
|
||||
});
|
||||
|
||||
|
@ -385,7 +385,8 @@ export const useExpressionEditor = ({
|
|||
function setCursorPosition(pos: number | 'lastExpression' | 'end'): void {
|
||||
if (pos === 'lastExpression') {
|
||||
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') {
|
||||
pos = editor.value?.state.doc.length ?? 0;
|
||||
}
|
||||
|
|
|
@ -382,7 +382,10 @@ function parseJson(value: string): unknown {
|
|||
try {
|
||||
return JSON.parse(value);
|
||||
} 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,
|
||||
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', () => {
|
||||
|
|
Loading…
Reference in a new issue