@@ -223,6 +225,7 @@ defineExpose({ inputRef });
:disabled="disabled"
:name="name"
:teleported="teleported"
+ :size="tagSize"
@update:model-value="onUpdateModelValue"
@focus="onFocus"
@blur="onBlur"
@@ -246,6 +249,7 @@ defineExpose({ inputRef });
:maxlength="maxlength"
:autocomplete="autocomplete"
:disabled="disabled"
+ :size="tagSize"
@update:model-value="onUpdateModelValue"
@blur="onBlur"
@focus="onFocus"
diff --git a/packages/design-system/src/components/N8nInputLabel/InputLabel.vue b/packages/design-system/src/components/N8nInputLabel/InputLabel.vue
index 31e0d1ca46..4a89233bf1 100644
--- a/packages/design-system/src/components/N8nInputLabel/InputLabel.vue
+++ b/packages/design-system/src/components/N8nInputLabel/InputLabel.vue
@@ -5,7 +5,7 @@ import N8nIcon from '../N8nIcon';
import N8nText from '../N8nText';
import N8nTooltip from '../N8nTooltip';
-const SIZE = ['small', 'medium'] as const;
+const SIZE = ['small', 'medium', 'large'] as const;
interface InputLabelProps {
compact?: boolean;
diff --git a/packages/editor-ui/index.html b/packages/editor-ui/index.html
index 12dbfaeb9d..8176735fdd 100644
--- a/packages/editor-ui/index.html
+++ b/packages/editor-ui/index.html
@@ -9,6 +9,7 @@
window.BASE_PATH = '/{{BASE_PATH}}/';
window.REST_ENDPOINT = '{{REST_ENDPOINT}}';
+
n8n.io - Workflow Automation
diff --git a/packages/editor-ui/package.json b/packages/editor-ui/package.json
index 5e67b123d3..2bd69ffb30 100644
--- a/packages/editor-ui/package.json
+++ b/packages/editor-ui/package.json
@@ -1,6 +1,6 @@
{
"name": "n8n-editor-ui",
- "version": "1.60.0",
+ "version": "1.61.0",
"description": "Workflow Editor UI for n8n",
"main": "index.js",
"scripts": {
@@ -38,6 +38,7 @@
"@n8n/codemirror-lang": "workspace:*",
"@n8n/codemirror-lang-sql": "^1.0.2",
"@n8n/permissions": "workspace:*",
+ "@sentry/vue": "^8.31.0",
"@vue-flow/background": "^1.3.0",
"@vue-flow/controls": "^1.1.1",
"@vue-flow/core": "^1.33.5",
@@ -83,7 +84,7 @@
"@faker-js/faker": "^8.0.2",
"@iconify/json": "^2.2.228",
"@pinia/testing": "^0.1.3",
- "@sentry/vite-plugin": "^2.5.0",
+ "@sentry/vite-plugin": "^2.22.4",
"@types/dateformat": "^3.0.0",
"@types/file-saver": "^2.0.1",
"@types/humanize-duration": "^3.27.1",
diff --git a/packages/editor-ui/src/api/events.ts b/packages/editor-ui/src/api/events.ts
new file mode 100644
index 0000000000..00182bcaa1
--- /dev/null
+++ b/packages/editor-ui/src/api/events.ts
@@ -0,0 +1,6 @@
+import type { IRestApiContext } from '@/Interface';
+import { makeRestApiRequest } from '@/utils/apiUtils';
+
+export async function sessionStarted(context: IRestApiContext): Promise
{
+ return await makeRestApiRequest(context, 'GET', '/events/session-started');
+}
diff --git a/packages/editor-ui/src/components/CodeNodeEditor/CodeNodeEditor.vue b/packages/editor-ui/src/components/CodeNodeEditor/CodeNodeEditor.vue
index 381380cdfc..3ef1782b05 100644
--- a/packages/editor-ui/src/components/CodeNodeEditor/CodeNodeEditor.vue
+++ b/packages/editor-ui/src/components/CodeNodeEditor/CodeNodeEditor.vue
@@ -10,7 +10,7 @@ import type { CodeExecutionMode, CodeNodeEditorLanguage } from 'n8n-workflow';
import { format } from 'prettier';
import jsParser from 'prettier/plugins/babel';
import * as estree from 'prettier/plugins/estree';
-import { type Ref, computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue';
+import { type Ref, computed, nextTick, onBeforeUnmount, onMounted, ref, toRaw, watch } from 'vue';
import { CODE_NODE_TYPE } from '@/constants';
import { codeNodeEditorEventBus } from '@/event-bus';
@@ -26,6 +26,7 @@ import { useLinter } from './linter';
import { codeNodeEditorTheme } from './theme';
import { useI18n } from '@/composables/useI18n';
import { useTelemetry } from '@/composables/useTelemetry';
+import { dropInCodeEditor, mappingDropCursor } from '@/plugins/codemirror/dragAndDrop';
type Props = {
mode: CodeExecutionMode;
@@ -51,6 +52,7 @@ const emit = defineEmits<{
const message = useMessage();
const editor = ref(null) as Ref;
const languageCompartment = ref(new Compartment());
+const dragAndDropCompartment = ref(new Compartment());
const linterCompartment = ref(new Compartment());
const isEditorHovered = ref(false);
const isEditorFocused = ref(false);
@@ -95,6 +97,7 @@ onMounted(() => {
extensions.push(
...writableEditorExtensions,
+ dragAndDropCompartment.value.of(dragAndDropExtension.value),
EditorView.domEventHandlers({
focus: () => {
isEditorFocused.value = true;
@@ -151,6 +154,12 @@ const placeholder = computed(() => {
return CODE_PLACEHOLDERS[props.language]?.[props.mode] ?? '';
});
+const dragAndDropEnabled = computed(() => {
+ return !props.isReadOnly && props.mode === 'runOnceForEachItem';
+});
+
+const dragAndDropExtension = computed(() => (dragAndDropEnabled.value ? mappingDropCursor() : []));
+
// eslint-disable-next-line vue/return-in-computed-property
const languageExtensions = computed<[LanguageSupport, ...Extension[]]>(() => {
switch (props.language) {
@@ -188,6 +197,12 @@ watch(
},
);
+watch(dragAndDropExtension, (extension) => {
+ editor.value?.dispatch({
+ effects: dragAndDropCompartment.value.reconfigure(extension),
+ });
+});
+
watch(
() => props.language,
(_newLanguage, previousLanguage: CodeNodeEditorLanguage) => {
@@ -202,7 +217,6 @@ watch(
reloadLinter();
},
);
-
watch(
aiEnabled,
async (isEnabled) => {
@@ -361,6 +375,12 @@ function onAiLoadStart() {
function onAiLoadEnd() {
isLoadingAIResponse.value = false;
}
+
+async function onDrop(value: string, event: MouseEvent) {
+ if (!editor.value) return;
+
+ await dropInCodeEditor(toRaw(editor.value), event, value);
+}
@@ -384,10 +404,20 @@ function onAiLoadEnd() {
data-test-id="code-node-tab-code"
:class="$style.fillHeight"
>
-
+
+
+
+
+
@@ -415,7 +457,7 @@ function onAiLoadEnd() {
diff --git a/packages/editor-ui/src/components/CredentialEdit/CredentialEdit.vue b/packages/editor-ui/src/components/CredentialEdit/CredentialEdit.vue
index 856f0c9315..ba3eb19ff0 100644
--- a/packages/editor-ui/src/components/CredentialEdit/CredentialEdit.vue
+++ b/packages/editor-ui/src/components/CredentialEdit/CredentialEdit.vue
@@ -280,8 +280,7 @@ const requiredPropertiesFilled = computed(() => {
const credentialPermissions = computed(() => {
return getResourcePermissions(
- ((credentialId.value ? currentCredential.value : credentialData.value) as ICredentialsResponse)
- ?.scopes,
+ (currentCredential.value as ICredentialsResponse)?.scopes ?? homeProject.value?.scopes,
).credential;
});
@@ -341,11 +340,8 @@ onMounted(async () => {
credentialTypeName: defaultCredentialTypeName.value,
});
- const scopes = homeProject.value?.scopes ?? [];
-
credentialData.value = {
...credentialData.value,
- scopes,
...(homeProject.value ? { homeProject: homeProject.value } : {}),
};
} else {
diff --git a/packages/editor-ui/src/components/ExpressionEditModal.vue b/packages/editor-ui/src/components/ExpressionEditModal.vue
index b92f1eca18..1f9f0b75fe 100644
--- a/packages/editor-ui/src/components/ExpressionEditModal.vue
+++ b/packages/editor-ui/src/components/ExpressionEditModal.vue
@@ -19,7 +19,7 @@ import OutputItemSelect from './InlineExpressionEditor/OutputItemSelect.vue';
import { useI18n } from '@/composables/useI18n';
import { useDebounce } from '@/composables/useDebounce';
import DraggableTarget from './DraggableTarget.vue';
-import { dropInEditor } from '@/plugins/codemirror/dragAndDrop';
+import { dropInExpressionEditor } from '@/plugins/codemirror/dragAndDrop';
import { APP_MODALS_ELEMENT_ID } from '@/constants';
@@ -119,7 +119,7 @@ function closeDialog() {
async function onDrop(expression: string, event: MouseEvent) {
if (!inputEditor.value) return;
- await dropInEditor(toRaw(inputEditor.value), event, expression);
+ await dropInExpressionEditor(toRaw(inputEditor.value), event, expression);
}
diff --git a/packages/editor-ui/src/components/ExpressionParameterInput.vue b/packages/editor-ui/src/components/ExpressionParameterInput.vue
index cb0c22ec30..8706e73991 100644
--- a/packages/editor-ui/src/components/ExpressionParameterInput.vue
+++ b/packages/editor-ui/src/components/ExpressionParameterInput.vue
@@ -10,7 +10,7 @@ import { useWorkflowsStore } from '@/stores/workflows.store';
import { createExpressionTelemetryPayload } from '@/utils/telemetryUtils';
import { useTelemetry } from '@/composables/useTelemetry';
-import { dropInEditor } from '@/plugins/codemirror/dragAndDrop';
+import { dropInExpressionEditor } from '@/plugins/codemirror/dragAndDrop';
import type { Segment } from '@/types/expressions';
import { startCompletion } from '@codemirror/autocomplete';
import type { EditorState, SelectionRange } from '@codemirror/state';
@@ -119,7 +119,9 @@ async function onDrop(value: string, event: MouseEvent) {
if (!editor) return;
- const droppedSelection = await dropInEditor(toRaw(editor), event, value);
+ const droppedSelection = await dropInExpressionEditor(toRaw(editor), event, value);
+
+ if (!ndvStore.isMappingOnboarded) ndvStore.setMappingOnboarded();
if (!ndvStore.isAutocompleteOnboarded) {
setCursorPosition((droppedSelection.ranges.at(0)?.head ?? 3) - 3);
diff --git a/packages/editor-ui/src/components/FilterConditions/Condition.vue b/packages/editor-ui/src/components/FilterConditions/Condition.vue
index 00d83218c1..316b6827fa 100644
--- a/packages/editor-ui/src/components/FilterConditions/Condition.vue
+++ b/packages/editor-ui/src/components/FilterConditions/Condition.vue
@@ -90,8 +90,8 @@ const allIssues = computed(() => {
const now = computed(() => DateTime.now().toISO());
const leftParameter = computed