refactor(editor): Improve linting for component and prop names (no-changelog) (#8169)

This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™
2023-12-28 09:49:58 +01:00
committed by GitHub
parent 639afcd7a5
commit 68cff4c59e
304 changed files with 3428 additions and 3516 deletions

View File

@@ -4,7 +4,7 @@
module.exports = { module.exports = {
plugins: ['vue'], plugins: ['vue'],
extends: ['plugin:vue/vue3-essential', '@vue/typescript', './base'], extends: ['plugin:vue/vue3-recommended', '@vue/typescript', './base'],
env: { env: {
browser: true, browser: true,
@@ -37,6 +37,22 @@ module.exports = {
'vue/no-unused-components': 'error', 'vue/no-unused-components': 'error',
'vue/multi-word-component-names': 'off', 'vue/multi-word-component-names': 'off',
'@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/no-explicit-any': 'error',
'vue/component-name-in-template-casing': [
'error',
'PascalCase',
{
registeredComponentsOnly: true,
},
],
'vue/no-reserved-component-names': [
'error',
{
disallowVueBuiltInComponents: true,
disallowVue3BuiltInComponents: false,
},
],
'vue/prop-name-casing': ['error', 'camelCase'],
'vue/attribute-hyphenation': ['error', 'always'],
// TODO: fix these // TODO: fix these
'@typescript-eslint/no-unsafe-call': 'off', '@typescript-eslint/no-unsafe-call': 'off',

View File

@@ -1,37 +1,37 @@
<template> <template>
<div :class="['n8n-action-box', $style.container]" data-test-id="action-box"> <div :class="['n8n-action-box', $style.container]" data-test-id="action-box">
<div :class="$style.emoji" v-if="emoji"> <div v-if="emoji" :class="$style.emoji">
{{ emoji }} {{ emoji }}
</div> </div>
<div :class="$style.heading" v-if="heading || $slots.heading"> <div v-if="heading || $slots.heading" :class="$style.heading">
<n8n-heading size="xlarge" align="center"> <N8nHeading size="xlarge" align="center">
<slot name="heading">{{ heading }}</slot> <slot name="heading">{{ heading }}</slot>
</n8n-heading> </N8nHeading>
</div> </div>
<div :class="$style.description" @click="$emit('descriptionClick', $event)"> <div :class="$style.description" @click="$emit('descriptionClick', $event)">
<n8n-text color="text-base"> <N8nText color="text-base">
<slot name="description"> <slot name="description">
<span v-html="description"></span> <span v-html="description"></span>
</slot> </slot>
</n8n-text> </N8nText>
</div> </div>
<n8n-button <N8nButton
v-if="buttonText" v-if="buttonText"
:label="buttonText" :label="buttonText"
:type="buttonType" :type="buttonType"
size="large" size="large"
@click="$emit('click:button', $event)" @click="$emit('click:button', $event)"
/> />
<n8n-callout <N8nCallout
v-if="calloutText" v-if="calloutText"
:theme="calloutTheme" :theme="calloutTheme"
:icon="calloutIcon" :icon="calloutIcon"
:class="$style.callout" :class="$style.callout"
> >
<n8n-text color="text-base"> <N8nText color="text-base">
<span size="small" v-html="calloutText"></span> <span size="small" v-html="calloutText"></span>
</n8n-text> </N8nText>
</n8n-callout> </N8nCallout>
</div> </div>
</template> </template>
@@ -43,7 +43,7 @@ import N8nCallout from '../N8nCallout';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
name: 'n8n-action-box', name: 'N8nActionBox',
components: { components: {
N8nButton, N8nButton,
N8nHeading, N8nHeading,

View File

@@ -1,27 +1,27 @@
<template> <template>
<div :class="['action-dropdown-container', $style.actionDropdownContainer]"> <div :class="['action-dropdown-container', $style.actionDropdownContainer]">
<el-dropdown <ElDropdown
ref="elementDropdown"
:placement="placement" :placement="placement"
:trigger="trigger" :trigger="trigger"
@command="onSelect"
:popper-class="{ [$style.shadow]: true, [$style.hideArrow]: hideArrow }" :popper-class="{ [$style.shadow]: true, [$style.hideArrow]: hideArrow }"
@command="onSelect"
@visible-change="onVisibleChange" @visible-change="onVisibleChange"
ref="elementDropdown"
> >
<slot v-if="$slots.activator" name="activator" /> <slot v-if="$slots.activator" name="activator" />
<n8n-icon-button <n8n-icon-button
v-else v-else
@blur="onButtonBlur"
type="tertiary" type="tertiary"
text text
:class="$style.activator" :class="$style.activator"
:size="activatorSize" :size="activatorSize"
:icon="activatorIcon" :icon="activatorIcon"
@blur="onButtonBlur"
/> />
<template #dropdown> <template #dropdown>
<el-dropdown-menu :class="$style.userActionsMenu"> <ElDropdownMenu :class="$style.userActionsMenu">
<el-dropdown-item <ElDropdownItem
v-for="item in items" v-for="item in items"
:key="item.id" :key="item.id"
:command="item.id" :command="item.id"
@@ -31,22 +31,22 @@
> >
<div :class="getItemClasses(item)" :data-test-id="`${testIdPrefix}-item-${item.id}`"> <div :class="getItemClasses(item)" :data-test-id="`${testIdPrefix}-item-${item.id}`">
<span v-if="item.icon" :class="$style.icon"> <span v-if="item.icon" :class="$style.icon">
<n8n-icon :icon="item.icon" :size="iconSize" /> <N8nIcon :icon="item.icon" :size="iconSize" />
</span> </span>
<span :class="$style.label"> <span :class="$style.label">
{{ item.label }} {{ item.label }}
</span> </span>
<n8n-keyboard-shortcut <N8nKeyboardShortcut
v-if="item.shortcut" v-if="item.shortcut"
v-bind="item.shortcut" v-bind="item.shortcut"
:class="$style.shortcut" :class="$style.shortcut"
> >
</n8n-keyboard-shortcut> </N8nKeyboardShortcut>
</div> </div>
</el-dropdown-item> </ElDropdownItem>
</el-dropdown-menu> </ElDropdownMenu>
</template> </template>
</el-dropdown> </ElDropdown>
</div> </div>
</template> </template>
@@ -75,7 +75,7 @@ export interface IActionDropdownItem {
// It can be used in different parts of editor UI while ActionToggle // It can be used in different parts of editor UI while ActionToggle
// is designed to be used in card components. // is designed to be used in card components.
export default defineComponent({ export default defineComponent({
name: 'n8n-action-dropdown', name: 'N8nActionDropdown',
components: { components: {
ElDropdown, ElDropdown,
ElDropdownMenu, ElDropdownMenu,
@@ -83,10 +83,6 @@ export default defineComponent({
N8nIcon, N8nIcon,
N8nKeyboardShortcut, N8nKeyboardShortcut,
}, },
data() {
const testIdPrefix = this.$attrs['data-test-id'];
return { testIdPrefix };
},
props: { props: {
items: { items: {
type: Array as PropType<IActionDropdownItem[]>, type: Array as PropType<IActionDropdownItem[]>,
@@ -121,6 +117,10 @@ export default defineComponent({
default: false, default: false,
}, },
}, },
data() {
const testIdPrefix = this.$attrs['data-test-id'];
return { testIdPrefix };
},
methods: { methods: {
getItemClasses(item: IActionDropdownItem): Record<string, boolean> { getItemClasses(item: IActionDropdownItem): Record<string, boolean> {
return { return {

View File

@@ -1,6 +1,6 @@
<template> <template>
<span @click.stop.prevent :class="$style.container" data-test-id="action-toggle"> <span :class="$style.container" data-test-id="action-toggle" @click.stop.prevent>
<el-dropdown <ElDropdown
:placement="placement" :placement="placement"
:size="size" :size="size"
trigger="click" trigger="click"
@@ -9,7 +9,7 @@
> >
<slot> <slot>
<span :class="{ [$style.button]: true, [$style[theme]]: !!theme }"> <span :class="{ [$style.button]: true, [$style[theme]]: !!theme }">
<n8n-icon <N8nIcon
:icon="iconOrientation === 'horizontal' ? 'ellipsis-h' : 'ellipsis-v'" :icon="iconOrientation === 'horizontal' ? 'ellipsis-h' : 'ellipsis-v'"
:size="iconSize" :size="iconSize"
/> />
@@ -17,8 +17,8 @@
</slot> </slot>
<template #dropdown> <template #dropdown>
<el-dropdown-menu data-test-id="action-toggle-dropdown"> <ElDropdownMenu data-test-id="action-toggle-dropdown">
<el-dropdown-item <ElDropdownItem
v-for="action in actions" v-for="action in actions"
:key="action.value" :key="action.value"
:command="action.value" :command="action.value"
@@ -27,17 +27,17 @@
> >
{{ action.label }} {{ action.label }}
<div :class="$style.iconContainer"> <div :class="$style.iconContainer">
<n8n-icon <N8nIcon
v-if="action.type === 'external-link'" v-if="action.type === 'external-link'"
icon="external-link-alt" icon="external-link-alt"
size="xsmall" size="xsmall"
color="text-base" color="text-base"
/> />
</div> </div>
</el-dropdown-item> </ElDropdownItem>
</el-dropdown-menu> </ElDropdownMenu>
</template> </template>
</el-dropdown> </ElDropdown>
</span> </span>
</template> </template>
@@ -49,7 +49,7 @@ import N8nIcon from '../N8nIcon';
import type { UserAction } from '@/types'; import type { UserAction } from '@/types';
export default defineComponent({ export default defineComponent({
name: 'n8n-action-toggle', name: 'N8nActionToggle',
components: { components: {
ElDropdown, ElDropdown,
ElDropdownMenu, ElDropdownMenu,

View File

@@ -2,7 +2,7 @@
<div :class="alertBoxClassNames" role="alert"> <div :class="alertBoxClassNames" role="alert">
<div :class="$style.content"> <div :class="$style.content">
<span v-if="showIcon || $slots.icon" :class="$style.icon"> <span v-if="showIcon || $slots.icon" :class="$style.icon">
<n8n-icon v-if="showIcon" :icon="icon" /> <N8nIcon v-if="showIcon" :icon="icon" />
<slot v-else-if="$slots.icon" name="icon" /> <slot v-else-if="$slots.icon" name="icon" />
</span> </span>
<div :class="$style.text"> <div :class="$style.text">

View File

@@ -24,7 +24,10 @@ const sizes: { [size: string]: number } = {
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
name: 'n8n-avatar', name: 'N8nAvatar',
components: {
Avatar,
},
props: { props: {
firstName: { firstName: {
type: String, type: String,
@@ -48,9 +51,6 @@ export default defineComponent({
], ],
}, },
}, },
components: {
Avatar,
},
computed: { computed: {
initials() { initials() {
return ( return (

View File

@@ -1,8 +1,8 @@
<template> <template>
<span :class="['n8n-badge', $style[theme]]"> <span :class="['n8n-badge', $style[theme]]">
<n8n-text :size="size" :bold="bold" :compact="true"> <N8nText :size="size" :bold="bold" :compact="true">
<slot></slot> <slot></slot>
</n8n-text> </N8nText>
</span> </span>
</template> </template>
@@ -12,6 +12,9 @@ import N8nText from '../N8nText';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
components: {
N8nText,
},
props: { props: {
theme: { theme: {
type: String, type: String,
@@ -30,9 +33,6 @@ export default defineComponent({
default: false, default: false,
}, },
}, },
components: {
N8nText,
},
}); });
</script> </script>

View File

@@ -9,9 +9,9 @@
aria-live="polite" aria-live="polite"
v-bind="$attrs" v-bind="$attrs"
> >
<span :class="$style.icon" v-if="loading || icon"> <span v-if="loading || icon" :class="$style.icon">
<n8n-spinner v-if="loading" :size="size" /> <N8nSpinner v-if="loading" :size="size" />
<n8n-icon v-else-if="icon" :icon="icon" :size="size" /> <N8nIcon v-else-if="icon" :icon="icon" :size="size" />
</span> </span>
<span v-if="label || $slots.default"> <span v-if="label || $slots.default">
<slot>{{ label }}</slot> <slot>{{ label }}</slot>

View File

@@ -1,12 +1,12 @@
<template> <template>
<div :class="classes" role="alert"> <div :class="classes" role="alert">
<div :class="$style.messageSection"> <div :class="$style.messageSection">
<div :class="$style.icon" v-if="!iconless"> <div v-if="!iconless" :class="$style.icon">
<n8n-icon :icon="getIcon" :size="getIconSize" /> <N8nIcon :icon="getIcon" :size="getIconSize" />
</div> </div>
<n8n-text size="small"> <N8nText size="small">
<slot /> <slot />
</n8n-text> </N8nText>
&nbsp; &nbsp;
<slot name="actions" /> <slot name="actions" />
</div> </div>
@@ -28,7 +28,7 @@ const CALLOUT_DEFAULT_ICONS: { [key: string]: string } = {
}; };
export default defineComponent({ export default defineComponent({
name: 'n8n-callout', name: 'N8nCallout',
components: { components: {
N8nText, N8nText,
N8nIcon, N8nIcon,

View File

@@ -1,16 +1,16 @@
<template> <template>
<div :class="classes" v-bind="$attrs"> <div :class="classes" v-bind="$attrs">
<div :class="$style.icon" v-if="$slots.prepend"> <div v-if="$slots.prepend" :class="$style.icon">
<slot name="prepend" /> <slot name="prepend" />
</div> </div>
<div :class="$style.content"> <div :class="$style.content">
<div :class="$style.header" v-if="$slots.header"> <div v-if="$slots.header" :class="$style.header">
<slot name="header" /> <slot name="header" />
</div> </div>
<div :class="$style.body" v-if="$slots.default"> <div v-if="$slots.default" :class="$style.body">
<slot /> <slot />
</div> </div>
<div :class="$style.footer" v-if="$slots.footer"> <div v-if="$slots.footer" :class="$style.footer">
<slot name="footer" /> <slot name="footer" />
</div> </div>
</div> </div>
@@ -24,7 +24,7 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
name: 'n8n-card', name: 'N8nCard',
inheritAttrs: true, inheritAttrs: true,
props: { props: {
hoverable: { hoverable: {

View File

@@ -1,23 +1,23 @@
<template> <template>
<el-checkbox <ElCheckbox
v-bind="$props" v-bind="$props"
ref="checkbox" ref="checkbox"
:class="['n8n-checkbox', $style.n8nCheckbox]" :class="['n8n-checkbox', $style.n8nCheckbox]"
:disabled="disabled" :disabled="disabled"
:indeterminate="indeterminate" :indeterminate="indeterminate"
:modelValue="modelValue" :model-value="modelValue"
@update:modelValue="onUpdateModelValue" @update:modelValue="onUpdateModelValue"
> >
<slot></slot> <slot></slot>
<n8n-input-label <N8nInputLabel
v-if="label" v-if="label"
:label="label" :label="label"
:tooltipText="tooltipText" :tooltip-text="tooltipText"
:bold="false" :bold="false"
:size="labelSize" :size="labelSize"
@click.prevent="onLabelClick" @click.prevent="onLabelClick"
/> />
</el-checkbox> </ElCheckbox>
</template> </template>
<script lang="ts"> <script lang="ts">
@@ -26,7 +26,7 @@ import { ElCheckbox } from 'element-plus';
import N8nInputLabel from '../N8nInputLabel'; import N8nInputLabel from '../N8nInputLabel';
export default defineComponent({ export default defineComponent({
name: 'n8n-checkbox', name: 'N8nCheckbox',
components: { components: {
ElCheckbox, ElCheckbox,
N8nInputLabel, N8nInputLabel,

View File

@@ -64,21 +64,21 @@ const onActiveChange = (value: string) => {
</script> </script>
<template> <template>
<span :class="['n8n-color-picker', $style.component]"> <span :class="['n8n-color-picker', $style.component]">
<el-color-picker <ElColorPicker
v-model="model" v-model="model"
v-bind="colorPickerProps" v-bind="colorPickerProps"
@change="onChange" @change="onChange"
@active-change="onActiveChange" @active-change="onActiveChange"
/> />
<n8n-input <N8nInput
v-if="showInput" v-if="showInput"
:class="$style.input" :class="$style.input"
:disabled="props.disabled" :disabled="props.disabled"
:size="props.size" :size="props.size"
:modelValue="color" :model-value="color"
:name="name" :name="name"
@update:modelValue="onInput"
type="text" type="text"
@update:modelValue="onInput"
/> />
</span> </span>
</template> </template>

View File

@@ -9,13 +9,12 @@ import N8nOption from '../N8nOption';
import N8nPagination from '../N8nPagination'; import N8nPagination from '../N8nPagination';
export default defineComponent({ export default defineComponent({
name: 'n8n-datatable', name: 'N8nDatatable',
components: { components: {
N8nSelect, N8nSelect,
N8nOption, N8nOption,
N8nPagination, N8nPagination,
}, },
emits: ['update:currentPage', 'update:rowsPerPage'],
props: { props: {
columns: { columns: {
type: Array as PropType<DatatableColumn[]>, type: Array as PropType<DatatableColumn[]>,
@@ -38,6 +37,7 @@ export default defineComponent({
default: 10, default: 10,
}, },
}, },
emits: ['update:currentPage', 'update:rowsPerPage'],
setup(props, { emit }) { setup(props, { emit }) {
const { t } = useI18n(); const { t } = useI18n();
const rowsPerPageOptions = ref([10, 25, 50, 100]); const rowsPerPageOptions = ref([10, 25, 50, 100]);
@@ -130,10 +130,10 @@ export default defineComponent({
</thead> </thead>
<tbody> <tbody>
<template v-for="row in visibleRows"> <template v-for="row in visibleRows">
<slot name="row" :columns="columns" :row="row" :getTdValue="getTdValue"> <slot name="row" :columns="columns" :row="row" :get-td-value="getTdValue">
<tr :key="row.id"> <tr :key="row.id">
<td v-for="column in columns" :key="column.id" :class="column.classes"> <td v-for="column in columns" :key="column.id" :class="column.classes">
<component v-if="column.render" :is="column.render" :row="row" :column="column" /> <component :is="column.render" v-if="column.render" :row="row" :column="column" />
<span v-else>{{ getTdValue(row, column) }}</span> <span v-else>{{ getTdValue(row, column) }}</span>
</td> </td>
</tr> </tr>
@@ -143,33 +143,33 @@ export default defineComponent({
</table> </table>
<div :class="$style.pagination"> <div :class="$style.pagination">
<n8n-pagination <N8nPagination
v-if="totalPages > 1" v-if="totalPages > 1"
background background
:pager-count="5" :pager-count="5"
:page-size="rowsPerPage" :page-size="rowsPerPage"
layout="prev, pager, next" layout="prev, pager, next"
:total="totalRows" :total="totalRows"
:currentPage="currentPage" :current-page="currentPage"
@update:currentPage="onUpdateCurrentPage" @update:currentPage="onUpdateCurrentPage"
/> />
<div :class="$style.pageSizeSelector"> <div :class="$style.pageSizeSelector">
<n8n-select <N8nSelect
size="mini" size="mini"
:modelValue="rowsPerPage" :model-value="rowsPerPage"
@update:modelValue="onRowsPerPageChange"
teleported teleported
@update:modelValue="onRowsPerPageChange"
> >
<template #prepend>{{ t('datatable.pageSize') }}</template> <template #prepend>{{ t('datatable.pageSize') }}</template>
<n8n-option <N8nOption
v-for="size in rowsPerPageOptions" v-for="size in rowsPerPageOptions"
:key="size" :key="size"
:label="`${size}`" :label="`${size}`"
:value="size" :value="size"
/> />
<n8n-option :label="`All`" value="*"> </n8n-option> <N8nOption :label="`All`" value="*"> </N8nOption>
</n8n-select> </N8nSelect>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,26 +1,26 @@
<template> <template>
<div :class="['n8n-form-box', $style.container]"> <div :class="['n8n-form-box', $style.container]">
<div v-if="title" :class="$style.heading"> <div v-if="title" :class="$style.heading">
<n8n-heading size="xlarge"> <N8nHeading size="xlarge">
{{ title }} {{ title }}
</n8n-heading> </N8nHeading>
</div> </div>
<div :class="$style.inputsContainer"> <div :class="$style.inputsContainer">
<n8n-form-inputs <N8nFormInputs
:inputs="inputs" :inputs="inputs"
:eventBus="formBus" :event-bus="formBus"
:columnView="true" :column-view="true"
@update="onUpdateModelValue" @update="onUpdateModelValue"
@submit="onSubmit" @submit="onSubmit"
/> />
</div> </div>
<div :class="$style.buttonsContainer" v-if="secondaryButtonText || buttonText"> <div v-if="secondaryButtonText || buttonText" :class="$style.buttonsContainer">
<span v-if="secondaryButtonText" :class="$style.secondaryButtonContainer"> <span v-if="secondaryButtonText" :class="$style.secondaryButtonContainer">
<n8n-link size="medium" theme="text" @click="onSecondaryButtonClick"> <N8nLink size="medium" theme="text" @click="onSecondaryButtonClick">
{{ secondaryButtonText }} {{ secondaryButtonText }}
</n8n-link> </N8nLink>
</span> </span>
<n8n-button <N8nButton
v-if="buttonText" v-if="buttonText"
:label="buttonText" :label="buttonText"
:loading="buttonLoading" :loading="buttonLoading"
@@ -30,9 +30,9 @@
/> />
</div> </div>
<div :class="$style.actionContainer"> <div :class="$style.actionContainer">
<n8n-link v-if="redirectText && redirectLink" :to="redirectLink"> <N8nLink v-if="redirectText && redirectLink" :to="redirectLink">
{{ redirectText }} {{ redirectText }}
</n8n-link> </N8nLink>
</div> </div>
<slot></slot> <slot></slot>
</div> </div>
@@ -47,7 +47,7 @@ import N8nButton from '../N8nButton';
import { createEventBus } from '../../utils'; import { createEventBus } from '../../utils';
export default defineComponent({ export default defineComponent({
name: 'n8n-form-box', name: 'N8nFormBox',
components: { components: {
N8nHeading, N8nHeading,
N8nFormInputs, N8nFormInputs,

View File

@@ -1,52 +1,52 @@
<template> <template>
<n8n-checkbox <N8nCheckbox
v-if="type === 'checkbox'" v-if="type === 'checkbox'"
v-bind="$props" v-bind="$props"
ref="inputRef"
@update:modelValue="onUpdateModelValue" @update:modelValue="onUpdateModelValue"
@focus="onFocus" @focus="onFocus"
ref="inputRef"
/> />
<n8n-input-label <N8nInputLabel
v-else-if="type === 'toggle'" v-else-if="type === 'toggle'"
:inputName="name" :input-name="name"
:label="label" :label="label"
:tooltipText="tooltipText" :tooltip-text="tooltipText"
:required="required && showRequiredAsterisk" :required="required && showRequiredAsterisk"
> >
<template #content> <template #content>
{{ tooltipText }} {{ tooltipText }}
</template> </template>
<el-switch <ElSwitch
:modelValue="modelValue" :model-value="modelValue"
:active-color="activeColor" :active-color="activeColor"
:inactive-color="inactiveColor" :inactive-color="inactiveColor"
@update:modelValue="onUpdateModelValue" @update:modelValue="onUpdateModelValue"
></el-switch> ></ElSwitch>
</n8n-input-label> </N8nInputLabel>
<n8n-input-label <N8nInputLabel
v-else v-else
:inputName="name" :input-name="name"
:label="label" :label="label"
:tooltipText="tooltipText" :tooltip-text="tooltipText"
:required="required && showRequiredAsterisk" :required="required && showRequiredAsterisk"
> >
<div :class="showErrors ? $style.errorInput : ''" @keydown.stop @keydown.enter="onEnter"> <div :class="showErrors ? $style.errorInput : ''" @keydown.stop @keydown.enter="onEnter">
<slot v-if="hasDefaultSlot" /> <slot v-if="hasDefaultSlot" />
<n8n-select <N8nSelect
:class="{ [$style.multiSelectSmallTags]: tagSize === 'small' }"
v-else-if="type === 'select' || type === 'multi-select'" v-else-if="type === 'select' || type === 'multi-select'"
:modelValue="modelValue" :class="{ [$style.multiSelectSmallTags]: tagSize === 'small' }"
:model-value="modelValue"
:placeholder="placeholder" :placeholder="placeholder"
:multiple="type === 'multi-select'" :multiple="type === 'multi-select'"
ref="inputRef"
:disabled="disabled" :disabled="disabled"
:name="name"
:teleported="teleported"
@update:modelValue="onUpdateModelValue" @update:modelValue="onUpdateModelValue"
@focus="onFocus" @focus="onFocus"
@blur="onBlur" @blur="onBlur"
:name="name"
:teleported="teleported"
ref="inputRef"
> >
<n8n-option <N8nOption
v-for="option in options || []" v-for="option in options || []"
:key="option.value" :key="option.value"
:value="option.value" :value="option.value"
@@ -54,38 +54,38 @@
:disabled="!!option.disabled" :disabled="!!option.disabled"
size="small" size="small"
/> />
</n8n-select> </N8nSelect>
<n8n-input <N8nInput
v-else v-else
:name="name" :name="name"
ref="inputRef"
:type="type" :type="type"
:placeholder="placeholder" :placeholder="placeholder"
:modelValue="modelValue" :model-value="modelValue"
:maxlength="maxlength" :maxlength="maxlength"
:autocomplete="autocomplete" :autocomplete="autocomplete"
:disabled="disabled" :disabled="disabled"
@update:modelValue="onUpdateModelValue" @update:modelValue="onUpdateModelValue"
@blur="onBlur" @blur="onBlur"
@focus="onFocus" @focus="onFocus"
ref="inputRef"
/> />
</div> </div>
<div :class="$style.errors" v-if="showErrors"> <div v-if="showErrors" :class="$style.errors">
<span v-text="validationError" /> <span v-text="validationError" />
<n8n-link <n8n-link
v-if="documentationUrl && documentationText" v-if="documentationUrl && documentationText"
:to="documentationUrl" :to="documentationUrl"
:newWindow="true" :new-window="true"
size="small" size="small"
theme="danger" theme="danger"
> >
{{ documentationText }} {{ documentationText }}
</n8n-link> </n8n-link>
</div> </div>
<div :class="$style.infoText" v-else-if="infoText"> <div v-else-if="infoText" :class="$style.infoText">
<span size="small" v-text="infoText" /> <span size="small" v-text="infoText" />
</div> </div>
</n8n-input-label> </N8nInputLabel>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>

View File

@@ -8,8 +8,8 @@
:class="{ [`mt-${verticalSpacing}`]: verticalSpacing && index > 0 }" :class="{ [`mt-${verticalSpacing}`]: verticalSpacing && index > 0 }"
> >
<n8n-text <n8n-text
color="text-base"
v-if="input.properties.type === 'info'" v-if="input.properties.type === 'info'"
color="text-base"
tag="div" tag="div"
:size="input.properties.labelSize" :size="input.properties.labelSize"
:align="input.properties.labelAlignment" :align="input.properties.labelAlignment"
@@ -17,16 +17,16 @@
> >
{{ input.properties.label }} {{ input.properties.label }}
</n8n-text> </n8n-text>
<n8n-form-input <N8nFormInput
v-else v-else
v-bind="input.properties" v-bind="input.properties"
:name="input.name" :name="input.name"
:label="input.properties.label || ''" :label="input.properties.label || ''"
:modelValue="values[input.name]" :model-value="values[input.name]"
:data-test-id="input.name" :data-test-id="input.name"
:showValidationWarnings="showValidationWarnings" :show-validation-warnings="showValidationWarnings"
:teleported="teleported" :teleported="teleported"
:tagSize="tagSize" :tag-size="tagSize"
@update:modelValue="(value) => onUpdateModelValue(input.name, value)" @update:modelValue="(value) => onUpdateModelValue(input.name, value)"
@validate="(value) => onValidate(input.name, value)" @validate="(value) => onValidate(input.name, value)"
@enter="onSubmit" @enter="onSubmit"
@@ -47,7 +47,7 @@ import type { EventBus } from '../../utils';
import { createEventBus } from '../../utils'; import { createEventBus } from '../../utils';
export default defineComponent({ export default defineComponent({
name: 'n8n-form-inputs', name: 'N8nFormInputs',
components: { components: {
N8nFormInput, N8nFormInput,
ResizeObserver, ResizeObserver,
@@ -87,20 +87,6 @@ export default defineComponent({
validity: {} as { [key: string]: boolean }, validity: {} as { [key: string]: boolean },
}; };
}, },
mounted() {
this.inputs.forEach((input) => {
if (input.hasOwnProperty('initialValue')) {
this.values = {
...this.values,
[input.name]: input.initialValue,
};
}
});
if (this.eventBus) {
this.eventBus.on('submit', () => this.onSubmit());
}
},
computed: { computed: {
filteredInputs(): IFormInput[] { filteredInputs(): IFormInput[] {
return this.inputs.filter((input) => return this.inputs.filter((input) =>
@@ -117,6 +103,25 @@ export default defineComponent({
return true; return true;
}, },
}, },
watch: {
isReadyToSubmit(ready: boolean) {
this.$emit('ready', ready);
},
},
mounted() {
this.inputs.forEach((input) => {
if (input.hasOwnProperty('initialValue')) {
this.values = {
...this.values,
[input.name]: input.initialValue,
};
}
});
if (this.eventBus) {
this.eventBus.on('submit', () => this.onSubmit());
}
},
methods: { methods: {
onUpdateModelValue(name: string, value: unknown) { onUpdateModelValue(name: string, value: unknown) {
this.values = { this.values = {
@@ -146,11 +151,6 @@ export default defineComponent({
} }
}, },
}, },
watch: {
isReadyToSubmit(ready: boolean) {
this.$emit('ready', ready);
},
},
}); });
</script> </script>

View File

@@ -8,7 +8,7 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
name: 'n8n-heading', name: 'N8nHeading',
props: { props: {
tag: { tag: {
type: String, type: String,

View File

@@ -1,7 +1,7 @@
<template> <template>
<n8n-text :size="size" :color="color" :compact="true" class="n8n-icon" v-bind="$attrs"> <N8nText :size="size" :color="color" :compact="true" class="n8n-icon" v-bind="$attrs">
<font-awesome-icon :icon="icon" :spin="spin" :class="$style[size]" /> <FontAwesomeIcon :icon="icon" :spin="spin" :class="$style[size]" />
</n8n-text> </N8nText>
</template> </template>
<script lang="ts"> <script lang="ts">
@@ -11,7 +11,7 @@ import N8nText from '../N8nText';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
name: 'n8n-icon', name: 'N8nIcon',
components: { components: {
FontAwesomeIcon, FontAwesomeIcon,
N8nText, N8nText,

View File

@@ -1,5 +1,5 @@
<template> <template>
<n8n-button square v-bind="{ ...$attrs, ...$props }" /> <N8nButton square v-bind="{ ...$attrs, ...$props }" />
</template> </template>
<script lang="ts"> <script lang="ts">
@@ -8,7 +8,7 @@ import N8nButton from '../N8nButton';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
name: 'n8n-icon-button', name: 'N8nIconButton',
components: { components: {
N8nButton, N8nButton,
}, },

View File

@@ -1,17 +1,17 @@
<template> <template>
<div :class="['accordion', $style.container]"> <div :class="['accordion', $style.container]">
<div :class="{ [$style.header]: true, [$style.expanded]: expanded }" @click="toggle"> <div :class="{ [$style.header]: true, [$style.expanded]: expanded }" @click="toggle">
<n8n-icon <N8nIcon
v-if="headerIcon" v-if="headerIcon"
:icon="headerIcon.icon" :icon="headerIcon.icon"
:color="headerIcon.color" :color="headerIcon.color"
size="small" size="small"
class="mr-2xs" class="mr-2xs"
/> />
<n8n-text :class="$style.headerText" color="text-base" size="small" align="left" bold>{{ <N8nText :class="$style.headerText" color="text-base" size="small" align="left" bold>{{
title title
}}</n8n-text> }}</N8nText>
<n8n-icon :icon="expanded ? 'chevron-up' : 'chevron-down'" bold /> <N8nIcon :icon="expanded ? 'chevron-up' : 'chevron-down'" bold />
</div> </div>
<div <div
v-if="expanded" v-if="expanded"
@@ -23,16 +23,16 @@
<div v-for="item in items" :key="item.id" :class="$style.accordionItem"> <div v-for="item in items" :key="item.id" :class="$style.accordionItem">
<n8n-tooltip :disabled="!item.tooltip"> <n8n-tooltip :disabled="!item.tooltip">
<template #content> <template #content>
<div v-html="item.tooltip" @click="onTooltipClick(item.id, $event)"></div> <div @click="onTooltipClick(item.id, $event)" v-html="item.tooltip"></div>
</template> </template>
<n8n-icon :icon="item.icon" :color="item.iconColor" size="small" class="mr-2xs" /> <N8nIcon :icon="item.icon" :color="item.iconColor" size="small" class="mr-2xs" />
</n8n-tooltip> </n8n-tooltip>
<n8n-text size="small" color="text-base">{{ item.label }}</n8n-text> <N8nText size="small" color="text-base">{{ item.label }}</N8nText>
</div> </div>
</div> </div>
<n8n-text color="text-base" size="small" align="left"> <N8nText color="text-base" size="small" align="left">
<span v-html="description"></span> <span v-html="description"></span>
</n8n-text> </N8nText>
<slot name="customContent"></slot> <slot name="customContent"></slot>
</div> </div>
</div> </div>
@@ -55,7 +55,7 @@ export interface IAccordionItem {
} }
export default defineComponent({ export default defineComponent({
name: 'n8n-info-accordion', name: 'N8nInfoAccordion',
components: { components: {
N8nText, N8nText,
N8nIcon, N8nIcon,
@@ -84,17 +84,17 @@ export default defineComponent({
default: () => createEventBus(), default: () => createEventBus(),
}, },
}, },
data() {
return {
expanded: false,
};
},
mounted() { mounted() {
this.eventBus.on('expand', () => { this.eventBus.on('expand', () => {
this.expanded = true; this.expanded = true;
}); });
this.expanded = this.initiallyExpanded; this.expanded = this.initiallyExpanded;
}, },
data() {
return {
expanded: false,
};
},
methods: { methods: {
toggle() { toggle() {
this.expanded = !this.expanded; this.expanded = !this.expanded;

View File

@@ -8,23 +8,23 @@
[$style.bold]: bold, [$style.bold]: bold,
}" }"
> >
<n8n-tooltip <N8nTooltip
v-if="type === 'tooltip'" v-if="type === 'tooltip'"
:placement="tooltipPlacement" :placement="tooltipPlacement"
:popperClass="$style.tooltipPopper" :popper-class="$style.tooltipPopper"
:disabled="type !== 'tooltip'" :disabled="type !== 'tooltip'"
> >
<span :class="$style.iconText" :style="{ color: iconData.color }"> <span :class="$style.iconText" :style="{ color: iconData.color }">
<n8n-icon :icon="iconData.icon" /> <N8nIcon :icon="iconData.icon" />
</span> </span>
<template #content> <template #content>
<span> <span>
<slot /> <slot />
</span> </span>
</template> </template>
</n8n-tooltip> </N8nTooltip>
<span :class="$style.iconText" v-else> <span v-else :class="$style.iconText">
<n8n-icon :icon="iconData.icon" /> <N8nIcon :icon="iconData.icon" />
<span> <span>
<slot /> <slot />
</span> </span>
@@ -39,7 +39,7 @@ import N8nTooltip from '../N8nTooltip';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
name: 'n8n-info-tip', name: 'N8nInfoTip',
components: { components: {
N8nIcon, N8nIcon,
N8nTooltip, N8nTooltip,

View File

@@ -1,25 +1,25 @@
<template> <template>
<el-input <ElInput
ref="innerInput"
:size="computedSize" :size="computedSize"
:class="['n8n-input', ...classes]" :class="['n8n-input', ...classes]"
:autoComplete="autocomplete" :autocomplete="autocomplete"
:name="name" :name="name"
ref="innerInput"
v-bind="{ ...$props, ...$attrs }" v-bind="{ ...$props, ...$attrs }"
> >
<template #prepend v-if="$slots.prepend"> <template v-if="$slots.prepend" #prepend>
<slot name="prepend" /> <slot name="prepend" />
</template> </template>
<template #append v-if="$slots.append"> <template v-if="$slots.append" #append>
<slot name="append" /> <slot name="append" />
</template> </template>
<template #prefix v-if="$slots.prefix"> <template v-if="$slots.prefix" #prefix>
<slot name="prefix" /> <slot name="prefix" />
</template> </template>
<template #suffix v-if="$slots.suffix"> <template v-if="$slots.suffix" #suffix>
<slot name="suffix" /> <slot name="suffix" />
</template> </template>
</el-input> </ElInput>
</template> </template>
<script lang="ts"> <script lang="ts">
@@ -31,7 +31,7 @@ import { uid } from '../../utils';
type InputRef = InstanceType<typeof ElInput>; type InputRef = InstanceType<typeof ElInput>;
export default defineComponent({ export default defineComponent({
name: 'n8n-input', name: 'N8nInput',
components: { components: {
ElInput, ElInput,
}, },

View File

@@ -7,7 +7,7 @@ exports[`N8nInput > should render correctly 1`] = `
<!--v-if--> <!--v-if-->
<div class="el-input__wrapper"> <div class="el-input__wrapper">
<!-- prefix slot --> <!-- prefix slot -->
<!--v-if--><input class="el-input__inner" autocomplete="off" name="input" rows="2" maxlength="Infinity" title="" type="text" tabindex="0" placeholder=""><!-- suffix slot --> <!--v-if--><input class="el-input__inner" name="input" rows="2" maxlength="Infinity" title="" type="text" autocomplete="off" tabindex="0" placeholder=""><!-- suffix slot -->
<!--v-if--> <!--v-if-->
</div><!-- append slot --> </div><!-- append slot -->
<!--v-if--> <!--v-if-->

View File

@@ -12,22 +12,22 @@
[$style.overflow]: !!$slots.options, [$style.overflow]: !!$slots.options,
}" }"
> >
<div :class="$style.title" v-if="label"> <div v-if="label" :class="$style.title">
<n8n-text :bold="bold" :size="size" :compact="compact" :color="color"> <N8nText :bold="bold" :size="size" :compact="compact" :color="color">
{{ label }} {{ label }}
<n8n-text color="primary" :bold="bold" :size="size" v-if="required">*</n8n-text> <N8nText v-if="required" color="primary" :bold="bold" :size="size">*</N8nText>
</n8n-text> </N8nText>
</div> </div>
<span <span
:class="[$style.infoIcon, showTooltip ? $style.visible : $style.hidden]"
v-if="tooltipText && label" v-if="tooltipText && label"
:class="[$style.infoIcon, showTooltip ? $style.visible : $style.hidden]"
> >
<n8n-tooltip placement="top" :popper-class="$style.tooltipPopper"> <N8nTooltip placement="top" :popper-class="$style.tooltipPopper">
<n8n-icon icon="question-circle" size="small" /> <N8nIcon icon="question-circle" size="small" />
<template #content> <template #content>
<div v-html="addTargetBlank(tooltipText)" /> <div v-html="addTargetBlank(tooltipText)" />
</template> </template>
</n8n-tooltip> </N8nTooltip>
</span> </span>
<div <div
v-if="$slots.options && label" v-if="$slots.options && label"
@@ -55,7 +55,7 @@ import { addTargetBlank } from '../utils/helpers';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
name: 'n8n-input-label', name: 'N8nInputLabel',
components: { components: {
N8nText, N8nText,
N8nIcon, N8nIcon,

View File

@@ -4,15 +4,15 @@ import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
name: 'N8nInputNumber', name: 'N8nInputNumber',
props: {
...ElInputNumber.props,
},
components: { components: {
ElInputNumber, ElInputNumber,
}, },
props: {
...ElInputNumber.props,
},
}); });
</script> </script>
<template> <template>
<el-input-number v-bind="{ ...$props, ...$attrs }" /> <ElInputNumber v-bind="{ ...$props, ...$attrs }" />
</template> </template>

View File

@@ -31,7 +31,7 @@ const keys = computed(() => {
<template> <template>
<div :class="$style.shortcut"> <div :class="$style.shortcut">
<div v-for="key of keys" :class="$style.keyWrapper" :key="key"> <div v-for="key of keys" :key="key" :class="$style.keyWrapper">
<div :class="$style.key">{{ key }}</div> <div :class="$style.key">{{ key }}</div>
</div> </div>
</div> </div>

View File

@@ -1,11 +1,11 @@
<template> <template>
<n8n-route :to="to" :newWindow="newWindow" v-bind="$attrs" class="n8n-link"> <N8nRoute :to="to" :new-window="newWindow" v-bind="$attrs" class="n8n-link">
<span :class="$style[`${underline ? `${theme}-underline` : theme}`]"> <span :class="$style[`${underline ? `${theme}-underline` : theme}`]">
<n8n-text :size="size" :bold="bold"> <N8nText :size="size" :bold="bold">
<slot></slot> <slot></slot>
</n8n-text> </N8nText>
</span> </span>
</n8n-route> </N8nRoute>
</template> </template>
<script lang="ts"> <script lang="ts">
@@ -14,7 +14,11 @@ import N8nText from '../N8nText';
import N8nRoute from '../N8nRoute'; import N8nRoute from '../N8nRoute';
export default defineComponent({ export default defineComponent({
name: 'n8n-link', name: 'N8nLink',
components: {
N8nText,
N8nRoute,
},
props: { props: {
size: { size: {
type: String, type: String,
@@ -41,10 +45,6 @@ export default defineComponent({
['primary', 'danger', 'text', 'secondary'].includes(value), ['primary', 'danger', 'text', 'secondary'].includes(value),
}, },
}, },
components: {
N8nText,
N8nRoute,
},
}); });
</script> </script>

View File

@@ -1,5 +1,5 @@
<template> <template>
<el-skeleton <ElSkeleton
:loading="loading" :loading="loading"
:animated="animated" :animated="animated"
:class="['n8n-loading', `n8n-loading-${variant}`]" :class="['n8n-loading', `n8n-loading-${variant}`]"
@@ -13,7 +13,7 @@
[$style.h1Last]: item === rows && rows > 1 && shrinkLast, [$style.h1Last]: item === rows && rows > 1 && shrinkLast,
}" }"
> >
<el-skeleton-item :variant="variant" /> <ElSkeletonItem :variant="variant" />
</div> </div>
</div> </div>
<div v-else-if="variant === 'p'"> <div v-else-if="variant === 'p'">
@@ -24,15 +24,15 @@
[$style.pLast]: item === rows && rows > 1 && shrinkLast, [$style.pLast]: item === rows && rows > 1 && shrinkLast,
}" }"
> >
<el-skeleton-item :variant="variant" /> <ElSkeletonItem :variant="variant" />
</div> </div>
</div> </div>
<div :class="$style.custom" v-else-if="variant === 'custom'"> <div v-else-if="variant === 'custom'" :class="$style.custom">
<el-skeleton-item /> <ElSkeletonItem />
</div> </div>
<el-skeleton-item v-else :variant="variant" /> <ElSkeletonItem v-else :variant="variant" />
</template> </template>
</el-skeleton> </ElSkeleton>
</template> </template>
<script lang="ts"> <script lang="ts">
@@ -40,7 +40,7 @@ import { ElSkeleton, ElSkeletonItem } from 'element-plus';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
name: 'n8n-loading', name: 'N8nLoading',
components: { components: {
ElSkeleton, ElSkeleton,
ElSkeletonItem, ElSkeletonItem,

View File

@@ -4,12 +4,12 @@
v-if="!loading" v-if="!loading"
ref="editor" ref="editor"
:class="$style[theme]" :class="$style[theme]"
v-html="htmlContent"
@click="onClick" @click="onClick"
v-html="htmlContent"
/> />
<div v-else :class="$style.markdown"> <div v-else :class="$style.markdown">
<div v-for="(block, index) in loadingBlocks" :key="index"> <div v-for="(block, index) in loadingBlocks" :key="index">
<n8n-loading :loading="loading" :rows="loadingRows" animated variant="p" /> <N8nLoading :loading="loading" :rows="loadingRows" animated variant="p" />
<div :class="$style.spacer" /> <div :class="$style.spacer" />
</div> </div>
</div> </div>
@@ -62,10 +62,10 @@ export interface Options {
} }
export default defineComponent({ export default defineComponent({
name: 'N8nMarkdown',
components: { components: {
N8nLoading, N8nLoading,
}, },
name: 'n8n-markdown',
props: { props: {
content: { content: {
type: String, type: String,

View File

@@ -15,33 +15,33 @@
<div v-if="$slots.menuPrefix" :class="$style.menuPrefix"> <div v-if="$slots.menuPrefix" :class="$style.menuPrefix">
<slot name="menuPrefix"></slot> <slot name="menuPrefix"></slot>
</div> </div>
<el-menu :defaultActive="defaultActive" :collapse="collapsed"> <ElMenu :default-active="defaultActive" :collapse="collapsed">
<n8n-menu-item <N8nMenuItem
v-for="item in upperMenuItems" v-for="item in upperMenuItems"
:key="item.id" :key="item.id"
:item="item" :item="item"
:compact="collapsed" :compact="collapsed"
:tooltipDelay="tooltipDelay" :tooltip-delay="tooltipDelay"
:mode="mode" :mode="mode"
:activeTab="activeTab" :active-tab="activeTab"
:handle-select="onSelect" :handle-select="onSelect"
/> />
</el-menu> </ElMenu>
</div> </div>
<div :class="[$style.lowerContent, 'pb-2xs']"> <div :class="[$style.lowerContent, 'pb-2xs']">
<slot name="beforeLowerMenu"></slot> <slot name="beforeLowerMenu"></slot>
<el-menu :defaultActive="defaultActive" :collapse="collapsed"> <ElMenu :default-active="defaultActive" :collapse="collapsed">
<n8n-menu-item <N8nMenuItem
v-for="item in lowerMenuItems" v-for="item in lowerMenuItems"
:key="item.id" :key="item.id"
:item="item" :item="item"
:compact="collapsed" :compact="collapsed"
:tooltipDelay="tooltipDelay" :tooltip-delay="tooltipDelay"
:mode="mode" :mode="mode"
:activeTab="activeTab" :active-tab="activeTab"
:handle-select="onSelect" :handle-select="onSelect"
/> />
</el-menu> </ElMenu>
<div v-if="$slots.menuSuffix" :class="$style.menuSuffix"> <div v-if="$slots.menuSuffix" :class="$style.menuSuffix">
<slot name="menuSuffix"></slot> <slot name="menuSuffix"></slot>
</div> </div>
@@ -61,16 +61,11 @@ import { defineComponent } from 'vue';
import type { IMenuItem, RouteObject } from '../../types'; import type { IMenuItem, RouteObject } from '../../types';
export default defineComponent({ export default defineComponent({
name: 'n8n-menu', name: 'N8nMenu',
components: { components: {
ElMenu, ElMenu,
N8nMenuItem, N8nMenuItem,
}, },
data() {
return {
activeTab: this.value,
};
},
props: { props: {
type: { type: {
type: String, type: String,
@@ -106,22 +101,10 @@ export default defineComponent({
default: '', default: '',
}, },
}, },
mounted() { data() {
if (this.mode === 'router') { return {
const found = this.items.find((item) => { activeTab: this.value,
return ( };
(Array.isArray(item.activateOnRouteNames) &&
item.activateOnRouteNames.includes(this.currentRoute.name || '')) ||
(Array.isArray(item.activateOnRoutePaths) &&
item.activateOnRoutePaths.includes(this.currentRoute.path))
);
});
this.activeTab = found ? found.id : '';
} else {
this.activeTab = this.items.length > 0 ? this.items[0].id : '';
}
this.$emit('update:modelValue', this.activeTab);
}, },
computed: { computed: {
upperMenuItems(): IMenuItem[] { upperMenuItems(): IMenuItem[] {
@@ -143,6 +126,23 @@ export default defineComponent({
); );
}, },
}, },
mounted() {
if (this.mode === 'router') {
const found = this.items.find((item) => {
return (
(Array.isArray(item.activateOnRouteNames) &&
item.activateOnRouteNames.includes(this.currentRoute.name || '')) ||
(Array.isArray(item.activateOnRoutePaths) &&
item.activateOnRoutePaths.includes(this.currentRoute.path))
);
});
this.activeTab = found ? found.id : '';
} else {
this.activeTab = this.items.length > 0 ? this.items[0].id : '';
}
this.$emit('update:modelValue', this.activeTab);
},
methods: { methods: {
onSelect(item: IMenuItem): void { onSelect(item: IMenuItem): void {
if (item && item.type === 'link' && item.properties) { if (item && item.type === 'link' && item.properties) {

View File

@@ -1,6 +1,6 @@
<template> <template>
<div :class="['n8n-menu-item', $style.item]"> <div :class="['n8n-menu-item', $style.item]">
<el-sub-menu <ElSubMenu
v-if="item.children?.length" v-if="item.children?.length"
:id="item.id" :id="item.id"
:class="{ :class="{
@@ -13,7 +13,7 @@
:popper-class="submenuPopperClass" :popper-class="submenuPopperClass"
> >
<template #title> <template #title>
<n8n-icon <N8nIcon
v-if="item.icon" v-if="item.icon"
:class="$style.icon" :class="$style.icon"
:icon="item.icon" :icon="item.icon"
@@ -26,21 +26,21 @@
:key="child.id" :key="child.id"
:item="child" :item="child"
:compact="false" :compact="false"
:tooltipDelay="tooltipDelay" :tooltip-delay="tooltipDelay"
:popperClass="popperClass" :popper-class="popperClass"
:mode="mode" :mode="mode"
:activeTab="activeTab" :active-tab="activeTab"
:handle-select="handleSelect" :handle-select="handleSelect"
/> />
</el-sub-menu> </ElSubMenu>
<n8n-tooltip <N8nTooltip
v-else v-else
placement="right" placement="right"
:content="item.label" :content="item.label"
:disabled="!compact" :disabled="!compact"
:show-after="tooltipDelay" :show-after="tooltipDelay"
> >
<el-menu-item <ElMenuItem
:id="item.id" :id="item.id"
:class="{ :class="{
[$style.menuItem]: true, [$style.menuItem]: true,
@@ -53,14 +53,14 @@
:index="item.id" :index="item.id"
@click="handleSelect(item)" @click="handleSelect(item)"
> >
<n8n-icon <N8nIcon
v-if="item.icon" v-if="item.icon"
:class="$style.icon" :class="$style.icon"
:icon="item.icon" :icon="item.icon"
:size="item.customIconSize || 'large'" :size="item.customIconSize || 'large'"
/> />
<span :class="$style.label">{{ item.label }}</span> <span :class="$style.label">{{ item.label }}</span>
<n8n-tooltip <N8nTooltip
v-if="item.secondaryIcon" v-if="item.secondaryIcon"
:class="$style.secondaryIcon" :class="$style.secondaryIcon"
:placement="item.secondaryIcon?.tooltip?.placement || 'right'" :placement="item.secondaryIcon?.tooltip?.placement || 'right'"
@@ -68,10 +68,10 @@
:disabled="compact || !item.secondaryIcon?.tooltip?.content" :disabled="compact || !item.secondaryIcon?.tooltip?.content"
:show-after="tooltipDelay" :show-after="tooltipDelay"
> >
<n8n-icon :icon="item.secondaryIcon.name" :size="item.secondaryIcon.size || 'small'" /> <N8nIcon :icon="item.secondaryIcon.name" :size="item.secondaryIcon.size || 'small'" />
</n8n-tooltip> </N8nTooltip>
</el-menu-item> </ElMenuItem>
</n8n-tooltip> </N8nTooltip>
</div> </div>
</template> </template>
@@ -84,7 +84,7 @@ import { defineComponent } from 'vue';
import type { IMenuItem, RouteObject } from '../../types'; import type { IMenuItem, RouteObject } from '../../types';
export default defineComponent({ export default defineComponent({
name: 'n8n-menu-item', name: 'N8nMenuItem',
components: { components: {
ElSubMenu, ElSubMenu,
ElMenuItem, ElMenuItem,

View File

@@ -36,18 +36,18 @@ const i18n = useI18n();
</div> </div>
<div> <div>
<div :class="$style.details"> <div :class="$style.details">
<span :class="$style.name" v-text="title" data-test-id="node-creator-item-name" /> <span :class="$style.name" data-test-id="node-creator-item-name" v-text="title" />
<el-tag v-if="tag" :class="$style.tag" size="small" round type="success"> <ElTag v-if="tag" :class="$style.tag" size="small" round type="success">
{{ tag }} {{ tag }}
</el-tag> </ElTag>
<font-awesome-icon <FontAwesomeIcon
icon="bolt"
v-if="isTrigger" v-if="isTrigger"
icon="bolt"
size="xs" size="xs"
:title="i18n.baseText('nodeCreator.nodeItem.triggerIconTitle')" :title="i18n.baseText('nodeCreator.nodeItem.triggerIconTitle')"
:class="$style.triggerIcon" :class="$style.triggerIcon"
/> />
<n8n-tooltip <N8nTooltip
v-if="!!$slots.tooltip" v-if="!!$slots.tooltip"
placement="top" placement="top"
data-test-id="node-creator-item-tooltip" data-test-id="node-creator-item-tooltip"
@@ -56,7 +56,7 @@ const i18n = useI18n();
<slot name="tooltip" /> <slot name="tooltip" />
</template> </template>
<n8n-icon :class="$style.tooltipIcon" icon="cube" /> <n8n-icon :class="$style.tooltipIcon" icon="cube" />
</n8n-tooltip> </N8nTooltip>
</div> </div>
<p <p
v-if="description" v-if="description"
@@ -66,8 +66,8 @@ const i18n = useI18n();
/> />
</div> </div>
<slot name="dragContent" /> <slot name="dragContent" />
<button :class="$style.panelIcon" v-if="showActionArrow"> <button v-if="showActionArrow" :class="$style.panelIcon">
<font-awesome-icon :class="$style.panelArrow" icon="arrow-right" /> <FontAwesomeIcon :class="$style.panelArrow" icon="arrow-right" />
</button> </button>
</div> </div>
</template> </template>

View File

@@ -9,20 +9,20 @@
:style="iconStyleData" :style="iconStyleData"
> >
<!-- ElementUI tooltip is prone to memory-leaking so we only render it if we really need it --> <!-- ElementUI tooltip is prone to memory-leaking so we only render it if we really need it -->
<n8n-tooltip :placement="tooltipPosition" :disabled="!showTooltip" v-if="showTooltip"> <N8nTooltip v-if="showTooltip" :placement="tooltipPosition" :disabled="!showTooltip">
<template #content>{{ nodeTypeName }}</template> <template #content>{{ nodeTypeName }}</template>
<div v-if="type !== 'unknown'" :class="$style.icon"> <div v-if="type !== 'unknown'" :class="$style.icon">
<img v-if="type === 'file'" :src="src" :class="$style.nodeIconImage" /> <img v-if="type === 'file'" :src="src" :class="$style.nodeIconImage" />
<font-awesome-icon v-else :icon="name" :class="$style.iconFa" :style="fontStyleData" /> <FontAwesomeIcon v-else :icon="name" :class="$style.iconFa" :style="fontStyleData" />
</div> </div>
<div v-else :class="$style.nodeIconPlaceholder"> <div v-else :class="$style.nodeIconPlaceholder">
{{ nodeTypeName ? nodeTypeName.charAt(0) : '?' }} {{ nodeTypeName ? nodeTypeName.charAt(0) : '?' }}
</div> </div>
</n8n-tooltip> </N8nTooltip>
<template v-else> <template v-else>
<div v-if="type !== 'unknown'" :class="$style.icon"> <div v-if="type !== 'unknown'" :class="$style.icon">
<img v-if="type === 'file'" :src="src" :class="$style.nodeIconImage" /> <img v-if="type === 'file'" :src="src" :class="$style.nodeIconImage" />
<font-awesome-icon v-else :icon="name" :style="fontStyleData" /> <FontAwesomeIcon v-else :icon="name" :style="fontStyleData" />
<div v-if="badge" :class="$style.badge" :style="badgeStyleData"> <div v-if="badge" :class="$style.badge" :style="badgeStyleData">
<n8n-node-icon :type="badge.type" :src="badge.src" :size="badgeSize"></n8n-node-icon> <n8n-node-icon :type="badge.type" :src="badge.src" :size="badgeSize"></n8n-node-icon>
</div> </div>
@@ -41,7 +41,7 @@ import { defineComponent, type PropType } from 'vue';
import N8nTooltip from '../N8nTooltip'; import N8nTooltip from '../N8nTooltip';
export default defineComponent({ export default defineComponent({
name: 'n8n-node-icon', name: 'N8nNodeIcon',
components: { components: {
N8nTooltip, N8nTooltip,
FontAwesomeIcon, FontAwesomeIcon,

View File

@@ -1,16 +1,16 @@
<template> <template>
<div :id="id" :class="classes" role="alert" @click="onClick"> <div :id="id" :class="classes" role="alert" @click="onClick">
<div class="notice-content"> <div class="notice-content">
<n8n-text size="small" :compact="true"> <N8nText size="small" :compact="true">
<slot> <slot>
<span <span
:class="showFullContent ? $style['expanded'] : $style['truncated']"
:id="`${id}-content`" :id="`${id}-content`"
:class="showFullContent ? $style['expanded'] : $style['truncated']"
role="region" role="region"
v-html="sanitizeHtml(showFullContent ? fullContent : content)" v-html="sanitizeHtml(showFullContent ? fullContent : content)"
/> />
</slot> </slot>
</n8n-text> </N8nText>
</div> </div>
</div> </div>
</template> </template>
@@ -23,8 +23,11 @@ import Locale from '../../mixins/locale';
import { uid } from '../../utils'; import { uid } from '../../utils';
export default defineComponent({ export default defineComponent({
name: 'n8n-notice', name: 'N8nNotice',
directives: {}, directives: {},
components: {
N8nText,
},
mixins: [Locale], mixins: [Locale],
props: { props: {
id: { id: {
@@ -44,9 +47,6 @@ export default defineComponent({
default: '', default: '',
}, },
}, },
components: {
N8nText,
},
data() { data() {
return { return {
showFullContent: false, showFullContent: false,

View File

@@ -2,7 +2,7 @@
exports[`components > N8nNotice > props > content > should render HTML 1`] = ` exports[`components > N8nNotice > props > content > should render HTML 1`] = `
"<div id="notice" class="notice notice warning" role="alert"> "<div id="notice" class="notice notice warning" role="alert">
<div class="notice-content"><span class="n8n-text compact size-small regular"><span class="truncated" id="notice-content" role="region"><strong>Hello world!</strong> This is a notice.</span></span></div> <div class="notice-content"><span class="n8n-text compact size-small regular"><span id="notice-content" class="truncated" role="region"><strong>Hello world!</strong> This is a notice.</span></span></div>
</div>" </div>"
`; `;

View File

@@ -3,15 +3,15 @@ import { ElOption } from 'element-plus';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
props: {
...ElOption.props,
},
components: { components: {
ElOption, ElOption,
}, },
props: {
...ElOption.props,
},
}); });
</script> </script>
<template> <template>
<el-option v-bind="{ ...$props, ...$attrs }"><slot /></el-option> <ElOption v-bind="{ ...$props, ...$attrs }"><slot /></ElOption>
</template> </template>

View File

@@ -3,17 +3,17 @@ import { defineComponent } from 'vue';
import { ElPagination } from 'element-plus'; import { ElPagination } from 'element-plus';
export default defineComponent({ export default defineComponent({
props: {
...ElPagination.props,
},
components: { components: {
ElPagination, ElPagination,
}, },
props: {
...ElPagination.props,
},
}); });
</script> </script>
<template> <template>
<el-pagination <ElPagination
class="is-background" class="is-background"
layout="prev, pager, next" layout="prev, pager, next"
v-bind="{ ...$props, ...$attrs }" v-bind="{ ...$props, ...$attrs }"

View File

@@ -4,22 +4,22 @@ import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
name: 'N8nPopover', name: 'N8nPopover',
props: {
...ElPopover.props,
},
components: { components: {
ElPopover, ElPopover,
}, },
props: {
...ElPopover.props,
},
}); });
</script> </script>
<template> <template>
<span> <span>
<el-popover v-bind="{ ...$props, ...$attrs }"> <ElPopover v-bind="{ ...$props, ...$attrs }">
<template #reference> <template #reference>
<slot name="reference" /> <slot name="reference" />
</template> </template>
<slot /> <slot />
</el-popover> </ElPopover>
</span> </span>
</template> </template>

View File

@@ -12,7 +12,7 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
name: 'n8n-pulse', name: 'N8nPulse',
}); });
</script> </script>

View File

@@ -27,7 +27,7 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
name: 'n8n-radio-button', name: 'N8nRadioButton',
props: { props: {
label: { label: {
type: String, type: String,

View File

@@ -28,7 +28,10 @@ export interface RadioOption {
} }
export default defineComponent({ export default defineComponent({
name: 'n8n-radio-buttons', name: 'N8nRadioButtons',
components: {
RadioButton,
},
props: { props: {
modelValue: { modelValue: {
type: String, type: String,
@@ -44,9 +47,6 @@ export default defineComponent({
type: Boolean, type: Boolean,
}, },
}, },
components: {
RadioButton,
},
methods: { methods: {
onClick(option: { label: string; value: string; disabled?: boolean }) { onClick(option: { label: string; value: string; disabled?: boolean }) {
if (this.disabled || option.disabled) { if (this.disabled || option.disabled) {

View File

@@ -5,7 +5,7 @@ import type { PropType, ComponentPublicInstance } from 'vue';
import { computed, defineComponent, onMounted, onBeforeMount, ref, nextTick, watch } from 'vue'; import { computed, defineComponent, onMounted, onBeforeMount, ref, nextTick, watch } from 'vue';
export default defineComponent({ export default defineComponent({
name: 'n8n-recycle-scroller', name: 'N8nRecycleScroller',
props: { props: {
itemSize: { itemSize: {
type: Number, type: Number,
@@ -223,16 +223,16 @@ export default defineComponent({
</script> </script>
<template> <template>
<div class="recycle-scroller-wrapper" ref="wrapperRef"> <div ref="wrapperRef" class="recycle-scroller-wrapper">
<div class="recycle-scroller" :style="scrollerStyles" ref="scrollerRef"> <div ref="scrollerRef" class="recycle-scroller" :style="scrollerStyles">
<div class="recycle-scroller-items-wrapper" :style="itemsStyles" ref="itemsRef"> <div ref="itemsRef" class="recycle-scroller-items-wrapper" :style="itemsStyles">
<div <div
v-for="item in itemsVisible" v-for="item in itemsVisible"
:key="item[itemKey]" :key="item[itemKey]"
class="recycle-scroller-item"
:ref="(element) => (itemRefs[item[itemKey]] = element)" :ref="(element) => (itemRefs[item[itemKey]] = element)"
class="recycle-scroller-item"
> >
<slot :item="item" :updateItemSize="onUpdateItemSize" /> <slot :item="item" :update-item-size="onUpdateItemSize" />
</div> </div>
</div> </div>
</div> </div>

View File

@@ -47,7 +47,7 @@ const directionsCursorMaps: { [key: string]: string } = {
}; };
export default defineComponent({ export default defineComponent({
name: 'n8n-resize', name: 'N8nResize',
props: { props: {
isResizingEnabled: { isResizingEnabled: {
type: Boolean, type: Boolean,

View File

@@ -11,7 +11,7 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
name: 'n8n-route', name: 'N8nRoute',
props: { props: {
to: { to: {
type: String || Object, type: String || Object,

View File

@@ -9,22 +9,22 @@
<div v-if="$slots.prepend" :class="$style.prepend"> <div v-if="$slots.prepend" :class="$style.prepend">
<slot name="prepend" /> <slot name="prepend" />
</div> </div>
<el-select <ElSelect
v-bind="{ ...$props, ...listeners }" v-bind="{ ...$props, ...listeners }"
:modelValue="modelValue" ref="innerSelect"
:model-value="modelValue"
:size="computedSize" :size="computedSize"
:class="$style[classes]" :class="$style[classes]"
:popper-class="popperClass" :popper-class="popperClass"
ref="innerSelect"
> >
<template #prefix v-if="$slots.prefix"> <template v-if="$slots.prefix" #prefix>
<slot name="prefix" /> <slot name="prefix" />
</template> </template>
<template #suffix v-if="$slots.suffix"> <template v-if="$slots.suffix" #suffix>
<slot name="suffix" /> <slot name="suffix" />
</template> </template>
<slot></slot> <slot></slot>
</el-select> </ElSelect>
</div> </div>
</template> </template>
@@ -41,7 +41,7 @@ export interface IProps {
} }
export default defineComponent({ export default defineComponent({
name: 'n8n-select', name: 'N8nSelect',
components: { components: {
ElSelect, ElSelect,
}, },

View File

@@ -26,11 +26,6 @@ describe('components', () => {
it('should select an option', async () => { it('should select an option', async () => {
const n8nSelectTestComponent = defineComponent({ const n8nSelectTestComponent = defineComponent({
template: `
<n8n-select v-model="selected">
<n8n-option v-for="o in options" :key="o" :value="o" :label="o" />
</n8n-select>
`,
setup() { setup() {
const options = ref(['1', '2', '3']); const options = ref(['1', '2', '3']);
const selected = ref(''); const selected = ref('');
@@ -40,6 +35,11 @@ describe('components', () => {
selected, selected,
}; };
}, },
template: `
<n8n-select v-model="selected">
<n8n-option v-for="o in options" :key="o" :value="o" :label="o" />
</n8n-select>
`,
}); });
const { container } = render(n8nSelectTestComponent, { const { container } = render(n8nSelectTestComponent, {

View File

@@ -6,7 +6,7 @@
<div></div> <div></div>
<div></div> <div></div>
</div> </div>
<n8n-icon v-else icon="spinner" :size="size" spin /> <N8nIcon v-else icon="spinner" :size="size" spin />
</span> </span>
</template> </template>
@@ -16,7 +16,7 @@ import N8nIcon from '../N8nIcon';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
name: 'n8n-spinner', name: 'N8nSpinner',
components: { components: {
N8nIcon, N8nIcon,
}, },

View File

@@ -9,51 +9,51 @@
:style="styles" :style="styles"
@keydown.prevent @keydown.prevent
> >
<n8n-resize-wrapper <N8nResizeWrapper
:isResizingEnabled="!readOnly" :is-resizing-enabled="!readOnly"
:height="height" :height="height"
:width="width" :width="width"
:minHeight="minHeight" :min-height="minHeight"
:minWidth="minWidth" :min-width="minWidth"
:scale="scale" :scale="scale"
:gridSize="gridSize" :grid-size="gridSize"
@resizeend="onResizeEnd" @resizeend="onResizeEnd"
@resize="onResize" @resize="onResize"
@resizestart="onResizeStart" @resizestart="onResizeStart"
> >
<div v-show="!editMode" :class="$style.wrapper" @dblclick.stop="onDoubleClick"> <div v-show="!editMode" :class="$style.wrapper" @dblclick.stop="onDoubleClick">
<n8n-markdown <N8nMarkdown
theme="sticky" theme="sticky"
:content="modelValue" :content="modelValue"
:withMultiBreaks="true" :with-multi-breaks="true"
@markdown-click="onMarkdownClick" @markdown-click="onMarkdownClick"
/> />
</div> </div>
<div <div
v-show="editMode" v-show="editMode"
:class="{ 'full-height': !shouldShowFooter, 'sticky-textarea': true }"
@click.stop @click.stop
@mousedown.stop @mousedown.stop
@mouseup.stop @mouseup.stop
@keydown.esc="onInputBlur" @keydown.esc="onInputBlur"
@keydown.stop @keydown.stop
:class="{ 'full-height': !shouldShowFooter, 'sticky-textarea': true }"
> >
<n8n-input <N8nInput
:modelValue="modelValue" ref="input"
:model-value="modelValue"
type="textarea" type="textarea"
:rows="5" :rows="5"
@blur="onInputBlur" @blur="onInputBlur"
@update:modelValue="onUpdateModelValue" @update:modelValue="onUpdateModelValue"
@wheel="onInputScroll" @wheel="onInputScroll"
ref="input"
/> />
</div> </div>
<div v-if="editMode && shouldShowFooter" :class="$style.footer"> <div v-if="editMode && shouldShowFooter" :class="$style.footer">
<n8n-text size="xsmall" aligh="right"> <N8nText size="xsmall" aligh="right">
<span v-html="t('sticky.markdownHint')"></span> <span v-html="t('sticky.markdownHint')"></span>
</n8n-text> </N8nText>
</div> </div>
</n8n-resize-wrapper> </N8nResizeWrapper>
</div> </div>
</template> </template>
@@ -66,7 +66,13 @@ import Locale from '../../mixins/locale';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
name: 'n8n-sticky', name: 'N8nSticky',
components: {
N8nInput,
N8nMarkdown,
N8nResizeWrapper,
N8nText,
},
mixins: [Locale], mixins: [Locale],
props: { props: {
modelValue: { modelValue: {
@@ -116,12 +122,6 @@ export default defineComponent({
default: 1, default: 1,
}, },
}, },
components: {
N8nInput,
N8nMarkdown,
N8nResizeWrapper,
N8nText,
},
data() { data() {
return { return {
isResizing: false, isResizing: false,
@@ -152,6 +152,19 @@ export default defineComponent({
return this.resHeight > 100 && this.resWidth > 155; return this.resHeight > 100 && this.resWidth > 155;
}, },
}, },
watch: {
editMode(newMode, prevMode) {
setTimeout(() => {
if (newMode && !prevMode && this.$refs.input) {
const textarea = this.$refs.input as HTMLTextAreaElement;
if (this.defaultText === this.modelValue) {
textarea.select();
}
textarea.focus();
}
}, 100);
},
},
methods: { methods: {
onDoubleClick() { onDoubleClick() {
if (!this.readOnly) { if (!this.readOnly) {
@@ -187,19 +200,6 @@ export default defineComponent({
} }
}, },
}, },
watch: {
editMode(newMode, prevMode) {
setTimeout(() => {
if (newMode && !prevMode && this.$refs.input) {
const textarea = this.$refs.input as HTMLTextAreaElement;
if (this.defaultText === this.modelValue) {
textarea.select();
}
textarea.focus();
}
}, 100);
},
},
}); });
</script> </script>

View File

@@ -1,21 +1,21 @@
<template> <template>
<div :class="['n8n-tabs', $style.container]"> <div :class="['n8n-tabs', $style.container]">
<div :class="$style.back" v-if="scrollPosition > 0" @click="scrollLeft"> <div v-if="scrollPosition > 0" :class="$style.back" @click="scrollLeft">
<n8n-icon icon="chevron-left" size="small" /> <N8nIcon icon="chevron-left" size="small" />
</div> </div>
<div :class="$style.next" v-if="canScrollRight" @click="scrollRight"> <div v-if="canScrollRight" :class="$style.next" @click="scrollRight">
<n8n-icon icon="chevron-right" size="small" /> <N8nIcon icon="chevron-right" size="small" />
</div> </div>
<div ref="tabs" :class="$style.tabs"> <div ref="tabs" :class="$style.tabs">
<div <div
v-for="option in options" v-for="option in options"
:key="option.value"
:id="option.value" :id="option.value"
:key="option.value"
:class="{ [$style.alignRight]: option.align === 'right' }" :class="{ [$style.alignRight]: option.align === 'right' }"
> >
<n8n-tooltip :disabled="!option.tooltip" placement="bottom"> <n8n-tooltip :disabled="!option.tooltip" placement="bottom">
<template #content> <template #content>
<div v-html="option.tooltip" @click="handleTooltipClick(option.value, $event)" /> <div @click="handleTooltipClick(option.value, $event)" v-html="option.tooltip" />
</template> </template>
<a <a
v-if="option.href" v-if="option.href"
@@ -27,7 +27,7 @@
<div> <div>
{{ option.label }} {{ option.label }}
<span :class="$style.external" <span :class="$style.external"
><n8n-icon icon="external-link-alt" size="small" ><N8nIcon icon="external-link-alt" size="small"
/></span> /></span>
</div> </div>
</a> </a>
@@ -38,7 +38,7 @@
:data-test-id="`tab-${option.value}`" :data-test-id="`tab-${option.value}`"
@click="() => handleTabClick(option.value)" @click="() => handleTabClick(option.value)"
> >
<n8n-icon v-if="option.icon" :icon="option.icon" size="medium" /> <N8nIcon v-if="option.icon" :icon="option.icon" size="medium" />
<span v-if="option.label">{{ option.label }}</span> <span v-if="option.label">{{ option.label }}</span>
</div> </div>
</n8n-tooltip> </n8n-tooltip>
@@ -66,6 +66,23 @@ export default defineComponent({
components: { components: {
N8nIcon, N8nIcon,
}, },
props: {
modelValue: {
type: String,
default: '',
},
options: {
type: Array as PropType<N8nTabOptions[]>,
default: (): N8nTabOptions[] => [],
},
},
data() {
return {
scrollPosition: 0,
canScrollRight: false,
resizeObserver: null as ResizeObserver | null,
};
},
mounted() { mounted() {
const container = this.$refs.tabs as HTMLDivElement | undefined; const container = this.$refs.tabs as HTMLDivElement | undefined;
if (container) { if (container) {
@@ -94,23 +111,6 @@ export default defineComponent({
this.resizeObserver.disconnect(); this.resizeObserver.disconnect();
} }
}, },
data() {
return {
scrollPosition: 0,
canScrollRight: false,
resizeObserver: null as ResizeObserver | null,
};
},
props: {
modelValue: {
type: String,
default: '',
},
options: {
type: Array as PropType<N8nTabOptions[]>,
default: (): N8nTabOptions[] => [],
},
},
methods: { methods: {
handleTooltipClick(tab: string, event: MouseEvent) { handleTooltipClick(tab: string, event: MouseEvent) {
this.$emit('tooltipClick', tab, event); this.$emit('tooltipClick', tab, event);

View File

@@ -8,7 +8,7 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
name: 'n8n-tag', name: 'N8nTag',
props: { props: {
text: { text: {
type: String, type: String,

View File

@@ -1,12 +1,12 @@
<template> <template>
<div :class="['n8n-tags', $style.tags]"> <div :class="['n8n-tags', $style.tags]">
<n8n-tag <N8nTag
v-for="tag in visibleTags" v-for="tag in visibleTags"
:key="tag.id" :key="tag.id"
:text="tag.name" :text="tag.name"
@click="$emit('click:tag', tag.id, $event)" @click="$emit('click:tag', tag.id, $event)"
/> />
<n8n-link <N8nLink
v-if="truncate && !showAll && hiddenTagsLength > 0" v-if="truncate && !showAll && hiddenTagsLength > 0"
theme="text" theme="text"
underline underline
@@ -14,7 +14,7 @@
@click.stop.prevent="onExpand" @click.stop.prevent="onExpand"
> >
{{ t('tags.showMore', `${hiddenTagsLength}`) }} {{ t('tags.showMore', `${hiddenTagsLength}`) }}
</n8n-link> </N8nLink>
</div> </div>
</template> </template>
@@ -31,17 +31,12 @@ export interface ITag {
} }
export default defineComponent({ export default defineComponent({
name: 'n8n-tags', name: 'N8nTags',
mixins: [Locale],
components: { components: {
N8nTag, N8nTag,
N8nLink, N8nLink,
}, },
data() { mixins: [Locale],
return {
showAll: false,
};
},
props: { props: {
tags: { tags: {
type: Array as PropType<ITag[]>, type: Array as PropType<ITag[]>,
@@ -56,6 +51,11 @@ export default defineComponent({
default: 3, default: 3,
}, },
}, },
data() {
return {
showAll: false,
};
},
computed: { computed: {
visibleTags(): ITag[] { visibleTags(): ITag[] {
if (this.truncate && !this.showAll && this.tags.length > this.truncateAt) { if (this.truncate && !this.showAll && this.tags.length > this.truncateAt) {

View File

@@ -7,7 +7,7 @@
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
name: 'n8n-text', name: 'N8nText',
props: { props: {
bold: { bold: {
type: Boolean, type: Boolean,

View File

@@ -1,5 +1,5 @@
<template> <template>
<el-tooltip v-bind="{ ...$props, ...$attrs }" :popperClass="$props.popperClass ?? 'n8n-tooltip'"> <ElTooltip v-bind="{ ...$props, ...$attrs }" :popper-class="$props.popperClass ?? 'n8n-tooltip'">
<slot /> <slot />
<template #content> <template #content>
<slot name="content"> <slot name="content">
@@ -10,14 +10,14 @@
:class="$style.buttons" :class="$style.buttons"
:style="{ justifyContent: justifyButtons }" :style="{ justifyContent: justifyButtons }"
> >
<n8n-button <N8nButton
v-for="button in buttons" v-for="button in buttons"
:key="button.attrs.label" :key="button.attrs.label"
v-bind="{ ...button.attrs, ...button.listeners }" v-bind="{ ...button.attrs, ...button.listeners }"
/> />
</div> </div>
</template> </template>
</el-tooltip> </ElTooltip>
</template> </template>
<script lang="ts"> <script lang="ts">
@@ -28,12 +28,12 @@ import type { IN8nButton } from '@/types';
import N8nButton from '../N8nButton'; import N8nButton from '../N8nButton';
export default defineComponent({ export default defineComponent({
name: 'n8n-tooltip', name: 'N8nTooltip',
inheritAttrs: false,
components: { components: {
ElTooltip, ElTooltip,
N8nButton, N8nButton,
}, },
inheritAttrs: false,
props: { props: {
...ElTooltip.props, ...ElTooltip.props,
content: { content: {

View File

@@ -1,21 +1,21 @@
<template> <template>
<div class="n8n-tree"> <div class="n8n-tree">
<div v-for="(label, i) in Object.keys(value)" :key="i" :class="classes"> <div v-for="(label, i) in Object.keys(value)" :key="i" :class="classes">
<div :class="$style.simple" v-if="isSimple(value[label])"> <div v-if="isSimple(value[label])" :class="$style.simple">
<slot v-if="$slots.label" name="label" v-bind:label="label" v-bind:path="getPath(label)" /> <slot v-if="$slots.label" name="label" :label="label" :path="getPath(label)" />
<span v-else>{{ label }}</span> <span v-else>{{ label }}</span>
<span>:</span> <span>:</span>
<slot v-if="$slots.value" name="value" v-bind:value="value[label]" /> <slot v-if="$slots.value" name="value" :value="value[label]" />
<span v-else>{{ value[label] }}</span> <span v-else>{{ value[label] }}</span>
</div> </div>
<div v-else> <div v-else>
<slot v-if="$slots.label" name="label" v-bind:label="label" v-bind:path="getPath(label)" /> <slot v-if="$slots.label" name="label" :label="label" :path="getPath(label)" />
<span v-else>{{ label }}</span> <span v-else>{{ label }}</span>
<n8n-tree <n8n-tree
:path="getPath(label)" :path="getPath(label)"
:depth="depth + 1" :depth="depth + 1"
:value="value[label]" :value="value[label]"
:nodeClass="nodeClass" :node-class="nodeClass"
> >
<template v-for="(index, name) in $slots" #[name]="data"> <template v-for="(index, name) in $slots" #[name]="data">
<slot :name="name" v-bind="data"></slot> <slot :name="name" v-bind="data"></slot>
@@ -31,7 +31,7 @@ import type { PropType } from 'vue';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
name: 'n8n-tree', name: 'N8nTree',
components: {}, components: {},
props: { props: {
value: { value: {

View File

@@ -1,25 +1,25 @@
<template> <template>
<div :class="classes"> <div :class="classes">
<div :class="$style.avatarContainer"> <div :class="$style.avatarContainer">
<n8n-avatar :firstName="firstName" :lastName="lastName" /> <N8nAvatar :first-name="firstName" :last-name="lastName" />
</div> </div>
<div v-if="isPendingUser" :class="$style.pendingUser"> <div v-if="isPendingUser" :class="$style.pendingUser">
<n8n-text :bold="true">{{ email }}</n8n-text> <N8nText :bold="true">{{ email }}</N8nText>
<span :class="$style.pendingBadge"><n8n-badge :bold="true">Pending</n8n-badge></span> <span :class="$style.pendingBadge"><N8nBadge :bold="true">Pending</N8nBadge></span>
</div> </div>
<div v-else :class="$style.infoContainer"> <div v-else :class="$style.infoContainer">
<div> <div>
<n8n-text :bold="true" color="text-dark"> <N8nText :bold="true" color="text-dark">
{{ firstName }} {{ lastName }} {{ firstName }} {{ lastName }}
{{ isCurrentUser ? t('nds.userInfo.you') : '' }} {{ isCurrentUser ? t('nds.userInfo.you') : '' }}
</n8n-text> </N8nText>
<span v-if="disabled" :class="$style.pendingBadge"> <span v-if="disabled" :class="$style.pendingBadge">
<n8n-badge :bold="true">Disabled</n8n-badge> <N8nBadge :bold="true">Disabled</N8nBadge>
</span> </span>
</div> </div>
<div> <div>
<n8n-text data-test-id="user-email" size="small" color="text-light">{{ email }}</n8n-text> <N8nText data-test-id="user-email" size="small" color="text-light">{{ email }}</N8nText>
</div> </div>
</div> </div>
</div> </div>
@@ -33,13 +33,13 @@ import Locale from '../../mixins/locale';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
name: 'n8n-users-info', name: 'N8nUsersInfo',
mixins: [Locale],
components: { components: {
N8nAvatar, N8nAvatar,
N8nText, N8nText,
N8nBadge, N8nBadge,
}, },
mixins: [Locale],
props: { props: {
firstName: { firstName: {
type: String, type: String,

View File

@@ -1,23 +1,23 @@
<template> <template>
<n8n-select <N8nSelect
data-test-id="user-select-trigger" data-test-id="user-select-trigger"
v-bind="$attrs" v-bind="$attrs"
:modelValue="modelValue" :model-value="modelValue"
:filterable="true" :filterable="true"
:filterMethod="setFilter" :filter-method="setFilter"
:placeholder="placeholder" :placeholder="placeholder"
:default-first-option="true" :default-first-option="true"
teleported teleported
:popper-class="$style.limitPopperWidth" :popper-class="$style.limitPopperWidth"
:noDataText="t('nds.userSelect.noMatchingUsers')" :no-data-text="t('nds.userSelect.noMatchingUsers')"
:size="size" :size="size"
@blur="onBlur" @blur="onBlur"
@focus="onFocus" @focus="onFocus"
> >
<template #prefix v-if="$slots.prefix"> <template v-if="$slots.prefix" #prefix>
<slot name="prefix" /> <slot name="prefix" />
</template> </template>
<n8n-option <N8nOption
v-for="user in sortedUsers" v-for="user in sortedUsers"
:key="user.id" :key="user.id"
:value="user.id" :value="user.id"
@@ -25,9 +25,9 @@
:label="getLabel(user)" :label="getLabel(user)"
:disabled="user.disabled" :disabled="user.disabled"
> >
<n8n-user-info v-bind="user" :isCurrentUser="currentUserId === user.id" /> <N8nUserInfo v-bind="user" :is-current-user="currentUserId === user.id" />
</n8n-option> </N8nOption>
</n8n-select> </N8nSelect>
</template> </template>
<script lang="ts"> <script lang="ts">
@@ -41,13 +41,13 @@ import type { PropType } from 'vue';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
name: 'n8n-user-select', name: 'N8nUserSelect',
mixins: [Locale],
components: { components: {
N8nUserInfo, N8nUserInfo,
N8nSelect, N8nSelect,
N8nOption, N8nOption,
}, },
mixins: [Locale],
props: { props: {
users: { users: {
type: Array as PropType<IUser[]>, type: Array as PropType<IUser[]>,

View File

@@ -76,11 +76,11 @@ const menuHeight = computed(() => {
popper-class="user-stack-popper" popper-class="user-stack-popper"
> >
<div :class="$style.avatars" data-test-id="user-stack-avatars"> <div :class="$style.avatars" data-test-id="user-stack-avatars">
<n8n-avatar <N8nAvatar
v-for="user in flatUserList.slice(0, visibleAvatarCount)" v-for="user in flatUserList.slice(0, visibleAvatarCount)"
:key="user.id" :key="user.id"
:firstName="user.firstName" :first-name="user.firstName"
:lastName="user.lastName" :last-name="user.lastName"
:class="$style.avatar" :class="$style.avatar"
:data-test-id="`user-stack-avatar-${user.id}`" :data-test-id="`user-stack-avatar-${user.id}`"
size="small" size="small"
@@ -101,9 +101,9 @@ const menuHeight = computed(() => {
:data-test-id="`user-stack-info-${user.id}`" :data-test-id="`user-stack-info-${user.id}`"
:class="$style.userInfoContainer" :class="$style.userInfoContainer"
> >
<n8n-user-info <N8nUserInfo
v-bind="user" v-bind="user"
:isCurrentUser="user.email === props.currentUserEmail" :is-current-user="user.email === props.currentUserEmail"
/> />
</el-dropdown-item> </el-dropdown-item>
</div> </div>

View File

@@ -6,17 +6,17 @@
:class="i === sortedUsers.length - 1 ? $style.itemContainer : $style.itemWithBorder" :class="i === sortedUsers.length - 1 ? $style.itemContainer : $style.itemWithBorder"
:data-test-id="`user-list-item-${user.email}`" :data-test-id="`user-list-item-${user.email}`"
> >
<n8n-user-info <N8nUserInfo
v-bind="user" v-bind="user"
:isCurrentUser="currentUserId === user.id" :is-current-user="currentUserId === user.id"
:isSamlLoginEnabled="isSamlLoginEnabled" :is-saml-login-enabled="isSamlLoginEnabled"
/> />
<div :class="$style.badgeContainer"> <div :class="$style.badgeContainer">
<n8n-badge v-if="user.isOwner" theme="tertiary" bold> <N8nBadge v-if="user.isOwner" theme="tertiary" bold>
{{ t('nds.auth.roles.owner') }} {{ t('nds.auth.roles.owner') }}
</n8n-badge> </N8nBadge>
<slot v-if="!user.isOwner && !readonly" name="actions" :user="user" /> <slot v-if="!user.isOwner && !readonly" name="actions" :user="user" />
<n8n-action-toggle <N8nActionToggle
v-if=" v-if="
!user.isOwner && !user.isOwner &&
user.signInType !== 'ldap' && user.signInType !== 'ldap' &&
@@ -44,13 +44,13 @@ import type { PropType } from 'vue';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
name: 'n8n-users-list', name: 'N8nUsersList',
mixins: [Locale],
components: { components: {
N8nActionToggle, N8nActionToggle,
N8nBadge, N8nBadge,
N8nUserInfo, N8nUserInfo,
}, },
mixins: [Locale],
props: { props: {
readonly: { readonly: {
type: Boolean, type: Boolean,

View File

@@ -1,118 +1,12 @@
import type { Plugin } from 'vue'; import type { Component, Plugin } from 'vue';
import { import * as components from './components';
N8nActionBox,
N8nActionDropdown,
N8nActionToggle,
N8nAlert,
N8nAvatar,
N8nBadge,
N8nBlockUi,
N8nButton,
N8nCallout,
N8nCard,
N8nCheckbox,
N8nCircleLoader,
N8nColorPicker,
N8nDatatable,
N8nFormBox,
N8nFormInputs,
N8nFormInput,
N8nHeading,
N8nIcon,
N8nIconButton,
N8nInfoAccordion,
N8nInfoTip,
N8nInput,
N8nInputLabel,
N8nInputNumber,
N8nLink,
N8nLoading,
N8nMarkdown,
N8nMenu,
N8nMenuItem,
N8nNodeCreatorNode,
N8nNodeIcon,
N8nNotice,
N8nOption,
N8nPopover,
N8nPulse,
N8nRadioButtons,
N8nRecycleScroller,
N8nResizeWrapper,
N8nSelect,
N8nSpinner,
N8nSticky,
N8nTabs,
N8nTag,
N8nTags,
N8nText,
N8nTooltip,
N8nTree,
N8nUserInfo,
N8nUserSelect,
N8nUsersList,
N8nResizeObserver,
N8nKeyboardShortcut,
N8nUserStack,
} from './components';
export interface N8nPluginOptions {} export interface N8nPluginOptions {}
export const N8nPlugin: Plugin<N8nPluginOptions> = { export const N8nPlugin: Plugin<N8nPluginOptions> = {
install: (app) => { install: (app) => {
app.component('n8n-action-box', N8nActionBox); for (const [name, component] of Object.entries(components)) {
app.component('n8n-action-dropdown', N8nActionDropdown); app.component(name, component as unknown as Component);
app.component('n8n-action-toggle', N8nActionToggle); }
app.component('n8n-alert', N8nAlert);
app.component('n8n-avatar', N8nAvatar);
app.component('n8n-badge', N8nBadge);
app.component('n8n-block-ui', N8nBlockUi);
app.component('n8n-button', N8nButton);
app.component('n8n-callout', N8nCallout);
app.component('n8n-card', N8nCard);
app.component('n8n-checkbox', N8nCheckbox);
app.component('n8n-circle-loader', N8nCircleLoader);
app.component('n8n-color-picker', N8nColorPicker);
app.component('n8n-datatable', N8nDatatable);
app.component('n8n-form-box', N8nFormBox);
app.component('n8n-form-inputs', N8nFormInputs);
app.component('n8n-form-input', N8nFormInput);
app.component('n8n-heading', N8nHeading);
app.component('n8n-icon', N8nIcon);
app.component('n8n-icon-button', N8nIconButton);
app.component('n8n-info-accordion', N8nInfoAccordion);
app.component('n8n-info-tip', N8nInfoTip);
app.component('n8n-input', N8nInput);
app.component('n8n-input-label', N8nInputLabel);
app.component('n8n-input-number', N8nInputNumber);
app.component('n8n-link', N8nLink);
app.component('n8n-loading', N8nLoading);
app.component('n8n-markdown', N8nMarkdown);
app.component('n8n-menu', N8nMenu);
app.component('n8n-menu-item', N8nMenuItem);
app.component('n8n-node-creator-node', N8nNodeCreatorNode);
app.component('n8n-node-icon', N8nNodeIcon);
app.component('n8n-notice', N8nNotice);
app.component('n8n-option', N8nOption);
app.component('n8n-popover', N8nPopover);
app.component('n8n-pulse', N8nPulse);
app.component('n8n-radio-buttons', N8nRadioButtons);
app.component('n8n-recycle-scroller', N8nRecycleScroller);
app.component('n8n-resize-wrapper', N8nResizeWrapper);
app.component('n8n-select', N8nSelect);
app.component('n8n-spinner', N8nSpinner);
app.component('n8n-sticky', N8nSticky);
app.component('n8n-tabs', N8nTabs);
app.component('n8n-tag', N8nTag);
app.component('n8n-tags', N8nTags);
app.component('n8n-text', N8nText);
app.component('n8n-tooltip', N8nTooltip);
app.component('n8n-tree', N8nTree);
app.component('n8n-user-stack', N8nUserStack);
app.component('n8n-user-info', N8nUserInfo);
app.component('n8n-users-list', N8nUsersList);
app.component('n8n-user-select', N8nUserSelect);
app.component('n8n-resize-observer', N8nResizeObserver);
app.component('n8n-keyboard-shortcut', N8nKeyboardShortcut);
}, },
}; };

View File

@@ -53,19 +53,19 @@ function getHex(hsl: string): string {
} }
export default defineComponent({ export default defineComponent({
name: 'color-circles', name: 'ColorCircles',
data() {
return {
observer: null as null | MutationObserver,
hsl: {} as { [color: string]: string },
};
},
props: { props: {
colors: { colors: {
type: Array as PropType<string[]>, type: Array as PropType<string[]>,
required: true, required: true,
}, },
}, },
data() {
return {
observer: null as null | MutationObserver,
hsl: {} as { [color: string]: string },
};
},
created() { created() {
const setColors = () => { const setColors = () => {
this.colors.forEach((color) => { this.colors.forEach((color) => {

View File

@@ -22,13 +22,7 @@ import type { PropType } from 'vue';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
name: 'sizes', name: 'Sizes',
data() {
return {
observer: null as null | MutationObserver,
sizes: {} as Record<string, { rem: string; px: number }>,
};
},
props: { props: {
variables: { variables: {
type: Array as PropType<string[]>, type: Array as PropType<string[]>,
@@ -39,6 +33,12 @@ export default defineComponent({
default: '', default: '',
}, },
}, },
data() {
return {
observer: null as null | MutationObserver,
sizes: {} as Record<string, { rem: string; px: number }>,
};
},
created() { created() {
const setSizes = () => { const setSizes = () => {
this.variables.forEach((variable: string) => { this.variables.forEach((variable: string) => {

View File

@@ -20,13 +20,7 @@ import type { PropType } from 'vue';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
name: 'variable-table', name: 'VariableTable',
data() {
return {
observer: null as null | MutationObserver,
values: {} as Record<string, string>,
};
},
props: { props: {
variables: { variables: {
type: Array as PropType<string[]>, type: Array as PropType<string[]>,
@@ -37,6 +31,12 @@ export default defineComponent({
default: '', default: '',
}, },
}, },
data() {
return {
observer: null as null | MutationObserver,
values: {} as Record<string, string>,
};
},
created() { created() {
const setValues = () => { const setValues = () => {
this.variables.forEach((variable) => { this.variables.forEach((variable) => {

View File

@@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<div v-for="size in sizes" class="spacing-group" :key="size"> <div v-for="size in sizes" :key="size" class="spacing-group">
<div class="spacing-example" :class="`${property[0]}${side ? side[0] : ''}-${size}`"> <div class="spacing-example" :class="`${property[0]}${side ? side[0] : ''}-${size}`">
<div class="spacing-box" /> <div class="spacing-box" />
<div class="label">{{ property[0] }}{{ side ? side[0] : '' }}-{{ size }}</div> <div class="label">{{ property[0] }}{{ side ? side[0] : '' }}-{{ size }}</div>

View File

@@ -10,7 +10,7 @@
}" }"
> >
<div id="banners" :class="$style.banners"> <div id="banners" :class="$style.banners">
<banner-stack v-if="!isDemoMode" /> <BannerStack v-if="!isDemoMode" />
</div> </div>
<div id="header" :class="$style.header"> <div id="header" :class="$style.header">
<router-view name="header"></router-view> <router-view name="header"></router-view>
@@ -23,7 +23,7 @@
<keep-alive v-if="$route.meta.keepWorkflowAlive" include="NodeView" :max="1"> <keep-alive v-if="$route.meta.keepWorkflowAlive" include="NodeView" :max="1">
<component :is="Component" /> <component :is="Component" />
</keep-alive> </keep-alive>
<component v-else :is="Component" /> <component :is="Component" v-else />
</router-view> </router-view>
</div> </div>
<Modals /> <Modals />
@@ -102,12 +102,16 @@ export default defineComponent({
loading: true, loading: true,
}; };
}, },
methods: { watch: {
logHiringBanner() { // eslint-disable-next-line @typescript-eslint/naming-convention
if (this.settingsStore.isHiringBannerEnabled && !this.isDemoMode) { async 'usersStore.currentUser'(currentValue, previousValue) {
console.log(HIRING_BANNER); if (currentValue && !previousValue) {
await initializeAuthenticatedFeatures();
} }
}, },
defaultLocale(newLocale) {
void loadLanguage(newLocale);
},
}, },
async mounted() { async mounted() {
this.logHiringBanner(); this.logHiringBanner();
@@ -117,16 +121,12 @@ export default defineComponent({
void useExternalHooks().run('app.mount'); void useExternalHooks().run('app.mount');
this.loading = false; this.loading = false;
}, },
watch: { methods: {
// eslint-disable-next-line @typescript-eslint/naming-convention logHiringBanner() {
async 'usersStore.currentUser'(currentValue, previousValue) { if (this.settingsStore.isHiringBannerEnabled && !this.isDemoMode) {
if (currentValue && !previousValue) { console.log(HIRING_BANNER);
await initializeAuthenticatedFeatures();
} }
}, },
defaultLocale(newLocale) {
void loadLanguage(newLocale);
},
}, },
}); });
</script> </script>

View File

@@ -2,7 +2,7 @@
<Modal <Modal
max-width="540px" max-width="540px"
:title="$locale.baseText('about.aboutN8n')" :title="$locale.baseText('about.aboutN8n')"
:eventBus="modalBus" :event-bus="modalBus"
:name="ABOUT_MODAL_KEY" :name="ABOUT_MODAL_KEY"
:center="true" :center="true"
> >
@@ -48,10 +48,10 @@
<template #footer> <template #footer>
<div class="action-buttons"> <div class="action-buttons">
<n8n-button <n8n-button
@click="closeDialog"
float="right" float="right"
:label="$locale.baseText('about.close')" :label="$locale.baseText('about.close')"
data-test-id="close-about-modal-button" data-test-id="close-about-modal-button"
@click="closeDialog"
/> />
</div> </div>
</template> </template>

View File

@@ -25,10 +25,10 @@
<template #footer="{ close }"> <template #footer="{ close }">
<div :class="$style.footer"> <div :class="$style.footer">
<el-checkbox :modelValue="checked" @update:modelValue="handleCheckboxChange">{{ <el-checkbox :model-value="checked" @update:modelValue="handleCheckboxChange">{{
$locale.baseText('generic.dontShowAgain') $locale.baseText('generic.dontShowAgain')
}}</el-checkbox> }}</el-checkbox>
<n8n-button @click="close" :label="$locale.baseText('activationModal.gotIt')" /> <n8n-button :label="$locale.baseText('activationModal.gotIt')" @click="close" />
</div> </div>
</template> </template>
</Modal> </Modal>

View File

@@ -16,7 +16,7 @@
</div> </div>
</div> </div>
<slot name="button" v-if="$slots.button" /> <slot v-if="$slots.button" name="button" />
<n8n-button <n8n-button
v-else-if="buttonLabel" v-else-if="buttonLabel"
:label="buttonLoading && buttonLoadingLabel ? buttonLoadingLabel : buttonLabel" :label="buttonLoading && buttonLoadingLabel ? buttonLoadingLabel : buttonLabel"
@@ -40,11 +40,6 @@ import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
name: 'Banner', name: 'Banner',
data() {
return {
expanded: false,
};
},
props: { props: {
theme: { theme: {
type: String, type: String,
@@ -70,6 +65,11 @@ export default defineComponent({
default: false, default: false,
}, },
}, },
data() {
return {
expanded: false,
};
},
methods: { methods: {
expand() { expand() {
this.expanded = true; this.expanded = true;

View File

@@ -1,19 +1,19 @@
<template> <template>
<div v-if="windowVisible" :class="['binary-data-window', binaryData?.fileType]"> <div v-if="windowVisible" :class="['binary-data-window', binaryData?.fileType]">
<n8n-button <n8n-button
@click.stop="closeWindow"
size="small" size="small"
class="binary-data-window-back" class="binary-data-window-back"
:title="$locale.baseText('binaryDataDisplay.backToOverviewPage')" :title="$locale.baseText('binaryDataDisplay.backToOverviewPage')"
icon="arrow-left" icon="arrow-left"
:label="$locale.baseText('binaryDataDisplay.backToList')" :label="$locale.baseText('binaryDataDisplay.backToList')"
@click.stop="closeWindow"
/> />
<div class="binary-data-window-wrapper"> <div class="binary-data-window-wrapper">
<div v-if="!binaryData"> <div v-if="!binaryData">
{{ $locale.baseText('binaryDataDisplay.noDataFoundToDisplay') }} {{ $locale.baseText('binaryDataDisplay.noDataFoundToDisplay') }}
</div> </div>
<BinaryDataDisplayEmbed v-else :binaryData="binaryData" /> <BinaryDataDisplayEmbed v-else :binary-data="binaryData" />
</div> </div>
</div> </div>
</template> </template>
@@ -34,6 +34,10 @@ export default defineComponent({
components: { components: {
BinaryDataDisplayEmbed, BinaryDataDisplayEmbed,
}, },
props: [
'displayData', // IBinaryData
'windowVisible', // boolean
],
setup() { setup() {
const nodeHelpers = useNodeHelpers(); const nodeHelpers = useNodeHelpers();
@@ -41,10 +45,6 @@ export default defineComponent({
nodeHelpers, nodeHelpers,
}; };
}, },
props: [
'displayData', // IBinaryData
'windowVisible', // boolean
],
computed: { computed: {
...mapStores(useWorkflowsStore), ...mapStores(useWorkflowsStore),
binaryData(): IBinaryData | null { binaryData(): IBinaryData | null {

View File

@@ -11,13 +11,13 @@
<source :src="embedSource" :type="binaryData.mimeType" /> <source :src="embedSource" :type="binaryData.mimeType" />
{{ $locale.baseText('binaryDataDisplay.yourBrowserDoesNotSupport') }} {{ $locale.baseText('binaryDataDisplay.yourBrowserDoesNotSupport') }}
</audio> </audio>
<vue-json-pretty <VueJsonPretty
v-else-if="binaryData.fileType === 'json'" v-else-if="binaryData.fileType === 'json'"
:data="data" :data="data"
:deep="3" :deep="3"
:showLength="true" :show-length="true"
/> />
<run-data-html v-else-if="binaryData.fileType === 'html'" :inputHtml="data" /> <RunDataHtml v-else-if="binaryData.fileType === 'html'" :input-html="data" />
<embed v-else :src="embedSource" class="binary-data" :class="embedClass()" /> <embed v-else :src="embedSource" class="binary-data" :class="embedClass()" />
</span> </span>
</span> </span>

View File

@@ -1,6 +1,6 @@
<template> <template>
<span> <span>
<slot v-bind:bp="bp" v-bind:value="value" /> <slot :bp="bp" :value="value" />
</span> </span>
</template> </template>
@@ -31,24 +31,6 @@ export default defineComponent({
width: window.innerWidth, width: window.innerWidth,
}; };
}, },
created() {
window.addEventListener('resize', this.onResize);
},
beforeUnmount() {
window.removeEventListener('resize', this.onResize);
},
methods: {
onResize() {
void this.callDebounced('onResizeEnd', { debounceTime: 50 });
},
async onResizeEnd() {
this.width = window.innerWidth;
await this.$nextTick();
const bannerHeight = await getBannerRowHeight();
useUIStore().updateBannersHeight(bannerHeight);
},
},
computed: { computed: {
bp(): string { bp(): string {
if (this.width < BREAKPOINT_SM) { if (this.width < BREAKPOINT_SM) {
@@ -94,5 +76,23 @@ export default defineComponent({
return this.valueDefault; return this.valueDefault;
}, },
}, },
created() {
window.addEventListener('resize', this.onResize);
},
beforeUnmount() {
window.removeEventListener('resize', this.onResize);
},
methods: {
onResize() {
void this.callDebounced('onResizeEnd', { debounceTime: 50 });
},
async onResizeEnd() {
this.width = window.innerWidth;
await this.$nextTick();
const bannerHeight = await getBannerRowHeight();
useUIStore().updateBannersHeight(bannerHeight);
},
},
}); });
</script> </script>

View File

@@ -6,55 +6,55 @@
[$style.demoZoomMenu]: isDemo, [$style.demoZoomMenu]: isDemo,
}" }"
> >
<keyboard-shortcut-tooltip <KeyboardShortcutTooltip
:label="$locale.baseText('nodeView.zoomToFit')" :label="$locale.baseText('nodeView.zoomToFit')"
:shortcut="{ keys: ['1'] }" :shortcut="{ keys: ['1'] }"
> >
<n8n-icon-button <n8n-icon-button
@click="zoomToFit"
type="tertiary" type="tertiary"
size="large" size="large"
icon="expand" icon="expand"
data-test-id="zoom-to-fit" data-test-id="zoom-to-fit"
@click="zoomToFit"
/> />
</keyboard-shortcut-tooltip> </KeyboardShortcutTooltip>
<keyboard-shortcut-tooltip <KeyboardShortcutTooltip
:label="$locale.baseText('nodeView.zoomIn')" :label="$locale.baseText('nodeView.zoomIn')"
:shortcut="{ keys: ['+'] }" :shortcut="{ keys: ['+'] }"
> >
<n8n-icon-button <n8n-icon-button
@click="zoomIn"
type="tertiary" type="tertiary"
size="large" size="large"
icon="search-plus" icon="search-plus"
data-test-id="zoom-in-button" data-test-id="zoom-in-button"
@click="zoomIn"
/> />
</keyboard-shortcut-tooltip> </KeyboardShortcutTooltip>
<keyboard-shortcut-tooltip <KeyboardShortcutTooltip
:label="$locale.baseText('nodeView.zoomOut')" :label="$locale.baseText('nodeView.zoomOut')"
:shortcut="{ keys: ['-'] }" :shortcut="{ keys: ['-'] }"
> >
<n8n-icon-button <n8n-icon-button
@click="zoomOut"
type="tertiary" type="tertiary"
size="large" size="large"
icon="search-minus" icon="search-minus"
data-test-id="zoom-out-button" data-test-id="zoom-out-button"
@click="zoomOut"
/> />
</keyboard-shortcut-tooltip> </KeyboardShortcutTooltip>
<keyboard-shortcut-tooltip <KeyboardShortcutTooltip
:label="$locale.baseText('nodeView.resetZoom')" :label="$locale.baseText('nodeView.resetZoom')"
:shortcut="{ keys: ['0'] }" :shortcut="{ keys: ['0'] }"
> >
<n8n-icon-button <n8n-icon-button
v-if="nodeViewScale !== 1 && !isDemo" v-if="nodeViewScale !== 1 && !isDemo"
@click="resetZoom"
type="tertiary" type="tertiary"
size="large" size="large"
icon="undo" icon="undo"
data-test-id="reset-zoom-button" data-test-id="reset-zoom-button"
@click="resetZoom"
/> />
</keyboard-shortcut-tooltip> </KeyboardShortcutTooltip>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>

View File

@@ -1,17 +1,17 @@
<template> <template>
<Modal <Modal
:name="CHANGE_PASSWORD_MODAL_KEY" :name="CHANGE_PASSWORD_MODAL_KEY"
@enter="onSubmit"
:title="$locale.baseText('auth.changePassword')" :title="$locale.baseText('auth.changePassword')"
:center="true" :center="true"
width="460px" width="460px"
:eventBus="modalBus" :event-bus="modalBus"
@enter="onSubmit"
> >
<template #content> <template #content>
<n8n-form-inputs <n8n-form-inputs
:inputs="config" :inputs="config"
:eventBus="formBus" :event-bus="formBus"
:columnView="true" :column-view="true"
@update="onInput" @update="onInput"
@submit="onSubmit" @submit="onSubmit"
/> />
@@ -20,9 +20,9 @@
<n8n-button <n8n-button
:loading="loading" :loading="loading"
:label="$locale.baseText('auth.changePassword')" :label="$locale.baseText('auth.changePassword')"
@click="onSubmitClick"
float="right" float="right"
data-test-id="change-password-button" data-test-id="change-password-button"
@click="onSubmitClick"
/> />
</template> </template>
</Modal> </Modal>

View File

@@ -117,19 +117,19 @@ function closeDialog() {
<Modal <Modal
max-width="960px" max-width="960px"
:title="i18n.baseText('chatEmbed.title')" :title="i18n.baseText('chatEmbed.title')"
:eventBus="modalBus" :event-bus="modalBus"
:name="CHAT_EMBED_MODAL_KEY" :name="CHAT_EMBED_MODAL_KEY"
:center="true" :center="true"
> >
<template #content> <template #content>
<div :class="$style.container"> <div :class="$style.container">
<n8n-tabs :options="tabs" v-model="currentTab" /> <n8n-tabs v-model="currentTab" :options="tabs" />
<div v-if="currentTab !== 'cdn'"> <div v-if="currentTab !== 'cdn'">
<n8n-text> <n8n-text>
{{ i18n.baseText('chatEmbed.install') }} {{ i18n.baseText('chatEmbed.install') }}
</n8n-text> </n8n-text>
<CodeNodeEditor :modelValue="commonCode.install" isReadOnly /> <CodeNodeEditor :model-value="commonCode.install" is-read-only />
</div> </div>
<n8n-text> <n8n-text>
@@ -139,10 +139,10 @@ function closeDialog() {
</template> </template>
</i18n-t> </i18n-t>
</n8n-text> </n8n-text>
<HtmlEditor v-if="currentTab === 'cdn'" :modelValue="cdnCode" isReadOnly /> <HtmlEditor v-if="currentTab === 'cdn'" :model-value="cdnCode" is-read-only />
<HtmlEditor v-if="currentTab === 'vue'" :modelValue="vueCode" isReadOnly /> <HtmlEditor v-if="currentTab === 'vue'" :model-value="vueCode" is-read-only />
<CodeNodeEditor v-if="currentTab === 'react'" :modelValue="reactCode" isReadOnly /> <CodeNodeEditor v-if="currentTab === 'react'" :model-value="reactCode" is-read-only />
<CodeNodeEditor v-if="currentTab === 'other'" :modelValue="otherCode" isReadOnly /> <CodeNodeEditor v-if="currentTab === 'other'" :model-value="otherCode" is-read-only />
<n8n-info-tip> <n8n-info-tip>
{{ i18n.baseText('chatEmbed.packageInfo.description') }} {{ i18n.baseText('chatEmbed.packageInfo.description') }}
@@ -155,7 +155,7 @@ function closeDialog() {
<template #footer> <template #footer>
<div class="action-buttons"> <div class="action-buttons">
<n8n-button @click="closeDialog" float="right" :label="i18n.baseText('chatEmbed.close')" /> <n8n-button float="right" :label="i18n.baseText('chatEmbed.close')" @click="closeDialog" />
</div> </div>
</template> </template>
</Modal> </Modal>

View File

@@ -249,8 +249,8 @@ onMounted(() => {
<span <span
v-show="prompt.length > 1" v-show="prompt.length > 1"
:class="$style.counter" :class="$style.counter"
v-text="`${prompt.length} / ${ASK_AI_MAX_PROMPT_LENGTH}`"
data-test-id="ask-ai-prompt-counter" data-test-id="ask-ai-prompt-counter"
v-text="`${prompt.length} / ${ASK_AI_MAX_PROMPT_LENGTH}`"
/> />
<a href="https://docs.n8n.io/code-examples/ai-code" target="_blank" :class="$style.help"> <a href="https://docs.n8n.io/code-examples/ai-code" target="_blank" :class="$style.help">
<n8n-icon icon="question-circle" color="text-light" size="large" />{{ <n8n-icon icon="question-circle" color="text-light" size="large" />{{
@@ -260,29 +260,29 @@ onMounted(() => {
</div> </div>
<N8nInput <N8nInput
v-model="prompt" v-model="prompt"
@input="onPromptInput"
:class="$style.input" :class="$style.input"
type="textarea" type="textarea"
:rows="6" :rows="6"
:maxlength="ASK_AI_MAX_PROMPT_LENGTH" :maxlength="ASK_AI_MAX_PROMPT_LENGTH"
:placeholder="i18n.baseText('codeNodeEditor.askAi.placeholder')" :placeholder="i18n.baseText('codeNodeEditor.askAi.placeholder')"
data-test-id="ask-ai-prompt-input" data-test-id="ask-ai-prompt-input"
@input="onPromptInput"
/> />
</div> </div>
<div :class="$style.controls"> <div :class="$style.controls">
<div :class="$style.loader" v-if="isLoading"> <div v-if="isLoading" :class="$style.loader">
<transition name="text-fade-in-out" mode="out-in"> <transition name="text-fade-in-out" mode="out-in">
<div v-text="loadingString" :key="loadingPhraseIndex" /> <div :key="loadingPhraseIndex" v-text="loadingString" />
</transition> </transition>
<n8n-circle-loader :radius="8" :progress="loaderProgress" :stroke-width="3" /> <n8n-circle-loader :radius="8" :progress="loaderProgress" :stroke-width="3" />
</div> </div>
<n8n-tooltip :disabled="isSubmitEnabled" v-else> <N8nTooltip v-else :disabled="isSubmitEnabled">
<div> <div>
<N8nButton <N8nButton
:disabled="!isSubmitEnabled" :disabled="!isSubmitEnabled"
@click="onSubmit"
size="small" size="small"
data-test-id="ask-ai-cta" data-test-id="ask-ai-cta"
@click="onSubmit"
> >
{{ i18n.baseText('codeNodeEditor.askAi.generateCode') }} {{ i18n.baseText('codeNodeEditor.askAi.generateCode') }}
</N8nButton> </N8nButton>
@@ -290,18 +290,18 @@ onMounted(() => {
<template #content> <template #content>
<span <span
v-if="!hasExecutionData" v-if="!hasExecutionData"
v-text="i18n.baseText('codeNodeEditor.askAi.noInputData')"
data-test-id="ask-ai-cta-tooltip-no-input-data" data-test-id="ask-ai-cta-tooltip-no-input-data"
v-text="i18n.baseText('codeNodeEditor.askAi.noInputData')"
/> />
<span <span
v-else-if="prompt.length === 0" v-else-if="prompt.length === 0"
v-text="i18n.baseText('codeNodeEditor.askAi.noPrompt')"
data-test-id="ask-ai-cta-tooltip-no-prompt" data-test-id="ask-ai-cta-tooltip-no-prompt"
v-text="i18n.baseText('codeNodeEditor.askAi.noPrompt')"
/> />
<span <span
v-else-if="isEachItemMode" v-else-if="isEachItemMode"
v-text="i18n.baseText('codeNodeEditor.askAi.onlyAllItemsMode')"
data-test-id="ask-ai-cta-tooltip-only-all-items-mode" data-test-id="ask-ai-cta-tooltip-only-all-items-mode"
v-text="i18n.baseText('codeNodeEditor.askAi.onlyAllItemsMode')"
/> />
<span <span
v-else-if="prompt.length < ASK_AI_MIN_PROMPT_LENGTH" v-else-if="prompt.length < ASK_AI_MIN_PROMPT_LENGTH"
@@ -313,7 +313,7 @@ onMounted(() => {
" "
/> />
</template> </template>
</n8n-tooltip> </N8nTooltip>
</div> </div>
</div> </div>
</template> </template>

View File

@@ -1,15 +1,15 @@
<template> <template>
<div <div
ref="codeNodeEditorContainer"
:class="['code-node-editor', $style['code-node-editor-container'], language]" :class="['code-node-editor', $style['code-node-editor-container'], language]"
@mouseover="onMouseOver" @mouseover="onMouseOver"
@mouseout="onMouseOut" @mouseout="onMouseOut"
ref="codeNodeEditorContainer"
> >
<el-tabs <el-tabs
type="card" v-if="aiEnabled"
ref="tabs" ref="tabs"
v-model="activeTab" v-model="activeTab"
v-if="aiEnabled" type="card"
:before-leave="onBeforeTabLeave" :before-leave="onBeforeTabLeave"
> >
<el-tab-pane <el-tab-pane
@@ -26,9 +26,9 @@
> >
<!-- Key the AskAI tab to make sure it re-mounts when changing tabs --> <!-- Key the AskAI tab to make sure it re-mounts when changing tabs -->
<AskAI <AskAI
@replaceCode="onReplaceCode"
:has-changes="hasChanges"
:key="activeTab" :key="activeTab"
:has-changes="hasChanges"
@replaceCode="onReplaceCode"
@started-loading="isLoadingAIResponse = true" @started-loading="isLoadingAIResponse = true"
@finished-loading="isLoadingAIResponse = false" @finished-loading="isLoadingAIResponse = false"
/> />
@@ -73,11 +73,11 @@ import { useMessage } from '@/composables/useMessage';
import { useSettingsStore } from '@/stores/settings.store'; import { useSettingsStore } from '@/stores/settings.store';
export default defineComponent({ export default defineComponent({
name: 'code-node-editor', name: 'CodeNodeEditor',
mixins: [linterExtension, completerExtension, workflowHelpers],
components: { components: {
AskAI, AskAI,
}, },
mixins: [linterExtension, completerExtension, workflowHelpers],
props: { props: {
aiButtonEnabled: { aiButtonEnabled: {
type: Boolean, type: Boolean,
@@ -184,6 +184,67 @@ export default defineComponent({
} }
}, },
}, },
beforeUnmount() {
if (!this.isReadOnly) codeNodeEditorEventBus.off('error-line-number', this.highlightLine);
},
mounted() {
if (!this.isReadOnly) codeNodeEditorEventBus.on('error-line-number', this.highlightLine);
const { isReadOnly, language } = this;
const extensions: Extension[] = [
...readOnlyEditorExtensions,
EditorState.readOnly.of(isReadOnly),
EditorView.editable.of(!isReadOnly),
codeNodeEditorTheme({ isReadOnly, customMinHeight: this.rows }),
];
if (!isReadOnly) {
const linter = this.createLinter(language);
if (linter) {
extensions.push(this.linterCompartment.of(linter));
}
extensions.push(
...writableEditorExtensions,
EditorView.domEventHandlers({
focus: () => {
this.isEditorFocused = true;
},
blur: () => {
this.isEditorFocused = false;
},
}),
EditorView.updateListener.of((viewUpdate) => {
if (!viewUpdate.docChanged) return;
this.trackCompletion(viewUpdate);
this.$emit('update:modelValue', this.editor?.state.doc.toString());
this.hasChanges = true;
}),
);
}
const [languageSupport, ...otherExtensions] = this.languageExtensions;
extensions.push(this.languageCompartment.of(languageSupport), ...otherExtensions);
const state = EditorState.create({
doc: this.modelValue ?? this.placeholder,
extensions,
});
this.editor = new EditorView({
parent: this.$refs.codeNodeEditor as HTMLDivElement,
state,
});
// empty on first load, default param value
if (!this.modelValue) {
this.refreshPlaceholder();
this.$emit('update:modelValue', this.placeholder);
}
},
methods: { methods: {
getCurrentEditorContent() { getCurrentEditorContent() {
return this.editor?.state.doc.toString() ?? ''; return this.editor?.state.doc.toString() ?? '';
@@ -311,67 +372,6 @@ export default defineComponent({
} catch {} } catch {}
}, },
}, },
beforeUnmount() {
if (!this.isReadOnly) codeNodeEditorEventBus.off('error-line-number', this.highlightLine);
},
mounted() {
if (!this.isReadOnly) codeNodeEditorEventBus.on('error-line-number', this.highlightLine);
const { isReadOnly, language } = this;
const extensions: Extension[] = [
...readOnlyEditorExtensions,
EditorState.readOnly.of(isReadOnly),
EditorView.editable.of(!isReadOnly),
codeNodeEditorTheme({ isReadOnly, customMinHeight: this.rows }),
];
if (!isReadOnly) {
const linter = this.createLinter(language);
if (linter) {
extensions.push(this.linterCompartment.of(linter));
}
extensions.push(
...writableEditorExtensions,
EditorView.domEventHandlers({
focus: () => {
this.isEditorFocused = true;
},
blur: () => {
this.isEditorFocused = false;
},
}),
EditorView.updateListener.of((viewUpdate) => {
if (!viewUpdate.docChanged) return;
this.trackCompletion(viewUpdate);
this.$emit('update:modelValue', this.editor?.state.doc.toString());
this.hasChanges = true;
}),
);
}
const [languageSupport, ...otherExtensions] = this.languageExtensions;
extensions.push(this.languageCompartment.of(languageSupport), ...otherExtensions);
const state = EditorState.create({
doc: this.modelValue ?? this.placeholder,
extensions,
});
this.editor = new EditorView({
parent: this.$refs.codeNodeEditor as HTMLDivElement,
state,
});
// empty on first load, default param value
if (!this.modelValue) {
this.refreshPlaceholder();
this.$emit('update:modelValue', this.placeholder);
}
},
}); });
</script> </script>

View File

@@ -1,18 +1,18 @@
<template> <template>
<div @keydown.stop class="collection-parameter"> <div class="collection-parameter" @keydown.stop>
<div class="collection-parameter-wrapper"> <div class="collection-parameter-wrapper">
<div v-if="getProperties.length === 0" class="no-items-exist"> <div v-if="getProperties.length === 0" class="no-items-exist">
<n8n-text size="small">{{ $locale.baseText('collectionParameter.noProperties') }}</n8n-text> <n8n-text size="small">{{ $locale.baseText('collectionParameter.noProperties') }}</n8n-text>
</div> </div>
<Suspense> <Suspense>
<parameter-input-list <ParameterInputList
:parameters="getProperties" :parameters="getProperties"
:nodeValues="nodeValues" :node-values="nodeValues"
:path="path" :path="path"
:hideDelete="hideDelete" :hide-delete="hideDelete"
:indent="true" :indent="true"
:isReadOnly="isReadOnly" :is-read-only="isReadOnly"
@valueChanged="valueChanged" @valueChanged="valueChanged"
/> />
</Suspense> </Suspense>
@@ -22,16 +22,16 @@
v-if="(parameter.options ?? []).length === 1" v-if="(parameter.options ?? []).length === 1"
type="tertiary" type="tertiary"
block block
@click="optionSelected((parameter.options ?? [])[0].name)"
:label="getPlaceholderText" :label="getPlaceholderText"
@click="optionSelected((parameter.options ?? [])[0].name)"
/> />
<div v-else class="add-option"> <div v-else class="add-option">
<n8n-select <n8n-select
v-model="selectedOption" v-model="selectedOption"
:placeholder="getPlaceholderText" :placeholder="getPlaceholderText"
size="small" size="small"
@update:modelValue="optionSelected"
filterable filterable
@update:modelValue="optionSelected"
> >
<n8n-option <n8n-option
v-for="item in parameterOptions" v-for="item in parameterOptions"

View File

@@ -1,10 +1,10 @@
<template> <template>
<n8n-card :class="$style.card" v-bind="$attrs"> <n8n-card :class="$style.card" v-bind="$attrs">
<template #header v-if="!loading"> <template v-if="!loading" #header>
<span v-text="title" :class="$style.title" /> <span :class="$style.title" v-text="title" />
</template> </template>
<n8n-loading :loading="loading" :rows="3" variant="p" /> <n8n-loading :loading="loading" :rows="3" variant="p" />
<template #footer v-if="!loading"> <template v-if="!loading" #footer>
<slot name="footer" /> <slot name="footer" />
</template> </template>
</n8n-card> </n8n-card>

View File

@@ -3,10 +3,10 @@
width="540px" width="540px"
:name="COMMUNITY_PACKAGE_INSTALL_MODAL_KEY" :name="COMMUNITY_PACKAGE_INSTALL_MODAL_KEY"
:title="$locale.baseText('settings.communityNodes.installModal.title')" :title="$locale.baseText('settings.communityNodes.installModal.title')"
:eventBus="modalBus" :event-bus="modalBus"
:center="true" :center="true"
:beforeClose="onModalClose" :before-close="onModalClose"
:showClose="!loading" :show-close="!loading"
> >
<template #content> <template #content>
<div :class="[$style.descriptionContainer, 'p-s']"> <div :class="[$style.descriptionContainer, 'p-s']">
@@ -30,15 +30,15 @@
<n8n-input-label <n8n-input-label
:class="$style.labelTooltip" :class="$style.labelTooltip"
:label="$locale.baseText('settings.communityNodes.installModal.packageName.label')" :label="$locale.baseText('settings.communityNodes.installModal.packageName.label')"
:tooltipText=" :tooltip-text="
$locale.baseText('settings.communityNodes.installModal.packageName.tooltip', { $locale.baseText('settings.communityNodes.installModal.packageName.tooltip', {
interpolate: { npmURL: NPM_KEYWORD_SEARCH_URL }, interpolate: { npmURL: NPM_KEYWORD_SEARCH_URL },
}) })
" "
> >
<n8n-input <n8n-input
name="packageNameInput"
v-model="packageName" v-model="packageName"
name="packageNameInput"
type="text" type="text"
:maxlength="214" :maxlength="214"
:placeholder=" :placeholder="
@@ -60,8 +60,8 @@
v-model="userAgreed" v-model="userAgreed"
:class="[$style.checkbox, checkboxWarning ? $style.error : '', 'mt-l']" :class="[$style.checkbox, checkboxWarning ? $style.error : '', 'mt-l']"
:disabled="loading" :disabled="loading"
@update:modelValue="onCheckboxChecked"
data-test-id="user-agreement-checkbox" data-test-id="user-agreement-checkbox"
@update:modelValue="onCheckboxChecked"
> >
<n8n-text> <n8n-text>
{{ $locale.baseText('settings.communityNodes.installModal.checkbox.label') }} </n8n-text {{ $locale.baseText('settings.communityNodes.installModal.checkbox.label') }} </n8n-text
@@ -83,8 +83,8 @@
" "
size="large" size="large"
float="right" float="right"
@click="onInstallClick"
data-test-id="install-community-package-button" data-test-id="install-community-package-button"
@click="onInstallClick"
/> />
</template> </template>
</Modal> </Modal>

View File

@@ -3,16 +3,16 @@
width="540px" width="540px"
:name="COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY" :name="COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY"
:title="getModalContent.title" :title="getModalContent.title"
:eventBus="modalBus" :event-bus="modalBus"
:center="true" :center="true"
:showClose="!loading" :show-close="!loading"
:beforeClose="onModalClose" :before-close="onModalClose"
> >
<template #content> <template #content>
<n8n-text>{{ getModalContent.message }}</n8n-text> <n8n-text>{{ getModalContent.message }}</n8n-text>
<div <div
:class="$style.descriptionContainer"
v-if="mode === COMMUNITY_PACKAGE_MANAGE_ACTIONS.UPDATE" v-if="mode === COMMUNITY_PACKAGE_MANAGE_ACTIONS.UPDATE"
:class="$style.descriptionContainer"
> >
<n8n-info-tip theme="info" type="note" :bold="false"> <n8n-info-tip theme="info" type="note" :bold="false">
<span v-text="getModalContent.description"></span> <span v-text="getModalContent.description"></span>

View File

@@ -1,11 +1,11 @@
<template> <template>
<Modal <Modal
:name="modalName" :name="modalName"
:eventBus="modalBus" :event-bus="modalBus"
:center="true" :center="true"
:closeOnPressEscape="false" :close-on-press-escape="false"
:beforeClose="closeDialog" :before-close="closeDialog"
customClass="contact-prompt-modal" custom-class="contact-prompt-modal"
width="460px" width="460px"
> >
<template #header> <template #header>
@@ -26,7 +26,7 @@
</template> </template>
<template #footer> <template #footer>
<div :class="$style.footer"> <div :class="$style.footer">
<n8n-button label="Send" float="right" @click="send" :disabled="!isEmailValid" /> <n8n-button label="Send" float="right" :disabled="!isEmailValid" @click="send" />
</div> </div>
</template> </template>
</Modal> </Modal>
@@ -46,8 +46,8 @@ import { useToast } from '@/composables/useToast';
export default defineComponent({ export default defineComponent({
name: 'ContactPromptModal', name: 'ContactPromptModal',
mixins: [workflowHelpers],
components: { Modal }, components: { Modal },
mixins: [workflowHelpers],
props: ['modalName'], props: ['modalName'],
setup() { setup() {
return { return {

View File

@@ -43,19 +43,19 @@ function onVisibleChange(open: boolean) {
top: `${position[1]}px`, top: `${position[1]}px`,
}" }"
> >
<n8n-action-dropdown <N8nActionDropdown
ref="dropdown" ref="dropdown"
:items="actions" :items="actions"
placement="bottom-start" placement="bottom-start"
data-test-id="context-menu" data-test-id="context-menu"
:hideArrow="target.source !== 'node-button'" :hide-arrow="target.source !== 'node-button'"
@select="onActionSelect" @select="onActionSelect"
@visibleChange="onVisibleChange" @visibleChange="onVisibleChange"
> >
<template #activator> <template #activator>
<div :class="$style.activator"></div> <div :class="$style.activator"></div>
</template> </template>
</n8n-action-dropdown> </N8nActionDropdown>
</div> </div>
</Teleport> </Teleport>
</template> </template>

View File

@@ -8,8 +8,8 @@
[$style.collapsed]: collapse, [$style.collapsed]: collapse,
'ph-no-capture': redactValue, 'ph-no-capture': redactValue,
}" }"
@click="copy"
data-test-id="copy-input" data-test-id="copy-input"
@click="copy"
> >
<span ref="copyInputValue">{{ value }}</span> <span ref="copyInputValue">{{ value }}</span>
<div :class="$style.copyButton"> <div :class="$style.copyButton">

View File

@@ -1,7 +1,7 @@
<template> <template>
<n8n-card :class="$style.cardLink" @click="onClick"> <n8n-card :class="$style.cardLink" @click="onClick">
<template #prepend> <template #prepend>
<credential-icon :credential-type-name="credentialType ? credentialType.name : ''" /> <CredentialIcon :credential-type-name="credentialType ? credentialType.name : ''" />
</template> </template>
<template #header> <template #header>
<n8n-heading tag="h2" bold :class="$style.cardHeading"> <n8n-heading tag="h2" bold :class="$style.cardHeading">
@@ -12,7 +12,7 @@
<n8n-text color="text-light" size="small"> <n8n-text color="text-light" size="small">
<span v-if="credentialType">{{ credentialType.displayName }} | </span> <span v-if="credentialType">{{ credentialType.displayName }} | </span>
<span v-show="data" <span v-show="data"
>{{ $locale.baseText('credentials.item.updated') }} <time-ago :date="data.updatedAt" /> | >{{ $locale.baseText('credentials.item.updated') }} <TimeAgo :date="data.updatedAt" /> |
</span> </span>
<span v-show="data" <span v-show="data"
>{{ $locale.baseText('credentials.item.created') }} {{ formattedCreatedAtDate }} >{{ $locale.baseText('credentials.item.created') }} {{ formattedCreatedAtDate }}
@@ -20,7 +20,7 @@
</n8n-text> </n8n-text>
</div> </div>
<template #append> <template #append>
<div :class="$style.cardActions" ref="cardActions"> <div ref="cardActions" :class="$style.cardActions">
<enterprise-edition :features="[EnterpriseEditionFeature.Sharing]"> <enterprise-edition :features="[EnterpriseEditionFeature.Sharing]">
<n8n-badge v-if="credentialPermissions.isOwner" class="mr-xs" theme="tertiary" bold> <n8n-badge v-if="credentialPermissions.isOwner" class="mr-xs" theme="tertiary" bold>
{{ $locale.baseText('credentials.item.owner') }} {{ $locale.baseText('credentials.item.owner') }}
@@ -54,16 +54,6 @@ export const CREDENTIAL_LIST_ITEM_ACTIONS = {
}; };
export default defineComponent({ export default defineComponent({
data() {
return {
EnterpriseEditionFeature,
};
},
setup() {
return {
...useMessage(),
};
},
components: { components: {
TimeAgo, TimeAgo,
CredentialIcon, CredentialIcon,
@@ -88,6 +78,16 @@ export default defineComponent({
default: false, default: false,
}, },
}, },
setup() {
return {
...useMessage(),
};
},
data() {
return {
EnterpriseEditionFeature,
};
},
computed: { computed: {
...mapStores(useCredentialsStore, useUIStore, useUsersStore), ...mapStores(useCredentialsStore, useUIStore, useUsersStore),
currentUser(): IUser | null { currentUser(): IUser | null {

View File

@@ -118,18 +118,18 @@ defineExpose({
<template> <template>
<div v-if="filteredNodeAuthOptions.length > 0" data-test-id="node-auth-type-selector"> <div v-if="filteredNodeAuthOptions.length > 0" data-test-id="node-auth-type-selector">
<div v-for="parameter in authRelatedFields" :key="parameter.name" class="mb-l"> <div v-for="parameter in authRelatedFields" :key="parameter.name" class="mb-l">
<parameter-input-full <ParameterInputFull
:parameter="parameter" :parameter="parameter"
:value="authRelatedFieldsValues[parameter.name] || parameter.default" :value="authRelatedFieldsValues[parameter.name] || parameter.default"
:path="parameter.name" :path="parameter.name"
:displayOptions="false" :display-options="false"
@update="valueChanged" @update="valueChanged"
/> />
</div> </div>
<div> <div>
<n8n-input-label <n8n-input-label
:label="$locale.baseText('credentialEdit.credentialConfig.authTypeSelectorLabel')" :label="$locale.baseText('credentialEdit.credentialConfig.authTypeSelectorLabel')"
:tooltipText="$locale.baseText('credentialEdit.credentialConfig.authTypeSelectorTooltip')" :tooltip-text="$locale.baseText('credentialEdit.credentialConfig.authTypeSelectorTooltip')"
:required="true" :required="true"
/> />
</div> </div>

View File

@@ -1,6 +1,6 @@
<template> <template>
<div :class="$style.container" data-test-id="node-credentials-config-container"> <div :class="$style.container" data-test-id="node-credentials-config-container">
<banner <Banner
v-show="showValidationWarning" v-show="showValidationWarning"
theme="danger" theme="danger"
:message=" :message="
@@ -13,7 +13,7 @@
" "
/> />
<banner <Banner
v-if="authError && !showValidationWarning" v-if="authError && !showValidationWarning"
theme="danger" theme="danger"
:message=" :message="
@@ -25,40 +25,40 @@
) )
" "
:details="authError" :details="authError"
:buttonLabel="$locale.baseText('credentialEdit.credentialConfig.retry')" :button-label="$locale.baseText('credentialEdit.credentialConfig.retry')"
buttonLoadingLabel="Retrying" button-loading-label="Retrying"
:buttonTitle="$locale.baseText('credentialEdit.credentialConfig.retryCredentialTest')" :button-title="$locale.baseText('credentialEdit.credentialConfig.retryCredentialTest')"
:buttonLoading="isRetesting" :button-loading="isRetesting"
@click="$emit('retest')" @click="$emit('retest')"
/> />
<banner <Banner
v-show="showOAuthSuccessBanner && !showValidationWarning" v-show="showOAuthSuccessBanner && !showValidationWarning"
theme="success" theme="success"
:message="$locale.baseText('credentialEdit.credentialConfig.accountConnected')" :message="$locale.baseText('credentialEdit.credentialConfig.accountConnected')"
:buttonLabel="$locale.baseText('credentialEdit.credentialConfig.reconnect')" :button-label="$locale.baseText('credentialEdit.credentialConfig.reconnect')"
:buttonTitle="$locale.baseText('credentialEdit.credentialConfig.reconnectOAuth2Credential')" :button-title="$locale.baseText('credentialEdit.credentialConfig.reconnectOAuth2Credential')"
@click="$emit('oauth')" @click="$emit('oauth')"
> >
<template #button v-if="isGoogleOAuthType"> <template v-if="isGoogleOAuthType" #button>
<p <p
v-text="`${$locale.baseText('credentialEdit.credentialConfig.reconnect')}:`"
:class="$style.googleReconnectLabel" :class="$style.googleReconnectLabel"
v-text="`${$locale.baseText('credentialEdit.credentialConfig.reconnect')}:`"
/> />
<GoogleAuthButton @click="$emit('oauth')" /> <GoogleAuthButton @click="$emit('oauth')" />
</template> </template>
</banner> </Banner>
<banner <Banner
v-show="testedSuccessfully && !showValidationWarning" v-show="testedSuccessfully && !showValidationWarning"
theme="success" theme="success"
:message="$locale.baseText('credentialEdit.credentialConfig.connectionTestedSuccessfully')" :message="$locale.baseText('credentialEdit.credentialConfig.connectionTestedSuccessfully')"
:buttonLabel="$locale.baseText('credentialEdit.credentialConfig.retry')" :button-label="$locale.baseText('credentialEdit.credentialConfig.retry')"
:buttonLoadingLabel="$locale.baseText('credentialEdit.credentialConfig.retrying')" :button-loading-label="$locale.baseText('credentialEdit.credentialConfig.retrying')"
:buttonTitle="$locale.baseText('credentialEdit.credentialConfig.retryCredentialTest')" :button-title="$locale.baseText('credentialEdit.credentialConfig.retryCredentialTest')"
:buttonLoading="isRetesting" :button-loading="isRetesting"
@click="$emit('retest')"
data-test-id="credentials-config-container-test-success" data-test-id="credentials-config-container-test-success"
@click="$emit('retest')"
/> />
<template v-if="credentialPermissions.update"> <template v-if="credentialPermissions.update">
@@ -73,7 +73,7 @@
<AuthTypeSelector <AuthTypeSelector
v-if="showAuthTypeSelector && isNewCredential" v-if="showAuthTypeSelector && isNewCredential"
:credentialType="credentialType" :credential-type="credentialType"
@authTypeChanged="onAuthTypeChange" @authTypeChanged="onAuthTypeChange"
/> />
@@ -81,17 +81,17 @@
v-if="isOAuthType && credentialProperties.length" v-if="isOAuthType && credentialProperties.length"
:label="$locale.baseText('credentialEdit.credentialConfig.oAuthRedirectUrl')" :label="$locale.baseText('credentialEdit.credentialConfig.oAuthRedirectUrl')"
:value="oAuthCallbackUrl" :value="oAuthCallbackUrl"
:copyButtonText="$locale.baseText('credentialEdit.credentialConfig.clickToCopy')" :copy-button-text="$locale.baseText('credentialEdit.credentialConfig.clickToCopy')"
:hint=" :hint="
$locale.baseText('credentialEdit.credentialConfig.subtitle', { interpolate: { appName } }) $locale.baseText('credentialEdit.credentialConfig.subtitle', { interpolate: { appName } })
" "
:toastTitle=" :toast-title="
$locale.baseText('credentialEdit.credentialConfig.redirectUrlCopiedToClipboard') $locale.baseText('credentialEdit.credentialConfig.redirectUrlCopiedToClipboard')
" "
:redactValue="true" :redact-value="true"
/> />
</template> </template>
<enterprise-edition v-else :features="[EnterpriseEditionFeature.Sharing]"> <EnterpriseEdition v-else :features="[EnterpriseEditionFeature.Sharing]">
<div> <div>
<n8n-info-tip :bold="false"> <n8n-info-tip :bold="false">
{{ {{
@@ -101,14 +101,14 @@
}} }}
</n8n-info-tip> </n8n-info-tip>
</div> </div>
</enterprise-edition> </EnterpriseEdition>
<CredentialInputs <CredentialInputs
v-if="credentialType && credentialPermissions.update" v-if="credentialType && credentialPermissions.update"
:credentialData="credentialData" :credential-data="credentialData"
:credentialProperties="credentialProperties" :credential-properties="credentialProperties"
:documentationUrl="documentationUrl" :documentation-url="documentationUrl"
:showValidationWarnings="showValidationWarning" :show-validation-warnings="showValidationWarning"
@update="onDataChange" @update="onDataChange"
/> />
@@ -119,7 +119,7 @@
!isOAuthConnected && !isOAuthConnected &&
credentialPermissions.isOwner credentialPermissions.isOwner
" "
:isGoogleOAuthType="isGoogleOAuthType" :is-google-o-auth-type="isGoogleOAuthType"
@click="$emit('oauth')" @click="$emit('oauth')"
/> />

View File

@@ -1,10 +1,10 @@
<template> <template>
<Modal <Modal
:name="modalName" :name="modalName"
:customClass="$style.credentialModal" :custom-class="$style.credentialModal"
:eventBus="modalBus" :event-bus="modalBus"
:loading="loading" :loading="loading"
:beforeClose="beforeClose" :before-close="beforeClose"
width="70%" width="70%"
height="80%" height="80%"
> >
@@ -12,15 +12,15 @@
<div :class="$style.header"> <div :class="$style.header">
<div :class="$style.credInfo"> <div :class="$style.credInfo">
<div :class="$style.credIcon"> <div :class="$style.credIcon">
<CredentialIcon :credentialTypeName="defaultCredentialTypeName" /> <CredentialIcon :credential-type-name="defaultCredentialTypeName" />
</div> </div>
<InlineNameEdit <InlineNameEdit
:modelValue="credentialName" :model-value="credentialName"
:subtitle="credentialType ? credentialType.displayName : ''" :subtitle="credentialType ? credentialType.displayName : ''"
:readonly="!credentialPermissions.update || !credentialType" :readonly="!credentialPermissions.update || !credentialType"
type="Credential" type="Credential"
@update:modelValue="onNameEdit"
data-test-id="credential-name" data-test-id="credential-name"
@update:modelValue="onNameEdit"
/> />
</div> </div>
<div :class="$style.credActions"> <div :class="$style.credActions">
@@ -31,20 +31,20 @@
type="tertiary" type="tertiary"
:disabled="isSaving" :disabled="isSaving"
:loading="isDeleting" :loading="isDeleting"
@click="deleteCredential"
data-test-id="credential-delete-button" data-test-id="credential-delete-button"
@click="deleteCredential"
/> />
<SaveButton <SaveButton
v-if="(hasUnsavedChanges || credentialId) && credentialPermissions.save" v-if="(hasUnsavedChanges || credentialId) && credentialPermissions.save"
:saved="!hasUnsavedChanges && !isTesting" :saved="!hasUnsavedChanges && !isTesting"
:isSaving="isSaving || isTesting" :is-saving="isSaving || isTesting"
:savingLabel=" :saving-label="
isTesting isTesting
? $locale.baseText('credentialEdit.credentialEdit.testing') ? $locale.baseText('credentialEdit.credentialEdit.testing')
: $locale.baseText('credentialEdit.credentialEdit.saving') : $locale.baseText('credentialEdit.credentialEdit.saving')
" "
@click="saveCredential"
data-test-id="credential-save-button" data-test-id="credential-save-button"
@click="saveCredential"
/> />
</div> </div>
</div> </div>
@@ -56,28 +56,28 @@
<n8n-menu <n8n-menu
mode="tabs" mode="tabs"
:items="sidebarItems" :items="sidebarItems"
:transparentBackground="true" :transparent-background="true"
@select="onTabSelect" @select="onTabSelect"
></n8n-menu> ></n8n-menu>
</div> </div>
<div v-if="activeTab === 'connection'" :class="$style.mainContent" ref="content"> <div v-if="activeTab === 'connection'" ref="content" :class="$style.mainContent">
<CredentialConfig <CredentialConfig
:credentialType="credentialType" :credential-type="credentialType"
:credentialProperties="credentialProperties" :credential-properties="credentialProperties"
:credentialData="credentialData" :credential-data="credentialData"
:credentialId="credentialId" :credential-id="credentialId"
:showValidationWarning="showValidationWarning" :show-validation-warning="showValidationWarning"
:authError="authError" :auth-error="authError"
:testedSuccessfully="testedSuccessfully" :tested-successfully="testedSuccessfully"
:isOAuthType="isOAuthType" :is-o-auth-type="isOAuthType"
:isOAuthConnected="isOAuthConnected" :is-o-auth-connected="isOAuthConnected"
:isRetesting="isRetesting" :is-retesting="isRetesting"
:parentTypes="parentTypes" :parent-types="parentTypes"
:requiredPropertiesFilled="requiredPropertiesFilled" :required-properties-filled="requiredPropertiesFilled"
:credentialPermissions="credentialPermissions" :credential-permissions="credentialPermissions"
:mode="mode" :mode="mode"
:selectedCredential="selectedCredential" :selected-credential="selectedCredential"
:showAuthTypeSelector="requiredCredentials" :show-auth-type-selector="requiredCredentials"
@update="onDataChange" @update="onDataChange"
@oauth="oAuthCredentialAuthorize" @oauth="oAuthCredentialAuthorize"
@retest="retestCredential" @retest="retestCredential"
@@ -88,24 +88,24 @@
<div v-else-if="activeTab === 'sharing' && credentialType" :class="$style.mainContent"> <div v-else-if="activeTab === 'sharing' && credentialType" :class="$style.mainContent">
<CredentialSharing <CredentialSharing
:credential="currentCredential" :credential="currentCredential"
:credentialData="credentialData" :credential-data="credentialData"
:credentialId="credentialId" :credential-id="credentialId"
:credentialPermissions="credentialPermissions" :credential-permissions="credentialPermissions"
:modalBus="modalBus" :modal-bus="modalBus"
@update:modelValue="onChangeSharedWith" @update:modelValue="onChangeSharedWith"
/> />
</div> </div>
<div v-else-if="activeTab === 'details' && credentialType" :class="$style.mainContent"> <div v-else-if="activeTab === 'details' && credentialType" :class="$style.mainContent">
<CredentialInfo <CredentialInfo
:nodeAccess="nodeAccess" :node-access="nodeAccess"
:nodesWithAccess="nodesWithAccess" :nodes-with-access="nodesWithAccess"
:currentCredential="currentCredential" :current-credential="currentCredential"
:credentialPermissions="credentialPermissions" :credential-permissions="credentialPermissions"
@accessChange="onNodeAccessChange" @accessChange="onNodeAccessChange"
/> />
</div> </div>
<div v-else-if="activeTab.startsWith('coming-soon')" :class="$style.mainContent"> <div v-else-if="activeTab.startsWith('coming-soon')" :class="$style.mainContent">
<FeatureComingSoon :featureId="activeTab.split('/')[1]"></FeatureComingSoon> <FeatureComingSoon :feature-id="activeTab.split('/')[1]"></FeatureComingSoon>
</div> </div>
</div> </div>
</template> </template>

View File

@@ -16,7 +16,7 @@
fallback: node.displayName, fallback: node.displayName,
}) })
" "
:modelValue="!!nodeAccess[node.name]" :model-value="!!nodeAccess[node.name]"
@update:modelValue="(val) => onNodeAccessChange(node.name, val)" @update:modelValue="(val) => onNodeAccessChange(node.name, val)"
/> />
<n8n-text v-else> <n8n-text v-else>
@@ -75,10 +75,10 @@ import type { INodeTypeDescription } from 'n8n-workflow';
export default defineComponent({ export default defineComponent({
name: 'CredentialInfo', name: 'CredentialInfo',
props: ['nodesWithAccess', 'nodeAccess', 'currentCredential', 'credentialPermissions'],
components: { components: {
TimeAgo, TimeAgo,
}, },
props: ['nodesWithAccess', 'nodeAccess', 'currentCredential', 'credentialPermissions'],
methods: { methods: {
onNodeAccessChange(name: string, value: string) { onNodeAccessChange(name: string, value: string) {
this.$emit('accessChange', { this.$emit('accessChange', {

View File

@@ -1,5 +1,5 @@
<template> <template>
<div @keydown.stop :class="$style.container" v-if="credentialProperties.length"> <div v-if="credentialProperties.length" :class="$style.container" @keydown.stop>
<form <form
v-for="parameter in credentialProperties" v-for="parameter in credentialProperties"
:key="parameter.name" :key="parameter.name"
@@ -9,14 +9,14 @@
> >
<!-- Why form? to break up inputs, to prevent Chrome autofill --> <!-- Why form? to break up inputs, to prevent Chrome autofill -->
<n8n-notice v-if="parameter.type === 'notice'" :content="parameter.displayName" /> <n8n-notice v-if="parameter.type === 'notice'" :content="parameter.displayName" />
<parameter-input-expanded <ParameterInputExpanded
v-else v-else
:parameter="parameter" :parameter="parameter"
:value="credentialData[parameter.name]" :value="credentialData[parameter.name]"
:documentationUrl="documentationUrl" :documentation-url="documentationUrl"
:showValidationWarnings="showValidationWarnings" :show-validation-warnings="showValidationWarnings"
:label="label" :label="label"
eventSource="credentials" event-source="credentials"
@update="valueChanged" @update="valueChanged"
/> />
</form> </form>
@@ -31,15 +31,15 @@ import ParameterInputExpanded from '../ParameterInputExpanded.vue';
export default defineComponent({ export default defineComponent({
name: 'CredentialsInput', name: 'CredentialsInput',
components: {
ParameterInputExpanded,
},
props: [ props: [
'credentialProperties', 'credentialProperties',
'credentialData', // ICredentialsDecryptedResponse 'credentialData', // ICredentialsDecryptedResponse
'documentationUrl', 'documentationUrl',
'showValidationWarnings', 'showValidationWarnings',
], ],
components: {
ParameterInputExpanded,
},
data(): { label: IParameterLabel } { data(): { label: IParameterLabel } {
return { return {
label: { label: {

View File

@@ -12,7 +12,7 @@
uiStore.contextBasedTranslationKeys.credentials.sharing.unavailable.description, uiStore.contextBasedTranslationKeys.credentials.sharing.unavailable.description,
) )
" "
:buttonText=" :button-text="
$locale.baseText( $locale.baseText(
uiStore.contextBasedTranslationKeys.credentials.sharing.unavailable.button, uiStore.contextBasedTranslationKeys.credentials.sharing.unavailable.button,
) )
@@ -26,7 +26,7 @@
:description=" :description="
$locale.baseText('credentialEdit.credentialSharing.isDefaultUser.description') $locale.baseText('credentialEdit.credentialSharing.isDefaultUser.description')
" "
:buttonText="$locale.baseText('credentialEdit.credentialSharing.isDefaultUser.button')" :button-text="$locale.baseText('credentialEdit.credentialSharing.isDefaultUser.button')"
@click:button="goToUsersSettings" @click:button="goToUsersSettings"
/> />
</div> </div>
@@ -61,7 +61,7 @@
class="mb-s" class="mb-s"
size="large" size="large"
:users="usersList" :users="usersList"
:currentUserId="usersStore.currentUser.id" :current-user-id="usersStore.currentUser.id"
:placeholder="$locale.baseText('credentialEdit.credentialSharing.select.placeholder')" :placeholder="$locale.baseText('credentialEdit.credentialSharing.select.placeholder')"
data-test-id="credential-sharing-modal-users-select" data-test-id="credential-sharing-modal-users-select"
@update:modelValue="onAddSharee" @update:modelValue="onAddSharee"
@@ -73,7 +73,7 @@
<n8n-users-list <n8n-users-list
:actions="usersListActions" :actions="usersListActions"
:users="sharedWithList" :users="sharedWithList"
:currentUserId="usersStore.currentUser.id" :current-user-id="usersStore.currentUser.id"
:readonly="!credentialPermissions.share" :readonly="!credentialPermissions.share"
@delete="onRemoveSharee" @delete="onRemoveSharee"
/> />
@@ -151,6 +151,9 @@ export default defineComponent({
}); });
}, },
}, },
mounted() {
void this.loadUsers();
},
methods: { methods: {
async onAddSharee(userId: string) { async onAddSharee(userId: string) {
const sharee = { ...this.usersStore.getUserById(userId), isOwner: false }; const sharee = { ...this.usersStore.getUserById(userId), isOwner: false };
@@ -196,9 +199,6 @@ export default defineComponent({
void this.uiStore.goToUpgrade('credential_sharing', 'upgrade-credentials-sharing'); void this.uiStore.goToUpgrade('credential_sharing', 'upgrade-credentials-sharing');
}, },
}, },
mounted() {
void this.loadUsers();
},
}); });
</script> </script>

View File

@@ -1,8 +1,8 @@
<template> <template>
<div> <div>
<img v-if="filePath" :class="$style.credIcon" :src="filePath" /> <img v-if="filePath" :class="$style.credIcon" :src="filePath" />
<NodeIcon v-else-if="relevantNode" :nodeType="relevantNode" :size="28" /> <NodeIcon v-else-if="relevantNode" :node-type="relevantNode" :size="28" />
<span :class="$style.fallback" v-else></span> <span v-else :class="$style.fallback"></span>
</div> </div>
</template> </template>

View File

@@ -104,16 +104,16 @@ listenForCredentialChanges({
[$style.invisible]: !props.selectedCredentialId, [$style.invisible]: !props.selectedCredentialId,
}" }"
:title="i18n.baseText('nodeCredentials.updateCredential')" :title="i18n.baseText('nodeCredentials.updateCredential')"
@click="editCredential()"
data-test-id="credential-edit-button" data-test-id="credential-edit-button"
@click="editCredential()"
/> />
</div> </div>
<n8n-button <n8n-button
v-else v-else
:label="`Create new ${props.appName} credential`" :label="`Create new ${props.appName} credential`"
@click="createNewCredential"
data-test-id="create-credential" data-test-id="create-credential"
@click="createNewCredential"
/> />
</div> </div>
</template> </template>

View File

@@ -40,13 +40,13 @@ const onCredentialSelected = (credentialId: string) => {
<template> <template>
<n8n-select <n8n-select
size="small" size="small"
:modelValue="props.selectedCredentialId" :model-value="props.selectedCredentialId"
@update:modelValue="onCredentialSelected" @update:modelValue="onCredentialSelected"
> >
<n8n-option <n8n-option
v-for="item in props.credentialOptions" v-for="item in props.credentialOptions"
:data-test-id="`node-credentials-select-item-${item.id}`"
:key="item.id" :key="item.id"
:data-test-id="`node-credentials-select-item-${item.id}`"
:label="item.name" :label="item.name"
:value="item.id" :value="item.id"
> >
@@ -56,8 +56,8 @@ const onCredentialSelected = (credentialId: string) => {
</div> </div>
</n8n-option> </n8n-option>
<n8n-option <n8n-option
data-test-id="node-credentials-select-item-new"
:key="NEW_CREDENTIALS_TEXT" :key="NEW_CREDENTIALS_TEXT"
data-test-id="node-credentials-select-item-new"
:value="NEW_CREDENTIALS_TEXT" :value="NEW_CREDENTIALS_TEXT"
:label="NEW_CREDENTIALS_TEXT" :label="NEW_CREDENTIALS_TEXT"
> >

View File

@@ -2,25 +2,25 @@
<div> <div>
<div :class="$style['parameter-value-container']"> <div :class="$style['parameter-value-container']">
<n8n-select <n8n-select
ref="innerSelect"
:size="inputSize" :size="inputSize"
filterable filterable
:modelValue="displayValue" :model-value="displayValue"
:placeholder=" :placeholder="
parameter.placeholder ? getPlaceholder() : $locale.baseText('parameterInput.select') parameter.placeholder ? getPlaceholder() : $locale.baseText('parameterInput.select')
" "
:title="displayTitle" :title="displayTitle"
:disabled="isReadOnly" :disabled="isReadOnly"
ref="innerSelect" data-test-id="credential-select"
@update:modelValue="(value) => $emit('update:modelValue', value)" @update:modelValue="(value) => $emit('update:modelValue', value)"
@keydown.stop @keydown.stop
@focus="$emit('setFocus')" @focus="$emit('setFocus')"
@blur="$emit('onBlur')" @blur="$emit('onBlur')"
data-test-id="credential-select"
> >
<n8n-option <n8n-option
v-for="credType in supportedCredentialTypes" v-for="credType in supportedCredentialTypes"
:value="credType.name"
:key="credType.name" :key="credType.name"
:value="credType.name"
:label="credType.displayName" :label="credType.displayName"
data-test-id="credential-select-option" data-test-id="credential-select-option"
> >
@@ -39,15 +39,15 @@
<slot name="issues-and-options" /> <slot name="issues-and-options" />
</div> </div>
<scopes-notice <ScopesNotice
v-if="scopes.length > 0" v-if="scopes.length > 0"
:activeCredentialType="activeCredentialType" :active-credential-type="activeCredentialType"
:scopes="scopes" :scopes="scopes"
/> />
<div> <div>
<node-credentials <NodeCredentials
:node="node" :node="node"
:overrideCredType="node.parameters[parameter.name]" :override-cred-type="node.parameters[parameter.name]"
@credentialSelected="(updateInformation) => $emit('credentialSelected', updateInformation)" @credentialSelected="(updateInformation) => $emit('credentialSelected', updateInformation)"
/> />
</div> </div>

View File

@@ -1,12 +1,12 @@
<template> <template>
<Modal <Modal
:name="CREDENTIAL_SELECT_MODAL_KEY" :name="CREDENTIAL_SELECT_MODAL_KEY"
:eventBus="modalBus" :event-bus="modalBus"
width="50%" width="50%"
:center="true" :center="true"
:loading="loading" :loading="loading"
maxWidth="460px" max-width="460px"
minHeight="250px" min-height="250px"
> >
<template #header> <template #header>
<h2 :class="$style.title"> <h2 :class="$style.title">
@@ -19,22 +19,22 @@
{{ $locale.baseText('credentialSelectModal.selectAnAppOrServiceToConnectTo') }} {{ $locale.baseText('credentialSelectModal.selectAnAppOrServiceToConnectTo') }}
</div> </div>
<n8n-select <n8n-select
ref="select"
filterable filterable
defaultFirstOption default-first-option
:placeholder="$locale.baseText('credentialSelectModal.searchForApp')" :placeholder="$locale.baseText('credentialSelectModal.searchForApp')"
size="xlarge" size="xlarge"
ref="select" :model-value="selected"
:modelValue="selected"
@update:modelValue="onSelect"
data-test-id="new-credential-type-select" data-test-id="new-credential-type-select"
@update:modelValue="onSelect"
> >
<template #prefix> <template #prefix>
<font-awesome-icon icon="search" /> <font-awesome-icon icon="search" />
</template> </template>
<n8n-option <n8n-option
v-for="credential in credentialsStore.allCredentialTypes" v-for="credential in credentialsStore.allCredentialTypes"
:value="credential.name"
:key="credential.name" :key="credential.name"
:value="credential.name"
:label="credential.displayName" :label="credential.displayName"
filterable filterable
data-test-id="new-credential-type-select-option" data-test-id="new-credential-type-select-option"
@@ -49,8 +49,8 @@
float="right" float="right"
size="large" size="large"
:disabled="!selected" :disabled="!selected"
@click="openCredentialType"
data-test-id="new-credential-type-button" data-test-id="new-credential-type-button"
@click="openCredentialType"
/> />
</div> </div>
</template> </template>
@@ -79,6 +79,14 @@ export default defineComponent({
externalHooks, externalHooks,
}; };
}, },
data() {
return {
modalBus: createEventBus(),
selected: '',
loading: true,
CREDENTIAL_SELECT_MODAL_KEY,
};
},
async mounted() { async mounted() {
try { try {
await this.credentialsStore.fetchCredentialTypes(false); await this.credentialsStore.fetchCredentialTypes(false);
@@ -92,14 +100,6 @@ export default defineComponent({
} }
}, 0); }, 0);
}, },
data() {
return {
modalBus: createEventBus(),
selected: '',
loading: true,
CREDENTIAL_SELECT_MODAL_KEY,
};
},
computed: { computed: {
...mapStores(useCredentialsStore, useUIStore, useWorkflowsStore), ...mapStores(useCredentialsStore, useUIStore, useWorkflowsStore),
}, },

View File

@@ -1,11 +1,11 @@
<template> <template>
<Modal <Modal
:name="modalName" :name="modalName"
@enter="onSubmit"
:title="title" :title="title"
:center="true" :center="true"
width="460px" width="460px"
:eventBus="modalBus" :event-bus="modalBus"
@enter="onSubmit"
> >
<template #content> <template #content>
<div> <div>
@@ -14,14 +14,14 @@
$locale.baseText('settings.users.confirmUserDeletion') $locale.baseText('settings.users.confirmUserDeletion')
}}</n8n-text> }}</n8n-text>
</div> </div>
<div :class="$style.content" v-else> <div v-else :class="$style.content">
<div> <div>
<n8n-text color="text-base">{{ <n8n-text color="text-base">{{
$locale.baseText('settings.users.confirmDataHandlingAfterDeletion') $locale.baseText('settings.users.confirmDataHandlingAfterDeletion')
}}</n8n-text> }}</n8n-text>
</div> </div>
<el-radio <el-radio
:modelValue="operation" :model-value="operation"
label="transfer" label="transfer"
@update:modelValue="() => setOperation('transfer')" @update:modelValue="() => setOperation('transfer')"
> >
@@ -29,19 +29,19 @@
$locale.baseText('settings.users.transferWorkflowsAndCredentials') $locale.baseText('settings.users.transferWorkflowsAndCredentials')
}}</n8n-text> }}</n8n-text>
</el-radio> </el-radio>
<div :class="$style.optionInput" v-if="operation === 'transfer'"> <div v-if="operation === 'transfer'" :class="$style.optionInput">
<n8n-input-label :label="$locale.baseText('settings.users.userToTransferTo')"> <n8n-input-label :label="$locale.baseText('settings.users.userToTransferTo')">
<n8n-user-select <n8n-user-select
:users="usersStore.allUsers" :users="usersStore.allUsers"
:modelValue="transferId" :model-value="transferId"
:ignoreIds="ignoreIds" :ignore-ids="ignoreIds"
:currentUserId="usersStore.currentUserId" :current-user-id="usersStore.currentUserId"
@update:modelValue="setTransferId" @update:modelValue="setTransferId"
/> />
</n8n-input-label> </n8n-input-label>
</div> </div>
<el-radio <el-radio
:modelValue="operation" :model-value="operation"
label="delete" label="delete"
@update:modelValue="() => setOperation('delete')" @update:modelValue="() => setOperation('delete')"
> >
@@ -50,13 +50,13 @@
}}</n8n-text> }}</n8n-text>
</el-radio> </el-radio>
<div <div
:class="$style.optionInput"
v-if="operation === 'delete'" v-if="operation === 'delete'"
:class="$style.optionInput"
data-test-id="delete-data-input" data-test-id="delete-data-input"
> >
<n8n-input-label :label="$locale.baseText('settings.users.deleteConfirmationMessage')"> <n8n-input-label :label="$locale.baseText('settings.users.deleteConfirmationMessage')">
<n8n-input <n8n-input
:modelValue="deleteConfirmText" :model-value="deleteConfirmText"
:placeholder="$locale.baseText('settings.users.deleteConfirmationText')" :placeholder="$locale.baseText('settings.users.deleteConfirmationText')"
@update:modelValue="setConfirmText" @update:modelValue="setConfirmText"
/> />
@@ -70,8 +70,8 @@
:loading="loading" :loading="loading"
:disabled="!enabled" :disabled="!enabled"
:label="$locale.baseText('settings.users.delete')" :label="$locale.baseText('settings.users.delete')"
@click="onSubmit"
float="right" float="right"
@click="onSubmit"
/> />
</template> </template>
</Modal> </Modal>

View File

@@ -1,15 +1,15 @@
<template> <template>
<component <component
:is="tag" :is="tag"
ref="wrapper"
:class="{ [$style.dragging]: isDragging }" :class="{ [$style.dragging]: isDragging }"
@mousedown="onDragStart" @mousedown="onDragStart"
ref="wrapper"
> >
<slot :isDragging="isDragging"></slot> <slot :is-dragging="isDragging"></slot>
<Teleport to="body"> <Teleport to="body">
<div ref="draggable" :class="$style.draggable" :style="draggableStyle" v-show="isDragging"> <div v-show="isDragging" ref="draggable" :class="$style.draggable" :style="draggableStyle">
<slot name="preview" :canDrop="canDrop" :el="draggingEl"></slot> <slot name="preview" :can-drop="canDrop" :el="draggingEl"></slot>
</div> </div>
</Teleport> </Teleport>
</component> </component>
@@ -22,7 +22,7 @@ import { mapStores } from 'pinia';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({
name: 'draggable', name: 'Draggable',
props: { props: {
disabled: { disabled: {
type: Boolean, type: Boolean,

View File

@@ -1,6 +1,6 @@
<template> <template>
<div ref="target"> <div ref="target">
<slot :droppable="droppable" :activeDrop="activeDrop"></slot> <slot :droppable="droppable" :active-drop="activeDrop"></slot>
</div> </div>
</template> </template>

View File

@@ -1,46 +1,46 @@
<template> <template>
<Modal <Modal
:name="modalName" :name="modalName"
:eventBus="modalBus" :event-bus="modalBus"
@enter="save"
:title="$locale.baseText('duplicateWorkflowDialog.duplicateWorkflow')" :title="$locale.baseText('duplicateWorkflowDialog.duplicateWorkflow')"
:center="true" :center="true"
width="420px" width="420px"
@enter="save"
> >
<template #content> <template #content>
<div :class="$style.content"> <div :class="$style.content">
<n8n-input <n8n-input
v-model="name"
ref="nameInput" ref="nameInput"
v-model="name"
:placeholder="$locale.baseText('duplicateWorkflowDialog.enterWorkflowName')" :placeholder="$locale.baseText('duplicateWorkflowDialog.enterWorkflowName')"
:maxlength="MAX_WORKFLOW_NAME_LENGTH" :maxlength="MAX_WORKFLOW_NAME_LENGTH"
/> />
<TagsDropdown <TagsDropdown
v-if="settingsStore.areTagsEnabled" v-if="settingsStore.areTagsEnabled"
ref="dropdown"
v-model="currentTagIds" v-model="currentTagIds"
:createEnabled="true" :create-enabled="true"
:eventBus="dropdownBus" :event-bus="dropdownBus"
:placeholder="$locale.baseText('duplicateWorkflowDialog.chooseOrCreateATag')"
@blur="onTagsBlur" @blur="onTagsBlur"
@esc="onTagsEsc" @esc="onTagsEsc"
:placeholder="$locale.baseText('duplicateWorkflowDialog.chooseOrCreateATag')"
ref="dropdown"
/> />
</div> </div>
</template> </template>
<template #footer="{ close }"> <template #footer="{ close }">
<div :class="$style.footer"> <div :class="$style.footer">
<n8n-button <n8n-button
@click="save"
:loading="isSaving" :loading="isSaving"
:label="$locale.baseText('duplicateWorkflowDialog.save')" :label="$locale.baseText('duplicateWorkflowDialog.save')"
float="right" float="right"
@click="save"
/> />
<n8n-button <n8n-button
type="secondary" type="secondary"
@click="close"
:disabled="isSaving" :disabled="isSaving"
:label="$locale.baseText('duplicateWorkflowDialog.cancel')" :label="$locale.baseText('duplicateWorkflowDialog.cancel')"
float="right" float="right"
@click="close"
/> />
</div> </div>
</template> </template>
@@ -66,8 +66,8 @@ import { useCredentialsStore } from '@/stores/credentials.store';
export default defineComponent({ export default defineComponent({
name: 'DuplicateWorkflow', name: 'DuplicateWorkflow',
mixins: [workflowHelpers],
components: { TagsDropdown, Modal }, components: { TagsDropdown, Modal },
mixins: [workflowHelpers],
props: ['modalName', 'isActive', 'data'], props: ['modalName', 'isActive', 'data'],
setup() { setup() {
return { return {

View File

@@ -1,7 +1,7 @@
<template> <template>
<div> <div>
<slot v-if="canAccess" /> <slot v-if="canAccess" />
<slot name="fallback" v-else /> <slot v-else name="fallback" />
</div> </div>
</template> </template>

View File

@@ -2,7 +2,7 @@
<div> <div>
<div class="error-header"> <div class="error-header">
<div class="error-message" v-text="getErrorMessage()" /> <div class="error-message" v-text="getErrorMessage()" />
<div class="error-description" v-if="error.description" v-html="getErrorDescription()"></div> <div v-if="error.description" class="error-description" v-html="getErrorDescription()"></div>
</div> </div>
<details> <details>
<summary class="error-details__summary"> <summary class="error-details__summary">
@@ -76,19 +76,19 @@
</div> </div>
</template> </template>
<div> <div>
<div class="copy-button" v-if="displayCause"> <div v-if="displayCause" class="copy-button">
<n8n-icon-button <n8n-icon-button
@click="copyCause"
:title="$locale.baseText('nodeErrorView.copyToClipboard')" :title="$locale.baseText('nodeErrorView.copyToClipboard')"
icon="copy" icon="copy"
@click="copyCause"
/> />
</div> </div>
<vue-json-pretty <VueJsonPretty
v-if="displayCause" v-if="displayCause"
:data="error.cause" :data="error.cause"
:deep="3" :deep="3"
:showLength="true" :show-length="true"
selectableType="single" selectable-type="single"
path="error" path="error"
class="json-data" class="json-data"
/> />
@@ -137,11 +137,11 @@ import { useNodeTypesStore } from '@/stores/nodeTypes.store';
export default defineComponent({ export default defineComponent({
name: 'NodeErrorView', name: 'NodeErrorView',
mixins: [copyPaste],
props: ['error'],
components: { components: {
VueJsonPretty, VueJsonPretty,
}, },
mixins: [copyPaste],
props: ['error'],
setup() { setup() {
return { return {
...useToast(), ...useToast(),

Some files were not shown because too many files have changed in this diff Show More