feat: Replace Vue.extend with defineComponent in design system (no-changelog) (#5918)

* refactor: replace new Vue() with custom event bus (no-changelog)

* fix: export types from design system main

* fix: update component types

* fix: update form inputs event bus

* refactor: replace global Vue references in design-system

* refactor: update prop types

* feat: improve types

* fix: further type improvements

* fix: further types improvements

* fix: further type improvements

* test: fix test snapshots

* test: fix snapshot

* chore: fix linting issues

* test: fix personalization modal snapshot
This commit is contained in:
Alex Grozav
2023-04-12 17:39:45 +03:00
committed by GitHub
parent 0a53c957c4
commit 430a8781e8
67 changed files with 447 additions and 375 deletions

View File

@@ -16,7 +16,7 @@
"scripts": {
"clean": "rimraf dist .turbo",
"build": "vite build",
"typecheck": "vue-tsc --emitDeclarationOnly",
"typecheck": "vue-tsc --declaration --emitDeclarationOnly",
"test": "vitest run --coverage",
"test:dev": "vitest",
"build:storybook": "storybook build",

View File

@@ -42,9 +42,9 @@ import N8nButton from '../N8nButton';
import N8nHeading from '../N8nHeading';
import N8nText from '../N8nText';
import N8nCallout from '../N8nCallout';
import Vue from 'vue';
import { defineComponent } from 'vue';
export default Vue.extend({
export default defineComponent({
name: 'n8n-action-box',
components: {
N8nButton,

View File

@@ -18,14 +18,7 @@
:disabled="item.disabled"
:divided="item.divided"
>
<div
:class="{
[$style.itemContainer]: true,
[$style.hasCustomStyling]: item.customClass !== undefined,
[item.customClass]: item.customClass !== undefined,
}"
:data-test-id="`workflow-menu-item-${item.id}`"
>
<div :class="getItemClasses(item)" :data-test-id="`workflow-menu-item-${item.id}`">
<span v-if="item.icon" :class="$style.icon">
<n8n-icon :icon="item.icon" :size="iconSize" />
</span>
@@ -41,7 +34,7 @@
</template>
<script lang="ts">
import Vue, { PropType } from 'vue';
import { defineComponent, PropType } from 'vue';
import {
Dropdown as ElDropdown,
DropdownMenu as ElDropdownMenu,
@@ -49,7 +42,7 @@ import {
} from 'element-ui';
import N8nIcon from '../N8nIcon';
interface IActionDropdownItem {
export interface IActionDropdownItem {
id: string;
label: string;
icon?: string;
@@ -64,7 +57,7 @@ interface IActionDropdownItem {
// by Element UI dropdown component).
// It can be used in different parts of editor UI while ActionToggle
// is designed to be used in card components.
export default Vue.extend({
export default defineComponent({
name: 'n8n-action-dropdown',
components: {
ElDropdown,
@@ -99,6 +92,13 @@ export default Vue.extend({
},
},
methods: {
getItemClasses(item: IActionDropdownItem): Record<string, boolean> {
return {
[this.$style.itemContainer]: true,
[this.$style.hasCustomStyling]: item.customClass !== undefined,
...(item.customClass !== undefined ? { [item.customClass]: true } : {}),
};
},
onSelect(action: string): void {
this.$emit('select', action);
},

View File

@@ -9,7 +9,7 @@
@visible-change="onVisibleChange"
>
<span :class="{ [$style.button]: true, [$style[theme]]: !!theme }">
<component :is="$options.components.N8nIcon" icon="ellipsis-v" :size="iconSize" />
<n8n-icon icon="ellipsis-v" :size="iconSize" />
</span>
<template #dropdown>
@@ -22,9 +22,8 @@
>
{{ action.label }}
<div :class="$style.iconContainer">
<component
<n8n-icon
v-if="action.type === 'external-link'"
:is="$options.components.N8nIcon"
icon="external-link-alt"
size="xsmall"
color="text-base"
@@ -38,22 +37,16 @@
</template>
<script lang="ts">
import Vue from 'vue';
import { defineComponent, PropType } from 'vue';
import {
Dropdown as ElDropdown,
DropdownMenu as ElDropdownMenu,
DropdownItem as ElDropdownItem,
} from 'element-ui';
import N8nIcon from '../N8nIcon';
import type { UserAction } from '@/types';
interface Action {
label: string;
value: string;
disabled: boolean;
type?: 'external-link';
}
export default Vue.extend({
export default defineComponent({
name: 'n8n-action-toggle',
components: {
ElDropdown,
@@ -63,7 +56,7 @@ export default Vue.extend({
},
props: {
actions: {
type: Array<Action>,
type: Array as PropType<UserAction[]>,
default: () => [],
},
placement: {

View File

@@ -21,9 +21,9 @@ const sizes: { [size: string]: number } = {
medium: 40,
};
import Vue from 'vue';
import { defineComponent } from 'vue';
export default Vue.extend({
export default defineComponent({
name: 'n8n-avatar',
props: {
firstName: {

View File

@@ -9,9 +9,9 @@
<script lang="ts">
import N8nText from '../N8nText';
import Vue from 'vue';
import { defineComponent } from 'vue';
export default Vue.extend({
export default defineComponent({
props: {
theme: {
type: String,

View File

@@ -18,11 +18,11 @@
</template>
<script lang="ts">
import Vue from 'vue';
import { defineComponent } from 'vue';
import N8nIcon from '../N8nIcon';
import N8nSpinner from '../N8nSpinner';
export default Vue.extend({
export default defineComponent({
name: 'n8n-button',
props: {
label: {
@@ -81,11 +81,11 @@ export default Vue.extend({
N8nIcon,
},
computed: {
ariaBusy(): string {
return this.loading ? 'true' : 'false';
ariaBusy(): 'true' | undefined {
return this.loading ? 'true' : undefined;
},
ariaDisabled(): string {
return this.disabled ? 'true' : 'false';
ariaDisabled(): 'true' | undefined {
return this.disabled ? 'true' : undefined;
},
classes(): string {
return (

View File

@@ -1,23 +1,23 @@
// Vitest Snapshot v1
exports[`components > N8nButton > overrides > should render as \`secondary\` when \`text\` is given as type 1`] = `"<button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button button secondary medium icon\\" icon=\\"plus-circle\\"><span class=\\"icon\\"><n8n-icon-stub icon=\\"plus-circle\\" size=\\"medium\\"></n8n-icon-stub></span><span>Button</span></button>"`;
exports[`components > N8nButton > overrides > should render as \`secondary\` when \`text\` is given as type 1`] = `"<button aria-live=\\"polite\\" class=\\"button button secondary medium icon\\" icon=\\"plus-circle\\"><span class=\\"icon\\"><n8n-icon-stub icon=\\"plus-circle\\" size=\\"medium\\"></n8n-icon-stub></span><span>Button</span></button>"`;
exports[`components > N8nButton > overrides > should render as \`tertiary\` when \`info\` is given as type 1`] = `"<button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button button tertiary medium icon\\" icon=\\"plus-circle\\"><span class=\\"icon\\"><n8n-icon-stub icon=\\"plus-circle\\" size=\\"medium\\"></n8n-icon-stub></span><span>Button</span></button>"`;
exports[`components > N8nButton > overrides > should render as \`tertiary\` when \`info\` is given as type 1`] = `"<button aria-live=\\"polite\\" class=\\"button button tertiary medium icon\\" icon=\\"plus-circle\\"><span class=\\"icon\\"><n8n-icon-stub icon=\\"plus-circle\\" size=\\"medium\\"></n8n-icon-stub></span><span>Button</span></button>"`;
exports[`components > N8nButton > overrides > should use default (\`primary\`) type when no type is given 1`] = `"<button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button button primary medium icon\\" icon=\\"plus-circle\\"><span class=\\"icon\\"><n8n-icon-stub icon=\\"plus-circle\\" size=\\"medium\\"></n8n-icon-stub></span><span>Button</span></button>"`;
exports[`components > N8nButton > overrides > should use default (\`primary\`) type when no type is given 1`] = `"<button aria-live=\\"polite\\" class=\\"button button primary medium icon\\" icon=\\"plus-circle\\"><span class=\\"icon\\"><n8n-icon-stub icon=\\"plus-circle\\" size=\\"medium\\"></n8n-icon-stub></span><span>Button</span></button>"`;
exports[`components > N8nButton > overrides > should use given (\`secondary\`) type 1`] = `"<button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button button secondary medium icon\\" icon=\\"plus-circle\\"><span class=\\"icon\\"><n8n-icon-stub icon=\\"plus-circle\\" size=\\"medium\\"></n8n-icon-stub></span><span>Button</span></button>"`;
exports[`components > N8nButton > overrides > should use given (\`secondary\`) type 1`] = `"<button aria-live=\\"polite\\" class=\\"button button secondary medium icon\\" icon=\\"plus-circle\\"><span class=\\"icon\\"><n8n-icon-stub icon=\\"plus-circle\\" size=\\"medium\\"></n8n-icon-stub></span><span>Button</span></button>"`;
exports[`components > N8nButton > props > icon > should render icon button 1`] = `"<button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button button primary medium icon\\"><span class=\\"icon\\"><n8n-icon-stub icon=\\"plus-circle\\" size=\\"medium\\"></n8n-icon-stub></span><span>Button</span></button>"`;
exports[`components > N8nButton > props > icon > should render icon button 1`] = `"<button aria-live=\\"polite\\" class=\\"button button primary medium icon\\"><span class=\\"icon\\"><n8n-icon-stub icon=\\"plus-circle\\" size=\\"medium\\"></n8n-icon-stub></span><span>Button</span></button>"`;
exports[`components > N8nButton > props > loading > should render loading spinner 1`] = `"<button disabled=\\"disabled\\" aria-disabled=\\"false\\" aria-busy=\\"true\\" aria-live=\\"polite\\" class=\\"button button primary medium loading icon\\"><span class=\\"icon\\"><n8n-spinner-stub size=\\"medium\\" type=\\"dots\\"></n8n-spinner-stub></span><span>Button</span></button>"`;
exports[`components > N8nButton > props > loading > should render loading spinner 1`] = `"<button disabled=\\"disabled\\" aria-busy=\\"true\\" aria-live=\\"polite\\" class=\\"button button primary medium loading icon\\"><span class=\\"icon\\"><n8n-spinner-stub size=\\"medium\\" type=\\"dots\\"></n8n-spinner-stub></span><span>Button</span></button>"`;
exports[`components > N8nButton > props > square > should render square button 1`] = `
"<button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button button primary medium square\\">
"<button aria-live=\\"polite\\" class=\\"button button primary medium square\\">
<!----><span>48</span></button>"
`;
exports[`components > N8nButton > should render correctly 1`] = `
"<button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button button primary medium\\">
"<button aria-live=\\"polite\\" class=\\"button button primary medium\\">
<!----><span>Button</span></button>"
`;

View File

@@ -5,7 +5,7 @@
</template>
<script lang="ts">
import Vue from 'vue';
import { defineComponent } from 'vue';
import N8nButton from '../Button.vue';
const classToTypeMap = {
@@ -13,7 +13,7 @@ const classToTypeMap = {
'el-picker-panel__link-btn': 'secondary',
};
export default Vue.extend({
export default defineComponent({
components: {
N8nButton,
},

View File

@@ -16,7 +16,7 @@
</template>
<script lang="ts">
import Vue from 'vue';
import { defineComponent } from 'vue';
import N8nText from '../N8nText';
import N8nIcon from '../N8nIcon';
@@ -27,7 +27,7 @@ const CALLOUT_DEFAULT_ICONS: { [key: string]: string } = {
danger: 'times-circle',
};
export default Vue.extend({
export default defineComponent({
name: 'n8n-callout',
components: {
N8nText,

View File

@@ -21,9 +21,9 @@
</template>
<script lang="ts">
import Vue from 'vue';
import { defineComponent } from 'vue';
export default Vue.extend({
export default defineComponent({
name: 'n8n-card',
inheritAttrs: true,
props: {

View File

@@ -19,11 +19,11 @@
</template>
<script lang="ts">
import Vue from 'vue';
import { defineComponent } from 'vue';
import { Checkbox as ElCheckbox } from 'element-ui';
import N8nInputLabel from '../N8nInputLabel';
export default Vue.extend({
export default defineComponent({
name: 'n8n-checkbox',
components: {
ElCheckbox,

View File

@@ -112,7 +112,7 @@ export default defineComponent({
</tr>
</thead>
<tbody>
<tr v-for="row in visibleRows" :key="row.id" :class="getTrClass(row)">
<tr v-for="row in visibleRows" :key="row.id" :class="getTrClass()">
<td v-for="column in columns" :key="column.id">
<component v-if="column.render" :is="column.render" :row="row" :column="column" />
<span v-else>{{ getTdValue(row, column) }}</span>

View File

@@ -16,70 +16,70 @@ exports[`components > N8nDatatable > should render correctly 1`] = `
<td><span>1</span></td>
<td><span>Richard Hendricks</span></td>
<td><span>29</span></td>
<td><button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button button primary medium\\" column=\\"[object Object]\\">
<td><button aria-live=\\"polite\\" class=\\"button button primary medium\\" column=\\"[object Object]\\">
<!----><span>Button 1</span></button></td>
</tr>
<tr class=\\"datatableRow\\">
<td><span>2</span></td>
<td><span>Bertram Gilfoyle</span></td>
<td><span>44</span></td>
<td><button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button button primary medium\\" column=\\"[object Object]\\">
<td><button aria-live=\\"polite\\" class=\\"button button primary medium\\" column=\\"[object Object]\\">
<!----><span>Button 2</span></button></td>
</tr>
<tr class=\\"datatableRow\\">
<td><span>3</span></td>
<td><span>Dinesh Chugtai</span></td>
<td><span>31</span></td>
<td><button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button button primary medium\\" column=\\"[object Object]\\">
<td><button aria-live=\\"polite\\" class=\\"button button primary medium\\" column=\\"[object Object]\\">
<!----><span>Button 3</span></button></td>
</tr>
<tr class=\\"datatableRow\\">
<td><span>4</span></td>
<td><span>Jared Dunn </span></td>
<td><span>38</span></td>
<td><button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button button primary medium\\" column=\\"[object Object]\\">
<td><button aria-live=\\"polite\\" class=\\"button button primary medium\\" column=\\"[object Object]\\">
<!----><span>Button 4</span></button></td>
</tr>
<tr class=\\"datatableRow\\">
<td><span>5</span></td>
<td><span>Richard Hendricks</span></td>
<td><span>29</span></td>
<td><button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button button primary medium\\" column=\\"[object Object]\\">
<td><button aria-live=\\"polite\\" class=\\"button button primary medium\\" column=\\"[object Object]\\">
<!----><span>Button 5</span></button></td>
</tr>
<tr class=\\"datatableRow\\">
<td><span>6</span></td>
<td><span>Bertram Gilfoyle</span></td>
<td><span>44</span></td>
<td><button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button button primary medium\\" column=\\"[object Object]\\">
<td><button aria-live=\\"polite\\" class=\\"button button primary medium\\" column=\\"[object Object]\\">
<!----><span>Button 6</span></button></td>
</tr>
<tr class=\\"datatableRow\\">
<td><span>7</span></td>
<td><span>Dinesh Chugtai</span></td>
<td><span>31</span></td>
<td><button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button button primary medium\\" column=\\"[object Object]\\">
<td><button aria-live=\\"polite\\" class=\\"button button primary medium\\" column=\\"[object Object]\\">
<!----><span>Button 7</span></button></td>
</tr>
<tr class=\\"datatableRow\\">
<td><span>8</span></td>
<td><span>Jared Dunn </span></td>
<td><span>38</span></td>
<td><button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button button primary medium\\" column=\\"[object Object]\\">
<td><button aria-live=\\"polite\\" class=\\"button button primary medium\\" column=\\"[object Object]\\">
<!----><span>Button 8</span></button></td>
</tr>
<tr class=\\"datatableRow\\">
<td><span>9</span></td>
<td><span>Richard Hendricks</span></td>
<td><span>29</span></td>
<td><button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button button primary medium\\" column=\\"[object Object]\\">
<td><button aria-live=\\"polite\\" class=\\"button button primary medium\\" column=\\"[object Object]\\">
<!----><span>Button 9</span></button></td>
</tr>
<tr class=\\"datatableRow\\">
<td><span>10</span></td>
<td><span>Bertram Gilfoyle</span></td>
<td><span>44</span></td>
<td><button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button button primary medium\\" column=\\"[object Object]\\">
<td><button aria-live=\\"polite\\" class=\\"button button primary medium\\" column=\\"[object Object]\\">
<!----><span>Button 10</span></button></td>
</tr>
</tbody>

View File

@@ -38,14 +38,14 @@
</template>
<script lang="ts">
import Vue from 'vue';
import { defineComponent } from 'vue';
import N8nFormInputs from '../N8nFormInputs';
import N8nHeading from '../N8nHeading';
import N8nLink from '../N8nLink';
import N8nButton from '../N8nButton';
import { createEventBus } from '../../utils';
export default Vue.extend({
export default defineComponent({
name: 'n8n-form-box',
components: {
N8nHeading,
@@ -56,12 +56,11 @@ export default Vue.extend({
props: {
title: {
type: String,
default: '',
},
inputs: {
type: Array,
default() {
return [];
},
default: () => [],
},
buttonText: {
type: String,
@@ -75,9 +74,11 @@ export default Vue.extend({
},
redirectText: {
type: String,
default: '',
},
redirectLink: {
type: String,
default: '',
},
},
data() {

View File

@@ -92,7 +92,7 @@ import N8nSelect from '../N8nSelect';
import N8nOption from '../N8nOption';
import N8nInputLabel from '../N8nInputLabel';
import N8nCheckbox from '../N8nCheckbox';
import ElSwitch from 'element-ui/lib/switch';
import { Switch as ElSwitch } from 'element-ui';
import { getValidationError, VALIDATORS } from './validators';
import { Rule, RuleGroup, IValidator, Validatable, FormState } from '../../types';

View File

@@ -3,106 +3,120 @@ import { IValidator, RuleGroup, Validatable } from '../../types';
export const emailRegex =
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
export const VALIDATORS: { [key: string]: IValidator | RuleGroup } = {
REQUIRED: {
validate: (value: Validatable) => {
if (typeof value === 'string' && !!value.trim()) {
return false;
}
export const requiredValidator: IValidator<{}> = {
validate: (value: Validatable) => {
if (typeof value === 'string' && !!value.trim()) {
return false;
}
if (typeof value === 'number' || typeof value === 'boolean') {
return false;
}
if (typeof value === 'number' || typeof value === 'boolean') {
return false;
}
return {
messageKey: 'formInput.validator.fieldRequired',
};
},
};
export const minLengthValidator: IValidator<{ minimum: number }> = {
validate: (value: Validatable, config: { minimum: number }) => {
if (typeof value === 'string' && value.length < config.minimum) {
return {
messageKey: 'formInput.validator.fieldRequired',
messageKey: 'formInput.validator.minCharactersRequired',
options: config,
};
},
},
MIN_LENGTH: {
validate: (value: Validatable, config: { minimum: number }) => {
if (typeof value === 'string' && value.length < config.minimum) {
return {
messageKey: 'formInput.validator.minCharactersRequired',
options: config,
};
}
}
return false;
},
};
export const maxLengthValidator: IValidator<{ maximum: number }> = {
validate: (value: Validatable, config: { maximum: number }) => {
if (typeof value === 'string' && value.length > config.maximum) {
return {
messageKey: 'formInput.validator.maxCharactersRequired',
options: config,
};
}
return false;
},
};
export const containsNumberValidator: IValidator<{ minimum: number }> = {
validate: (value: Validatable, config: { minimum: number }) => {
if (typeof value !== 'string') {
return false;
},
},
MAX_LENGTH: {
validate: (value: Validatable, config: { maximum: number }) => {
if (typeof value === 'string' && value.length > config.maximum) {
return {
messageKey: 'formInput.validator.maxCharactersRequired',
options: config,
};
}
}
const numberCount = (value.match(/\d/g) || []).length;
if (numberCount < config.minimum) {
return {
messageKey: 'formInput.validator.numbersRequired',
options: config,
};
}
return false;
},
};
export const emailValidator: IValidator<{}> = {
validate: (value: Validatable) => {
if (!emailRegex.test(String(value).trim().toLowerCase())) {
return {
messageKey: 'formInput.validator.validEmailRequired',
};
}
return false;
},
};
export const containsUpperCaseValidator: IValidator<{ minimum: number }> = {
validate: (value: Validatable, config: { minimum: number }) => {
if (typeof value !== 'string') {
return false;
},
}
const uppercaseCount = (value.match(/[A-Z]/g) || []).length;
if (uppercaseCount < config.minimum) {
return {
messageKey: 'formInput.validator.uppercaseCharsRequired',
options: config,
};
}
return false;
},
CONTAINS_NUMBER: {
validate: (value: Validatable, config: { minimum: number }) => {
if (typeof value !== 'string') {
return false;
}
};
const numberCount = (value.match(/\d/g) || []).length;
if (numberCount < config.minimum) {
return {
messageKey: 'formInput.validator.numbersRequired',
options: config,
};
}
return false;
},
},
VALID_EMAIL: {
validate: (value: Validatable) => {
if (!emailRegex.test(String(value).trim().toLowerCase())) {
return {
messageKey: 'formInput.validator.validEmailRequired',
};
}
return false;
},
},
CONTAINS_UPPERCASE: {
validate: (value: Validatable, config: { minimum: number }) => {
if (typeof value !== 'string') {
return false;
}
const uppercaseCount = (value.match(/[A-Z]/g) || []).length;
if (uppercaseCount < config.minimum) {
return {
messageKey: 'formInput.validator.uppercaseCharsRequired',
options: config,
};
}
return false;
},
},
DEFAULT_PASSWORD_RULES: {
rules: [
{
rules: [
{ name: 'MIN_LENGTH', config: { minimum: 8 } },
{ name: 'CONTAINS_NUMBER', config: { minimum: 1 } },
{ name: 'CONTAINS_UPPERCASE', config: { minimum: 1 } },
],
defaultError: {
messageKey: 'formInput.validator.defaultPasswordRequirements',
},
export const defaultPasswordRules: RuleGroup = {
rules: [
{
rules: [
{ name: 'MIN_LENGTH', config: { minimum: 8 } },
{ name: 'CONTAINS_NUMBER', config: { minimum: 1 } },
{ name: 'CONTAINS_UPPERCASE', config: { minimum: 1 } },
],
defaultError: {
messageKey: 'formInput.validator.defaultPasswordRequirements',
},
{ name: 'MAX_LENGTH', config: { maximum: 64 } },
],
},
},
{ name: 'MAX_LENGTH', config: { maximum: 64 } },
],
};
export const VALIDATORS = {
REQUIRED: requiredValidator,
MIN_LENGTH: minLengthValidator,
MAX_LENGTH: maxLengthValidator,
CONTAINS_NUMBER: containsNumberValidator,
VALID_EMAIL: emailValidator,
CONTAINS_UPPERCASE: containsUpperCaseValidator,
DEFAULT_PASSWORD_RULES: defaultPasswordRules,
};
export const getValidationError = <T extends Validatable, C>(

View File

@@ -5,7 +5,7 @@
<div
v-for="(input, index) in filteredInputs"
:key="input.name"
:class="{ [`mt-${verticalSpacing}`]: index > 0 }"
:class="{ [`mt-${verticalSpacing}`]: verticalSpacing && index > 0 }"
>
<n8n-text
color="text-base"
@@ -37,13 +37,13 @@
</template>
<script lang="ts">
import Vue, { PropType } from 'vue';
import { defineComponent, PropType } from 'vue';
import N8nFormInput from '../N8nFormInput';
import type { IFormInput } from '../../types';
import ResizeObserver from '../ResizeObserver';
import { EventBus } from '@/utils';
import { createEventBus, EventBus } from '../../utils';
export default Vue.extend({
export default defineComponent({
name: 'n8n-form-inputs',
components: {
N8nFormInput,
@@ -51,13 +51,12 @@ export default Vue.extend({
},
props: {
inputs: {
type: Array,
default() {
return [[]];
},
type: Array as PropType<IFormInput[]>,
default: (): IFormInput[] => [],
},
eventBus: {
type: Object as PropType<EventBus>,
default: (): EventBus => createEventBus(),
},
columnView: {
type: Boolean,
@@ -65,8 +64,8 @@ export default Vue.extend({
},
verticalSpacing: {
type: String,
required: false,
validator: (value: string): boolean => ['xs', 's', 'm', 'm', 'l', 'xl'].includes(value),
default: '',
validator: (value: string): boolean => ['', 'xs', 's', 'm', 'm', 'l', 'xl'].includes(value),
},
},
data() {
@@ -77,19 +76,19 @@ export default Vue.extend({
};
},
mounted() {
(this.inputs as IFormInput[]).forEach((input) => {
this.inputs.forEach((input) => {
if (input.hasOwnProperty('initialValue')) {
Vue.set(this.values, input.name, input.initialValue);
this.$set(this.values, input.name, input.initialValue);
}
});
if (this.eventBus) {
this.eventBus.on('submit', this.onSubmit); // eslint-disable-line @typescript-eslint/unbound-method
this.eventBus.on('submit', () => this.onSubmit());
}
},
computed: {
filteredInputs(): IFormInput[] {
return (this.inputs as IFormInput[]).filter((input) =>
return this.inputs.filter((input) =>
typeof input.shouldDisplay === 'function' ? input.shouldDisplay(this.values) : true,
);
},
@@ -107,15 +106,16 @@ export default Vue.extend({
onInput(name: string, value: unknown) {
this.values = {
...this.values,
[name]: value, // eslint-disable-line @typescript-eslint/no-unsafe-assignment
[name]: value,
};
this.$emit('input', { name, value }); // eslint-disable-line @typescript-eslint/no-unsafe-assignment
this.$emit('input', { name, value });
},
onValidate(name: string, valid: boolean) {
Vue.set(this.validity, name, valid);
this.$set(this.validity, name, valid);
},
onSubmit() {
this.showValidationWarnings = true;
if (this.isReadyToSubmit) {
const toSubmit = this.filteredInputs.reduce<{ [key: string]: unknown }>((accu, input) => {
if (this.values[input.name]) {

View File

@@ -5,9 +5,9 @@
</template>
<script lang="ts">
import Vue from 'vue';
import { defineComponent } from 'vue';
export default Vue.extend({
export default defineComponent({
name: 'n8n-heading',
props: {
tag: {

View File

@@ -8,9 +8,9 @@
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
import N8nText from '../N8nText';
import Vue from 'vue';
import { defineComponent } from 'vue';
export default Vue.extend({
export default defineComponent({
name: 'n8n-icon',
components: {
FontAwesomeIcon,

View File

@@ -5,9 +5,9 @@
<script lang="ts">
import N8nButton from '../N8nButton';
import Vue from 'vue';
import { defineComponent } from 'vue';
export default Vue.extend({
export default defineComponent({
name: 'n8n-icon-button',
components: {
N8nButton,

View File

@@ -41,15 +41,17 @@
<script lang="ts">
import N8nText from '../N8nText';
import N8nIcon from '../N8nIcon';
import Vue, { PropType } from 'vue';
import { defineComponent, PropType } from 'vue';
interface IAccordionItem {
export interface IAccordionItem {
id: string;
label: string;
icon: string;
iconColor?: string;
tooltip?: string;
}
export default Vue.extend({
export default defineComponent({
name: 'n8n-info-accordion',
components: {
N8nText,
@@ -64,9 +66,7 @@ export default Vue.extend({
},
items: {
type: Array as PropType<IAccordionItem[]>,
default() {
return [];
},
default: () => [],
},
initiallyExpanded: {
type: Boolean,
@@ -92,7 +92,7 @@ export default Vue.extend({
toggle() {
this.expanded = !this.expanded;
},
onClick(e) {
onClick(e: MouseEvent) {
this.$emit('click', e);
},
onTooltipClick(item: string, event: MouseEvent) {

View File

@@ -35,9 +35,9 @@
import N8nIcon from '../N8nIcon';
import N8nTooltip from '../N8nTooltip';
import Vue from 'vue';
import { defineComponent } from 'vue';
export default Vue.extend({
export default defineComponent({
name: 'n8n-info-tip',
components: {
N8nIcon,

View File

@@ -25,9 +25,9 @@
<script lang="ts">
import { Input as ElInput } from 'element-ui';
import Vue from 'vue';
import { defineComponent } from 'vue';
export default Vue.extend({
export default defineComponent({
name: 'n8n-input',
components: {
ElInput,

View File

@@ -47,9 +47,9 @@ import N8nIcon from '../N8nIcon';
import { addTargetBlank } from '../utils/helpers';
import Vue from 'vue';
import { defineComponent } from 'vue';
export default Vue.extend({
export default defineComponent({
name: 'n8n-input-label',
components: {
N8nText,

View File

@@ -9,11 +9,11 @@
</template>
<script lang="ts">
import Vue from 'vue';
import { defineComponent } from 'vue';
import N8nText from '../N8nText';
import N8nRoute from '../N8nRoute';
export default Vue.extend({
export default defineComponent({
name: 'n8n-link',
props: {
size: {

View File

@@ -38,9 +38,9 @@
<script lang="ts">
import { Skeleton as ElSkeleton, SkeletonItem as ElSkeletonItem } from 'element-ui';
import Vue from 'vue';
import { defineComponent } from 'vue';
export default Vue.extend({
export default defineComponent({
name: 'n8n-loading',
components: {
ElSkeleton,

View File

@@ -19,14 +19,14 @@
<script lang="ts">
import N8nLoading from '../N8nLoading';
import Markdown from 'markdown-it';
import Markdown, { PluginSimple } from 'markdown-it';
import markdownLink from 'markdown-it-link-attributes';
import markdownEmoji from 'markdown-it-emoji';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import markdownTasklists from 'markdown-it-task-lists';
import { defineComponent, PropType } from 'vue';
import xss, { friendlyAttrValue } from 'xss';
import { escapeMarkdown } from '../../utils/markdown';
@@ -49,20 +49,18 @@ const DEFAULT_OPTIONS_TASKLISTS = {
labelAfter: true,
} as const;
interface IImage {
export interface IImage {
id: string;
url: string;
}
interface Options {
export interface Options {
markdown: typeof DEFAULT_OPTIONS_MARKDOWN;
linkAttributes: typeof DEFAULT_OPTIONS_LINK_ATTRIBUTES;
tasklists: typeof DEFAULT_OPTIONS_TASKLISTS;
}
import Vue, { PropType } from 'vue';
export default Vue.extend({
export default defineComponent({
components: {
N8nLoading,
},
@@ -70,15 +68,19 @@ export default Vue.extend({
props: {
content: {
type: String,
default: '',
},
withMultiBreaks: {
type: Boolean,
default: false,
},
images: {
type: Array<IImage>,
type: Array as PropType<IImage[]>,
default: () => [],
},
loading: {
type: Boolean,
default: false,
},
loadingBlocks: {
type: Number,
@@ -86,7 +88,7 @@ export default Vue.extend({
},
loadingRows: {
type: Number,
default: () => 3,
default: 3,
},
theme: {
type: String,
@@ -94,15 +96,21 @@ export default Vue.extend({
},
options: {
type: Object as PropType<Options>,
default() {
return {
markdown: DEFAULT_OPTIONS_MARKDOWN,
linkAttributes: DEFAULT_OPTIONS_LINK_ATTRIBUTES,
tasklists: DEFAULT_OPTIONS_TASKLISTS,
};
},
default: (): Options => ({
markdown: DEFAULT_OPTIONS_MARKDOWN,
linkAttributes: DEFAULT_OPTIONS_LINK_ATTRIBUTES,
tasklists: DEFAULT_OPTIONS_TASKLISTS,
}),
},
},
data(): { md: Markdown } {
return {
md: new Markdown(this.options.markdown)
.use(markdownLink, this.options.linkAttributes)
.use(markdownEmoji)
.use(markdownTasklists as PluginSimple, this.options.tasklists),
};
},
computed: {
htmlContent(): string {
if (!this.content) {
@@ -154,14 +162,6 @@ export default Vue.extend({
return safeHtml;
},
},
data() {
return {
md: new Markdown(this.options.markdown) // eslint-disable-line @typescript-eslint/no-unsafe-member-access
.use(markdownLink, this.options.linkAttributes) // eslint-disable-line @typescript-eslint/no-unsafe-member-access
.use(markdownEmoji)
.use(markdownTasklists, this.options.tasklists), // eslint-disable-line @typescript-eslint/no-unsafe-member-access
};
},
methods: {
onClick(event: MouseEvent) {
let clickedLink = null;

View File

@@ -54,11 +54,10 @@
<script lang="ts">
import { Menu as ElMenu } from 'element-ui';
import N8nMenuItem from '../N8nMenuItem';
import { defineComponent, PropType } from 'vue';
import type { IMenuItem, RouteObject } from '../../types';
import Vue, { PropType } from 'vue';
import { IMenuItem } from '../../types';
export default Vue.extend({
export default defineComponent({
name: 'n8n-menu',
components: {
ElMenu,
@@ -93,6 +92,7 @@ export default Vue.extend({
},
items: {
type: Array as PropType<IMenuItem[]>,
default: (): IMenuItem[] => [],
},
value: {
type: String,
@@ -104,9 +104,9 @@ export default Vue.extend({
const found = this.items.find((item) => {
return (
(Array.isArray(item.activateOnRouteNames) &&
item.activateOnRouteNames.includes(this.$route.name || '')) ||
item.activateOnRouteNames.includes(this.currentRoute.name || '')) ||
(Array.isArray(item.activateOnRoutePaths) &&
item.activateOnRoutePaths.includes(this.$route.path))
item.activateOnRoutePaths.includes(this.currentRoute.path))
);
});
this.activeTab = found ? found.id : '';
@@ -127,6 +127,14 @@ export default Vue.extend({
(item: IMenuItem) => item.position === 'bottom' && item.available !== false,
);
},
currentRoute(): RouteObject {
return (
(this as typeof this & { $route: RouteObject }).$route || {
name: '',
path: '',
}
);
},
},
methods: {
onSelect(event: MouseEvent, option: string): void {

View File

@@ -32,7 +32,7 @@
}"
data-test-id="menu-item"
:index="child.id"
@click="onItemClick(child)"
@click="onItemClick(child, $event)"
>
<n8n-icon v-if="child.icon" :class="$style.icon" :icon="child.icon" />
<span :class="$style.label">{{ child.label }}</span>
@@ -56,7 +56,7 @@
}"
data-test-id="menu-item"
:index="item.id"
@click="onItemClick(item)"
@click="onItemClick(item, $event)"
>
<n8n-icon
v-if="item.icon"
@@ -74,10 +74,10 @@
import { Submenu as ElSubmenu, MenuItem as ElMenuItem } from 'element-ui';
import N8nTooltip from '../N8nTooltip';
import N8nIcon from '../N8nIcon';
import { IMenuItem } from '../../types';
import Vue, { PropType } from 'vue';
import { defineComponent, PropType } from 'vue';
import type { IMenuItem, RouteObject } from '../../types';
export default Vue.extend({
export default defineComponent({
name: 'n8n-menu-item',
components: {
ElSubmenu,
@@ -117,6 +117,14 @@ export default Vue.extend({
? this.item.children.filter((child) => child.available !== false)
: [];
},
currentRoute(): RouteObject {
return (
(this as typeof this & { $route: RouteObject }).$route || {
name: '',
path: '',
}
);
},
},
methods: {
isItemActive(item: IMenuItem): boolean {
@@ -130,12 +138,12 @@ export default Vue.extend({
if (item.activateOnRoutePaths) {
return (
Array.isArray(item.activateOnRoutePaths) &&
item.activateOnRoutePaths.includes(this.$route.path)
item.activateOnRoutePaths.includes(this.currentRoute.path)
);
} else if (item.activateOnRouteNames) {
return (
Array.isArray(item.activateOnRouteNames) &&
item.activateOnRouteNames.includes(this.$route.name || '')
item.activateOnRouteNames.includes(this.currentRoute.name || '')
);
}
return false;

View File

@@ -36,11 +36,11 @@
</template>
<script lang="ts">
import Vue from 'vue';
import { defineComponent } from 'vue';
import N8nTooltip from '../N8nTooltip';
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
export default Vue.extend({
export default defineComponent({
name: 'n8n-node-icon',
components: {
N8nTooltip,
@@ -78,12 +78,13 @@ export default Vue.extend({
},
},
computed: {
iconStyleData(): object {
iconStyleData(): Record<string, string> {
if (!this.size) {
return {
color: this.color || '',
};
}
return {
color: this.color || '',
width: `${this.size}px`,
@@ -92,7 +93,11 @@ export default Vue.extend({
'line-height': `${this.size}px`,
};
},
fontStyleData(): object {
fontStyleData(): Record<string, string> {
if (!this.size) {
return {};
}
return {
'max-width': `${this.size}px`,
};

View File

@@ -16,13 +16,13 @@
</template>
<script lang="ts">
import Vue from 'vue';
import { defineComponent } from 'vue';
import sanitizeHtml from 'sanitize-html';
import N8nText from '../../components/N8nText';
import Locale from '../../mixins/locale';
import { uid } from '../../utils';
export default Vue.extend({
export default defineComponent({
name: 'n8n-notice',
directives: {},
mixins: [Locale],

View File

@@ -9,9 +9,9 @@
</template>
<script>
import Vue from 'vue';
import { defineComponent } from 'vue';
export default Vue.extend({
export default defineComponent({
name: 'n8n-pulse',
});
</script>

View File

@@ -5,7 +5,7 @@
:class="{
'n8n-radio-button': true,
[$style.container]: true,
[$style.hoverable]: !this.disabled,
[$style.hoverable]: !disabled,
}"
aria-checked="true"
>
@@ -26,9 +26,9 @@
</template>
<script lang="ts">
import Vue from 'vue';
import { defineComponent } from 'vue';
export default Vue.extend({
export default defineComponent({
name: 'n8n-radio-button',
props: {
label: {

View File

@@ -10,7 +10,7 @@
:active="value === option.value"
:size="size"
:disabled="disabled || option.disabled"
@click="(e) => onClick(option, e)"
@click="() => onClick(option)"
/>
</div>
</template>
@@ -18,15 +18,24 @@
<script lang="ts">
import RadioButton from './RadioButton.vue';
import Vue from 'vue';
import { defineComponent, PropType } from 'vue';
export default Vue.extend({
export interface RadioOption {
label: string;
value: string;
disabled?: boolean;
}
export default defineComponent({
name: 'n8n-radio-buttons',
props: {
value: {
type: String,
},
options: {},
options: {
type: Array as PropType<RadioOption[]>,
default: (): RadioOption[] => [],
},
size: {
type: String,
},

View File

@@ -10,6 +10,7 @@ import {
PropType,
nextTick,
watch,
ComponentPublicInstance,
} from 'vue';
export default defineComponent({
@@ -36,7 +37,7 @@ export default defineComponent({
const wrapperRef = ref<HTMLElement | null>(null);
const scrollerRef = ref<HTMLElement | null>(null);
const itemsRef = ref<HTMLElement | null>(null);
const itemRefs = ref<Record<string, HTMLElement | null>>({});
const itemRefs = ref<Record<string, Element | ComponentPublicInstance | null>>({});
const scrollTop = ref(0);
const wrapperHeight = ref(0);
@@ -174,7 +175,7 @@ export default defineComponent({
function onUpdateItemSize(item: { [key: string]: string }) {
nextTick(() => {
const itemId = item[props.itemKey];
const itemRef = itemRefs.value[itemId];
const itemRef = itemRefs.value[itemId] as HTMLElement;
const previousSize = itemSizeCache.value[itemId];
const size = itemRef ? itemRef.offsetHeight : props.itemSize;
const difference = size - previousSize;

View File

@@ -4,7 +4,7 @@
v-for="direction in enabledDirections"
:key="direction"
:data-dir="direction"
:class="[$style.resizer, $style[direction]]"
:class="{ [$style.resizer]: true, [$style[direction]]: true }"
@mousedown="resizerMove"
/>
<slot></slot>
@@ -13,7 +13,7 @@
<script lang="ts">
/* eslint-disable @typescript-eslint/unbound-method */
import Vue from 'vue';
import { defineComponent, PropType } from 'vue';
function closestNumber(value: number, divisor: number): number {
const q = value / divisor;
@@ -46,7 +46,7 @@ const directionsCursorMaps: { [key: string]: string } = {
bottomRight: 'se-resize',
};
export default Vue.extend({
export default defineComponent({
name: 'n8n-resize',
props: {
isResizingEnabled: {
@@ -55,15 +55,19 @@ export default Vue.extend({
},
height: {
type: Number,
default: 0,
},
width: {
type: Number,
default: 0,
},
minHeight: {
type: Number,
default: 0,
},
minWidth: {
type: Number,
default: 0,
},
scale: {
type: Number,
@@ -71,10 +75,11 @@ export default Vue.extend({
},
gridSize: {
type: Number,
default: 20,
},
supportedDirections: {
type: Array,
default: () => [],
type: Array as PropType<string[]>,
default: (): string[] => [],
},
},
data() {
@@ -90,7 +95,7 @@ export default Vue.extend({
};
},
computed: {
enabledDirections() {
enabledDirections(): string[] {
const availableDirections = Object.keys(directionsCursorMaps);
if (!this.isResizingEnabled) return [];

View File

@@ -10,9 +10,9 @@
</template>
<script lang="ts">
import Vue from 'vue';
import { defineComponent } from 'vue';
export default Vue.extend({
export default defineComponent({
name: 'n8n-route',
props: {
to: {
@@ -25,12 +25,13 @@ export default Vue.extend({
},
computed: {
useRouterLink() {
if (this.newWindow === true) {
if (this.newWindow) {
// router-link does not support click events and opening in new window
return false;
}
if (typeof this.to === 'string') {
return (this.to as string).startsWith('/');
return this.to.startsWith('/');
}
return this.to !== undefined;
@@ -39,8 +40,9 @@ export default Vue.extend({
if (this.newWindow !== undefined) {
return this.newWindow;
}
if (typeof this.to === 'string') {
return !(this.to as string).startsWith('/');
return !this.to.startsWith('/');
}
return true;
},

View File

@@ -33,7 +33,7 @@
<script lang="ts">
import { Select as ElSelect } from 'element-ui';
import Vue from 'vue';
import { defineComponent } from 'vue';
export interface IProps {
size?: string;
@@ -41,7 +41,7 @@ export interface IProps {
popperClass?: string;
}
export default Vue.extend({
export default defineComponent({
name: 'n8n-select',
components: {
ElSelect,

View File

@@ -13,9 +13,9 @@
<script lang="ts">
import N8nIcon from '../N8nIcon';
import Vue from 'vue';
import { defineComponent } from 'vue';
export default Vue.extend({
export default defineComponent({
name: 'n8n-spinner',
components: {
N8nIcon,

View File

@@ -66,10 +66,11 @@ import N8nMarkdown from '../N8nMarkdown';
import N8nResizeWrapper from '../N8nResizeWrapper';
import N8nText from '../N8nText';
import Locale from '../../mixins/locale';
import mixins from 'vue-typed-mixins';
import { defineComponent } from 'vue';
export default mixins(Locale).extend({
export default defineComponent({
name: 'n8n-sticky',
mixins: [Locale],
props: {
content: {
type: String,

View File

@@ -47,10 +47,19 @@
</template>
<script lang="ts">
import Vue from 'vue';
import { defineComponent, PropType } from 'vue';
import N8nIcon from '../N8nIcon';
export default Vue.extend({
export interface N8nTabOptions {
value: string;
label?: string;
icon?: string;
href?: string;
tooltip?: string;
align?: 'left' | 'right';
}
export default defineComponent({
name: 'N8nTabs',
components: {
N8nIcon,
@@ -92,7 +101,10 @@ export default Vue.extend({
},
props: {
value: {},
options: {},
options: {
type: Array as PropType<N8nTabOptions[]>,
default: (): N8nTabOptions[] => [],
},
},
methods: {
handleTooltipClick(tab: string, event: MouseEvent) {

View File

@@ -5,9 +5,9 @@
</template>
<script lang="ts">
import Vue from 'vue';
import { defineComponent } from 'vue';
export default Vue.extend({
export default defineComponent({
name: 'n8n-tag',
props: {
text: {

View File

@@ -13,7 +13,7 @@
size="small"
@click.stop.prevent="onExpand"
>
{{ t('tags.showMore', hiddenTagsLength) }}
{{ t('tags.showMore', `${hiddenTagsLength}`) }}
</n8n-link>
</div>
</template>
@@ -22,16 +22,16 @@
import N8nTag from '../N8nTag';
import N8nLink from '../N8nLink';
import Locale from '../../mixins/locale';
import { PropType } from 'vue';
import mixins from 'vue-typed-mixins';
import { defineComponent, PropType } from 'vue';
interface ITag {
export interface ITag {
id: string;
name: string;
}
export default mixins(Locale).extend({
export default defineComponent({
name: 'n8n-tags',
mixins: [Locale],
components: {
N8nTag,
N8nLink,

View File

@@ -5,8 +5,8 @@
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
import { defineComponent } from 'vue';
export default defineComponent({
name: 'n8n-text',
props: {
bold: {

View File

@@ -20,12 +20,12 @@
</template>
<script lang="ts">
import Vue, { PropType } from 'vue';
import { defineComponent, PropType } from 'vue';
import { Tooltip as ElTooltip } from 'element-ui';
import type { IN8nButton } from '@/types';
import N8nButton from '../N8nButton';
export default Vue.extend({
export default defineComponent({
name: 'n8n-tooltip',
inheritAttrs: false,
components: {

View File

@@ -1,10 +1,6 @@
<template>
<div class="n8n-tree">
<div
v-for="(label, i) in Object.keys(value || {})"
:key="i"
:class="{ [nodeClass]: !!nodeClass, [$style.indent]: depth > 0 }"
>
<div v-for="(label, i) in Object.keys(value)" :key="i" :class="classes">
<div :class="$style.simple" v-if="isSimple(value[label])">
<slot
v-if="$scopedSlots.label"
@@ -41,15 +37,18 @@
</template>
<script lang="ts">
import Vue from 'vue';
import { defineComponent, PropType } from 'vue';
export default Vue.extend({
export default defineComponent({
name: 'n8n-tree',
components: {},
props: {
value: {},
value: {
type: Object as PropType<Record<string, unknown>>,
default: () => ({}),
},
path: {
type: Array,
type: Array as PropType<string[]>,
default: () => [],
},
depth: {
@@ -58,6 +57,12 @@ export default Vue.extend({
},
nodeClass: {
type: String,
default: '',
},
},
computed: {
classes(): Record<string, boolean> {
return { [this.nodeClass]: !!this.nodeClass, [this.$style.indent]: this.depth > 0 };
},
},
methods: {

View File

@@ -12,7 +12,7 @@
<div>
<n8n-text :bold="true" color="text-dark">
{{ firstName }} {{ lastName }}
{{ isCurrentUser ? this.t('nds.userInfo.you') : '' }}
{{ isCurrentUser ? t('nds.userInfo.you') : '' }}
</n8n-text>
<span v-if="disabled" :class="$style.pendingBadge">
<n8n-badge :bold="true">Disabled</n8n-badge>
@@ -31,15 +31,15 @@
</template>
<script lang="ts">
import 'vue';
import N8nText from '../N8nText';
import N8nAvatar from '../N8nAvatar';
import N8nBadge from '../N8nBadge';
import Locale from '../../mixins/locale';
import mixins from 'vue-typed-mixins';
import { defineComponent } from 'vue';
export default mixins(Locale).extend({
export default defineComponent({
name: 'n8n-users-info',
mixins: [Locale],
components: {
N8nAvatar,
N8nText,

View File

@@ -30,41 +30,39 @@
</template>
<script lang="ts">
import 'vue';
import mixins from 'vue-typed-mixins';
import { Select as ElSelect, Option as ElOption } from 'element-ui';
import N8nUserInfo from '../N8nUserInfo';
import N8nSelect from '../N8nSelect';
import N8nOption from '../N8nOption';
import { IUser } from '../../types';
import Locale from '../../mixins/locale';
import { t } from '../../locale';
import { defineComponent, PropType } from 'vue';
export default mixins(Locale).extend({
export default defineComponent({
name: 'n8n-user-select',
mixins: [Locale],
components: {
N8nUserInfo,
ElSelect,
ElOption,
N8nSelect,
N8nOption,
},
props: {
users: {
type: Array,
default() {
return [];
},
type: Array as PropType<IUser[]>,
default: () => [],
},
value: {
type: String,
default: '',
},
ignoreIds: {
type: Array,
default() {
return [];
},
type: Array as PropType<string[]>,
default: () => [],
validator: (ids: string[]) => !ids.find((id) => typeof id !== 'string'),
},
currentUserId: {
type: String,
default: '',
},
placeholder: {
type: String,
@@ -72,6 +70,7 @@ export default mixins(Locale).extend({
},
size: {
type: String,
default: '',
validator: (value: string): boolean => ['mini', 'small', 'medium', 'large'].includes(value),
},
},
@@ -82,12 +81,12 @@ export default mixins(Locale).extend({
},
computed: {
filteredUsers(): IUser[] {
return (this.users as IUser[]).filter((user) => {
return this.users.filter((user) => {
if (user.isPendingUser || !user.email) {
return false;
}
if (this.ignoreIds && this.ignoreIds.includes(user.id)) {
if (this.ignoreIds.includes(user.id)) {
return false;
}

View File

@@ -32,16 +32,16 @@
</template>
<script lang="ts">
import { IUser, IUserListAction } from '../../types';
import type { IUser, UserAction } from '../../types';
import N8nActionToggle from '../N8nActionToggle';
import N8nBadge from '../N8nBadge';
import N8nUserInfo from '../N8nUserInfo';
import Locale from '../../mixins/locale';
import mixins from 'vue-typed-mixins';
import { PropType } from 'vue';
import { defineComponent, PropType } from 'vue';
export default mixins(Locale).extend({
export default defineComponent({
name: 'n8n-users-list',
mixins: [Locale],
components: {
N8nActionToggle,
N8nBadge,
@@ -63,7 +63,7 @@ export default mixins(Locale).extend({
type: String,
},
actions: {
type: Array as PropType<IUserListAction[]>,
type: Array as PropType<UserAction[]>,
default: () => [],
},
},
@@ -107,7 +107,7 @@ export default mixins(Locale).extend({
},
},
methods: {
getActions(user: IUser): IUserListAction[] {
getActions(user: IUser): UserAction[] {
if (user.isOwner) {
return [];
}

View File

@@ -5,9 +5,9 @@
</template>
<script lang="ts">
import Vue from 'vue';
import { defineComponent } from 'vue';
export default Vue.extend({
export default defineComponent({
name: 'ResizeObserver',
props: {
enabled: {
@@ -62,7 +62,7 @@ export default Vue.extend({
});
});
this.$data.observer = observer;
this.observer = observer;
if (this.$refs.root) {
observer.observe(this.$refs.root as HTMLDivElement);
@@ -70,7 +70,7 @@ export default Vue.extend({
},
beforeDestroy() {
if (this.enabled) {
this.$data.observer.disconnect(); // eslint-disable-line
this.observer?.disconnect(); // eslint-disable-line
}
},
});

View File

@@ -11,7 +11,7 @@ const RE_NARGS = /(%|)\{([0-9a-zA-Z_]+)\}/g;
* https://github.com/ElemeFE/element/blob/dev/src/locale/format.js
* https://github.com/Matt-Esch/string-template/index.js
*/
export default function (Vue) {
export default function () {
/**
* template
*

View File

@@ -1,12 +1,11 @@
import defaultLang from '../locale/lang/en';
import Vue from 'vue';
import Format from './format';
import ElementLocale from 'element-ui/lib/locale';
import ElementLang from 'element-ui/lib/locale/lang/en';
ElementLocale.use(ElementLang);
const format = Format(Vue);
const format = Format();
let lang = defaultLang;
let i18nHandler;

View File

@@ -1,4 +1,4 @@
import Vue from 'vue';
import type { PluginObject } from 'vue';
import N8nActionBox from '../components/N8nActionBox';
import N8nActionDropdown from '../components/N8nActionDropdown';
import N8nActionToggle from '../components/N8nActionToggle';
@@ -47,8 +47,8 @@ import N8nUsersList from '../components/N8nUsersList';
import N8nResizeWrapper from '../components/N8nResizeWrapper';
import N8nRecycleScroller from '../components/N8nRecycleScroller';
export default {
install: (app: typeof Vue) => {
const n8nComponentsPlugin: PluginObject<{}> = {
install: (app) => {
app.component('n8n-info-accordion', N8nInfoAccordion);
app.component('n8n-action-box', N8nActionBox);
app.component('n8n-action-dropdown', N8nActionDropdown);
@@ -98,3 +98,5 @@ export default {
app.component('n8n-recycle-scroller', N8nRecycleScroller);
},
};
export default n8nComponentsPlugin;

View File

@@ -0,0 +1,4 @@
declare module 'markdown-it-task-lists' {
import type { PluginSimple } from 'markdown-it';
export default plugin as PluginSimple<{}>;
}

View File

@@ -10,7 +10,7 @@
</template>
<script lang="ts">
import Vue from 'vue';
import { defineComponent, PropType } from 'vue';
function hslToHex(h: number, s: number, l: number): string {
l /= 100;
@@ -36,7 +36,7 @@ function getHex(hsl: string): string {
return hslToHex(colors[0], colors[1], colors[2]);
}
export default Vue.extend({
export default defineComponent({
name: 'color-circles',
data() {
return {
@@ -46,16 +46,16 @@ export default Vue.extend({
},
props: {
colors: {
type: Array,
type: Array as PropType<string[]>,
required: true,
},
},
created() {
const setColors = () => {
(this.colors as string[]).forEach((color: string) => {
this.colors.forEach((color) => {
const style = getComputedStyle(document.body);
Vue.set(this.hsl, color, style.getPropertyValue(color));
this.$set(this.hsl, color, style.getPropertyValue(color));
});
};

View File

@@ -18,33 +18,34 @@
</template>
<script lang="ts">
import Vue from 'vue';
import { defineComponent, PropType } from 'vue';
export default Vue.extend({
export default defineComponent({
name: 'sizes',
data() {
return {
observer: null as null | MutationObserver,
sizes: {},
sizes: {} as Record<string, { rem: string; px: number }>,
};
},
props: {
variables: {
type: Array,
type: Array as PropType<string[]>,
required: true,
},
attr: {
type: String,
default: '',
},
},
created() {
const setSizes = () => {
(this.variables as string[]).forEach((variable: string) => {
this.variables.forEach((variable: string) => {
const style = getComputedStyle(document.body);
const rem = style.getPropertyValue(variable);
const px = parseFloat(rem.replace('rem', '')) * 16;
Vue.set(this.sizes, variable, { rem, px });
this.$set(this.sizes, variable, { rem, px });
});
};

View File

@@ -16,32 +16,33 @@
</template>
<script lang="ts">
import Vue from 'vue';
import { defineComponent, PropType } from 'vue';
export default Vue.extend({
export default defineComponent({
name: 'variable-table',
data() {
return {
observer: null as null | MutationObserver,
values: {},
values: {} as Record<string, string>,
};
},
props: {
variables: {
type: Array,
type: Array as PropType<string[]>,
required: true,
},
attr: {
type: String,
default: '',
},
},
created() {
const setValues = () => {
(this.variables as string[]).forEach((variable: string) => {
this.variables.forEach((variable) => {
const style = getComputedStyle(document.body);
const value = style.getPropertyValue(variable);
Vue.set(this.values, variable, value);
this.$set(this.values, variable, value);
});
};

View File

@@ -8,10 +8,11 @@
</div>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'SpacingPreview',
props: {
property: {

View File

@@ -7,10 +7,10 @@ export type RuleGroup = {
export type Validatable = string | number | boolean | null | undefined;
export type IValidator = {
export type IValidator<T = unknown> = {
validate: (
value: Validatable,
config: unknown,
config: T,
) => false | { messageKey: string; options?: unknown } | null;
};

View File

@@ -1,4 +1,5 @@
export * from './button';
export * from './form';
export * from './user';
export * from './menu';
export * from './router';
export * from './user';

View File

@@ -0,0 +1,4 @@
export interface RouteObject {
name: string;
path: string;
}

View File

@@ -11,14 +11,10 @@ export interface IUser {
signInType: string;
}
export interface IUserListAction {
label: string;
value: string;
guard?: (user: IUser) => boolean;
}
export interface IUserListAction {
export interface UserAction {
label: string;
value: string;
disabled: boolean;
type?: 'external-link';
guard?: (user: IUser) => boolean;
}

View File

@@ -2,7 +2,13 @@
export type CallbackFn = Function;
export type UnregisterFn = () => void;
export function createEventBus() {
export interface EventBus {
on: (eventName: string, fn: CallbackFn) => UnregisterFn;
off: (eventName: string, fn: CallbackFn) => void;
emit: <T = Event>(eventName: string, event?: T) => void;
}
export function createEventBus(): EventBus {
const handlers = new Map<string, CallbackFn[]>();
function off(eventName: string, fn: CallbackFn) {
@@ -43,5 +49,3 @@ export function createEventBus() {
emit,
};
}
export type EventBus = ReturnType<typeof createEventBus>;

View File

@@ -22,9 +22,7 @@ export default Vue.extend({
},
stickyOffset: {
type: Array as PropType<number[]>,
default() {
return [0, 0];
},
default: () => [0, 0],
},
},
data() {

View File

@@ -195,15 +195,13 @@ export default mixins(debounceHelper, workflowHelpers, nodeHelpers).extend({
inputSize: {
type: String,
default: 'small',
validator: (size) => {
validator: (size: string) => {
return ['mini', 'small', 'medium', 'large', 'xlarge'].includes(size);
},
},
parameterIssues: {
type: Array as PropType<string[]>,
default() {
return [];
},
default: () => [],
},
displayTitle: {
type: String,

View File

@@ -70,7 +70,7 @@ exports[`PersonalizationModal.vue > should render correctly 1`] = `
<!---->
</div>
</div>
<div class=\\"mt-undefined\\">
<div class=\\"\\">
<div class=\\"container\\" data-test-id=\\"role\\"><label for=\\"role\\" class=\\"inputLabel heading medium\\">
<div class=\\"title\\"><span class=\\"n8n-text size-medium bold\\"> Which role best describes you? <!----></span></div>
<!---->
@@ -122,7 +122,7 @@ exports[`PersonalizationModal.vue > should render correctly 1`] = `
<!---->
</div>
</div>
<div class=\\"mt-undefined\\">
<div class=\\"\\">
<div class=\\"container\\" data-test-id=\\"automationBeneficiary\\"><label for=\\"automationBeneficiary\\" class=\\"inputLabel heading medium\\">
<div class=\\"title\\"><span class=\\"n8n-text size-medium bold\\"> Who will your automations mainly be for? <!----></span></div>
<!---->
@@ -168,7 +168,7 @@ exports[`PersonalizationModal.vue > should render correctly 1`] = `
<!---->
</div>
</div>
<div class=\\"mt-undefined\\">
<div class=\\"\\">
<div class=\\"container\\" data-test-id=\\"companySize\\"><label for=\\"companySize\\" class=\\"inputLabel heading medium\\">
<div class=\\"title\\"><span class=\\"n8n-text size-medium bold\\"> How big is your company? <!----></span></div>
<!---->
@@ -217,7 +217,7 @@ exports[`PersonalizationModal.vue > should render correctly 1`] = `
<!---->
</div>
</div>
<div class=\\"mt-undefined\\">
<div class=\\"\\">
<div class=\\"container\\" data-test-id=\\"reportedSource\\"><label for=\\"reportedSource\\" class=\\"inputLabel heading medium\\">
<div class=\\"title\\"><span class=\\"n8n-text size-medium bold\\"> How did you hear about n8n? <!----></span></div>
<!---->
@@ -273,7 +273,7 @@ exports[`PersonalizationModal.vue > should render correctly 1`] = `
</div>
</div>
<div class=\\"footer\\">
<div><button aria-disabled=\\"false\\" aria-busy=\\"false\\" aria-live=\\"polite\\" class=\\"button button primary medium float-right\\">
<div><button aria-live=\\"polite\\" class=\\"button button primary medium float-right\\">
<!----><span>Get started</span></button></div>
</div>
</div>