mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
fix(editor): Escape node names with quotes in autocomplete and drag'n'drop (#8663)
This commit is contained in:
parent
c346002cc6
commit
890c2bd52b
|
@ -5,6 +5,7 @@ import type { Completion, CompletionContext, CompletionResult } from '@codemirro
|
||||||
import type { INodeUi } from '@/Interface';
|
import type { INodeUi } from '@/Interface';
|
||||||
import { mapStores } from 'pinia';
|
import { mapStores } from 'pinia';
|
||||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||||
|
import { escapeMappingString } from '@/utils/mappingUtils';
|
||||||
|
|
||||||
function getAutoCompletableNodeNames(nodes: INodeUi[]) {
|
function getAutoCompletableNodeNames(nodes: INodeUi[]) {
|
||||||
return nodes
|
return nodes
|
||||||
|
@ -98,7 +99,7 @@ export const baseCompletions = defineComponent({
|
||||||
options.push(
|
options.push(
|
||||||
...getAutoCompletableNodeNames(this.workflowsStore.allNodes).map((nodeName) => {
|
...getAutoCompletableNodeNames(this.workflowsStore.allNodes).map((nodeName) => {
|
||||||
return {
|
return {
|
||||||
label: `${prefix}('${nodeName}')`,
|
label: `${prefix}('${escapeMappingString(nodeName)}')`,
|
||||||
type: 'variable',
|
type: 'variable',
|
||||||
info: this.$locale.baseText('codeNodeEditor.completer.$()', {
|
info: this.$locale.baseText('codeNodeEditor.completer.$()', {
|
||||||
interpolate: { nodeName },
|
interpolate: { nodeName },
|
||||||
|
@ -138,7 +139,7 @@ export const baseCompletions = defineComponent({
|
||||||
const options: Completion[] = getAutoCompletableNodeNames(this.workflowsStore.allNodes).map(
|
const options: Completion[] = getAutoCompletableNodeNames(this.workflowsStore.allNodes).map(
|
||||||
(nodeName) => {
|
(nodeName) => {
|
||||||
return {
|
return {
|
||||||
label: `${prefix}('${nodeName}')`,
|
label: `${prefix}('${escapeMappingString(nodeName)}')`,
|
||||||
type: 'variable',
|
type: 'variable',
|
||||||
info: this.$locale.baseText('codeNodeEditor.completer.$()', {
|
info: this.$locale.baseText('codeNodeEditor.completer.$()', {
|
||||||
interpolate: { nodeName },
|
interpolate: { nodeName },
|
||||||
|
|
|
@ -50,6 +50,7 @@ import { useRootStore } from '@/stores/n8nRoot.store';
|
||||||
import { useNDVStore } from '@/stores/ndv.store';
|
import { useNDVStore } from '@/stores/ndv.store';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
|
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
|
||||||
|
import { escapeMappingString } from '@/utils/mappingUtils';
|
||||||
|
|
||||||
// Node types that should not be displayed in variable selector
|
// Node types that should not be displayed in variable selector
|
||||||
const SKIPPED_NODE_TYPES = [STICKY_NODE_TYPE];
|
const SKIPPED_NODE_TYPES = [STICKY_NODE_TYPE];
|
||||||
|
@ -398,7 +399,9 @@ export default defineComponent({
|
||||||
|
|
||||||
// Get json data
|
// Get json data
|
||||||
if (outputData.hasOwnProperty('json')) {
|
if (outputData.hasOwnProperty('json')) {
|
||||||
const jsonPropertyPrefix = useShort ? '$json' : `$('${nodeName}').item.json`;
|
const jsonPropertyPrefix = useShort
|
||||||
|
? '$json'
|
||||||
|
: `$('${escapeMappingString(nodeName)}').item.json`;
|
||||||
|
|
||||||
const jsonDataOptions: IVariableSelectorOption[] = [];
|
const jsonDataOptions: IVariableSelectorOption[] = [];
|
||||||
for (const propertyName of Object.keys(outputData.json)) {
|
for (const propertyName of Object.keys(outputData.json)) {
|
||||||
|
@ -423,7 +426,9 @@ export default defineComponent({
|
||||||
|
|
||||||
// Get binary data
|
// Get binary data
|
||||||
if (outputData.hasOwnProperty('binary')) {
|
if (outputData.hasOwnProperty('binary')) {
|
||||||
const binaryPropertyPrefix = useShort ? '$binary' : `$('${nodeName}').item.binary`;
|
const binaryPropertyPrefix = useShort
|
||||||
|
? '$binary'
|
||||||
|
: `$('${escapeMappingString(nodeName)}').item.binary`;
|
||||||
|
|
||||||
const binaryData = [];
|
const binaryData = [];
|
||||||
let binaryPropertyData = [];
|
let binaryPropertyData = [];
|
||||||
|
@ -537,7 +542,7 @@ export default defineComponent({
|
||||||
|
|
||||||
returnData.push({
|
returnData.push({
|
||||||
name: key,
|
name: key,
|
||||||
key: `$('${nodeName}').context["${key}"]`,
|
key: `$('${escapeMappingString(nodeName)}').context['${escapeMappingString(key)}']`,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
value: nodeContext[key],
|
value: nodeContext[key],
|
||||||
});
|
});
|
||||||
|
@ -793,7 +798,12 @@ export default defineComponent({
|
||||||
{
|
{
|
||||||
name: this.$locale.baseText('variableSelector.parameters'),
|
name: this.$locale.baseText('variableSelector.parameters'),
|
||||||
options: this.sortOptions(
|
options: this.sortOptions(
|
||||||
this.getNodeParameters(nodeName, `$('${nodeName}').params`, undefined, filterText),
|
this.getNodeParameters(
|
||||||
|
nodeName,
|
||||||
|
`$('${escapeMappingString(nodeName)}').params`,
|
||||||
|
undefined,
|
||||||
|
filterText,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
} as IVariableSelectorOption,
|
} as IVariableSelectorOption,
|
||||||
];
|
];
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
} from './utils';
|
} from './utils';
|
||||||
import type { Completion, CompletionContext, CompletionResult } from '@codemirror/autocomplete';
|
import type { Completion, CompletionContext, CompletionResult } from '@codemirror/autocomplete';
|
||||||
import { useExternalSecretsStore } from '@/stores/externalSecrets.ee.store';
|
import { useExternalSecretsStore } from '@/stores/externalSecrets.ee.store';
|
||||||
|
import { escapeMappingString } from '@/utils/mappingUtils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Completions offered at the dollar position: `$|`
|
* Completions offered at the dollar position: `$|`
|
||||||
|
@ -90,7 +91,7 @@ export function dollarOptions() {
|
||||||
})
|
})
|
||||||
.concat(
|
.concat(
|
||||||
autocompletableNodeNames().map((nodeName) => ({
|
autocompletableNodeNames().map((nodeName) => ({
|
||||||
label: `$('${nodeName}')`,
|
label: `$('${escapeMappingString(nodeName)}')`,
|
||||||
type: 'keyword',
|
type: 'keyword',
|
||||||
info: i18n.baseText('codeNodeEditor.completer.$()', { interpolate: { nodeName } }),
|
info: i18n.baseText('codeNodeEditor.completer.$()', { interpolate: { nodeName } }),
|
||||||
})),
|
})),
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import type { INodeProperties } from 'n8n-workflow';
|
import type { INodeProperties } from 'n8n-workflow';
|
||||||
import { getMappedResult, getMappedExpression } from '../mappingUtils';
|
import { getMappedResult, getMappedExpression, escapeMappingString } from '../mappingUtils';
|
||||||
|
|
||||||
const RLC_PARAM: INodeProperties = {
|
const RLC_PARAM: INodeProperties = {
|
||||||
displayName: 'Base',
|
displayName: 'Base',
|
||||||
|
@ -273,4 +273,12 @@ describe('Mapping Utils', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('escapeMappingString', () => {
|
||||||
|
test.each([
|
||||||
|
{ input: 'Normal node name (here)', output: 'Normal node name (here)' },
|
||||||
|
{ input: "'Should es'ape quotes here'", output: "\\'Should es\\'ape quotes here\\'" },
|
||||||
|
])('should escape "$input" to "$output"', ({ input, output }) => {
|
||||||
|
expect(escapeMappingString(input)).toEqual(output);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -18,6 +18,10 @@ export function generatePath(root: string, path: Array<string | number>): string
|
||||||
}, root);
|
}, root);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function escapeMappingString(str: string): string {
|
||||||
|
return str.replace(/\'/g, "\\'");
|
||||||
|
}
|
||||||
|
|
||||||
export function getMappedExpression({
|
export function getMappedExpression({
|
||||||
nodeName,
|
nodeName,
|
||||||
distanceFromActive,
|
distanceFromActive,
|
||||||
|
@ -28,7 +32,9 @@ export function getMappedExpression({
|
||||||
path: Array<string | number> | string;
|
path: Array<string | number> | string;
|
||||||
}) {
|
}) {
|
||||||
const root =
|
const root =
|
||||||
distanceFromActive === 1 ? '$json' : generatePath(`$('${nodeName}')`, ['item', 'json']);
|
distanceFromActive === 1
|
||||||
|
? '$json'
|
||||||
|
: generatePath(`$('${escapeMappingString(nodeName)}')`, ['item', 'json']);
|
||||||
|
|
||||||
if (typeof path === 'string') {
|
if (typeof path === 'string') {
|
||||||
return `{{ ${root}${path} }}`;
|
return `{{ ${root}${path} }}`;
|
||||||
|
|
Loading…
Reference in a new issue