feat(editor): Add examples for root expression methods (#9373)

This commit is contained in:
Elias Meire 2024-05-22 10:05:31 +02:00 committed by GitHub
parent 7cb431f506
commit a591f63e3f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 800 additions and 263 deletions

View file

@ -1,5 +1,5 @@
import { NODE_TYPES_EXCLUDED_FROM_AUTOCOMPLETION } from '../constants'; import { NODE_TYPES_EXCLUDED_FROM_AUTOCOMPLETION } from '../constants';
import { addVarType } from '../utils'; import { addInfoRenderer, addVarType } from '../utils';
import type { Completion, CompletionContext, CompletionResult } from '@codemirror/autocomplete'; import type { Completion, CompletionContext, CompletionResult } from '@codemirror/autocomplete';
import type { INodeUi } from '@/Interface'; import type { INodeUi } from '@/Interface';
import { useWorkflowsStore } from '@/stores/workflows.store'; import { useWorkflowsStore } from '@/stores/workflows.store';
@ -128,7 +128,7 @@ export function useBaseCompletions(
return { return {
from: preCursor.from, from: preCursor.from,
options, options: options.map(addInfoRenderer),
}; };
}; };

View file

@ -1,4 +1,4 @@
import { addVarType, escape } from '../utils'; import { addInfoRenderer, addVarType, escape } from '../utils';
import type { Completion, CompletionContext, CompletionResult } from '@codemirror/autocomplete'; import type { Completion, CompletionContext, CompletionResult } from '@codemirror/autocomplete';
import { useI18n } from '@/composables/useI18n'; import { useI18n } from '@/composables/useI18n';
@ -18,15 +18,6 @@ export function useExecutionCompletions() {
if (!preCursor || (preCursor.from === preCursor.to && !context.explicit)) return null; if (!preCursor || (preCursor.from === preCursor.to && !context.explicit)) return null;
const buildLinkNode = (text: string) => {
const wrapper = document.createElement('span');
// This is being loaded from the locales file. This could
// cause an XSS of some kind but multiple other locales strings
// do the same thing.
wrapper.innerHTML = text;
return () => wrapper;
};
const options: Completion[] = [ const options: Completion[] = [
{ {
label: `${matcher}.id`, label: `${matcher}.id`,
@ -46,29 +37,25 @@ export function useExecutionCompletions() {
}, },
{ {
label: `${matcher}.customData.set("key", "value")`, label: `${matcher}.customData.set("key", "value")`,
info: buildLinkNode(i18n.baseText('codeNodeEditor.completer.$execution.customData.set()')), info: i18n.baseText('codeNodeEditor.completer.$execution.customData.set'),
}, },
{ {
label: `${matcher}.customData.get("key")`, label: `${matcher}.customData.get("key")`,
info: buildLinkNode(i18n.baseText('codeNodeEditor.completer.$execution.customData.get()')), info: i18n.baseText('codeNodeEditor.completer.$execution.customData.get'),
}, },
{ {
label: `${matcher}.customData.setAll({})`, label: `${matcher}.customData.setAll({})`,
info: buildLinkNode( info: i18n.baseText('codeNodeEditor.completer.$execution.customData.setAll'),
i18n.baseText('codeNodeEditor.completer.$execution.customData.setAll()'),
),
}, },
{ {
label: `${matcher}.customData.getAll()`, label: `${matcher}.customData.getAll()`,
info: buildLinkNode( info: i18n.baseText('codeNodeEditor.completer.$execution.customData.getAll'),
i18n.baseText('codeNodeEditor.completer.$execution.customData.getAll()'),
),
}, },
]; ];
return { return {
from: preCursor.from, from: preCursor.from,
options: options.map(addVarType), options: options.map(addVarType).map(addInfoRenderer),
}; };
}; };

View file

@ -2,6 +2,7 @@ import type * as esprima from 'esprima-next';
import type { Completion } from '@codemirror/autocomplete'; import type { Completion } from '@codemirror/autocomplete';
import type { Node } from 'estree'; import type { Node } from 'estree';
import type { RangeNode } from './types'; import type { RangeNode } from './types';
import { sanitizeHtml } from '@/utils/htmlUtils';
export function walk<T extends RangeNode>( export function walk<T extends RangeNode>(
node: Node | esprima.Program, node: Node | esprima.Program,
@ -40,3 +41,15 @@ export const escape = (str: string) =>
export const toVariableOption = (label: string) => ({ label, type: 'variable' }); export const toVariableOption = (label: string) => ({ label, type: 'variable' });
export const addVarType = (option: Completion) => ({ ...option, type: 'variable' }); export const addVarType = (option: Completion) => ({ ...option, type: 'variable' });
export const addInfoRenderer = (option: Completion): Completion => {
const { info } = option;
if (typeof info === 'string') {
option.info = () => {
const wrapper = document.createElement('span');
wrapper.innerHTML = sanitizeHtml(info);
return wrapper;
};
}
return option;
};

View file

@ -772,11 +772,19 @@ describe('Resolution-based completions', () => {
test('should not display type information for other completions', () => { test('should not display type information for other completions', () => {
vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValue({ vi.spyOn(workflowHelpers, 'resolveParameter').mockReturnValue({
str: 'bar', str: 'bar',
id: '123',
isExecuted: false,
}); });
expect(completions('{{ $execution.| }}')?.every((item) => !item.detail)).toBe(true); expect(completions('{{ $execution.| }}')).not.toContainEqual(
expect(completions('{{ $input.params.| }}')?.every((item) => !item.detail)).toBe(true); expect.objectContaining({ detail: expect.any(String) }),
expect(completions('{{ $("My Node").| }}')?.every((item) => !item.detail)).toBe(true); );
expect(completions('{{ $input.params.| }}')).not.toContainEqual(
expect.objectContaining({ detail: expect.any(String) }),
);
expect(completions('{{ $("My Node").| }}')).not.toContainEqual(
expect.objectContaining({ detail: expect.any(String) }),
);
}); });
}); });
}); });

View file

@ -55,7 +55,7 @@ export const ROOT_DOLLAR_COMPLETIONS: Completion[] = [
info: createInfoBoxRenderer({ info: createInfoBoxRenderer({
name: '$json', name: '$json',
returnType: 'object', returnType: 'object',
description: i18n.rootVars.$json, description: i18n.baseText('codeNodeEditor.completer.json'),
docURL: 'https://docs.n8n.io/data/data-structure/', docURL: 'https://docs.n8n.io/data/data-structure/',
}), }),
}, },
@ -65,7 +65,7 @@ export const ROOT_DOLLAR_COMPLETIONS: Completion[] = [
info: createInfoBoxRenderer({ info: createInfoBoxRenderer({
name: '$binary', name: '$binary',
returnType: 'object', returnType: 'object',
description: i18n.rootVars.$binary, description: i18n.baseText('codeNodeEditor.completer.binary'),
}), }),
}, },
{ {
@ -74,7 +74,7 @@ export const ROOT_DOLLAR_COMPLETIONS: Completion[] = [
info: createInfoBoxRenderer({ info: createInfoBoxRenderer({
name: '$now', name: '$now',
returnType: 'DateTime', returnType: 'DateTime',
description: i18n.rootVars.$now, description: i18n.baseText('codeNodeEditor.completer.$now'),
}), }),
}, },
{ {
@ -83,25 +83,36 @@ export const ROOT_DOLLAR_COMPLETIONS: Completion[] = [
info: createInfoBoxRenderer( info: createInfoBoxRenderer(
{ {
name: '$if', name: '$if',
returnType: 'boolean', returnType: 'any',
description: i18n.rootVars.$if, description: i18n.baseText('codeNodeEditor.completer.$if'),
args: [ args: [
{ {
name: 'condition', name: 'condition',
optional: false, description: i18n.baseText('codeNodeEditor.completer.$if.args.condition'),
type: 'boolean', type: 'boolean',
}, },
{ {
name: 'valueIfTrue', name: 'valueIfTrue',
optional: false, description: i18n.baseText('codeNodeEditor.completer.$if.args.valueIfTrue'),
type: 'any', type: 'any',
}, },
{ {
name: 'valueIfFalse', name: 'valueIfFalse',
optional: false, description: i18n.baseText('codeNodeEditor.completer.$if.args.valueIfFalse'),
type: 'any', type: 'any',
}, },
], ],
examples: [
{
example: '$if($now.hour < 17, "Good day", "Good evening")',
description: i18n.baseText('codeNodeEditor.completer.$if.examples.1'),
},
{
description: i18n.baseText('codeNodeEditor.completer.$if.examples.2'),
example:
'$if($now.hour < 10, "Good morning", $if($now.hour < 17, "Good day", "Good evening"))',
},
],
}, },
true, true,
), ),
@ -112,20 +123,26 @@ export const ROOT_DOLLAR_COMPLETIONS: Completion[] = [
info: createInfoBoxRenderer( info: createInfoBoxRenderer(
{ {
name: '$ifEmpty', name: '$ifEmpty',
returnType: 'boolean', returnType: 'any',
description: i18n.rootVars.$ifEmpty, description: i18n.baseText('codeNodeEditor.completer.$ifEmpty'),
args: [ args: [
{ {
name: 'value', name: 'value',
optional: false, description: i18n.baseText('codeNodeEditor.completer.$ifEmpty.args.value'),
type: 'any', type: 'any',
}, },
{ {
name: 'valueIfEmpty', name: 'valueIfEmpty',
optional: false, description: i18n.baseText('codeNodeEditor.completer.$ifEmpty.args.valueIfEmpty'),
type: 'any', type: 'any',
}, },
], ],
examples: [
{
example: '"Hi " + $ifEmpty(name, "there")',
evaluated: 'e.g. "Hi Nathan" or "Hi there"',
},
],
}, },
true, true,
), ),
@ -135,8 +152,8 @@ export const ROOT_DOLLAR_COMPLETIONS: Completion[] = [
section: METADATA_SECTION, section: METADATA_SECTION,
info: createInfoBoxRenderer({ info: createInfoBoxRenderer({
name: '$execution', name: '$execution',
returnType: 'object', returnType: 'ExecData',
description: i18n.rootVars.$execution, description: i18n.baseText('codeNodeEditor.completer.$execution'),
}), }),
}, },
{ {
@ -145,7 +162,7 @@ export const ROOT_DOLLAR_COMPLETIONS: Completion[] = [
info: createInfoBoxRenderer({ info: createInfoBoxRenderer({
name: '$itemIndex', name: '$itemIndex',
returnType: 'number', returnType: 'number',
description: i18n.rootVars.$itemIndex, description: i18n.baseText('codeNodeEditor.completer.$itemIndex'),
}), }),
}, },
{ {
@ -154,7 +171,7 @@ export const ROOT_DOLLAR_COMPLETIONS: Completion[] = [
info: createInfoBoxRenderer({ info: createInfoBoxRenderer({
name: '$input', name: '$input',
returnType: 'object', returnType: 'object',
description: i18n.rootVars.$input, description: i18n.baseText('codeNodeEditor.completer.$input'),
}), }),
}, },
{ {
@ -163,7 +180,7 @@ export const ROOT_DOLLAR_COMPLETIONS: Completion[] = [
info: createInfoBoxRenderer({ info: createInfoBoxRenderer({
name: '$parameter', name: '$parameter',
returnType: 'object', returnType: 'object',
description: i18n.rootVars.$parameter, description: i18n.baseText('codeNodeEditor.completer.$parameter'),
}), }),
}, },
{ {
@ -171,8 +188,8 @@ export const ROOT_DOLLAR_COMPLETIONS: Completion[] = [
section: METADATA_SECTION, section: METADATA_SECTION,
info: createInfoBoxRenderer({ info: createInfoBoxRenderer({
name: '$prevNode', name: '$prevNode',
returnType: 'object', returnType: 'PrevNodeData',
description: i18n.rootVars.$prevNode, description: i18n.baseText('codeNodeEditor.completer.$prevNode'),
}), }),
}, },
{ {
@ -181,7 +198,7 @@ export const ROOT_DOLLAR_COMPLETIONS: Completion[] = [
info: createInfoBoxRenderer({ info: createInfoBoxRenderer({
name: '$runIndex', name: '$runIndex',
returnType: 'number', returnType: 'number',
description: i18n.rootVars.$runIndex, description: i18n.baseText('codeNodeEditor.completer.$runIndex'),
}), }),
}, },
{ {
@ -190,7 +207,7 @@ export const ROOT_DOLLAR_COMPLETIONS: Completion[] = [
info: createInfoBoxRenderer({ info: createInfoBoxRenderer({
name: '$today', name: '$today',
returnType: 'DateTime', returnType: 'DateTime',
description: i18n.rootVars.$today, description: i18n.baseText('codeNodeEditor.completer.$today'),
}), }),
}, },
{ {
@ -199,7 +216,7 @@ export const ROOT_DOLLAR_COMPLETIONS: Completion[] = [
info: createInfoBoxRenderer({ info: createInfoBoxRenderer({
name: '$vars', name: '$vars',
returnType: 'object', returnType: 'object',
description: i18n.rootVars.$vars, description: i18n.baseText('codeNodeEditor.completer.$vars'),
}), }),
}, },
{ {
@ -207,8 +224,8 @@ export const ROOT_DOLLAR_COMPLETIONS: Completion[] = [
section: METADATA_SECTION, section: METADATA_SECTION,
info: createInfoBoxRenderer({ info: createInfoBoxRenderer({
name: '$workflow', name: '$workflow',
returnType: 'object', returnType: 'WorkflowData',
description: i18n.rootVars.$workflow, description: i18n.baseText('codeNodeEditor.completer.$workflow'),
}), }),
}, },
{ {
@ -217,8 +234,44 @@ export const ROOT_DOLLAR_COMPLETIONS: Completion[] = [
info: createInfoBoxRenderer( info: createInfoBoxRenderer(
{ {
name: '$jmespath', name: '$jmespath',
description: i18n.baseText('codeNodeEditor.completer.$jmespath'),
returnType: 'any', returnType: 'any',
description: i18n.rootVars.$jmespath, args: [
{
name: 'obj',
description: i18n.baseText('codeNodeEditor.completer.$jmespath.args.obj'),
type: 'Object | Array',
},
{
name: 'expression',
description: i18n.baseText('codeNodeEditor.completer.$jmespath.args.expression'),
type: 'string',
},
],
examples: [
{
example:
'data = {\n "people": [\n {\n "name": "Bob",\n "age": 20,\n "other": "foo"\n },\n {\n "name": "Fred",\n "age": 25,\n "other": "bar"\n },\n {\n "name": "George",\n "age": 30,\n "other": "baz"\n }\n ]\n}\n\n$jmespath(data.people, \'[*].name\')',
evaluated: "['Bob', 'Fred', 'George']",
description: i18n.baseText('codeNodeEditor.completer.$jmespath.examples.1'),
},
{
example: "$jmespath(data.people, '[?age > `20`].[name, age]')",
evaluated: "[['Fred', 25], ['George', 30]]",
description: i18n.baseText('codeNodeEditor.completer.$jmespath.examples.2'),
},
{
example: "$jmespath(data.people, '[?age > `20`].name | [0]')",
evaluated: 'Fred',
description: i18n.baseText('codeNodeEditor.completer.$jmespath.examples.3'),
},
{
example:
'data = {\n "reservations": [\n {\n "id": 1,\n "guests": [\n {\n "name": "Nathan",\n "requirements": {\n "room": "double",\n "meal": "vegetarian"\n }\n },\n {\n "name": "Meg",\n "requirements": {\n "room": "single"\n }\n }\n ]\n },\n {\n "id": 2,\n "guests": [\n {\n "name": "Lex",\n "requirements": {\n "room": "double"\n }\n }\n ]\n }\n ]\n}\n\n$jmespath(data, "reservations[].guests[?requirements.room==\'double\'][].name")',
evaluated: "['Nathan', 'Lex']",
description: i18n.baseText('codeNodeEditor.completer.$jmespath.examples.4'),
},
],
}, },
true, true,
), ),
@ -230,24 +283,16 @@ export const ROOT_DOLLAR_COMPLETIONS: Completion[] = [
{ {
name: '$max', name: '$max',
returnType: 'number', returnType: 'number',
description: i18n.rootVars.$max, description: i18n.baseText('codeNodeEditor.completer.$max'),
args: [ args: [
{ {
name: 'number1', name: 'numbers',
optional: false, description: i18n.baseText('codeNodeEditor.completer.$max.args.numbers'),
type: 'number',
},
{
name: 'number2',
optional: true,
type: 'number',
},
{
name: 'numberN',
optional: true,
type: 'number', type: 'number',
variadic: true,
}, },
], ],
examples: [{ example: '$max(1, 5, 42, 0.5)', evaluated: '42' }],
}, },
true, true,
), ),
@ -259,24 +304,16 @@ export const ROOT_DOLLAR_COMPLETIONS: Completion[] = [
{ {
name: '$min', name: '$min',
returnType: 'number', returnType: 'number',
description: i18n.rootVars.$min, description: i18n.baseText('codeNodeEditor.completer.$min'),
args: [ args: [
{ {
name: 'number1', name: 'numbers',
optional: false, description: i18n.baseText('codeNodeEditor.completer.$max.args.numbers'),
type: 'number', variadic: true,
},
{
name: 'number2',
optional: true,
type: 'number',
},
{
name: 'numberN',
optional: true,
type: 'number', type: 'number',
}, },
], ],
examples: [{ example: '$min(1, 5, 42, 0.5)', evaluated: '0.5' }],
}, },
true, true,
), ),
@ -284,7 +321,11 @@ export const ROOT_DOLLAR_COMPLETIONS: Completion[] = [
{ {
label: '$nodeVersion', label: '$nodeVersion',
section: METADATA_SECTION, section: METADATA_SECTION,
info: i18n.rootVars.$nodeVersion, info: createInfoBoxRenderer({
name: '$nodeVersion',
returnType: 'number',
description: i18n.baseText('codeNodeEditor.completer.$nodeVersion'),
}),
}, },
]; ];

View file

@ -48,7 +48,6 @@ import {
isSplitInBatchesAbsent, isSplitInBatchesAbsent,
longestCommonPrefix, longestCommonPrefix,
prefixMatch, prefixMatch,
setRank,
sortCompletionsAlpha, sortCompletionsAlpha,
splitBaseTail, splitBaseTail,
stripExcessParens, stripExcessParens,
@ -244,6 +243,20 @@ export const isInputData = (base: string): boolean => {
); );
}; };
export const isItem = (input: AutocompleteInput<IDataObject>): boolean => {
const { base, resolved } = input;
return /^(\$\(.*\)|\$input)/.test(base) && 'pairedItem' in resolved;
};
export const isBinary = (input: AutocompleteInput<IDataObject>): boolean => {
const { base, resolved } = input;
return (
/^(\$\(.*\)\..*\.binary\..*|\$binary)/.test(base) &&
'mimeType' in resolved &&
'fileExtension' in resolved
);
};
export const getDetail = (base: string, value: unknown): string | undefined => { export const getDetail = (base: string, value: unknown): string | undefined => {
const type = getType(value); const type = getType(value);
if (!isInputData(base) || type === 'function') return undefined; if (!isInputData(base) || type === 'function') return undefined;
@ -300,29 +313,54 @@ const createCompletionOption = ({
return option; return option;
}; };
const customObjectOptions = (input: AutocompleteInput<IDataObject>): Completion[] => {
const { base, resolved } = input;
if (!resolved) return [];
if (base === '$execution') {
return executionOptions();
} else if (base === '$execution.customData') {
return customDataOptions();
} else if (base === '$workflow') {
return workflowOptions();
} else if (base === '$input') {
return inputOptions(base);
} else if (base === '$prevNode') {
return prevNodeOptions();
} else if (/^\$\(['"][\S\s]+['"]\)$/.test(base)) {
return nodeRefOptions(base);
} else if (base === '$response') {
return responseOptions();
} else if (isItem(input)) {
return itemOptions();
} else if (isBinary(input)) {
return binaryOptions();
}
return [];
};
const objectOptions = (input: AutocompleteInput<IDataObject>): Completion[] => { const objectOptions = (input: AutocompleteInput<IDataObject>): Completion[] => {
const { base, resolved, transformLabel = (label) => label } = input; const { base, resolved, transformLabel = (label) => label } = input;
const rank = setRank(['item', 'all', 'first', 'last']);
const SKIP = new Set(['__ob__', 'pairedItem']); const SKIP = new Set(['__ob__', 'pairedItem']);
if (isSplitInBatchesAbsent()) SKIP.add('context'); if (isSplitInBatchesAbsent()) SKIP.add('context');
const name = /^\$\(.*\)$/.test(base) ? '$()' : base;
if (['$input', '$()'].includes(name) && hasNoParams(base)) SKIP.add('params');
let rawKeys = Object.keys(resolved); let rawKeys = Object.keys(resolved);
if (name === '$()') {
rawKeys = Reflect.ownKeys(resolved) as string[];
}
if (base === 'Math') { if (base === 'Math') {
const descriptors = Object.getOwnPropertyDescriptors(Math); const descriptors = Object.getOwnPropertyDescriptors(Math);
rawKeys = Object.keys(descriptors).sort((a, b) => a.localeCompare(b)); rawKeys = Object.keys(descriptors).sort((a, b) => a.localeCompare(b));
} }
const localKeys = rank(rawKeys) const customOptions = customObjectOptions(input);
if (customOptions.length > 0) {
// Only return completions that are present in the resolved data
return customOptions.filter((option) => option.label in resolved);
}
const localKeys = rawKeys
.filter((key) => !SKIP.has(key) && !isPseudoParam(key)) .filter((key) => !SKIP.has(key) && !isPseudoParam(key))
.map((key) => { .map((key) => {
ensureKeyCanBeResolved(resolved, key); ensureKeyCanBeResolved(resolved, key);
@ -330,28 +368,26 @@ const objectOptions = (input: AutocompleteInput<IDataObject>): Completion[] => {
const resolvedProp = resolved[key]; const resolvedProp = resolved[key];
const isFunction = typeof resolvedProp === 'function'; const isFunction = typeof resolvedProp === 'function';
const hasArgs = isFunction && resolvedProp.length > 0 && name !== '$()'; const hasArgs = isFunction && resolvedProp.length > 0;
const option: Completion = { const option: Completion = {
label: isFunction ? key + '()' : key, label: isFunction ? key + '()' : key,
section: getObjectPropertySection({ name, key, isFunction }), section: isFunction ? METHODS_SECTION : FIELDS_SECTION,
apply: needsBracketAccess apply: needsBracketAccess
? applyBracketAccessCompletion ? applyBracketAccessCompletion
: applyCompletion({ : applyCompletion({
hasArgs, hasArgs,
transformLabel, transformLabel,
}), }),
detail: getDetail(name, resolvedProp), detail: getDetail(base, resolvedProp),
}; };
const infoKey = [name, key].join('.');
const infoName = needsBracketAccess ? applyBracketAccess(key) : key; const infoName = needsBracketAccess ? applyBracketAccess(key) : key;
option.info = createCompletionOption({ option.info = createCompletionOption({
name: infoName, name: infoName,
doc: { doc: {
name: infoName, name: infoName,
returnType: isFunction ? 'any' : getType(resolvedProp), returnType: isFunction ? 'any' : getType(resolvedProp),
description: i18n.proxyVars[infoKey],
}, },
isFunction, isFunction,
transformLabel, transformLabel,
@ -366,6 +402,7 @@ const objectOptions = (input: AutocompleteInput<IDataObject>): Completion[] => {
/json('])$/.test(base) || /json('])$/.test(base) ||
base === '$execution' || base === '$execution' ||
base.endsWith('params') || base.endsWith('params') ||
base.endsWith('binary') ||
base === 'Math'; base === 'Math';
if (skipObjectExtensions) { if (skipObjectExtensions) {
@ -386,23 +423,6 @@ const objectOptions = (input: AutocompleteInput<IDataObject>): Completion[] => {
}); });
}; };
const getObjectPropertySection = ({
name,
key,
isFunction,
}: {
name: string;
key: string;
isFunction: boolean;
}): CompletionSection => {
if (name === '$input' || name === '$()') {
if (key === 'item') return RECOMMENDED_SECTION;
return OTHER_SECTION;
}
return isFunction ? METHODS_SECTION : FIELDS_SECTION;
};
const applySections = ({ const applySections = ({
options, options,
sections, sections,
@ -685,6 +705,420 @@ export const variablesOptions = () => {
); );
}; };
export const responseOptions = () => {
return [
{
name: 'statusCode',
returnType: 'number',
docURL: 'https://docs.n8n.io/code/builtin/http-node-variables/',
description: i18n.baseText('codeNodeEditor.completer.$response.statusCode'),
},
{
name: 'statusMessage',
returnType: 'string',
description: i18n.baseText('codeNodeEditor.completer.$response.statusMessage'),
},
{
name: 'headers',
returnType: 'Object',
docURL: 'https://docs.n8n.io/code/builtin/http-node-variables/',
description: i18n.baseText('codeNodeEditor.completer.$response.headers'),
},
{
name: 'body',
returnType: 'Object',
docURL: 'https://docs.n8n.io/code/builtin/http-node-variables/',
description: i18n.baseText('codeNodeEditor.completer.$response.body'),
},
].map((doc) => createCompletionOption({ name: doc.name, doc }));
};
export const executionOptions = () => {
return [
{
name: 'id',
returnType: 'string',
description: i18n.baseText('codeNodeEditor.completer.$execution.id'),
},
{
name: 'mode',
returnType: 'string',
description: i18n.baseText('codeNodeEditor.completer.$execution.mode'),
},
{
name: 'resumeUrl',
returnType: 'string',
docURL: 'https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.wait/',
description: i18n.baseText('codeNodeEditor.completer.$execution.resumeUrl'),
},
{
name: 'resumeFormUrl',
returnType: 'string',
docURL: 'https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.wait/',
description: i18n.baseText('codeNodeEditor.completer.$execution.resumeFormUrl'),
},
{
name: 'customData',
returnType: 'CustomData',
docURL: 'https://docs.n8n.io/workflows/executions/custom-executions-data/',
description: i18n.baseText('codeNodeEditor.completer.$execution.customData'),
},
].map((doc) => createCompletionOption({ name: doc.name, doc }));
};
export const customDataOptions = () => {
return [
{
name: 'get',
returnType: 'any',
docURL: 'https://docs.n8n.io/workflows/executions/custom-executions-data/',
args: [
{
name: 'key',
description: 'The key (identifier) under which the data is stored',
type: 'string',
},
],
description: i18n.baseText('codeNodeEditor.completer.$execution.customData.get'),
examples: [
{
description: i18n.baseText(
'codeNodeEditor.completer.$execution.customData.get.examples.1',
),
example: '$execution.customData.get("user_email")',
evaluated: '"me@example.com"',
},
],
},
{
name: 'set',
returnType: 'void',
args: [
{
name: 'key',
description: i18n.baseText('codeNodeEditor.completer.$execution.customData.set.args.key'),
type: 'string',
},
{
name: 'value',
description: i18n.baseText(
'codeNodeEditor.completer.$execution.customData.set.args.value',
),
type: 'any',
},
],
docURL: 'https://docs.n8n.io/workflows/executions/custom-executions-data/',
description: i18n.baseText('codeNodeEditor.completer.$execution.customData.set'),
examples: [
{
description: i18n.baseText(
'codeNodeEditor.completer.$execution.customData.set.examples.1',
),
example: '$execution.customData.set("user_email", "me@example.com")',
},
],
},
{
name: 'getAll',
returnType: 'object',
docURL: 'https://docs.n8n.io/workflows/executions/custom-executions-data/',
description: i18n.baseText('codeNodeEditor.completer.$execution.customData.getAll'),
examples: [
{
example: '$execution.customData.getAll()',
evaluated: '{ user_email: "me@example.com", id: 1234 }',
},
],
},
{
name: 'setAll',
returnType: 'void',
args: [
{
name: 'obj',
description: i18n.baseText(
'codeNodeEditor.completer.$execution.customData.setAll.args.obj',
),
type: 'object',
},
],
docURL: 'https://docs.n8n.io/workflows/executions/custom-executions-data/',
description: i18n.baseText('codeNodeEditor.completer.$execution.customData.setAll'),
examples: [
{ example: '$execution.customData.setAll({ user_email: "me@example.com", id: 1234 })' },
],
},
].map((doc) => createCompletionOption({ name: doc.name, doc, isFunction: true }));
};
export const nodeRefOptions = (base: string) => {
const itemArgs = [
{
name: 'branchIndex',
optional: true,
description: i18n.baseText('codeNodeEditor.completer.selector.args.branchIndex'),
default: '0',
type: 'number',
},
{
name: 'runIndex',
optional: true,
description: i18n.baseText('codeNodeEditor.completer.selector.args.runIndex'),
default: '0',
type: 'number',
},
];
const options: Array<{ doc: DocMetadata; isFunction?: boolean }> = [
{
doc: {
name: 'item',
returnType: 'Item',
docURL: 'https://docs.n8n.io/data/data-mapping/data-item-linking/',
description: i18n.baseText('codeNodeEditor.completer.selector.item'),
},
},
{
doc: {
name: 'isExecuted',
returnType: 'boolean',
description: i18n.baseText('codeNodeEditor.completer.selector.isExecuted'),
},
},
{
doc: {
name: 'params',
returnType: 'NodeParams',
description: i18n.baseText('codeNodeEditor.completer.selector.params'),
},
},
{
doc: {
name: 'itemMatching',
returnType: 'Item',
args: [
{
name: 'currentItemIndex',
description: i18n.baseText(
'codeNodeEditor.completer.selector.itemMatching.args.currentItemIndex',
),
default: '0',
type: 'number',
},
],
docURL: 'https://docs.n8n.io/data/data-mapping/data-item-linking/',
description: i18n.baseText('codeNodeEditor.completer.selector.itemMatching'),
},
isFunction: true,
},
{
doc: {
name: 'first',
returnType: 'Item',
args: itemArgs,
description: i18n.baseText('codeNodeEditor.completer.selector.first'),
},
isFunction: true,
},
{
doc: {
name: 'last',
returnType: 'Item',
args: itemArgs,
description: i18n.baseText('codeNodeEditor.completer.selector.last'),
},
isFunction: true,
},
{
doc: {
name: 'all',
returnType: 'Item[]',
args: itemArgs,
description: i18n.baseText('codeNodeEditor.completer.selector.all'),
},
isFunction: true,
},
];
return applySections({
options: options
.filter((option) => !(option.doc.name === 'params' && hasNoParams(base)))
.map(({ doc, isFunction }) => createCompletionOption({ name: doc.name, doc, isFunction })),
sections: {},
recommended: ['item'],
});
};
export const inputOptions = (base: string) => {
const itemArgs = [
{
name: 'branchIndex',
optional: true,
description: i18n.baseText('codeNodeEditor.completer.selector.args.branchIndex'),
default: '0',
type: 'number',
},
{
name: 'runIndex',
optional: true,
description: i18n.baseText('codeNodeEditor.completer.selector.args.runIndex'),
default: '0',
type: 'number',
},
];
const options: Array<{ doc: DocMetadata; isFunction?: boolean }> = [
{
doc: {
name: 'item',
returnType: 'Item',
docURL: 'https://docs.n8n.io/data/data-mapping/data-item-linking/',
description: i18n.baseText('codeNodeEditor.completer.selector.item'),
},
},
{
doc: {
name: 'params',
returnType: 'NodeParams',
description: i18n.baseText('codeNodeEditor.completer.selector.params'),
},
},
{
doc: {
name: 'first',
returnType: 'Item',
args: itemArgs,
description: i18n.baseText('codeNodeEditor.completer.selector.first'),
},
isFunction: true,
},
{
doc: {
name: 'last',
returnType: 'Item',
args: itemArgs,
description: i18n.baseText('codeNodeEditor.completer.selector.last'),
},
isFunction: true,
},
{
doc: {
name: 'all',
returnType: 'Item[]',
args: itemArgs,
description: i18n.baseText('codeNodeEditor.completer.selector.all'),
},
isFunction: true,
},
];
return applySections({
options: options
.filter((option) => !(option.doc.name === 'params' && hasNoParams(base)))
.map(({ doc, isFunction }) => createCompletionOption({ name: doc.name, doc, isFunction })),
recommended: ['item'],
sections: {},
});
};
export const prevNodeOptions = () => {
return [
{
name: 'name',
returnType: 'string',
description: i18n.baseText('codeNodeEditor.completer.$prevNode.name'),
},
{
name: 'outputIndex',
returnType: 'number',
description: i18n.baseText('codeNodeEditor.completer.$prevNode.outputIndex'),
},
{
name: 'runIndex',
returnType: 'number',
description: i18n.baseText('codeNodeEditor.completer.$prevNode.runIndex'),
},
].map((doc) => createCompletionOption({ name: doc.name, doc }));
};
export const itemOptions = () => {
return [
{
name: 'json',
returnType: 'object',
docURL: 'https://docs.n8n.io/data/data-structure/',
description: i18n.baseText('codeNodeEditor.completer.item.json'),
},
{
name: 'binary',
returnType: 'object',
docURL: 'https://docs.n8n.io/data/data-structure/',
description: i18n.baseText('codeNodeEditor.completer.item.binary'),
},
].map((doc) => createCompletionOption({ name: doc.name, doc }));
};
export const binaryOptions = () => {
return [
{
name: 'id',
returnType: 'String',
description: i18n.baseText('codeNodeEditor.completer.binary.id'),
},
{
name: 'fileExtension',
returnType: 'string',
description: i18n.baseText('codeNodeEditor.completer.binary.fileExtension'),
},
{
name: 'fileName',
returnType: 'string',
description: i18n.baseText('codeNodeEditor.completer.binary.fileName'),
},
{
name: 'fileSize',
returnType: 'string',
description: i18n.baseText('codeNodeEditor.completer.binary.fileSize'),
},
{
name: 'fileType',
returnType: 'String',
description: i18n.baseText('codeNodeEditor.completer.binary.fileType'),
},
{
name: 'mimeType',
returnType: 'string',
description: i18n.baseText('codeNodeEditor.completer.binary.mimeType'),
},
{
name: 'directory',
returnType: 'String',
description: i18n.baseText('codeNodeEditor.completer.binary.directory'),
},
].map((doc) => createCompletionOption({ name: doc.name, doc }));
};
export const workflowOptions = () => {
return [
{
name: 'id',
returnType: 'string',
description: i18n.baseText('codeNodeEditor.completer.$workflow.id'),
},
{
name: 'name',
returnType: 'string',
description: i18n.baseText('codeNodeEditor.completer.$workflow.name'),
},
{
name: 'active',
returnType: 'boolean',
description: i18n.baseText('codeNodeEditor.completer.$workflow.active'),
},
].map((doc) => createCompletionOption({ name: doc.name, doc }));
};
export const secretOptions = (base: string) => { export const secretOptions = (base: string) => {
const externalSecretsStore = useExternalSecretsStore(); const externalSecretsStore = useExternalSecretsStore();
let resolved: Resolved; let resolved: Resolved;
@ -841,18 +1275,89 @@ const createLuxonAutocompleteOption = ({
* Methods defined on the global `Object`. * Methods defined on the global `Object`.
*/ */
export const objectGlobalOptions = () => { export const objectGlobalOptions = () => {
return ['assign', 'entries', 'keys', 'values'].map((key) => { return [
const option: Completion = { {
label: key + '()', name: 'assign',
type: 'function', description: i18n.baseText('codeNodeEditor.completer.globalObject.assign'),
}; args: [
{
const info = i18n.globalObject[key]; name: 'target',
type: 'object',
if (info) option.info = info; },
{
return option; name: 'sources',
}); variadic: true,
type: 'object',
},
],
examples: [
{
example: "Object.assign(\n {},\n { id: 1, name: 'Apple' },\n { name: 'Banana' }\n);",
evaluated: "{ id: 1, name: 'Banana' }",
},
],
returnType: 'object',
docURL:
'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign',
},
{
name: 'entries',
returnType: 'Array<[string, any]>',
args: [
{
name: 'obj',
type: 'object',
},
],
examples: [
{
example: "Object.entries({ id: 1, name: 'Apple' })",
evaluated: "[['id', 1], ['name', 'Apple']]",
},
],
description: i18n.baseText('codeNodeEditor.completer.globalObject.entries'),
docURL:
'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries',
},
{
name: 'keys',
args: [
{
name: 'obj',
type: 'object',
},
],
examples: [
{
example: "Object.keys({ id: 1, name: 'Apple' })",
evaluated: "['id', 'name']",
},
],
returnType: 'string[]',
description: i18n.baseText('codeNodeEditor.completer.globalObject.keys'),
docURL:
'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys',
},
{
name: 'values',
args: [
{
name: 'obj',
type: 'object',
},
],
examples: [
{
example: "Object.values({ id: 1, name: 'Apple' })",
evaluated: "[1, 'Apple']",
},
],
description: i18n.baseText('codeNodeEditor.completer.globalObject.values'),
returnType: 'Array',
docURL:
'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/values',
},
].map((doc) => createCompletionOption({ name: doc.name, doc, isFunction: true }));
}; };
const regexes = { const regexes = {

View file

@ -60,7 +60,8 @@ export function dollarOptions(): Completion[] {
info: createInfoBoxRenderer({ info: createInfoBoxRenderer({
name: '$pageCount', name: '$pageCount',
returnType: 'number', returnType: 'number',
description: i18n.rootVars.$pageCount, docURL: 'https://docs.n8n.io/code/builtin/http-node-variables/',
description: i18n.baseText('codeNodeEditor.completer.$pageCount'),
}), }),
}, },
{ {
@ -68,8 +69,9 @@ export function dollarOptions(): Completion[] {
section: RECOMMENDED_SECTION, section: RECOMMENDED_SECTION,
info: createInfoBoxRenderer({ info: createInfoBoxRenderer({
name: '$response', name: '$response',
returnType: 'object', returnType: 'HTTPResponse',
description: i18n.rootVars.$response, docURL: 'https://docs.n8n.io/code/builtin/http-node-variables/',
description: i18n.baseText('codeNodeEditor.completer.$response'),
}), }),
}, },
{ {
@ -78,7 +80,8 @@ export function dollarOptions(): Completion[] {
info: createInfoBoxRenderer({ info: createInfoBoxRenderer({
name: '$request', name: '$request',
returnType: 'object', returnType: 'object',
description: i18n.rootVars.$request, docURL: 'https://docs.n8n.io/code/builtin/http-node-variables/',
description: i18n.baseText('codeNodeEditor.completer.$request'),
}), }),
}, },
]; ];

View file

@ -960,13 +960,13 @@ export const luxonInstanceDocs: Required<NativeDoc> = {
returnType: 'DateTime', returnType: 'DateTime',
args: [ args: [
{ {
name: 'zone', name: 'offset',
optional: true, optional: true,
description: i18n.baseText( description: i18n.baseText(
'codeNodeEditor.completer.luxon.instanceMethods.toUTC.args.zone', 'codeNodeEditor.completer.luxon.instanceMethods.toUTC.args.offset',
), ),
default: '"local"', default: '0',
type: 'string', type: 'number',
}, },
{ {
name: 'options', name: 'options',

View file

@ -1,6 +1,7 @@
import { i18n } from '@/plugins/i18n'; import { i18n } from '@/plugins/i18n';
import type { CompletionContext, CompletionResult } from '@codemirror/autocomplete'; import type { CompletionContext, CompletionResult } from '@codemirror/autocomplete';
import { prefixMatch } from './utils'; import { prefixMatch } from './utils';
import { createInfoBoxRenderer } from './infoBoxRenderer';
/** /**
* Completions offered at the initial position for any char other than `$`. * Completions offered at the initial position for any char other than `$`.
@ -23,16 +24,32 @@ export function nonDollarCompletions(context: CompletionContext): CompletionResu
const nonDollarOptions = [ const nonDollarOptions = [
{ {
label: 'DateTime', label: 'DateTime',
type: 'keyword', info: createInfoBoxRenderer({
info: i18n.rootVars.DateTime, name: 'DateTime',
returnType: 'DateTimeGlobal',
description: i18n.baseText('codeNodeEditor.completer.dateTime'),
docURL: 'https://moment.github.io/luxon/api-docs/index.html#datetime',
}),
}, },
{ {
label: 'Math', label: 'Math',
type: 'keyword', info: createInfoBoxRenderer({
name: 'Math',
returnType: 'MathGlobal',
description: i18n.baseText('codeNodeEditor.completer.math'),
docURL:
'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math',
}),
}, },
{ {
label: 'Object', label: 'Object',
type: 'keyword', info: createInfoBoxRenderer({
name: 'Object',
returnType: 'ObjectGlobal',
description: i18n.baseText('codeNodeEditor.completer.globalObject'),
docURL:
'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object',
}),
}, },
]; ];

View file

@ -61,21 +61,6 @@ export function longestCommonPrefix(...strings: string[]) {
export const prefixMatch = (first: string, second: string) => export const prefixMatch = (first: string, second: string) =>
first.startsWith(second) && first !== second; first.startsWith(second) && first !== second;
/**
* Make a function to bring selected elements to the start of an array, in order.
*/
export const setRank = (selected: string[]) => (full: string[]) => {
const fullCopy = [...full];
[...selected].reverse().forEach((s) => {
const index = fullCopy.indexOf(s);
if (index !== -1) fullCopy.unshift(fullCopy.splice(index, 1)[0]);
});
return fullCopy;
};
export const isPseudoParam = (candidate: string) => { export const isPseudoParam = (candidate: string) => {
const PSEUDO_PARAMS = ['notice']; // user input disallowed const PSEUDO_PARAMS = ['notice']; // user input disallowed

View file

@ -357,67 +357,6 @@ export class I18nClass {
}); });
} }
rootVars = {
$binary: this.baseText('codeNodeEditor.completer.binary'),
$execution: this.baseText('codeNodeEditor.completer.$execution'),
$ifEmpty: this.baseText('codeNodeEditor.completer.$ifEmpty'),
$input: this.baseText('codeNodeEditor.completer.$input'),
$jmespath: this.baseText('codeNodeEditor.completer.$jmespath'),
$json: this.baseText('codeNodeEditor.completer.json'),
$itemIndex: this.baseText('codeNodeEditor.completer.$itemIndex'),
$now: this.baseText('codeNodeEditor.completer.$now'),
$parameter: this.baseText('codeNodeEditor.completer.$parameter'),
$prevNode: this.baseText('codeNodeEditor.completer.$prevNode'),
$if: this.baseText('codeNodeEditor.completer.$if'),
$max: this.baseText('codeNodeEditor.completer.$max'),
$min: this.baseText('codeNodeEditor.completer.$min'),
$runIndex: this.baseText('codeNodeEditor.completer.$runIndex'),
$today: this.baseText('codeNodeEditor.completer.$today'),
$vars: this.baseText('codeNodeEditor.completer.$vars'),
$workflow: this.baseText('codeNodeEditor.completer.$workflow'),
DateTime: this.baseText('codeNodeEditor.completer.dateTime'),
$request: this.baseText('codeNodeEditor.completer.$request'),
$response: this.baseText('codeNodeEditor.completer.$response'),
$pageCount: this.baseText('codeNodeEditor.completer.$pageCount'),
$nodeVersion: this.baseText('codeNodeEditor.completer.$nodeVersion'),
} as const satisfies Record<string, string | undefined>;
proxyVars: Record<string, string | undefined> = {
'$input.all': this.baseText('codeNodeEditor.completer.$input.all'),
'$input.first': this.baseText('codeNodeEditor.completer.$input.first'),
'$input.item': this.baseText('codeNodeEditor.completer.$input.item'),
'$input.last': this.baseText('codeNodeEditor.completer.$input.last'),
'$().all': this.baseText('codeNodeEditor.completer.selector.all'),
'$().context': this.baseText('codeNodeEditor.completer.selector.context'),
'$().first': this.baseText('codeNodeEditor.completer.selector.first'),
'$().item': this.baseText('codeNodeEditor.completer.selector.item'),
'$().itemMatching': this.baseText('codeNodeEditor.completer.selector.itemMatching'),
'$().last': this.baseText('codeNodeEditor.completer.selector.last'),
'$().params': this.baseText('codeNodeEditor.completer.selector.params'),
'$().isExecuted': this.baseText('codeNodeEditor.completer.selector.isExecuted'),
'$prevNode.name': this.baseText('codeNodeEditor.completer.$prevNode.name'),
'$prevNode.outputIndex': this.baseText('codeNodeEditor.completer.$prevNode.outputIndex'),
'$prevNode.runIndex': this.baseText('codeNodeEditor.completer.$prevNode.runIndex'),
'$execution.id': this.baseText('codeNodeEditor.completer.$workflow.id'),
'$execution.mode': this.baseText('codeNodeEditor.completer.$execution.mode'),
'$execution.resumeUrl': this.baseText('codeNodeEditor.completer.$execution.resumeUrl'),
'$execution.resumeFormUrl': this.baseText('codeNodeEditor.completer.$execution.resumeFormUrl'),
'$workflow.active': this.baseText('codeNodeEditor.completer.$workflow.active'),
'$workflow.id': this.baseText('codeNodeEditor.completer.$workflow.id'),
'$workflow.name': this.baseText('codeNodeEditor.completer.$workflow.name'),
};
globalObject: Record<string, string | undefined> = {
assign: this.baseText('codeNodeEditor.completer.globalObject.assign'),
entries: this.baseText('codeNodeEditor.completer.globalObject.entries'),
keys: this.baseText('codeNodeEditor.completer.globalObject.keys'),
values: this.baseText('codeNodeEditor.completer.globalObject.values'),
};
autocompleteUIValues: Record<string, string | undefined> = { autocompleteUIValues: Record<string, string | undefined> = {
docLinkLabel: this.baseText('expressionEdit.learnMore'), docLinkLabel: this.baseText('expressionEdit.learnMore'),
}; };

View file

@ -6,10 +6,10 @@
"useJson": "Access the properties of an item under `.json`, e.g. `item.json`" "useJson": "Access the properties of an item under `.json`, e.g. `item.json`"
}, },
"completer": { "completer": {
"all": "Get all items", "all": "Returns an array of the node's output items",
"first": "Get the first item", "first": "Returns the first item output by the node",
"last": "Get the last item", "last": "Returns the last item output by the node",
"itemMatching": "Get the item matching a specified input item. Pass the input item index" "itemMatching": "Returns the matching item, i.e. the one used to produce the item in the current node at the specified index."
} }
}, },
"name": "Name", "name": "Name",
@ -190,55 +190,91 @@
"codeEdit.edit": "Edit", "codeEdit.edit": "Edit",
"codeNodeEditor.askAi": "✨ Ask AI", "codeNodeEditor.askAi": "✨ Ask AI",
"codeNodeEditor.completer.$()": "Output data of the {nodeName} node", "codeNodeEditor.completer.$()": "Output data of the {nodeName} node",
"codeNodeEditor.completer.$execution": "Information about the current execution", "codeNodeEditor.completer.$execution": "Retrieve or set metadata for the current execution",
"codeNodeEditor.completer.$execution.id": "The ID of the current execution", "codeNodeEditor.completer.$execution.id": "The ID of the current workflow execution",
"codeNodeEditor.completer.$execution.mode": "How the execution was triggered: 'test' or 'production'", "codeNodeEditor.completer.$execution.mode": "Returns either <code>test</code> (meaning the execution was triggered by clicking a button in n8n) or <code>production</code> (meaning the execution was triggered automatically)",
"codeNodeEditor.completer.$execution.resumeUrl": "Used when using the 'wait' node to wait for a webhook. The webhook to call to resume execution", "codeNodeEditor.completer.$execution.resumeUrl": "The webhook URL to call to resume a workflow waiting at a 'Wait' node.",
"codeNodeEditor.completer.$execution.resumeFormUrl": "Used when using the 'wait' node to wait for a form submission. The url of form submitting which will resume execution", "codeNodeEditor.completer.$execution.resumeFormUrl": "The URL to access a form generated by the 'Wait' node.",
"codeNodeEditor.completer.$execution.customData.set()": "Set custom data for the current execution. <a href=\"https://docs.n8n.io/workflows/executions/custom-executions-data/\" target=\"_blank\">Learn More</a>", "codeNodeEditor.completer.$execution.customData": "Set and get custom execution data (e.g. to filter executions by). You can also do this with the 'Execution Data' node.",
"codeNodeEditor.completer.$execution.customData.get()": "Get custom data set in the current execution. <a href=\"https://docs.n8n.io/workflows/executions/custom-executions-data/\" target=\"_blank\">Learn More</a>", "codeNodeEditor.completer.$execution.customData.set": "Stores custom execution data under the key specified. Use this to easily filter executions by this data.",
"codeNodeEditor.completer.$execution.customData.setAll()": "Set multiple custom data key/value pairs with an object for the current execution. <a href=\"https://docs.n8n.io/workflows/executions/custom-executions-data/\" target=\"_blank\">Learn More</a>", "codeNodeEditor.completer.$execution.customData.set.args.key": "The key (identifier) under which the data is stored",
"codeNodeEditor.completer.$execution.customData.getAll()": "Get all custom data for the current execution. <a href=\"https://docs.n8n.io/workflows/executions/custom-executions-data/\" target=\"_blank\">Learn More</a>", "codeNodeEditor.completer.$execution.customData.set.args.value": "The data to store",
"codeNodeEditor.completer.$ifEmpty": "Checks whether the first parameter is empty, and if so returns the second parameter. Otherwise returns the first parameter. The following count as empty: null/undefined values, empty strings, empty arrays, objects with no keys.", "codeNodeEditor.completer.$execution.customData.set.examples.1": "Store the user's email, to easily retrieve all execs related to that user later",
"codeNodeEditor.completer.$input": "This nodes input data", "codeNodeEditor.completer.$execution.customData.get": "Returns the custom execution data stored under the given key.",
"codeNodeEditor.completer.$execution.customData.get.args.key": "The key (identifier) under which the data is stored",
"codeNodeEditor.completer.$execution.customData.get.examples.1": "Get the user's email (which was previously stored)",
"codeNodeEditor.completer.$execution.customData.setAll": "Sets multiple key-value pairs of custom data for the execution. Use this to easily filter executions by this data.",
"codeNodeEditor.completer.$execution.customData.setAll.args.obj": "A JavaScript object containing key-value pairs of the data to set",
"codeNodeEditor.completer.$execution.customData.getAll": "Returns all the key-value pairs of custom data that have been set in the current execution.",
"codeNodeEditor.completer.$ifEmpty": "Returns the first parameter if it isn't empty, otherwise returns the second parameter. The following count as empty: <code>\"</code>, <code>[]</code>, <code>{'{}'}</code>, <code>null</code>, <code>undefined</code>",
"codeNodeEditor.completer.$ifEmpty.args.value": "The value to return, provided it isn't empty",
"codeNodeEditor.completer.$ifEmpty.args.valueIfEmpty": "What to return if <code>value</code> is empty",
"codeNodeEditor.completer.$input": "The input data of the current node",
"codeNodeEditor.completer.$input.all": "@:_reusableBaseText.codeNodeEditor.completer.all", "codeNodeEditor.completer.$input.all": "@:_reusableBaseText.codeNodeEditor.completer.all",
"codeNodeEditor.completer.$input.first": "@:_reusableBaseText.codeNodeEditor.completer.first", "codeNodeEditor.completer.$input.first": "@:_reusableBaseText.codeNodeEditor.completer.first",
"codeNodeEditor.completer.$input.item": "The item that generated the current one", "codeNodeEditor.completer.$input.item": "The item that generated the current one",
"codeNodeEditor.completer.$input.itemMatching": "@:_reusableBaseText.codeNodeEditor.completer.itemMatching", "codeNodeEditor.completer.$input.itemMatching": "@:_reusableBaseText.codeNodeEditor.completer.itemMatching",
"codeNodeEditor.completer.$input.last": "@:_reusableBaseText.codeNodeEditor.completer.last", "codeNodeEditor.completer.$input.last": "@:_reusableBaseText.codeNodeEditor.completer.last",
"codeNodeEditor.completer.$itemIndex": "The position of the current item in the list of items", "codeNodeEditor.completer.$itemIndex": "The position of the item currently being processed in the list of input items",
"codeNodeEditor.completer.$jmespath": "Evaluate a JMESPath expression", "codeNodeEditor.completer.$jmespath": "Extracts data from an object (or array of objects) using a <a target=\"_blank\" href=\"https://docs.n8n.io/code/cookbook/jmespath/\">JMESPath</a> expression. Useful for querying complex, nested objects. Returns <code>undefined</code> if the expression is invalid.",
"codeNodeEditor.completer.$if": "Function that takes a condition and returns a value based on whether it's true or false.", "codeNodeEditor.completer.$jmespath.args.obj": "The Object or array of Objects to retrieve data from",
"codeNodeEditor.completer.$max": "Returns the largest of the numbers given as input parameters, or -Infinity if there are no parameters.", "codeNodeEditor.completer.$jmespath.args.expression": "A <a target=\"_blank\" href=\"https://jmespath.org/examples.html\">JMESPath expression</a> defining the data to retrieve from the object",
"codeNodeEditor.completer.$min": "Returns the smallest of the numbers given as input parameters, or Infinity if there are no parameters.", "codeNodeEditor.completer.$jmespath.examples.1": "Get all names, in an array",
"codeNodeEditor.completer.$now": "The current timestamp (as a Luxon object)", "codeNodeEditor.completer.$jmespath.examples.2": "Get the names and ages of everyone under 20",
"codeNodeEditor.completer.$parameter": "The parameters of the current node", "codeNodeEditor.completer.$jmespath.examples.3": "Get the name of the first person under 20",
"codeNodeEditor.completer.$prevNode": "The node providing the input data for this run", "codeNodeEditor.completer.$jmespath.examples.4": "Get the names of all the guests in each reservation that require a double room",
"codeNodeEditor.completer.$prevNode.name": "The name of the node providing the input data for this run", "codeNodeEditor.completer.$if": "Returns one of two values depending on the <code>condition</code>. Similar to the <code>?</code> operator in JavaScript.",
"codeNodeEditor.completer.$prevNode.outputIndex": "The output connector of the node providing input data for this run", "codeNodeEditor.completer.$if.args.condition": "The check to make. Should evaluate to either <code>true</code> or <code>false</code>",
"codeNodeEditor.completer.$prevNode.runIndex": "The run of the node providing input data to the current one", "codeNodeEditor.completer.$if.args.valueIfTrue": "The value to return if the condition is true",
"codeNodeEditor.completer.$runIndex": "The index of the current run of this node", "codeNodeEditor.completer.$if.args.valueIfFalse": "The value to return if the condition is false",
"codeNodeEditor.completer.$today": "A timestamp representing the current day (at midnight, as a Luxon object)", "codeNodeEditor.completer.$if.examples.1": "Return \"Good day\" if time is before 5pm, otherwise \"Good evening\"",
"codeNodeEditor.completer.$nodeVersion": "The type version of the current node", "codeNodeEditor.completer.$if.examples.2": "$if() calls can be combined\nReturn \"Good morning\" if time is before 10am, \"Good day\" it's before 5pm, otherwise \"Good evening\"",
"codeNodeEditor.completer.$vars": "The variables defined in your instance", "codeNodeEditor.completer.$max": "Returns the highest of the given numbers, or -Infinity if there are no parameters.",
"codeNodeEditor.completer.$max.args.numbers": "The numbers to compare",
"codeNodeEditor.completer.$min": "Returns the lowest of the given numbers, or Infinity if there are no parameters.",
"codeNodeEditor.completer.$now": "A DateTime representing the current moment. \n\nUses the workflow's time zone (which can be changed in the workflow settings).",
"codeNodeEditor.completer.$parameter": "The configuration settings of the current node. These are the parameters you fill out within the node's UI (e.g. its operation).",
"codeNodeEditor.completer.$prevNode": "Information about the node that the current input came from. \n\nWhen in a 'Merge' node, always uses the first input connector.",
"codeNodeEditor.completer.$prevNode.name": "The name of the node that the current input came from. \n\nAlways uses the current node's first input connector if there is more than one (e.g. in the 'Merge' node).",
"codeNodeEditor.completer.$prevNode.outputIndex": "The index of the output connector that the current input came from. Use this when the previous node had multiple outputs (such as an 'If' or 'Switch' node). \n\nAlways uses the current node's first input connector if there is more than one (e.g. in the 'Merge' node).",
"codeNodeEditor.completer.$prevNode.runIndex": "The run of the previous node that generated the current input. \n\nAlways uses the current node's first input connector if there is more than one (e.g. in the 'Merge' node). ",
"codeNodeEditor.completer.$runIndex": "The index of the current run of the current node execution. Starts at 0.",
"codeNodeEditor.completer.$nodeVersion": "The version of the current node (as displayed at the bottom of the nodes's settings pane)",
"codeNodeEditor.completer.$today": "A DateTime representing midnight at the start of the current day. \n\nUses the instance's time zone (unless overridden in the workflow's settings).",
"codeNodeEditor.completer.$vars": "The <a target=\"_blank\" href=\"https://docs.n8n.io/code/variables/\">variables</a> available to the workflow",
"codeNodeEditor.completer.$vars.varName": "Variable set on this n8n instance. All variables evaluate to strings.", "codeNodeEditor.completer.$vars.varName": "Variable set on this n8n instance. All variables evaluate to strings.",
"codeNodeEditor.completer.$secrets": "The external secrets connected to your instance", "codeNodeEditor.completer.$secrets": "The external secrets connected to your instance",
"codeNodeEditor.completer.$secrets.provider": "External secrets providers connected to this n8n instance.", "codeNodeEditor.completer.$secrets.provider": "External secrets providers connected to this n8n instance.",
"codeNodeEditor.completer.$secrets.provider.varName": "External secrets connected to this n8n instance. All secrets evaluate to strings.", "codeNodeEditor.completer.$secrets.provider.varName": "External secrets connected to this n8n instance. All secrets evaluate to strings.",
"codeNodeEditor.completer.$workflow": "Information about the workflow", "codeNodeEditor.completer.$workflow": "Information about the current workflow",
"codeNodeEditor.completer.$workflow.active": "Whether the workflow is active or not (boolean)", "codeNodeEditor.completer.$workflow.active": "Whether the workflow is active",
"codeNodeEditor.completer.$workflow.id": "The ID of the workflow", "codeNodeEditor.completer.$workflow.id": "The workflow ID. Can also be found in the workflow's URL.",
"codeNodeEditor.completer.$workflow.name": "The name of the workflow", "codeNodeEditor.completer.$workflow.name": "The name of the workflow, as shown at the top of the editor",
"codeNodeEditor.completer.$response": "The response object received by the HTTP node.", "codeNodeEditor.completer.$response": "The response returned by the last HTTP call. Only available in the 'HTTP Request' node.",
"codeNodeEditor.completer.$request": "The request object sent by the HTTP node.", "codeNodeEditor.completer.$response.headers": "The headers returned by the last HTTP call. Only available in the 'HTTP Request' node.",
"codeNodeEditor.completer.$pageCount": "Tracks how many pages the HTTP node has fetched.", "codeNodeEditor.completer.$response.statusCode": "The HTTP status code returned by the last HTTP call. Only available in the 'HTTP Request' node.",
"codeNodeEditor.completer.dateTime": "Luxon DateTime. Use this object to parse, format and manipulate dates and times", "codeNodeEditor.completer.$response.statusMessage": "An optional message regarding the request status. Only available in the 'HTTP Request' node.",
"codeNodeEditor.completer.binary": "The item's binary (file) data", "codeNodeEditor.completer.$response.body": "The body of the response object from the last HTTP call. Only available in the 'HTTP Request' node",
"codeNodeEditor.completer.globalObject.assign": "Copy of the object containing all enumerable own properties", "codeNodeEditor.completer.$request": "The request object sent during the last run of the node. Only available in the 'HTTP Request' node.",
"codeNodeEditor.completer.$pageCount": "The number of results pages the node has fetched. Only available in the 'HTTP Request' node.",
"codeNodeEditor.completer.dateTime": "Luxon DateTime. Use this object to parse, format and manipulate dates and times.",
"codeNodeEditor.completer.binary": "Returns any binary input data to the current node, for the current item. Shorthand for <code>$input.item.binary</code>.",
"codeNodeEditor.completer.binary.mimeType": "A string representing the format of the file's contents, e.g. <code>image/jpeg</code>",
"codeNodeEditor.completer.binary.fileSize": "A string representing the size of the file (e.g. <code>1 kB</code>)",
"codeNodeEditor.completer.binary.fileName": "The name of the file, including extension",
"codeNodeEditor.completer.binary.fileExtension": "The suffix attached to the filename (e.g. <code>txt</code>)",
"codeNodeEditor.completer.binary.fileType": "A string representing the type of the file, e.g. <code>image</code>. Corresponds to the first part of the MIME type.",
"codeNodeEditor.completer.binary.id": "The unique ID of the file. Used to identify the file when it is stored on disk or in a storage service such as S3.",
"codeNodeEditor.completer.binary.directory": "The path to the directory that the file is stored in. Useful for distinguishing between files with the same name in different directories. Not set if n8n is configured to store files in its database.",
"codeNodeEditor.completer.item.binary": "Returns any binary data the item contains.",
"codeNodeEditor.completer.item.json": "Returns the JSON data the item contains.",
"codeNodeEditor.completer.math": "Mathematical utility methods",
"codeNodeEditor.completer.globalObject": "Methods to manipulate JavaScript Objects",
"codeNodeEditor.completer.globalObject.assign": "Merge all enumerable object properties into a target object. Returns the modified target object.",
"codeNodeEditor.completer.globalObject.entries": "The object's keys and values", "codeNodeEditor.completer.globalObject.entries": "The object's keys and values",
"codeNodeEditor.completer.globalObject.keys": "The object's keys", "codeNodeEditor.completer.globalObject.keys": "The object's keys",
"codeNodeEditor.completer.globalObject.values": "The object's values", "codeNodeEditor.completer.globalObject.values": "The object's values",
"codeNodeEditor.completer.json": "The item's JSON data. When in doubt, use this", "codeNodeEditor.completer.json": "Returns the JSON input data to the current node, for the current item. Shorthand for <code>$input.item.json</code>.",
"codeNodeEditor.completer.luxon.dateTimeStaticMethods.expandFormat": "Produce the the fully expanded format token for the locale Does NOT quote characters, so quoted tokens will not round trip correctly.", "codeNodeEditor.completer.luxon.dateTimeStaticMethods.expandFormat": "Produce the the fully expanded format token for the locale Does NOT quote characters, so quoted tokens will not round trip correctly.",
"codeNodeEditor.completer.luxon.dateTimeStaticMethods.fromFormat": "Create a DateTime from an input string and format string.", "codeNodeEditor.completer.luxon.dateTimeStaticMethods.fromFormat": "Create a DateTime from an input string and format string.",
"codeNodeEditor.completer.luxon.dateTimeStaticMethods.fromFormatExplain": "Explain how a string would be parsed by fromFormat().", "codeNodeEditor.completer.luxon.dateTimeStaticMethods.fromFormatExplain": "Explain how a string would be parsed by fromFormat().",
@ -347,8 +383,8 @@
"codeNodeEditor.completer.luxon.instanceMethods.toSQLTime": "Returns a string representation of this DateTime appropriate for use in SQL Time.", "codeNodeEditor.completer.luxon.instanceMethods.toSQLTime": "Returns a string representation of this DateTime appropriate for use in SQL Time.",
"codeNodeEditor.completer.luxon.instanceMethods.toSeconds": "Returns a Unix timestamp in seconds (the number elapsed since 1st Jan 1970)", "codeNodeEditor.completer.luxon.instanceMethods.toSeconds": "Returns a Unix timestamp in seconds (the number elapsed since 1st Jan 1970)",
"codeNodeEditor.completer.luxon.instanceMethods.toString": "Returns a string representation of the DateTime. Similar to <code>toISO()</code>. For more formatting options, see <code>format()</code> or <code>toLocaleString()</code>.", "codeNodeEditor.completer.luxon.instanceMethods.toString": "Returns a string representation of the DateTime. Similar to <code>toISO()</code>. For more formatting options, see <code>format()</code> or <code>toLocaleString()</code>.",
"codeNodeEditor.completer.luxon.instanceMethods.toUTC": "Converts the DateTime to the given time zone. The DateTime still represents the same moment unless specified in the options. See also <code>toLocal()</code> and <code>toUTC()</code>.", "codeNodeEditor.completer.luxon.instanceMethods.toUTC": "Converts a DateTime to the UTC time zone. The DateTime still represents the same moment unless specified in the parameters. Use <code>setZone()</code> to convert to other zones.",
"codeNodeEditor.completer.luxon.instanceMethods.toUTC.args.zone": "A zone identifier, either in the format 'America/New_York', 'UTC+3', or the strings 'local' or 'utc'", "codeNodeEditor.completer.luxon.instanceMethods.toUTC.args.offset": "An offset from UTC in minutes",
"codeNodeEditor.completer.luxon.instanceMethods.toUTC.args.opts": "Options that affect the output. Possible properties:\n<code>keepCalendarTime</code> (boolean): Whether to keep the time the same and only change the offset. Defaults to false.", "codeNodeEditor.completer.luxon.instanceMethods.toUTC.args.opts": "Options that affect the output. Possible properties:\n<code>keepCalendarTime</code> (boolean): Whether to keep the time the same and only change the offset. Defaults to false.",
"codeNodeEditor.completer.luxon.instanceMethods.toUnixInteger": "Returns the epoch seconds (as a whole number) of this DateTime.", "codeNodeEditor.completer.luxon.instanceMethods.toUnixInteger": "Returns the epoch seconds (as a whole number) of this DateTime.",
"codeNodeEditor.completer.luxon.instanceMethods.until": "Return an Interval spanning between this DateTime and another DateTime.", "codeNodeEditor.completer.luxon.instanceMethods.until": "Return an Interval spanning between this DateTime and another DateTime.",
@ -365,11 +401,14 @@
"codeNodeEditor.completer.selector.all": "@:_reusableBaseText.codeNodeEditor.completer.all", "codeNodeEditor.completer.selector.all": "@:_reusableBaseText.codeNodeEditor.completer.all",
"codeNodeEditor.completer.selector.context": "Extra data about the node", "codeNodeEditor.completer.selector.context": "Extra data about the node",
"codeNodeEditor.completer.selector.first": "@:_reusableBaseText.codeNodeEditor.completer.first", "codeNodeEditor.completer.selector.first": "@:_reusableBaseText.codeNodeEditor.completer.first",
"codeNodeEditor.completer.selector.item": "The item that generated the current one", "codeNodeEditor.completer.selector.item": "Returns the matching item, i.e. the one used to produce the current item in the current node.",
"codeNodeEditor.completer.selector.args.branchIndex": "The output branch of the node to use. Defaults to the first branch (index 0)",
"codeNodeEditor.completer.selector.args.runIndex": "The run of the node to use. Defaults to the first run (index 0)",
"codeNodeEditor.completer.selector.itemMatching": "@:_reusableBaseText.codeNodeEditor.completer.itemMatching", "codeNodeEditor.completer.selector.itemMatching": "@:_reusableBaseText.codeNodeEditor.completer.itemMatching",
"codeNodeEditor.completer.selector.itemMatching.args.currentItemIndex": "The index of the item in the current node to be matched with.",
"codeNodeEditor.completer.selector.last": "@:_reusableBaseText.codeNodeEditor.completer.last", "codeNodeEditor.completer.selector.last": "@:_reusableBaseText.codeNodeEditor.completer.last",
"codeNodeEditor.completer.selector.params": "The parameters of the node", "codeNodeEditor.completer.selector.params": "The configuration settings of the given node. These are the parameters you fill out within the node's UI (e.g. its operation).",
"codeNodeEditor.completer.selector.isExecuted": "Whether the node has executed", "codeNodeEditor.completer.selector.isExecuted": "Is <code>true</code> if the node has executed, <code>false</code> otherwise",
"codeNodeEditor.completer.section.input": "Input", "codeNodeEditor.completer.section.input": "Input",
"codeNodeEditor.completer.section.prevNodes": "Earlier nodes", "codeNodeEditor.completer.section.prevNodes": "Earlier nodes",
"codeNodeEditor.completer.section.metadata": "Metadata", "codeNodeEditor.completer.section.metadata": "Metadata",
@ -883,7 +922,7 @@
"ndv.output.items": "{count} item | {count} items", "ndv.output.items": "{count} item | {count} items",
"ndv.output.noOutputData.message": "n8n stops executing the workflow when a node has no output data. You can change this default behaviour via", "ndv.output.noOutputData.message": "n8n stops executing the workflow when a node has no output data. You can change this default behaviour via",
"ndv.output.noOutputData.message.settings": "Settings", "ndv.output.noOutputData.message.settings": "Settings",
"ndv.output.noOutputData.message.settingsOption": "> “Always Output Data”.", "ndv.output.noOutputData.message.settingsOption": "> \"Always Output Data\".",
"ndv.output.noOutputData.title": "No output data returned", "ndv.output.noOutputData.title": "No output data returned",
"ndv.output.noOutputDataInBranch": "No output data in this branch", "ndv.output.noOutputDataInBranch": "No output data in this branch",
"ndv.output.of": " of ", "ndv.output.of": " of ",
@ -1087,17 +1126,17 @@
"nodeErrorView.theErrorCauseIsTooLargeToBeDisplayed": "The error cause is too large to be displayed", "nodeErrorView.theErrorCauseIsTooLargeToBeDisplayed": "The error cause is too large to be displayed",
"nodeErrorView.time": "Time", "nodeErrorView.time": "Time",
"nodeErrorView.inputPanel.previousNodeError.title": "Error running node '{nodeName}'", "nodeErrorView.inputPanel.previousNodeError.title": "Error running node '{nodeName}'",
"nodeErrorView.description.pairedItemInvalidInfo": "An expression here won't work because it uses <code>.item</code> and n8n can't figure out the <a href=”https://docs.n8n.io/data/data-mapping/data-item-linking/item-linking-errors/”>matching item</a>. This is because the node <strong>'{nodeCause}'</strong> returned incorrect matching information (for item {itemIndex} of run {runIndex}). <br/><br/>Try using <code>.first()</code>, <code>.last()</code> or <code>.all()[index]</code> instead of <code>.item</code>.", "nodeErrorView.description.pairedItemInvalidInfo": "An expression here won't work because it uses <code>.item</code> and n8n can't figure out the <a target=\"_blank\" href=\"https://docs.n8n.io/data/data-mapping/data-item-linking/item-linking-errors/\">matching item</a>. This is because the node <strong>'{nodeCause}'</strong> returned incorrect matching information (for item {itemIndex} of run {runIndex}). <br/><br/>Try using <code>.first()</code>, <code>.last()</code> or <code>.all()[index]</code> instead of <code>.item</code>.",
"nodeErrorView.description.pairedItemNoInfo": "An expression here won't work because it uses <code>.item</code> and n8n can't figure out the <a href=”https://docs.n8n.io/data/data-mapping/data-item-linking/item-linking-errors/”>matching item</a>. The node <strong>'{nodeCause}'</strong> didn't return enough information.", "nodeErrorView.description.pairedItemNoInfo": "An expression here won't work because it uses <code>.item</code> and n8n can't figure out the <a target=\"_blank\" href=\"https://docs.n8n.io/data/data-mapping/data-item-linking/item-linking-errors/\">matching item</a>. The node <strong>'{nodeCause}'</strong> didn't return enough information.",
"nodeErrorView.description.pairedItemNoInfoCodeNode": "An expression here won't work because it uses <code>.item</code> and n8n can't figure out the <a href=\"https://docs.n8n.io/data/data-mapping/data-item-linking/item-linking-errors/\">matching item</a>. You can either: <ul><li>Add the <a href=\"https://docs.n8n.io/data/data-mapping/data-item-linking/item-linking-code-node/\">missing information</a> to the node <strong>'{nodeCause}'</strong></li><li>Or use <code>.first()</code>, <code>.last()</code> or <code>.all()[index]</code> instead of <code>.item</code></li></ul>", "nodeErrorView.description.pairedItemNoInfoCodeNode": "An expression here won't work because it uses <code>.item</code> and n8n can't figure out the <a target=\"_blank\" href=\"https://docs.n8n.io/data/data-mapping/data-item-linking/item-linking-errors/\">matching item</a>. You can either: <ul><li>Add the <a target=\"_blank\" href=\"https://docs.n8n.io/data/data-mapping/data-item-linking/item-linking-code-node/\">missing information</a> to the node <strong>'{nodeCause}'</strong></li><li>Or use <code>.first()</code>, <code>.last()</code> or <code>.all()[index]</code> instead of <code>.item</code></li></ul>",
"nodeErrorView.description.pairedItemNoConnection": "There is no connection back to the node <strong>'{nodeCause}'</strong>, but it's used in an expression here.<br/><br/>Please wire up the node (there can be other nodes in between).", "nodeErrorView.description.pairedItemNoConnection": "There is no connection back to the node <strong>'{nodeCause}'</strong>, but it's used in an expression here.<br/><br/>Please wire up the node (there can be other nodes in between).",
"nodeErrorView.description.pairedItemNoConnectionCodeNode": "There is no connection back to the node <strong>'{nodeCause}'</strong>, but it's used in code here.<br/><br/>Please wire up the node (there can be other nodes in between).", "nodeErrorView.description.pairedItemNoConnectionCodeNode": "There is no connection back to the node <strong>'{nodeCause}'</strong>, but it's used in code here.<br/><br/>Please wire up the node (there can be other nodes in between).",
"nodeErrorView.description.noNodeExecutionData": "An expression references the node <strong>'{nodeCause}'</strong>, but it hasn't been executed yet. Either change the expression, or re-wire your workflow to make sure that node executes first.", "nodeErrorView.description.noNodeExecutionData": "An expression references the node <strong>'{nodeCause}'</strong>, but it hasn't been executed yet. Either change the expression, or re-wire your workflow to make sure that node executes first.",
"nodeErrorView.description.nodeNotFound": "The node <strong>'{nodeCause}'</strong> doesn't exist, but it's used in an expression here.", "nodeErrorView.description.nodeNotFound": "The node <strong>'{nodeCause}'</strong> doesn't exist, but it's used in an expression here.",
"nodeErrorView.description.noInputConnection": "This node has no input data. Please make sure this node is connected to another node.", "nodeErrorView.description.noInputConnection": "This node has no input data. Please make sure this node is connected to another node.",
"nodeErrorView.description.pairedItemMultipleMatches": "An expression here won't work because it uses <code>.item</code> and n8n can't figure out the <a href=”https://docs.n8n.io/data/data-mapping/data-item-linking/item-linking-errors/”>matching item</a>. (There are multiple possible matches) <br/><br/>Try using <code>.first()</code>, <code>.last()</code> or <code>.all()[index]</code> instead of <code>.item</code> or <a href=”https://docs.n8n.io/data/data-mapping/data-item-linking/item-linking-code-node/”>reference a different node</a>.", "nodeErrorView.description.pairedItemMultipleMatches": "An expression here won't work because it uses <code>.item</code> and n8n can't figure out the <a target=\"_blank\" href=\"https://docs.n8n.io/data/data-mapping/data-item-linking/item-linking-errors/\">matching item</a>. (There are multiple possible matches) <br/><br/>Try using <code>.first()</code>, <code>.last()</code> or <code>.all()[index]</code> instead of <code>.item</code> or <a target=\"_blank\" href=\"https://docs.n8n.io/data/data-mapping/data-item-linking/item-linking-code-node/\">reference a different node</a>.",
"nodeErrorView.description.pairedItemMultipleMatchesCodeNode": "The code here won't work because it uses <code>.item</code> and n8n can't figure out the <a href=”https://docs.n8n.io/data/data-mapping/data-item-linking/item-linking-errors/”>matching item</a>. (There are multiple possible matches) <br/><br/>Try using <code>.first()</code>, <code>.last()</code> or <code>.all()[index]</code> instead of <code>.item</code> or <a href=”https://docs.n8n.io/data/data-mapping/data-item-linking/item-linking-code-node/”>reference a different node</a>.", "nodeErrorView.description.pairedItemMultipleMatchesCodeNode": "The code here won't work because it uses <code>.item</code> and n8n can't figure out the <a target=\"_blank\" href=\"https://docs.n8n.io/data/data-mapping/data-item-linking/item-linking-errors/\">matching item</a>. (There are multiple possible matches) <br/><br/>Try using <code>.first()</code>, <code>.last()</code> or <code>.all()[index]</code> instead of <code>.item</code> or <a target=\"_blank\" href=\"https://docs.n8n.io/data/data-mapping/data-item-linking/item-linking-code-node/\">reference a different node</a>.",
"nodeErrorView.description.pairedItemPinned": "The <a href=”https://docs.n8n.io/data/data-mapping/data-item-linking/item-linking-errors/”>item-matching</a> data in that node may be stale. It is needed by an expression in this node that uses <code>.item</code>.", "nodeErrorView.description.pairedItemPinned": "The <a target=\"_blank\" href=\"https://docs.n8n.io/data/data-mapping/data-item-linking/item-linking-errors/\">item-matching</a> data in that node may be stale. It is needed by an expression in this node that uses <code>.item</code>.",
"nodeHelpers.credentialsUnset": "Credentials for '{credentialType}' are not set.", "nodeHelpers.credentialsUnset": "Credentials for '{credentialType}' are not set.",
"nodeSettings.alwaysOutputData.description": "If active, will output a single, empty item when the output would have been empty. Use to prevent the workflow finishing on this node.", "nodeSettings.alwaysOutputData.description": "If active, will output a single, empty item when the output would have been empty. Use to prevent the workflow finishing on this node.",
"nodeSettings.alwaysOutputData.displayName": "Always Output Data", "nodeSettings.alwaysOutputData.displayName": "Always Output Data",
@ -1566,7 +1605,7 @@
"settings.users.confirmDataHandlingAfterDeletion": "What should we do with their data?", "settings.users.confirmDataHandlingAfterDeletion": "What should we do with their data?",
"settings.users.confirmUserDeletion": "Are you sure you want to delete this invited user?", "settings.users.confirmUserDeletion": "Are you sure you want to delete this invited user?",
"settings.users.delete": "Delete", "settings.users.delete": "Delete",
"settings.users.deleteConfirmationMessage": "Type “delete all data” to confirm", "settings.users.deleteConfirmationMessage": "Type \"delete all data\" to confirm",
"settings.users.deleteConfirmationText": "delete all data", "settings.users.deleteConfirmationText": "delete all data",
"settings.users.deleteUser": "Delete {user}", "settings.users.deleteUser": "Delete {user}",
"settings.users.actions.delete": "Delete User", "settings.users.actions.delete": "Delete User",