mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
🔀 Merge parent branch
This commit is contained in:
commit
e8e1e606b7
|
@ -1,3 +1,2 @@
|
|||
packages/nodes-base
|
||||
packages/editor-ui
|
||||
packages/design-system
|
||||
|
|
80
.eslintrc.js
80
.eslintrc.js
|
@ -1,4 +1,6 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
|
||||
env: {
|
||||
browser: true,
|
||||
es6: true,
|
||||
|
@ -21,6 +23,28 @@ module.exports = {
|
|||
'**/migrations/**',
|
||||
],
|
||||
|
||||
overrides: [
|
||||
{
|
||||
files: './packages/*(cli|core|workflow|node-dev)/**/*.ts',
|
||||
plugins: [
|
||||
/**
|
||||
* Plugin with lint rules for import/export syntax
|
||||
* https://github.com/import-js/eslint-plugin-import
|
||||
*/
|
||||
'eslint-plugin-import',
|
||||
|
||||
/**
|
||||
* @typescript-eslint/eslint-plugin is required by eslint-config-airbnb-typescript
|
||||
* See step 2: https://github.com/iamturns/eslint-config-airbnb-typescript#2-install-eslint-plugins
|
||||
*/
|
||||
'@typescript-eslint',
|
||||
|
||||
/**
|
||||
* Plugin to report formatting violations as lint violations
|
||||
* https://github.com/prettier/eslint-plugin-prettier
|
||||
*/
|
||||
'eslint-plugin-prettier',
|
||||
],
|
||||
extends: [
|
||||
/**
|
||||
* Config for typescript-eslint recommended ruleset (without type checking)
|
||||
|
@ -51,27 +75,6 @@ module.exports = {
|
|||
*/
|
||||
'eslint-config-prettier',
|
||||
],
|
||||
|
||||
plugins: [
|
||||
/**
|
||||
* Plugin with lint rules for import/export syntax
|
||||
* https://github.com/import-js/eslint-plugin-import
|
||||
*/
|
||||
'eslint-plugin-import',
|
||||
|
||||
/**
|
||||
* @typescript-eslint/eslint-plugin is required by eslint-config-airbnb-typescript
|
||||
* See step 2: https://github.com/iamturns/eslint-config-airbnb-typescript#2-install-eslint-plugins
|
||||
*/
|
||||
'@typescript-eslint',
|
||||
|
||||
/**
|
||||
* Plugin to report formatting violations as lint violations
|
||||
* https://github.com/prettier/eslint-plugin-prettier
|
||||
*/
|
||||
'eslint-plugin-prettier',
|
||||
],
|
||||
|
||||
rules: {
|
||||
// ******************************************************************
|
||||
// required by prettier plugin
|
||||
|
@ -122,7 +125,7 @@ module.exports = {
|
|||
'undefined',
|
||||
],
|
||||
|
||||
'no-void': ['error', { 'allowAsStatement': true }],
|
||||
'no-void': ['error', { allowAsStatement: true }],
|
||||
|
||||
// ----------------------------------
|
||||
// @typescript-eslint
|
||||
|
@ -186,7 +189,10 @@ module.exports = {
|
|||
/**
|
||||
* https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/explicit-member-accessibility.md
|
||||
*/
|
||||
'@typescript-eslint/explicit-member-accessibility': ['error', { accessibility: 'no-public' }],
|
||||
'@typescript-eslint/explicit-member-accessibility': [
|
||||
'error',
|
||||
{ accessibility: 'no-public' },
|
||||
],
|
||||
|
||||
/**
|
||||
* https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/member-delimiter-style.md
|
||||
|
@ -363,4 +369,32 @@ module.exports = {
|
|||
*/
|
||||
'import/prefer-default-export': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['./packages/nodes-base/nodes/**/*.ts'],
|
||||
plugins: ['eslint-plugin-n8n-nodes-base'],
|
||||
rules: {
|
||||
"n8n-nodes-base/node-param-default-missing": "error",
|
||||
"n8n-nodes-base/node-class-description-missing-subtitle": "error",
|
||||
"n8n-nodes-base/node-class-description-inputs-wrong-trigger-node": "error",
|
||||
"n8n-nodes-base/node-class-description-inputs-wrong-regular-node": "error",
|
||||
"n8n-nodes-base/node-class-description-outputs-wrong": "error",
|
||||
"n8n-nodes-base/node-execute-block-double-assertion-for-items": "error",
|
||||
"n8n-nodes-base/node-param-default-wrong-for-collection": "error",
|
||||
"n8n-nodes-base/node-param-default-wrong-for-boolean": "error",
|
||||
"n8n-nodes-base/node-param-collection-type-unsorted-items": "error",
|
||||
"n8n-nodes-base/node-param-default-wrong-for-fixed-collection": "error",
|
||||
"n8n-nodes-base/node-param-default-wrong-for-multi-options": "error",
|
||||
"n8n-nodes-base/node-param-description-excess-inner-whitespace": "error",
|
||||
"n8n-nodes-base/node-param-description-empty-string": "error",
|
||||
"n8n-nodes-base/node-param-description-comma-separated-hyphen": "error",
|
||||
"n8n-nodes-base/node-param-default-wrong-for-simplify": "error",
|
||||
"n8n-nodes-base/node-param-description-missing-for-return-all": "error",
|
||||
"n8n-nodes-base/node-param-description-missing-final-period": "error",
|
||||
"n8n-nodes-base/node-param-description-missing-for-simplify": "error",
|
||||
"n8n-nodes-base/node-param-description-missing-for-ignore-ssl-issues": "error",
|
||||
"n8n-nodes-base/node-param-description-identical-to-display-name": "error",
|
||||
}
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
51
CHANGELOG.md
51
CHANGELOG.md
|
@ -1,3 +1,48 @@
|
|||
# [0.174.0](https://github.com/n8n-io/n8n/compare/n8n@0.173.1...n8n@0.174.0) (2022-04-25)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **core:** Open oauth callback endpoints to be public ([#3168](https://github.com/n8n-io/n8n/issues/3168)) ([01807d6](https://github.com/n8n-io/n8n/commit/01807d613654eb14dad0eb82defa4fab234a1d71))
|
||||
- **MicrosoftOneDrive Node:** Fix issue with filenames that contain special characters from uploading ([#3183](https://github.com/n8n-io/n8n/issues/3183)) ([ff26a98](https://github.com/n8n-io/n8n/commit/ff26a987fe244b30f67d6516d84f1f43fed3ec43))
|
||||
- **Slack Node:** Fix credential test ([#3151](https://github.com/n8n-io/n8n/issues/3151)) ([15e6d92](https://github.com/n8n-io/n8n/commit/15e6d9274ad0627dd5ebc30e70757878368042bc))
|
||||
|
||||
### Features
|
||||
|
||||
- **All AWS Nodes:** Enable support for AWS temporary credentials ([#2587](https://github.com/n8n-io/n8n/issues/2587)) ([ce79e6b](https://github.com/n8n-io/n8n/commit/ce79e6b74f6d94694f16988c8601f7c0639a04b3))
|
||||
- **editor:** Add Workflow Stickies (Notes) ([#3154](https://github.com/n8n-io/n8n/issues/3154)) ([31dd01f](https://github.com/n8n-io/n8n/commit/31dd01f9cb7e1b6908a89c3402c78515a6475e61))
|
||||
- **Google Sheets Node:** Add upsert support ([#2733](https://github.com/n8n-io/n8n/issues/2733)) ([aeb5a12](https://github.com/n8n-io/n8n/commit/aeb5a1234aa610b333525512085fe3b3bd60abef))
|
||||
- **Microsoft Teams Node:** Enhancements and cleanup ([#2940](https://github.com/n8n-io/n8n/issues/2940)) ([d446f9e](https://github.com/n8n-io/n8n/commit/d446f9e28176e6ae2875d526cf4b6ac769dc750c))
|
||||
- **MongoDB Node:** Allow parsing dates using dot notation ([#2487](https://github.com/n8n-io/n8n/issues/2487)) ([83998a1](https://github.com/n8n-io/n8n/commit/83998a15b0b4bea94aa07984136bdc56523d4f89))
|
||||
|
||||
# [0.173.1](https://github.com/n8n-io/n8n/compare/n8n@0.173.0...n8n@0.173.1) (2022-04-19)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **Discord Node:** Fix icon name
|
||||
|
||||
# [0.173.0](https://github.com/n8n-io/n8n/compare/n8n@0.172.0...n8n@0.173.0) (2022-04-19)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **core:** Add "rawBody" also for xml requests ([#3143](https://github.com/n8n-io/n8n/issues/3143)) ([5719e44](https://github.com/n8n-io/n8n/commit/5719e44b5999bb84e2fd50c8a469cc8934539747))
|
||||
- **core:** Make email for UM case insensitive ([#3078](https://github.com/n8n-io/n8n/issues/3078)) ([8532b00](https://github.com/n8n-io/n8n/commit/8532b0030dbdeb85b2f74ce078adb44f43a7c4d3))
|
||||
- **Discourse Node:** Fix issue with not all posts getting returned and add credential test ([#3007](https://github.com/n8n-io/n8n/issues/3007)) ([d68b7a4](https://github.com/n8n-io/n8n/commit/d68b7a4cf4b1025ce19e23f6bfc9e423595b6c0b))
|
||||
- **editor:** Fix breaking Drop-downs after removing expressions ([#3094](https://github.com/n8n-io/n8n/issues/3094)) ([17b0cd8](https://github.com/n8n-io/n8n/commit/17b0cd8f765ce262241c827a635e64c189acc0f8))
|
||||
- **Postgres Node:** Fix issue with columns containing spaces ([#2989](https://github.com/n8n-io/n8n/issues/2989)) ([0081d02](https://github.com/n8n-io/n8n/commit/0081d02b979ff5d98c5a834c60d8d8b5e83924ef))
|
||||
- **ui:** Reset text-edit input value when pressing esc key to have matching input values ([#3098](https://github.com/n8n-io/n8n/issues/3098)) ([29fdd77](https://github.com/n8n-io/n8n/commit/29fdd77d7b4ac3bbb9faae73b0932183d48ae9a6))
|
||||
- **ZendeskTrigger Node:** Fix deprecated targets, replaced with webhooks ([#3025](https://github.com/n8n-io/n8n/issues/3025)) ([794ad7c](https://github.com/n8n-io/n8n/commit/794ad7c756c68e0459d8f105acc3bcc1347d1e59))
|
||||
- **Zoho Node:** Fix pagination issue ([#3129](https://github.com/n8n-io/n8n/issues/3129)) ([47bbe98](https://github.com/n8n-io/n8n/commit/47bbe9857b5f3321c9402595041afcb6b96411c4))
|
||||
|
||||
### Features
|
||||
|
||||
- **Discord Node:** Add additional options ([#2918](https://github.com/n8n-io/n8n/issues/2918)) ([310bffe](https://github.com/n8n-io/n8n/commit/310bffe7137f6baf36b93719c1e5abe8596dd346))
|
||||
- **editor:** Add drag and drop from nodes panel ([#3123](https://github.com/n8n-io/n8n/issues/3123)) ([f566569](https://github.com/n8n-io/n8n/commit/f56656929992b98a3473944fd2a395e05d5c42f0))
|
||||
- **Google Cloud Realtime Database Node:** Make it possible to select region ([#3096](https://github.com/n8n-io/n8n/issues/3096)) ([176538e](https://github.com/n8n-io/n8n/commit/176538e5f21f14ea3e5964dbe905fe4af89faaef))
|
||||
- **GoogleBigQuery Node:** Add support for service account authentication ([#3128](https://github.com/n8n-io/n8n/issues/3128)) ([ac5f357](https://github.com/n8n-io/n8n/commit/ac5f357001b6887d649f65bc32a30e30aa75584b))
|
||||
- **Markdown Node:** Add new node to covert between Markdown <> HTML ([#1728](https://github.com/n8n-io/n8n/issues/1728)) ([5d1ddb0](https://github.com/n8n-io/n8n/commit/5d1ddb0e9b56d999ec4d9278b81262aafceb43a9))
|
||||
- **PagerDuty Node:** Add support for additional details in incidents ([#3140](https://github.com/n8n-io/n8n/issues/3140)) ([6ca7454](https://github.com/n8n-io/n8n/commit/6ca74540782623ac2301550b62f3382e88b8ed83)), closes [#3094](https://github.com/n8n-io/n8n/issues/3094) [#3105](https://github.com/n8n-io/n8n/issues/3105) [#3112](https://github.com/n8n-io/n8n/issues/3112) [#3078](https://github.com/n8n-io/n8n/issues/3078) [#3133](https://github.com/n8n-io/n8n/issues/3133) [#2918](https://github.com/n8n-io/n8n/issues/2918)
|
||||
- **Slack Node:** Add blocks to slack message update ([#2182](https://github.com/n8n-io/n8n/issues/2182)) ([b5b6000](https://github.com/n8n-io/n8n/commit/b5b60008d680cd843a418390d451743fc13cac9c)), closes [#1728](https://github.com/n8n-io/n8n/issues/1728)
|
||||
|
||||
# [0.172.0](https://github.com/n8n-io/n8n/compare/n8n@0.171.1...n8n@0.172.0) (2022-04-11)
|
||||
|
||||
### Bug Fixes
|
||||
|
@ -16,9 +61,9 @@
|
|||
|
||||
### Bug Fixes
|
||||
|
||||
* **core:** Fix issue with current executions not getting displayed ([#3093](https://github.com/n8n-io/n8n/issues/3093)) ([4af5168](https://github.com/n8n-io/n8n/commit/4af5168b3bc92578dc807bab1c11e3d90e151928))
|
||||
* **core:** Fix issue with falsely skip authorizing ([#3087](https://github.com/n8n-io/n8n/issues/3087)) ([358a683](https://github.com/n8n-io/n8n/commit/358a683f381aa8eb7edd4886d6bdfe7ada61ec35))
|
||||
* **WooCommerce Node:** Fix pagination issue with "Get All" operation ([#2529](https://github.com/n8n-io/n8n/issues/2529)) ([c2a5e0d](https://github.com/n8n-io/n8n/commit/c2a5e0d1b6a89cb7397b93bbb0f0be9be0df9c86))
|
||||
- **core:** Fix issue with current executions not getting displayed ([#3093](https://github.com/n8n-io/n8n/issues/3093)) ([4af5168](https://github.com/n8n-io/n8n/commit/4af5168b3bc92578dc807bab1c11e3d90e151928))
|
||||
- **core:** Fix issue with falsely skip authorizing ([#3087](https://github.com/n8n-io/n8n/issues/3087)) ([358a683](https://github.com/n8n-io/n8n/commit/358a683f381aa8eb7edd4886d6bdfe7ada61ec35))
|
||||
- **WooCommerce Node:** Fix pagination issue with "Get All" operation ([#2529](https://github.com/n8n-io/n8n/issues/2529)) ([c2a5e0d](https://github.com/n8n-io/n8n/commit/c2a5e0d1b6a89cb7397b93bbb0f0be9be0df9c86))
|
||||
|
||||
# [0.171.0](https://github.com/n8n-io/n8n/compare/n8n@0.170.0...n8n@0.171.0) (2022-04-03)
|
||||
|
||||
|
|
2337
package-lock.json
generated
2337
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n",
|
||||
"version": "0.168.2",
|
||||
"version": "0.174.0",
|
||||
"private": true,
|
||||
"homepage": "https://n8n.io",
|
||||
"scripts": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n",
|
||||
"version": "0.172.0",
|
||||
"version": "0.174.0",
|
||||
"description": "n8n Workflow Automation Tool",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
|
@ -125,10 +125,10 @@
|
|||
"lodash.get": "^4.4.2",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"mysql2": "~2.3.0",
|
||||
"n8n-core": "~0.113.0",
|
||||
"n8n-editor-ui": "~0.139.0",
|
||||
"n8n-nodes-base": "~0.170.0",
|
||||
"n8n-workflow": "~0.95.0",
|
||||
"n8n-core": "~0.115.0",
|
||||
"n8n-editor-ui": "~0.141.0",
|
||||
"n8n-nodes-base": "~0.172.0",
|
||||
"n8n-workflow": "~0.97.0",
|
||||
"nodemailer": "^6.7.1",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
"open": "^7.0.0",
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
|
||||
import { Credentials, NodeExecuteFunctions } from 'n8n-core';
|
||||
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import { get } from 'lodash';
|
||||
import { NodeVersionedType } from 'n8n-nodes-base';
|
||||
|
||||
import {
|
||||
|
@ -450,7 +451,7 @@ export class CredentialsHelper extends ICredentialsHelper {
|
|||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
||||
const credentials = await this.getCredentials(nodeCredentials, type);
|
||||
|
||||
if (Db.collections.Credentials === null) {
|
||||
if (!Db.isInitialized) {
|
||||
// The first time executeWorkflow gets called the Database has
|
||||
// to get initialized first
|
||||
await Db.init();
|
||||
|
@ -660,8 +661,10 @@ export class CredentialsHelper extends ICredentialsHelper {
|
|||
mode,
|
||||
);
|
||||
|
||||
let response: INodeExecutionData[][] | null | undefined;
|
||||
|
||||
try {
|
||||
await routingNode.runNode(
|
||||
response = await routingNode.runNode(
|
||||
inputData,
|
||||
runIndex,
|
||||
nodeTypeCopy,
|
||||
|
@ -710,6 +713,24 @@ export class CredentialsHelper extends ICredentialsHelper {
|
|||
};
|
||||
}
|
||||
|
||||
if (
|
||||
credentialTestFunction.testRequest.rules &&
|
||||
Array.isArray(credentialTestFunction.testRequest.rules)
|
||||
) {
|
||||
// Special testing rules are defined so check all in order
|
||||
for (const rule of credentialTestFunction.testRequest.rules) {
|
||||
if (rule.type === 'responseSuccessBody') {
|
||||
const responseData = response![0][0].json;
|
||||
if (get(responseData, rule.properties.key) === rule.properties.value) {
|
||||
return {
|
||||
status: 'Error',
|
||||
message: rule.properties.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'OK',
|
||||
message: 'Connection successful!',
|
||||
|
@ -759,3 +780,13 @@ export async function getCredentialForUser(
|
|||
|
||||
return sharedCredential.credentials as ICredentialsDb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a credential without user check
|
||||
*/
|
||||
export async function getCredentialWithoutUser(
|
||||
credentialId: string,
|
||||
): Promise<ICredentialsDb | undefined> {
|
||||
const credential = await Db.collections.Credentials.findOne(credentialId);
|
||||
return credential;
|
||||
}
|
||||
|
|
|
@ -84,11 +84,18 @@ export class InternalHooksClass implements IInternalHooksClass {
|
|||
async onWorkflowSaved(userId: string, workflow: IWorkflowDb): Promise<void> {
|
||||
const { nodeGraph } = TelemetryHelpers.generateNodesGraph(workflow, this.nodeTypes);
|
||||
|
||||
const notesCount = Object.keys(nodeGraph.notes).length;
|
||||
const overlappingCount = Object.values(nodeGraph.notes).filter(
|
||||
(note) => note.overlapping,
|
||||
).length;
|
||||
|
||||
return this.telemetry.track('User saved workflow', {
|
||||
user_id: userId,
|
||||
workflow_id: workflow.id,
|
||||
node_graph: nodeGraph,
|
||||
node_graph_string: JSON.stringify(nodeGraph),
|
||||
notes_count_overlapping: overlappingCount,
|
||||
notes_count_non_overlapping: notesCount - overlappingCount,
|
||||
version_cli: this.versionCli,
|
||||
num_tags: workflow.tags?.length ?? 0,
|
||||
});
|
||||
|
|
|
@ -137,6 +137,7 @@ import {
|
|||
WorkflowHelpers,
|
||||
WorkflowRunner,
|
||||
getCredentialForUser,
|
||||
getCredentialWithoutUser,
|
||||
} from '.';
|
||||
|
||||
import config from '../config';
|
||||
|
@ -1823,18 +1824,18 @@ class App {
|
|||
LoggerProxy.error(
|
||||
'OAuth1 callback failed because of insufficient parameters received',
|
||||
{
|
||||
userId: req.user.id,
|
||||
userId: req.user?.id,
|
||||
credentialId,
|
||||
},
|
||||
);
|
||||
return ResponseHelper.sendErrorResponse(res, errorResponse);
|
||||
}
|
||||
|
||||
const credential = await getCredentialForUser(credentialId, req.user);
|
||||
const credential = await getCredentialWithoutUser(credentialId);
|
||||
|
||||
if (!credential) {
|
||||
LoggerProxy.error('OAuth1 callback failed because of insufficient user permissions', {
|
||||
userId: req.user.id,
|
||||
userId: req.user?.id,
|
||||
credentialId,
|
||||
});
|
||||
const errorResponse = new ResponseHelper.ResponseError(
|
||||
|
@ -1884,7 +1885,7 @@ class App {
|
|||
oauthToken = await requestPromise(options);
|
||||
} catch (error) {
|
||||
LoggerProxy.error('Unable to fetch tokens for OAuth1 callback', {
|
||||
userId: req.user.id,
|
||||
userId: req.user?.id,
|
||||
credentialId,
|
||||
});
|
||||
const errorResponse = new ResponseHelper.ResponseError(
|
||||
|
@ -1914,13 +1915,13 @@ class App {
|
|||
await Db.collections.Credentials!.update(credentialId, newCredentialsData);
|
||||
|
||||
LoggerProxy.verbose('OAuth1 callback successful for new credential', {
|
||||
userId: req.user.id,
|
||||
userId: req.user?.id,
|
||||
credentialId,
|
||||
});
|
||||
res.sendFile(pathResolve(__dirname, '../../templates/oauth-callback.html'));
|
||||
} catch (error) {
|
||||
LoggerProxy.error('OAuth1 callback failed because of insufficient user permissions', {
|
||||
userId: req.user.id,
|
||||
userId: req.user?.id,
|
||||
credentialId: req.query.cid,
|
||||
});
|
||||
// Error response
|
||||
|
@ -2087,11 +2088,11 @@ class App {
|
|||
return ResponseHelper.sendErrorResponse(res, errorResponse);
|
||||
}
|
||||
|
||||
const credential = await getCredentialForUser(state.cid, req.user);
|
||||
const credential = await getCredentialWithoutUser(state.cid);
|
||||
|
||||
if (!credential) {
|
||||
LoggerProxy.error('OAuth2 callback failed because of insufficient permissions', {
|
||||
userId: req.user.id,
|
||||
userId: req.user?.id,
|
||||
credentialId: state.cid,
|
||||
});
|
||||
const errorResponse = new ResponseHelper.ResponseError(
|
||||
|
@ -2132,7 +2133,7 @@ class App {
|
|||
!token.verify(decryptedDataOriginal.csrfSecret as string, state.token)
|
||||
) {
|
||||
LoggerProxy.debug('OAuth2 callback state is invalid', {
|
||||
userId: req.user.id,
|
||||
userId: req.user?.id,
|
||||
credentialId: state.cid,
|
||||
});
|
||||
const errorResponse = new ResponseHelper.ResponseError(
|
||||
|
@ -2183,7 +2184,7 @@ class App {
|
|||
|
||||
if (oauthToken === undefined) {
|
||||
LoggerProxy.error('OAuth2 callback failed: unable to get access tokens', {
|
||||
userId: req.user.id,
|
||||
userId: req.user?.id,
|
||||
credentialId: state.cid,
|
||||
});
|
||||
const errorResponse = new ResponseHelper.ResponseError(
|
||||
|
@ -2217,7 +2218,7 @@ class App {
|
|||
// Save the credentials in DB
|
||||
await Db.collections.Credentials!.update(state.cid, newCredentialsData);
|
||||
LoggerProxy.verbose('OAuth2 callback successful for new credential', {
|
||||
userId: req.user.id,
|
||||
userId: req.user?.id,
|
||||
credentialId: state.cid,
|
||||
});
|
||||
|
||||
|
|
|
@ -66,6 +66,8 @@ export function addRoutes(this: N8nApp, ignoredEndpoints: string[], restEndpoint
|
|||
req.url.startsWith(`/${restEndpoint}/forgot-password`) ||
|
||||
req.url.startsWith(`/${restEndpoint}/resolve-password-token`) ||
|
||||
req.url.startsWith(`/${restEndpoint}/change-password`) ||
|
||||
req.url.startsWith(`/${restEndpoint}/oauth2-credential/callback`) ||
|
||||
req.url.startsWith(`/${restEndpoint}/oauth1-credential/callback`) ||
|
||||
isAuthExcluded(req.url, ignoredEndpoints)
|
||||
) {
|
||||
return next();
|
||||
|
|
|
@ -300,7 +300,7 @@ class App {
|
|||
}
|
||||
|
||||
this.app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
if (Db.collections.Workflow === null) {
|
||||
if (!Db.isInitialized) {
|
||||
const error = new ResponseHelper.ResponseError('Database is not ready!', undefined, 503);
|
||||
return ResponseHelper.sendErrorResponse(res, error);
|
||||
}
|
||||
|
|
|
@ -789,7 +789,7 @@ export async function getWorkflowData(
|
|||
|
||||
let workflowData: IWorkflowBase | undefined;
|
||||
if (workflowInfo.id !== undefined) {
|
||||
if (Db.collections.Workflow === null) {
|
||||
if (!Db.isInitialized) {
|
||||
// The first time executeWorkflow gets called the Database has
|
||||
// to get initialized first
|
||||
await Db.init();
|
||||
|
|
4
packages/cli/src/requests.d.ts
vendored
4
packages/cli/src/requests.d.ts
vendored
|
@ -237,7 +237,9 @@ export declare namespace OAuthRequest {
|
|||
{},
|
||||
{},
|
||||
{ oauth_verifier: string; oauth_token: string; cid: string }
|
||||
>;
|
||||
> & {
|
||||
user?: User;
|
||||
};
|
||||
}
|
||||
|
||||
namespace OAuth2Credential {
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
} from './shared/random';
|
||||
import * as testDb from './shared/testDb';
|
||||
import type { Role } from '../../src/databases/entities/Role';
|
||||
import { SMTP_TEST_TIMEOUT } from './shared/constants';
|
||||
|
||||
jest.mock('../../src/telemetry');
|
||||
|
||||
|
@ -40,19 +41,22 @@ beforeEach(async () => {
|
|||
|
||||
config.set('userManagement.isInstanceOwnerSetUp', true);
|
||||
config.set('userManagement.emails.mode', '');
|
||||
|
||||
jest.setTimeout(30000); // fake SMTP service might be slow
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await testDb.terminate(testDbName);
|
||||
});
|
||||
|
||||
test('POST /forgot-password should send password reset email', async () => {
|
||||
test(
|
||||
'POST /forgot-password should send password reset email',
|
||||
async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
|
||||
const authlessAgent = utils.createAgent(app);
|
||||
const member = await testDb.createUser({ email: 'test@test.com', globalRole: globalMemberRole });
|
||||
const member = await testDb.createUser({
|
||||
email: 'test@test.com',
|
||||
globalRole: globalMemberRole,
|
||||
});
|
||||
|
||||
await utils.configureSmtp();
|
||||
|
||||
|
@ -68,7 +72,9 @@ test('POST /forgot-password should send password reset email', async () => {
|
|||
expect(user.resetPasswordTokenExpiration).toBeGreaterThan(Math.ceil(Date.now() / 1000));
|
||||
}),
|
||||
);
|
||||
});
|
||||
},
|
||||
SMTP_TEST_TIMEOUT,
|
||||
);
|
||||
|
||||
test('POST /forgot-password should fail if emailing is not set up', async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
|
|
|
@ -57,3 +57,8 @@ export const BOOTSTRAP_POSTGRES_CONNECTION_NAME: Readonly<string> = 'n8n_bs_post
|
|||
* for each suite test run.
|
||||
*/
|
||||
export const BOOTSTRAP_MYSQL_CONNECTION_NAME: Readonly<string> = 'n8n_bs_mysql';
|
||||
|
||||
/**
|
||||
* Timeout (in milliseconds) to account for fake SMTP service being slow to respond.
|
||||
*/
|
||||
export const SMTP_TEST_TIMEOUT = 30_000;
|
||||
|
|
|
@ -4,7 +4,7 @@ import { v4 as uuid } from 'uuid';
|
|||
|
||||
import { Db } from '../../src';
|
||||
import config from '../../config';
|
||||
import { SUCCESS_RESPONSE_BODY } from './shared/constants';
|
||||
import { SMTP_TEST_TIMEOUT, SUCCESS_RESPONSE_BODY } from './shared/constants';
|
||||
import {
|
||||
randomEmail,
|
||||
randomValidPassword,
|
||||
|
@ -47,8 +47,6 @@ beforeAll(async () => {
|
|||
|
||||
utils.initTestTelemetry();
|
||||
utils.initTestLogger();
|
||||
|
||||
jest.setTimeout(30000); // fake SMTP service might be slow
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
|
@ -481,7 +479,9 @@ test('POST /users should fail if user management is disabled', async () => {
|
|||
expect(response.statusCode).toBe(500);
|
||||
});
|
||||
|
||||
test('POST /users should email invites and create user shells but ignore existing', async () => {
|
||||
test(
|
||||
'POST /users should email invites and create user shells but ignore existing',
|
||||
async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
const member = await testDb.createUser({ globalRole: globalMemberRole });
|
||||
const memberShell = await testDb.createUserShell(globalMemberRole);
|
||||
|
@ -489,7 +489,12 @@ test('POST /users should email invites and create user shells but ignore existin
|
|||
|
||||
await utils.configureSmtp();
|
||||
|
||||
const testEmails = [randomEmail(), randomEmail().toUpperCase(), memberShell.email, member.email];
|
||||
const testEmails = [
|
||||
randomEmail(),
|
||||
randomEmail().toUpperCase(),
|
||||
memberShell.email,
|
||||
member.email,
|
||||
];
|
||||
|
||||
const payload = testEmails.map((e) => ({ email: e }));
|
||||
|
||||
|
@ -522,9 +527,13 @@ test('POST /users should email invites and create user shells but ignore existin
|
|||
expect(password).toBeNull();
|
||||
expect(resetPasswordToken).toBeNull();
|
||||
}
|
||||
});
|
||||
},
|
||||
SMTP_TEST_TIMEOUT,
|
||||
);
|
||||
|
||||
test('POST /users should fail with invalid inputs', async () => {
|
||||
test(
|
||||
'POST /users should fail with invalid inputs',
|
||||
async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||
|
||||
|
@ -547,9 +556,13 @@ test('POST /users should fail with invalid inputs', async () => {
|
|||
expect(users.length).toBe(1); // DB unaffected
|
||||
}),
|
||||
);
|
||||
});
|
||||
},
|
||||
SMTP_TEST_TIMEOUT,
|
||||
);
|
||||
|
||||
test('POST /users should ignore an empty payload', async () => {
|
||||
test(
|
||||
'POST /users should ignore an empty payload',
|
||||
async () => {
|
||||
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
|
||||
const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner });
|
||||
|
||||
|
@ -565,7 +578,9 @@ test('POST /users should ignore an empty payload', async () => {
|
|||
|
||||
const users = await Db.collections.User!.find();
|
||||
expect(users.length).toBe(1);
|
||||
});
|
||||
},
|
||||
SMTP_TEST_TIMEOUT,
|
||||
);
|
||||
|
||||
// TODO: /users/:id/reinvite route tests missing
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n-core",
|
||||
"version": "0.113.0",
|
||||
"version": "0.115.0",
|
||||
"description": "Core functionality of n8n",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
|
@ -52,7 +52,7 @@
|
|||
"form-data": "^4.0.0",
|
||||
"lodash.get": "^4.4.2",
|
||||
"mime-types": "^2.1.27",
|
||||
"n8n-workflow": "~0.95.0",
|
||||
"n8n-workflow": "~0.97.0",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
"p-cancelable": "^2.0.0",
|
||||
"qs": "^6.10.1",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n-design-system",
|
||||
"version": "0.17.0",
|
||||
"version": "0.18.0",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
"author": {
|
||||
|
@ -79,6 +79,7 @@
|
|||
"vue-loader": "^15.9.7",
|
||||
"vue-property-decorator": "^9.1.2",
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"vue-typed-mixins": "^0.2.0",
|
||||
"vue2-boring-avatars": "0.3.4",
|
||||
"xss": "^1.0.10"
|
||||
}
|
||||
|
|
|
@ -69,7 +69,6 @@ export default {
|
|||
default: false,
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
},
|
||||
round: {
|
||||
type: Boolean,
|
||||
|
|
|
@ -26,7 +26,6 @@ export default {
|
|||
},
|
||||
props: {
|
||||
icon: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
size: {
|
||||
|
|
|
@ -40,7 +40,6 @@ export default {
|
|||
default: false,
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
theme: {
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
<template>
|
||||
<div>
|
||||
<div v-if="!loading" ref="editor" :class="$style.markdown" v-html="htmlContent" />
|
||||
<div
|
||||
v-if="!loading"
|
||||
ref="editor"
|
||||
:class="$style[theme]" v-html="htmlContent"
|
||||
/>
|
||||
<div v-else :class="$style.markdown">
|
||||
<div v-for="(block, index) in loadingBlocks"
|
||||
:key="index">
|
||||
|
@ -59,6 +63,9 @@ export default {
|
|||
content: {
|
||||
type: String,
|
||||
},
|
||||
withMultiBreaks: {
|
||||
type: Boolean,
|
||||
},
|
||||
images: {
|
||||
type: Array,
|
||||
},
|
||||
|
@ -75,6 +82,10 @@ export default {
|
|||
return 3;
|
||||
},
|
||||
},
|
||||
theme: {
|
||||
type: String,
|
||||
default: 'markdown',
|
||||
},
|
||||
options: {
|
||||
type: Object,
|
||||
default() {
|
||||
|
@ -106,7 +117,11 @@ export default {
|
|||
}
|
||||
|
||||
const fileIdRegex = new RegExp('fileId:([0-9]+)');
|
||||
const html = this.md.render(escapeMarkdown(this.content));
|
||||
let contentToRender = this.content;
|
||||
if (this.withMultiBreaks) {
|
||||
contentToRender = contentToRender.replaceAll('\n\n', '\n \n');
|
||||
}
|
||||
const html = this.md.render(escapeMarkdown(contentToRender));
|
||||
const safeHtml = xss(html, {
|
||||
onTagAttr: (tag, name, value, isWhiteAttr) => {
|
||||
if (tag === 'img' && name === 'src') {
|
||||
|
@ -214,6 +229,67 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
.sticky {
|
||||
color: var(--color-text-dark);
|
||||
|
||||
h1, h2, h3, h4 {
|
||||
margin-bottom: var(--spacing-2xs);
|
||||
font-weight: var(--font-weight-bold);
|
||||
line-height: var(--font-line-height-loose);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 36px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
h3, h4, h5, h6 {
|
||||
font-size: var(--font-size-m);
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: var(--spacing-2xs);
|
||||
font-size: var(--font-size-s);
|
||||
font-weight: var(--font-weight-regular);
|
||||
line-height: var(--font-line-height-loose);
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
margin-bottom: var(--spacing-2xs);
|
||||
padding-left: var(--spacing-m);
|
||||
|
||||
li {
|
||||
margin-top: 0.25em;
|
||||
font-size: var(--font-size-s);
|
||||
font-weight: var(--font-weight-regular);
|
||||
line-height: var(--font-line-height-regular);
|
||||
}
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: var(--color-background-base);
|
||||
padding: 0 var(--spacing-4xs);
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
||||
pre > code,li > code, p > code {
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
||||
a {
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
.spacer {
|
||||
margin: var(--spacing-2xl);
|
||||
}
|
||||
|
|
238
packages/design-system/src/components/N8nSticky/Resize.vue
Normal file
238
packages/design-system/src/components/N8nSticky/Resize.vue
Normal file
|
@ -0,0 +1,238 @@
|
|||
<template>
|
||||
<div :class="$style.resize">
|
||||
<div v-if="isResizingEnabled" @mousedown="resizerMove" data-dir="right" :class="[$style.resizer, $style.right]" />
|
||||
<div v-if="isResizingEnabled" @mousedown="resizerMove" data-dir="left" :class="[$style.resizer, $style.left]" />
|
||||
<div v-if="isResizingEnabled" @mousedown="resizerMove" data-dir="top" :class="[$style.resizer, $style.top]" />
|
||||
<div v-if="isResizingEnabled" @mousedown="resizerMove" data-dir="bottom" :class="[$style.resizer, $style.bottom]" />
|
||||
<div v-if="isResizingEnabled" @mousedown="resizerMove" data-dir="top-left" :class="[$style.resizer, $style.topLeft]" />
|
||||
<div v-if="isResizingEnabled" @mousedown="resizerMove" data-dir="top-right" :class="[$style.resizer, $style.topRight]" />
|
||||
<div v-if="isResizingEnabled" @mousedown="resizerMove" data-dir="bottom-left" :class="[$style.resizer, $style.bottomLeft]" />
|
||||
<div v-if="isResizingEnabled" @mousedown="resizerMove" data-dir="bottom-right" :class="[$style.resizer, $style.bottomRight]" />
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
const cursorMap = {
|
||||
right: 'ew-resize',
|
||||
top: 'ns-resize',
|
||||
bottom: 'ns-resize',
|
||||
left: 'ew-resize',
|
||||
'top-left': 'nw-resize',
|
||||
'top-right' : 'ne-resize',
|
||||
'bottom-left': 'sw-resize',
|
||||
'bottom-right': 'se-resize',
|
||||
};
|
||||
|
||||
function closestNumber(value: number, divisor: number): number {
|
||||
let q = parseInt(value / divisor);
|
||||
let n1 = divisor * q;
|
||||
|
||||
let n2 = (value * divisor) > 0 ?
|
||||
(divisor * (q + 1)) : (divisor * (q - 1));
|
||||
|
||||
if (Math.abs(value - n1) < Math.abs(value - n2))
|
||||
return n1;
|
||||
|
||||
return n2;
|
||||
}
|
||||
|
||||
function getSize(delta, min, virtual, gridSize): number {
|
||||
const target = closestNumber(virtual, gridSize);
|
||||
if (target >= min && virtual > 0) {
|
||||
return target;
|
||||
}
|
||||
|
||||
return min;
|
||||
};
|
||||
|
||||
export default {
|
||||
name: 'n8n-resize',
|
||||
props: {
|
||||
isResizingEnabled: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
},
|
||||
minHeight: {
|
||||
type: Number,
|
||||
},
|
||||
minWidth: {
|
||||
type: Number,
|
||||
},
|
||||
scale: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
gridSize: {
|
||||
type: Number,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dir: '',
|
||||
dHeight: 0,
|
||||
dWidth: 0,
|
||||
vHeight: 0,
|
||||
vWidth: 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
resizerMove(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const targetResizer = e.target;
|
||||
this.dir = targetResizer.dataset.dir;
|
||||
document.body.style.cursor = cursorMap[this.dir];
|
||||
|
||||
this.x = e.pageX;
|
||||
this.y = e.pageY;
|
||||
this.dWidth = 0;
|
||||
this.dHeight = 0;
|
||||
this.vHeight = this.height;
|
||||
this.vWidth = this.width;
|
||||
|
||||
window.addEventListener('mousemove', this.mouseMove);
|
||||
window.addEventListener('mouseup', this.mouseUp);
|
||||
this.$emit('resizestart');
|
||||
},
|
||||
mouseMove(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
let dWidth = 0;
|
||||
let dHeight = 0;
|
||||
let top = false;
|
||||
let left = false;
|
||||
|
||||
if (this.dir.includes('right')) {
|
||||
dWidth = e.pageX - this.x;
|
||||
}
|
||||
if (this.dir.includes('left')) {
|
||||
dWidth = this.x - e.pageX;
|
||||
left = true;
|
||||
}
|
||||
if (this.dir.includes('top')) {
|
||||
dHeight = this.y - e.pageY;
|
||||
top = true;
|
||||
}
|
||||
if (this.dir.includes('bottom')) {
|
||||
dHeight = e.pageY - this.y;
|
||||
}
|
||||
|
||||
const deltaWidth = (dWidth - this.dWidth) / this.scale;
|
||||
const deltaHeight = (dHeight - this.dHeight) / this.scale;
|
||||
|
||||
this.vHeight = this.vHeight + deltaHeight;
|
||||
this.vWidth = this.vWidth + deltaWidth;
|
||||
const height = getSize(deltaHeight, this.minHeight, this.vHeight, this.gridSize);
|
||||
const width = getSize(deltaWidth, this.minWidth, this.vWidth, this.gridSize);
|
||||
|
||||
const dX = left && width !== this.width ? -1 * (width - this.width) : 0;
|
||||
const dY = top && height !== this.height ? -1 * (height - this.height): 0;
|
||||
|
||||
this.$emit('resize', { height, width, dX, dY });
|
||||
this.dHeight = dHeight;
|
||||
this.dWidth = dWidth;
|
||||
},
|
||||
mouseUp(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.$emit('resizeend');
|
||||
window.removeEventListener('mousemove', this.mouseMove);
|
||||
window.removeEventListener('mouseup', this.mouseUp);
|
||||
document.body.style.cursor = 'unset';
|
||||
this.dir = '';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.resize {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.resizer {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.right {
|
||||
width: 12px;
|
||||
height: 100%;
|
||||
top: -2px;
|
||||
right: -2px;
|
||||
cursor: ew-resize;
|
||||
}
|
||||
|
||||
.top {
|
||||
width: 100%;
|
||||
height: 12px;
|
||||
top: -2px;
|
||||
left: -2px;
|
||||
cursor: ns-resize;
|
||||
}
|
||||
|
||||
.bottom {
|
||||
width: 100%;
|
||||
height: 12px;
|
||||
bottom: -2px;
|
||||
left: -2px;
|
||||
cursor: ns-resize;
|
||||
}
|
||||
|
||||
.left {
|
||||
width: 12px;
|
||||
height: 100%;
|
||||
top: -2px;
|
||||
left: -2px;
|
||||
cursor: ew-resize;
|
||||
}
|
||||
|
||||
.topLeft {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
top: -3px;
|
||||
left: -3px;
|
||||
cursor: nw-resize;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.topRight {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
top: -3px;
|
||||
right: -3px;
|
||||
cursor: ne-resize;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.bottomLeft {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
bottom: -3px;
|
||||
left: -3px;
|
||||
cursor: sw-resize;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.bottomRight {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
bottom: -3px;
|
||||
right: -3px;
|
||||
cursor: se-resize;
|
||||
z-index: 3;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,67 @@
|
|||
import { action } from '@storybook/addon-actions';
|
||||
import N8nSticky from './Sticky.vue';
|
||||
|
||||
export default {
|
||||
title: 'Atoms/Sticky',
|
||||
component: N8nSticky,
|
||||
argTypes: {
|
||||
content: {
|
||||
control: {
|
||||
control: 'text',
|
||||
},
|
||||
},
|
||||
height: {
|
||||
control: {
|
||||
control: 'number',
|
||||
},
|
||||
},
|
||||
minHeight: {
|
||||
control: {
|
||||
control: 'number',
|
||||
},
|
||||
},
|
||||
minWidth: {
|
||||
control: {
|
||||
control: 'number',
|
||||
},
|
||||
},
|
||||
readOnly: {
|
||||
control: {
|
||||
control: 'Boolean',
|
||||
},
|
||||
},
|
||||
width: {
|
||||
control: {
|
||||
control: 'number',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const methods = {
|
||||
onInput: action('input'),
|
||||
onResize: action('resize'),
|
||||
onResizeEnd: action('resizeend'),
|
||||
onResizeStart: action('resizestart'),
|
||||
};
|
||||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: {
|
||||
N8nSticky,
|
||||
},
|
||||
template:
|
||||
'<n8n-sticky v-bind="$props" @resize="onResize" @resizeend="onResizeEnd" @resizeStart="onResizeStart" @input="onInput"></n8n-sticky>',
|
||||
methods,
|
||||
});
|
||||
|
||||
export const Sticky = Template.bind({});
|
||||
Sticky.args = {
|
||||
height: 160,
|
||||
width: 150,
|
||||
content: `## I'm a note \n**Double click** to edit me. [Guide](https://docs.n8n.io/workflows/sticky-notes/)`,
|
||||
defaultText: `## I'm a note \n**Double click** to edit me. [Guide](https://docs.n8n.io/workflows/sticky-notes/)`,
|
||||
minHeight: 80,
|
||||
minWidth: 150,
|
||||
readOnly: false,
|
||||
};
|
253
packages/design-system/src/components/N8nSticky/Sticky.vue
Normal file
253
packages/design-system/src/components/N8nSticky/Sticky.vue
Normal file
|
@ -0,0 +1,253 @@
|
|||
<template>
|
||||
<div
|
||||
:class="{[$style.sticky]: true, [$style.clickable]: !isResizing}"
|
||||
:style="styles"
|
||||
@keydown.prevent
|
||||
>
|
||||
<resize
|
||||
:isResizingEnabled="!readOnly"
|
||||
:height="height"
|
||||
:width="width"
|
||||
:minHeight="minHeight"
|
||||
:minWidth="minWidth"
|
||||
:scale="scale"
|
||||
:gridSize="gridSize"
|
||||
@resizeend="onResizeEnd"
|
||||
@resize="onResize"
|
||||
@resizestart="onResizeStart"
|
||||
>
|
||||
<template>
|
||||
<div
|
||||
v-show="!editMode"
|
||||
:class="$style.wrapper"
|
||||
@dblclick.stop="onDoubleClick"
|
||||
>
|
||||
<n8n-markdown
|
||||
theme="sticky"
|
||||
:content="content"
|
||||
:withMultiBreaks="true"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-show="editMode"
|
||||
@click.stop
|
||||
@mousedown.stop
|
||||
@mouseup.stop
|
||||
@keydown.esc="onInputBlur"
|
||||
@keydown.stop
|
||||
@wheel.stop
|
||||
class="sticky-textarea"
|
||||
:class="{'full-height': !shouldShowFooter}"
|
||||
>
|
||||
<n8n-input
|
||||
:value="content"
|
||||
type="textarea"
|
||||
:rows="5"
|
||||
@blur="onInputBlur"
|
||||
@input="onInput"
|
||||
ref="input"
|
||||
/>
|
||||
|
||||
</div>
|
||||
<div v-if="editMode && shouldShowFooter" :class="$style.footer">
|
||||
<n8n-text
|
||||
size="xsmall"
|
||||
aligh="right"
|
||||
>
|
||||
<span v-html="t('sticky.markdownHint')"></span>
|
||||
</n8n-text>
|
||||
</div>
|
||||
</template>
|
||||
</resize>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import N8nInput from '../N8nInput';
|
||||
import N8nMarkdown from '../N8nMarkdown';
|
||||
import Resize from './Resize';
|
||||
import N8nText from '../N8nText';
|
||||
import Locale from '../../mixins/locale';
|
||||
import mixins from 'vue-typed-mixins';
|
||||
|
||||
export default mixins(Locale).extend({
|
||||
name: 'n8n-sticky',
|
||||
props: {
|
||||
content: {
|
||||
type: String,
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
default: 180,
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
default: 240,
|
||||
},
|
||||
minHeight: {
|
||||
type: Number,
|
||||
default: 80,
|
||||
},
|
||||
minWidth: {
|
||||
type: Number,
|
||||
default: 150,
|
||||
},
|
||||
scale: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
gridSize: {
|
||||
type: Number,
|
||||
default: 20,
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: '0',
|
||||
},
|
||||
defaultText: {
|
||||
type: String,
|
||||
},
|
||||
editMode: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
N8nInput,
|
||||
N8nMarkdown,
|
||||
Resize,
|
||||
N8nText,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isResizing: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
resHeight(): number {
|
||||
if (this.height < this.minHeight) {
|
||||
return this.minHeight;
|
||||
}
|
||||
return this.height;
|
||||
},
|
||||
resWidth(): number {
|
||||
if (this.width < this.minWidth) {
|
||||
return this.minWidth;
|
||||
}
|
||||
return this.width;
|
||||
},
|
||||
styles() {
|
||||
return {
|
||||
height: this.resHeight + 'px',
|
||||
width: this.resWidth + 'px',
|
||||
};
|
||||
},
|
||||
shouldShowFooter() {
|
||||
return this.resHeight > 100 && this.resWidth > 155;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onDoubleClick() {
|
||||
if (!this.readOnly) {
|
||||
this.$emit('edit', true);
|
||||
}
|
||||
},
|
||||
onInputBlur(value) {
|
||||
if (!this.isResizing) {
|
||||
this.$emit('edit', false);
|
||||
}
|
||||
},
|
||||
onInput(value: string) {
|
||||
this.$emit('input', value);
|
||||
},
|
||||
onResize(values) {
|
||||
this.$emit('resize', values);
|
||||
},
|
||||
onResizeEnd(resizeEnd) {
|
||||
this.isResizing = false;
|
||||
this.$emit('resizeend', resizeEnd);
|
||||
},
|
||||
onResizeStart() {
|
||||
this.isResizing = true;
|
||||
this.$emit('resizestart');
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
editMode(newMode, prevMode) {
|
||||
setTimeout(() => {
|
||||
if (newMode && !prevMode && this.$refs.input && this.$refs.input.$refs && this.$refs.input.$refs.textarea) {
|
||||
const textarea = this.$refs.input.$refs.textarea;
|
||||
if (this.defaultText === this.content) {
|
||||
textarea.select();
|
||||
}
|
||||
textarea.focus();
|
||||
}
|
||||
}, 100);
|
||||
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.sticky {
|
||||
position: absolute;
|
||||
background-color: var(--color-sticky-default-background);
|
||||
border: 1px solid var(--color-sticky-default-border);
|
||||
border-radius: var(--border-radius-base);
|
||||
}
|
||||
|
||||
.clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
padding: var(--spacing-2xs) var(--spacing-xs) 0;
|
||||
overflow: hidden;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
width: 100%;
|
||||
height: 24px;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
position: absolute;
|
||||
background: linear-gradient(180deg, var(--color-sticky-default-background), #fff5d600 0.01%, var(--color-sticky-default-background));
|
||||
border-radius: var(--border-radius-base);
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding: var(--spacing-5xs) var(--spacing-2xs) 0 var(--spacing-2xs);
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
.sticky-textarea {
|
||||
height: calc(100% - var(--spacing-l));
|
||||
padding: var(--spacing-2xs) var(--spacing-2xs) 0 var(--spacing-2xs);
|
||||
cursor: default;
|
||||
|
||||
.el-textarea {
|
||||
height: 100%;
|
||||
|
||||
.el-textarea__inner {
|
||||
height: 100%;
|
||||
resize: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.full-height {
|
||||
height: calc(100% - var(--spacing-2xs));
|
||||
}
|
||||
</style>
|
3
packages/design-system/src/components/N8nSticky/index.js
Normal file
3
packages/design-system/src/components/N8nSticky/index.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
import Sticky from './Sticky.vue';
|
||||
|
||||
export default Sticky;
|
|
@ -57,6 +57,7 @@ import N8nOption from './N8nOption';
|
|||
import N8nRadioButtons from './N8nRadioButtons';
|
||||
import N8nSelect from './N8nSelect';
|
||||
import N8nSpinner from './N8nSpinner';
|
||||
import N8nSticky from './N8nSticky';
|
||||
import N8nSquareButton from './N8nSquareButton';
|
||||
import N8nTags from './N8nTags';
|
||||
import N8nTabs from './N8nTabs';
|
||||
|
@ -93,6 +94,7 @@ export {
|
|||
N8nRadioButtons,
|
||||
N8nSelect,
|
||||
N8nSpinner,
|
||||
N8nSticky,
|
||||
N8nSquareButton,
|
||||
N8nTabs,
|
||||
N8nTags,
|
||||
|
|
|
@ -16,4 +16,5 @@ export default {
|
|||
config.minimum > 1 ? 's' : ''
|
||||
}`),
|
||||
"formInput.validator.defaultPasswordRequirements": "8+ characters, at least 1 number and 1 capital letter",
|
||||
"sticky.markdownHint": `You can style with <a href="https://docs.n8n.io/workflows/sticky-notes/" target="_blank">Markdown</a>`,
|
||||
};
|
||||
|
|
|
@ -166,3 +166,17 @@ import ColorCircles from './ColorCircles.vue';
|
|||
}}
|
||||
</Story>
|
||||
</Canvas>
|
||||
|
||||
## Sticky
|
||||
|
||||
<Canvas>
|
||||
<Story name="sticky">
|
||||
{{
|
||||
template: `<color-circles :colors="['--color-sticky-default-background', '--color-sticky-default-border']" />`,
|
||||
components: {
|
||||
ColorCircles,
|
||||
},
|
||||
}}
|
||||
</Story>
|
||||
</Canvas>
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ export const escapeMarkdown = (html: string | undefined): string => {
|
|||
return '';
|
||||
}
|
||||
const escaped = html.replace(/</g, "<").replace(/>/g, ">");
|
||||
|
||||
// unescape greater than quotes at start of line
|
||||
const withQuotes = escaped.replace(/^((\s)*(>)+)+\s*/gm, (matches) => {
|
||||
return matches.replace(/>/g, '>');
|
||||
|
|
|
@ -70,13 +70,6 @@
|
|||
var(--color-secondary-l)
|
||||
);
|
||||
|
||||
--color-secondary-tint-1-l: 92%;
|
||||
--color-secondary-tint-1: hsl(
|
||||
var(--color-secondary-h),
|
||||
var(--color-secondary-s),
|
||||
var(--color-secondary-tint-1-l)
|
||||
);
|
||||
|
||||
--color-success-h: 150.4;
|
||||
--color-success-s: 60%;
|
||||
--color-success-l: 40.4%;
|
||||
|
@ -340,6 +333,24 @@
|
|||
--color-json-line: #bfcbd9;
|
||||
--color-json-highlight: #E2E5EE;
|
||||
|
||||
--color-sticky-default-background-h: 46;
|
||||
--color-sticky-default-background-s: 100%;
|
||||
--color-sticky-default-background-l: 92%;
|
||||
--color-sticky-default-background: hsl(
|
||||
var(--color-sticky-default-background-h),
|
||||
var(--color-sticky-default-background-s),
|
||||
var(--color-sticky-default-background-l)
|
||||
);
|
||||
|
||||
--color-sticky-default-border-h: 43;
|
||||
--color-sticky-default-border-s: 75%;
|
||||
--color-sticky-default-border-l: 80%;
|
||||
--color-sticky-default-border: hsl(
|
||||
var(--color-sticky-default-border-h),
|
||||
var(--color-sticky-default-border-s),
|
||||
var(--color-sticky-default-border-l)
|
||||
);
|
||||
|
||||
--border-radius-xlarge: 12px;
|
||||
--border-radius-large: 8px;
|
||||
--border-radius-base: 4px;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n-editor-ui",
|
||||
"version": "0.139.0",
|
||||
"version": "0.141.0",
|
||||
"description": "Workflow Editor UI for n8n",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
|
@ -25,14 +25,15 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@fontsource/open-sans": "^4.5.0",
|
||||
"@fortawesome/free-regular-svg-icons": "^6.1.1",
|
||||
"luxon": "^2.3.0",
|
||||
"n8n-design-system": "~0.17.0",
|
||||
"monaco-editor": "^0.29.1",
|
||||
"n8n-design-system": "~0.18.0",
|
||||
"timeago.js": "^4.0.2",
|
||||
"v-click-outside": "^3.1.2",
|
||||
"vue-fragment": "^1.5.2",
|
||||
"vue2-boring-avatars": "0.3.4",
|
||||
"vue-i18n": "^8.26.7",
|
||||
"vue2-boring-avatars": "0.3.4",
|
||||
"xss": "^1.0.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -77,7 +78,7 @@
|
|||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.get": "^4.4.2",
|
||||
"lodash.set": "^4.3.2",
|
||||
"n8n-workflow": "~0.95.0",
|
||||
"n8n-workflow": "~0.97.0",
|
||||
"monaco-editor-webpack-plugin": "^5.0.0",
|
||||
"normalize-wheel": "^1.0.1",
|
||||
"prismjs": "^1.17.1",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
:visible="!!node || renaming"
|
||||
:visible="(!!node || renaming) && !isActiveStickyNode"
|
||||
:before-close="close"
|
||||
:show-close="false"
|
||||
custom-class="data-display-wrapper"
|
||||
|
@ -41,6 +41,7 @@ import RunData from '@/components/RunData.vue';
|
|||
import mixins from 'vue-typed-mixins';
|
||||
import Vue from 'vue';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { STICKY_NODE_TYPE } from '@/constants';
|
||||
|
||||
export default mixins(externalHooks, nodeHelpers, workflowHelpers).extend({
|
||||
name: 'DataDisplay',
|
||||
|
@ -76,15 +77,18 @@ export default mixins(externalHooks, nodeHelpers, workflowHelpers).extend({
|
|||
}
|
||||
return null;
|
||||
},
|
||||
isActiveStickyNode(): boolean {
|
||||
return !!this.$store.getters.activeNode && this.$store.getters.activeNode.type === STICKY_NODE_TYPE;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
node (node, oldNode) {
|
||||
if(node && !oldNode) {
|
||||
if(node && !oldNode && !this.isActiveStickyNode) {
|
||||
this.triggerWaitingWarningEnabled = false;
|
||||
this.$externalHooks().run('dataDisplay.nodeTypeChanged', { nodeSubtitle: this.getNodeSubtitle(node, this.nodeType, this.getWorkflow()) });
|
||||
this.$telemetry.track('User opened node modal', { node_type: this.nodeType ? this.nodeType.name : '', workflow_id: this.$store.getters.workflowId });
|
||||
}
|
||||
if (window.top) {
|
||||
if (window.top && !this.isActiveStickyNode) {
|
||||
window.top.postMessage(JSON.stringify({command: (node? 'openNDV': 'closeNDV')}), '*');
|
||||
}
|
||||
},
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
clickable: props.clickable,
|
||||
active: props.active,
|
||||
}"
|
||||
@click="listeners['click']"
|
||||
@click="listeners.click"
|
||||
>
|
||||
<CategoryItem
|
||||
v-if="props.item.type === 'category'"
|
||||
|
@ -21,7 +21,9 @@
|
|||
v-else-if="props.item.type === 'node'"
|
||||
:nodeType="props.item.properties.nodeType"
|
||||
:bordered="!props.lastNode"
|
||||
></NodeItem>
|
||||
@dragstart="listeners.dragstart"
|
||||
@dragend="listeners.dragend"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -8,7 +8,12 @@
|
|||
@before-leave="beforeLeave"
|
||||
@leave="leave"
|
||||
>
|
||||
<div v-for="(item, index) in elements" :key="item.key" :class="item.type" :data-key="item.key">
|
||||
<div
|
||||
v-for="(item, index) in elements"
|
||||
:key="item.key"
|
||||
:class="item.type"
|
||||
:data-key="item.key"
|
||||
>
|
||||
<CreatorItem
|
||||
:item="item"
|
||||
:active="activeIndex === index && !disabled"
|
||||
|
@ -16,7 +21,9 @@
|
|||
:lastNode="
|
||||
index === elements.length - 1 || elements[index + 1].type !== 'node'
|
||||
"
|
||||
@click="() => selected(item)"
|
||||
@click="$emit('selected', item)"
|
||||
@dragstart="emit('dragstart', item, $event)"
|
||||
@dragend="emit('dragend', item, $event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -36,12 +43,12 @@ export default Vue.extend({
|
|||
},
|
||||
props: ['elements', 'activeIndex', 'disabled', 'transitionsEnabled'],
|
||||
methods: {
|
||||
selected(element: INodeCreateElement) {
|
||||
emit(eventName: string, element: INodeCreateElement, event: Event) {
|
||||
if (this.$props.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$emit('selected', element);
|
||||
this.$emit(eventName, { element, event });
|
||||
},
|
||||
beforeEnter(el: HTMLElement) {
|
||||
el.style.height = '0';
|
||||
|
|
|
@ -1,7 +1,18 @@
|
|||
<template>
|
||||
<div @click="onClickInside" class="container">
|
||||
<div
|
||||
class="container"
|
||||
ref="mainPanelContainer"
|
||||
@click="onClickInside"
|
||||
>
|
||||
<SlideTransition>
|
||||
<SubcategoryPanel v-if="activeSubcategory" :elements="subcategorizedNodes" :title="activeSubcategory.properties.subcategory" :activeIndex="activeSubcategoryIndex" @close="onSubcategoryClose" @selected="selected" />
|
||||
<SubcategoryPanel
|
||||
v-if="activeSubcategory"
|
||||
:elements="subcategorizedNodes"
|
||||
:title="activeSubcategory.properties.subcategory"
|
||||
:activeIndex="activeSubcategoryIndex"
|
||||
@close="onSubcategoryClose"
|
||||
@selected="selected"
|
||||
/>
|
||||
</SlideTransition>
|
||||
<div class="main-panel">
|
||||
<SearchBar
|
||||
|
@ -35,7 +46,10 @@
|
|||
@selected="selected"
|
||||
/>
|
||||
</div>
|
||||
<NoResults v-else @nodeTypeSelected="nodeTypeSelected" />
|
||||
<NoResults
|
||||
v-else
|
||||
@nodeTypeSelected="$emit('nodeTypeSelected', $event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -56,7 +70,6 @@ import { ALL_NODE_FILTER, CORE_NODES_CATEGORY, REGULAR_NODE_FILTER, TRIGGER_NODE
|
|||
import SlideTransition from '../transitions/SlideTransition.vue';
|
||||
import { matchesNodeType, matchesSelectType } from './helpers';
|
||||
|
||||
|
||||
export default mixins(externalHooks).extend({
|
||||
name: 'NodeCreateList',
|
||||
components: {
|
||||
|
@ -235,18 +248,13 @@ export default mixins(externalHooks).extend({
|
|||
},
|
||||
selected(element: INodeCreateElement) {
|
||||
if (element.type === 'node') {
|
||||
const properties = element.properties as INodeItemProps;
|
||||
|
||||
this.nodeTypeSelected(properties.nodeType.name);
|
||||
this.$emit('nodeTypeSelected', (element.properties as INodeItemProps).nodeType.name);
|
||||
} else if (element.type === 'category') {
|
||||
this.onCategorySelected(element.category);
|
||||
} else if (element.type === 'subcategory') {
|
||||
this.onSubcategorySelected(element);
|
||||
}
|
||||
},
|
||||
nodeTypeSelected(nodeTypeName: string) {
|
||||
this.$emit('nodeTypeSelected', nodeTypeName);
|
||||
},
|
||||
onCategorySelected(category: string) {
|
||||
if (this.activeCategory.includes(category)) {
|
||||
this.activeCategory = this.activeCategory.filter(
|
||||
|
|
|
@ -1,8 +1,20 @@
|
|||
<template>
|
||||
<div>
|
||||
<SlideTransition>
|
||||
<div class="node-creator" v-if="active" v-click-outside="onClickOutside">
|
||||
<MainPanel @nodeTypeSelected="nodeTypeSelected" :categorizedItems="categorizedItems" :categoriesWithNodes="categoriesWithNodes" :searchItems="searchItems"></MainPanel>
|
||||
<div
|
||||
v-if="active"
|
||||
class="node-creator"
|
||||
ref="nodeCreator"
|
||||
v-click-outside="onClickOutside"
|
||||
@dragover="onDragOver"
|
||||
@drop="onDrop"
|
||||
>
|
||||
<MainPanel
|
||||
@nodeTypeSelected="nodeTypeSelected"
|
||||
:categorizedItems="categorizedItems"
|
||||
:categoriesWithNodes="categoriesWithNodes"
|
||||
:searchItems="searchItems"
|
||||
/>
|
||||
</div>
|
||||
</SlideTransition>
|
||||
</div>
|
||||
|
@ -94,6 +106,22 @@ export default Vue.extend({
|
|||
nodeTypeSelected (nodeTypeName: string) {
|
||||
this.$emit('nodeTypeSelected', nodeTypeName);
|
||||
},
|
||||
onDragOver(event: DragEvent) {
|
||||
event.preventDefault();
|
||||
},
|
||||
onDrop(event: DragEvent) {
|
||||
if (!event.dataTransfer) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nodeTypeName = event.dataTransfer.getData('nodeTypeName');
|
||||
const nodeCreatorBoundingRect = (this.$refs.nodeCreator as Element).getBoundingClientRect();
|
||||
|
||||
// Abort drag end event propagation if dropped inside nodes panel
|
||||
if (nodeTypeName && event.pageX >= nodeCreatorBoundingRect.x && event.pageY >= nodeCreatorBoundingRect.y) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
nodeTypes(newList) {
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
<template>
|
||||
<div :class="{[$style['node-item']]: true, [$style.bordered]: bordered}">
|
||||
<div
|
||||
draggable
|
||||
@dragstart="onDragStart"
|
||||
@dragend="onDragEnd"
|
||||
:class="{[$style['node-item']]: true, [$style.bordered]: bordered}"
|
||||
>
|
||||
<NodeIcon :class="$style['node-icon']" :nodeType="nodeType" />
|
||||
<div>
|
||||
<div :class="$style.details">
|
||||
|
@ -11,7 +16,7 @@
|
|||
}}
|
||||
</span>
|
||||
<span :class="$style['trigger-icon']">
|
||||
<TriggerIcon v-if="$options.isTrigger(nodeType)" />
|
||||
<TriggerIcon v-if="isTrigger" />
|
||||
</span>
|
||||
</div>
|
||||
<div :class="$style.description">
|
||||
|
@ -21,14 +26,26 @@
|
|||
})
|
||||
}}
|
||||
</div>
|
||||
|
||||
<div :class="$style['draggable-data-transfer']" ref="draggableDataTransfer" />
|
||||
<transition name="node-item-transition">
|
||||
<div
|
||||
:class="$style.draggable"
|
||||
:style="draggableStyle"
|
||||
ref="draggable"
|
||||
v-show="dragging"
|
||||
>
|
||||
<NodeIcon class="node-icon" :nodeType="nodeType" :size="40" :shrink="false" />
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
import {getNewNodePosition, NODE_SIZE} from '@/views/canvasHelpers';
|
||||
import Vue from 'vue';
|
||||
import { INodeTypeDescription } from 'n8n-workflow';
|
||||
|
||||
import NodeIcon from '../NodeIcon.vue';
|
||||
import TriggerIcon from '../TriggerIcon.vue';
|
||||
|
@ -44,14 +61,73 @@ export default Vue.extend({
|
|||
'nodeType',
|
||||
'bordered',
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
dragging: false,
|
||||
draggablePosition: {
|
||||
x: -100,
|
||||
y: -100,
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
shortNodeType() {
|
||||
shortNodeType(): string {
|
||||
return this.$locale.shortNodeType(this.nodeType.name);
|
||||
},
|
||||
isTrigger (): boolean {
|
||||
return this.nodeType.group.includes('trigger');
|
||||
},
|
||||
draggableStyle(): { top: string; left: string; } {
|
||||
return {
|
||||
top: `${this.draggablePosition.y}px`,
|
||||
left: `${this.draggablePosition.x}px`,
|
||||
};
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
/**
|
||||
* Workaround for firefox, that doesn't attach the pageX and pageY coordinates to "ondrag" event.
|
||||
* All browsers attach the correct page coordinates to the "dragover" event.
|
||||
* @bug https://bugzilla.mozilla.org/show_bug.cgi?id=505521
|
||||
*/
|
||||
document.body.addEventListener("dragover", this.onDragOver);
|
||||
},
|
||||
destroyed() {
|
||||
document.body.removeEventListener("dragover", this.onDragOver);
|
||||
},
|
||||
methods: {
|
||||
onDragStart(event: DragEvent): void {
|
||||
const { pageX: x, pageY: y } = event;
|
||||
|
||||
this.$emit('dragstart', event);
|
||||
|
||||
if (event.dataTransfer) {
|
||||
event.dataTransfer.effectAllowed = "copy";
|
||||
event.dataTransfer.dropEffect = "copy";
|
||||
event.dataTransfer.setData('nodeTypeName', this.nodeType.name);
|
||||
event.dataTransfer.setDragImage(this.$refs.draggableDataTransfer as Element, 0, 0);
|
||||
}
|
||||
|
||||
this.dragging = true;
|
||||
this.draggablePosition = { x, y };
|
||||
},
|
||||
onDragOver(event: DragEvent): void {
|
||||
if (!this.dragging || event.pageX === 0 && event.pageY === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [x,y] = getNewNodePosition([], [event.pageX - NODE_SIZE / 2, event.pageY - NODE_SIZE / 2]);
|
||||
|
||||
this.draggablePosition = { x, y };
|
||||
},
|
||||
onDragEnd(event: DragEvent): void {
|
||||
this.$emit('dragend', event);
|
||||
|
||||
this.dragging = false;
|
||||
setTimeout(() => {
|
||||
this.draggablePosition = { x: -100, y: -100 };
|
||||
}, 300);
|
||||
},
|
||||
// @ts-ignore
|
||||
isTrigger (nodeType: INodeTypeDescription): boolean {
|
||||
return nodeType.group.includes('trigger');
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
@ -100,4 +176,39 @@ export default Vue.extend({
|
|||
display: flex;
|
||||
}
|
||||
|
||||
.draggable {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
opacity: 0.66;
|
||||
border: 2px solid var(--color-foreground-xdark);
|
||||
border-radius: var(--border-radius-large);
|
||||
background-color: var(--color-background-xlight);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.draggable-data-transfer {
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.node-item-transition {
|
||||
&-enter-active,
|
||||
&-leave-active {
|
||||
transition-property: opacity, transform;
|
||||
transition-duration: 300ms;
|
||||
transition-timing-function: ease;
|
||||
}
|
||||
|
||||
&-enter,
|
||||
&-leave-to {
|
||||
opacity: 0;
|
||||
transform: scale(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -13,7 +13,9 @@
|
|||
<ItemIterator
|
||||
:elements="elements"
|
||||
:activeIndex="activeIndex"
|
||||
@selected="selected"
|
||||
@selected="$emit('selected', $event)"
|
||||
@dragstart="$emit('dragstart', $event)"
|
||||
@dragend="$emit('dragend', $event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -38,9 +40,6 @@ export default Vue.extend({
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
selected(element: INodeCreateElement) {
|
||||
this.$emit('selected', element);
|
||||
},
|
||||
onBackArrowClick() {
|
||||
this.$emit('close');
|
||||
},
|
||||
|
|
288
packages/editor-ui/src/components/Sticky.vue
Normal file
288
packages/editor-ui/src/components/Sticky.vue
Normal file
|
@ -0,0 +1,288 @@
|
|||
<template>
|
||||
<div class="sticky-wrapper" :style="stickyPosition">
|
||||
<div
|
||||
:class="{'sticky-default': true, 'touch-active': isTouchActive, 'is-touch-device': isTouchDevice}"
|
||||
:style="stickySize"
|
||||
>
|
||||
<div class="select-sticky-background" v-show="isSelected" />
|
||||
<div
|
||||
class="sticky-box"
|
||||
:data-name="data.name"
|
||||
:ref="data.name"
|
||||
@click.left="mouseLeftClick"
|
||||
v-touch:start="touchStart"
|
||||
v-touch:end="touchEnd"
|
||||
>
|
||||
<n8n-sticky
|
||||
:content.sync="node.parameters.content"
|
||||
:height="node.parameters.height"
|
||||
:width="node.parameters.width"
|
||||
:scale="nodeViewScale"
|
||||
:id="nodeIndex"
|
||||
:readOnly="isReadOnly"
|
||||
:defaultText="defaultText"
|
||||
:editMode="isActive && !isReadOnly"
|
||||
:gridSize="gridSize"
|
||||
@input="onInputChange"
|
||||
@edit="onEdit"
|
||||
@resizestart="onResizeStart"
|
||||
@resize="onResize"
|
||||
@resizeend="onResizeEnd"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-show="showActions" class="sticky-options no-select-on-click">
|
||||
<div v-touch:tap="deleteNode" class="option" :title="$locale.baseText('node.deleteNode')" >
|
||||
<font-awesome-icon icon="trash" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
|
||||
import mixins from 'vue-typed-mixins';
|
||||
import { externalHooks } from '@/components/mixins/externalHooks';
|
||||
import { nodeBase } from '@/components/mixins/nodeBase';
|
||||
import { nodeHelpers } from '@/components/mixins/nodeHelpers';
|
||||
import { workflowHelpers } from '@/components/mixins/workflowHelpers';
|
||||
import { getStyleTokenValue, isNumber, isString } from './helpers';
|
||||
import { INodeUi, XYPosition } from '@/Interface';
|
||||
|
||||
import {
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export default mixins(externalHooks, nodeBase, nodeHelpers, workflowHelpers).extend({
|
||||
name: 'Sticky',
|
||||
props: {
|
||||
nodeViewScale: {
|
||||
type: Number,
|
||||
},
|
||||
gridSize: {
|
||||
type: Number,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
defaultText (): string {
|
||||
if (!this.nodeType) {
|
||||
return '';
|
||||
}
|
||||
const properties = this.nodeType.properties;
|
||||
const content = properties.find((property) => property.name === 'content');
|
||||
|
||||
return content && isString(content.default) ? content.default : '';
|
||||
},
|
||||
isSelected (): boolean {
|
||||
return this.$store.getters.getSelectedNodes.find((node: INodeUi) => node.name === this.data.name);
|
||||
},
|
||||
nodeType (): INodeTypeDescription | null {
|
||||
return this.data && this.$store.getters.nodeType(this.data.type, this.data.typeVersion);
|
||||
},
|
||||
node (): INodeUi | undefined { // same as this.data but reactive..
|
||||
return this.$store.getters.nodesByName[this.name] as INodeUi | undefined;
|
||||
},
|
||||
position (): XYPosition {
|
||||
if (this.node) {
|
||||
return this.node.position;
|
||||
} else {
|
||||
return [0, 0];
|
||||
}
|
||||
},
|
||||
height(): number {
|
||||
return this.node && isNumber(this.node.parameters.height)? this.node.parameters.height : 0;
|
||||
},
|
||||
width(): number {
|
||||
return this.node && isNumber(this.node.parameters.width)? this.node.parameters.width : 0;
|
||||
},
|
||||
stickySize(): object {
|
||||
const returnStyles: {
|
||||
[key: string]: string | number;
|
||||
} = {
|
||||
height: this.height + 'px',
|
||||
width: this.width + 'px',
|
||||
};
|
||||
|
||||
return returnStyles;
|
||||
},
|
||||
stickyPosition (): object {
|
||||
const returnStyles: {
|
||||
[key: string]: string | number;
|
||||
} = {
|
||||
left: this.position[0] + 'px',
|
||||
top: this.position[1] + 'px',
|
||||
zIndex: this.isActive ? 9999999 : -1 * Math.floor((this.height * this.width) / 1000),
|
||||
};
|
||||
|
||||
return returnStyles;
|
||||
},
|
||||
showActions(): boolean {
|
||||
return !(this.hideActions || this.isReadOnly || this.workflowRunning || this.isResizing);
|
||||
},
|
||||
workflowRunning (): boolean {
|
||||
return this.$store.getters.isActionActive('workflowRunning');
|
||||
},
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isResizing: false,
|
||||
isTouchActive: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
deleteNode () {
|
||||
Vue.nextTick(() => {
|
||||
// Wait a tick else vue causes problems because the data is gone
|
||||
this.$emit('removeNode', this.data.name);
|
||||
});
|
||||
},
|
||||
onEdit(edit: boolean) {
|
||||
if (edit && !this.isActive && this.node) {
|
||||
this.$store.commit('setActiveNode', this.node.name);
|
||||
}
|
||||
else if (this.isActive && !edit) {
|
||||
this.$store.commit('setActiveNode', null);
|
||||
}
|
||||
},
|
||||
onInputChange(content: string) {
|
||||
this.setParameters({content});
|
||||
},
|
||||
onResizeStart() {
|
||||
this.isResizing = true;
|
||||
if (!this.isSelected && this.node) {
|
||||
this.$emit('nodeSelected', this.node.name, false, true);
|
||||
}
|
||||
const nodeIndex = this.$store.getters.getNodeIndex(this.data.name);
|
||||
const nodeIdName = `node-${nodeIndex}`;
|
||||
this.instance.destroyDraggable(nodeIdName); // todo
|
||||
},
|
||||
onResize({height, width, dX, dY}: { width: number, height: number, dX: number, dY: number }) {
|
||||
if (!this.node) {
|
||||
return;
|
||||
}
|
||||
if (dX !== 0 || dY !== 0) {
|
||||
this.setPosition([this.node.position[0] + (dX || 0), this.node.position[1] + (dY || 0)]);
|
||||
}
|
||||
|
||||
this.setParameters({ height, width });
|
||||
},
|
||||
onResizeEnd() {
|
||||
this.isResizing = false;
|
||||
this.__makeInstanceDraggable(this.data);
|
||||
},
|
||||
setParameters(params: {content?: string, height?: number, width?: number}) {
|
||||
if (this.node) {
|
||||
const nodeParameters = {
|
||||
content: isString(params.content) ? params.content : this.node.parameters.content,
|
||||
height: isNumber(params.height) ? params.height : this.node.parameters.height,
|
||||
width: isNumber(params.width) ? params.width : this.node.parameters.width,
|
||||
};
|
||||
|
||||
const updateInformation = {
|
||||
name: this.node.name,
|
||||
value: nodeParameters,
|
||||
};
|
||||
|
||||
this.$store.commit('setNodeParameters', updateInformation);
|
||||
}
|
||||
},
|
||||
setPosition(position: XYPosition) {
|
||||
if (!this.node) {
|
||||
return;
|
||||
}
|
||||
|
||||
const updateInformation = {
|
||||
name: this.node.name,
|
||||
properties: {
|
||||
position,
|
||||
},
|
||||
};
|
||||
|
||||
this.$store.commit('updateNodeProperties', updateInformation);
|
||||
},
|
||||
touchStart () {
|
||||
if (this.isTouchDevice === true && this.isMacOs === false && this.isTouchActive === false) {
|
||||
this.isTouchActive = true;
|
||||
setTimeout(() => {
|
||||
this.isTouchActive = false;
|
||||
}, 2000);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.sticky-wrapper {
|
||||
position: absolute;
|
||||
|
||||
.sticky-default {
|
||||
position: absolute;
|
||||
|
||||
.sticky-box {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&.touch-active,
|
||||
&:hover {
|
||||
.sticky-options {
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.sticky-options {
|
||||
display: none;
|
||||
justify-content: flex-start;
|
||||
position: absolute;
|
||||
top: -25px;
|
||||
left: -8px;
|
||||
height: 26px;
|
||||
font-size: 0.9em;
|
||||
text-align: left;
|
||||
z-index: 10;
|
||||
color: #aaa;
|
||||
text-align: center;
|
||||
|
||||
.option {
|
||||
width: 28px;
|
||||
display: inline-block;
|
||||
|
||||
&.touch {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: $--color-primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.is-touch-device .sticky-options {
|
||||
left: -25px;
|
||||
width: 150px;
|
||||
|
||||
.option.touch {
|
||||
display: initial;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.select-sticky-background {
|
||||
display: block;
|
||||
position: absolute;
|
||||
background-color: hsla(var(--color-foreground-base-h), var(--color-foreground-base-s), var(--color-foreground-base-l), 60%);
|
||||
border-radius: var(--border-radius-xlarge);
|
||||
overflow: hidden;
|
||||
height: calc(100% + 16px);
|
||||
width: calc(100% + 16px);
|
||||
left: -8px;
|
||||
top: -8px;
|
||||
z-index: 0;
|
||||
}
|
||||
</style>
|
|
@ -59,3 +59,11 @@ export function filterTemplateNodes(nodes: ITemplatesNode[]) {
|
|||
export function setPageTitle(title: string) {
|
||||
window.document.title = title;
|
||||
}
|
||||
|
||||
export function isString(value: unknown): value is string {
|
||||
return typeof value === 'string';
|
||||
}
|
||||
|
||||
export function isNumber(value: unknown): value is number {
|
||||
return typeof value === 'number';
|
||||
}
|
||||
|
|
|
@ -190,7 +190,6 @@ export const mouseSelect = mixins(
|
|||
this.$store.commit('resetSelectedNodes');
|
||||
this.$store.commit('setLastSelectedNode', null);
|
||||
this.$store.commit('setLastSelectedNodeOutputIndex', null);
|
||||
this.$store.commit('setActiveNode', null);
|
||||
// @ts-ignore
|
||||
this.lastSelectedConnection = null;
|
||||
// @ts-ignore
|
||||
|
|
|
@ -4,7 +4,7 @@ import mixins from 'vue-typed-mixins';
|
|||
|
||||
import { deviceSupportHelpers } from '@/components/mixins/deviceSupportHelpers';
|
||||
import { nodeIndex } from '@/components/mixins/nodeIndex';
|
||||
import { NODE_NAME_PREFIX, NO_OP_NODE_TYPE } from '@/constants';
|
||||
import { NODE_NAME_PREFIX, NO_OP_NODE_TYPE, STICKY_NODE_TYPE } from '@/constants';
|
||||
import * as CanvasHelpers from '@/views/canvasHelpers';
|
||||
import { Endpoint } from 'jsplumb';
|
||||
|
||||
|
@ -221,7 +221,15 @@ export const nodeBase = mixins(
|
|||
// @ts-ignore
|
||||
this.dragging = true;
|
||||
|
||||
if (params.e && !this.$store.getters.isNodeSelected(this.data.name)) {
|
||||
const isSelected = this.$store.getters.isNodeSelected(this.data.name);
|
||||
const nodeName = this.data.name;
|
||||
if (this.data.type === STICKY_NODE_TYPE && !isSelected) {
|
||||
setTimeout(() => {
|
||||
this.$emit('nodeSelected', nodeName, false, true);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
if (params.e && !isSelected) {
|
||||
// Only the node which gets dragged directly gets an event, for all others it is
|
||||
// undefined. So check if the currently dragged node is selected and if not clear
|
||||
// the drag-selection.
|
||||
|
|
|
@ -65,6 +65,7 @@ export const JIRA_TRIGGER_NODE_TYPE = 'n8n-nodes-base.jiraTrigger';
|
|||
export const MICROSOFT_EXCEL_NODE_TYPE = 'n8n-nodes-base.microsoftExcel';
|
||||
export const MICROSOFT_TEAMS_NODE_TYPE = 'n8n-nodes-base.microsoftTeams';
|
||||
export const NO_OP_NODE_TYPE = 'n8n-nodes-base.noOp';
|
||||
export const STICKY_NODE_TYPE = 'n8n-nodes-base.stickyNote';
|
||||
export const NOTION_TRIGGER_NODE_TYPE = 'n8n-nodes-base.notionTrigger';
|
||||
export const PAGERDUTY_NODE_TYPE = 'n8n-nodes-base.pagerDuty';
|
||||
export const SALESFORCE_NODE_TYPE = 'n8n-nodes-base.salesforce';
|
||||
|
|
|
@ -65,6 +65,7 @@ import {
|
|||
N8nRadioButtons,
|
||||
N8nSelect,
|
||||
N8nSpinner,
|
||||
N8nSticky,
|
||||
N8nTabs,
|
||||
N8nFormInputs,
|
||||
N8nFormBox,
|
||||
|
@ -100,6 +101,7 @@ Vue.use(N8nMenuItem);
|
|||
Vue.use(N8nOption);
|
||||
Vue.use(N8nSelect);
|
||||
Vue.use(N8nSpinner);
|
||||
Vue.component('n8n-sticky', N8nSticky);
|
||||
Vue.use(N8nRadioButtons);
|
||||
Vue.component('n8n-square-button', N8nSquareButton);
|
||||
Vue.use(N8nTags);
|
||||
|
|
|
@ -411,6 +411,7 @@
|
|||
"nodeSettings.waitBetweenTries.description": "How long to wait between each attempt (in milliseconds)",
|
||||
"nodeSettings.waitBetweenTries.displayName": "Wait Between Tries (ms)",
|
||||
"nodeView.addNode": "Add node",
|
||||
"nodeView.addSticky": "Click to add sticky note",
|
||||
"nodeView.confirmMessage.beforeRouteLeave.cancelButtonText": "Leave without saving",
|
||||
"nodeView.confirmMessage.beforeRouteLeave.confirmButtonText": "Save",
|
||||
"nodeView.confirmMessage.beforeRouteLeave.headline": "Save changes before leaving?",
|
||||
|
|
|
@ -93,7 +93,11 @@ import {
|
|||
faUserCircle,
|
||||
faUserFriends,
|
||||
faUsers,
|
||||
faStickyNote as faSolidStickyNote,
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
import {
|
||||
faStickyNote,
|
||||
} from '@fortawesome/free-regular-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
||||
|
||||
function addIcon(icon: any) { // tslint:disable-line:no-any
|
||||
|
@ -177,6 +181,8 @@ addIcon(faServer);
|
|||
addIcon(faSignInAlt);
|
||||
addIcon(faSlidersH);
|
||||
addIcon(faSpinner);
|
||||
addIcon(faSolidStickyNote);
|
||||
addIcon(faStickyNote);
|
||||
addIcon(faStop);
|
||||
addIcon(faSun);
|
||||
addIcon(faSync);
|
||||
|
|
|
@ -169,6 +169,9 @@ class Telemetry {
|
|||
case 'nodeView.addNodeButton':
|
||||
this.telemetry.track('User added node to workflow canvas', properties);
|
||||
break;
|
||||
case 'nodeView.addSticky':
|
||||
this.telemetry.track('User inserted workflow note', properties);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
<template>
|
||||
<div class="node-view-root">
|
||||
<div
|
||||
class="node-view-root"
|
||||
@dragover="onDragOver"
|
||||
@drop="onDrop"
|
||||
>
|
||||
<div
|
||||
class="node-view-wrapper"
|
||||
:class="workflowClasses"
|
||||
|
@ -11,10 +15,15 @@
|
|||
@mouseup="mouseUp"
|
||||
@wheel="wheelScroll"
|
||||
>
|
||||
<div id="node-view-background" class="node-view-background" :style="backgroundStyle"></div>
|
||||
<div id="node-view" class="node-view" :style="workflowStyle">
|
||||
<div id="node-view-background" class="node-view-background" :style="backgroundStyle" />
|
||||
<div
|
||||
id="node-view"
|
||||
class="node-view"
|
||||
:style="workflowStyle"
|
||||
>
|
||||
<div v-for="nodeData in nodes" :key="getNodeIndex(nodeData.name)">
|
||||
<node
|
||||
v-for="nodeData in nodes"
|
||||
v-if="nodeData.type !== STICKY_NODE_TYPE"
|
||||
@duplicateNode="duplicateNode"
|
||||
@deselectAllNodes="deselectAllNodes"
|
||||
@deselectNode="nodeDeselectedByName"
|
||||
|
@ -30,18 +39,42 @@
|
|||
:instance="instance"
|
||||
:isActive="!!activeNode && activeNode.name === nodeData.name"
|
||||
:hideActions="pullConnActive"
|
||||
></node>
|
||||
/>
|
||||
<Sticky
|
||||
v-else
|
||||
@deselectAllNodes="deselectAllNodes"
|
||||
@deselectNode="nodeDeselectedByName"
|
||||
@nodeSelected="nodeSelectedByName"
|
||||
@removeNode="removeNode"
|
||||
:id="'node-' + getNodeIndex(nodeData.name)"
|
||||
:name="nodeData.name"
|
||||
:isReadOnly="isReadOnly"
|
||||
:instance="instance"
|
||||
:isActive="!!activeNode && activeNode.name === nodeData.name"
|
||||
:nodeViewScale="nodeViewScale"
|
||||
:gridSize="GRID_SIZE"
|
||||
:hideActions="pullConnActive"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DataDisplay :renaming="renamingActive" @valueChanged="valueChanged"/>
|
||||
<div v-if="!createNodeActive && !isReadOnly" class="node-creator-button" :title="$locale.baseText('nodeView.addNode')" @click="() => openNodeCreator('add_node_button')">
|
||||
<n8n-icon-button size="xlarge" icon="plus" />
|
||||
<div
|
||||
class="node-buttons-wrapper"
|
||||
v-if="!createNodeActive && !isReadOnly"
|
||||
>
|
||||
<div class="node-creator-button">
|
||||
<n8n-icon-button size="xlarge" icon="plus" @click="() => openNodeCreator('add_node_button')" :title="$locale.baseText('nodeView.addNode')"/>
|
||||
<div class="add-sticky-button" @click="nodeTypeSelected(STICKY_NODE_TYPE)">
|
||||
<n8n-icon-button size="large" :icon="['far', 'note-sticky']" type="outline" :title="$locale.baseText('nodeView.addSticky')"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<node-creator
|
||||
:active="createNodeActive"
|
||||
@nodeTypeSelected="nodeTypeSelected"
|
||||
@closeNodeCreator="closeNodeCreator"
|
||||
></node-creator>
|
||||
/>
|
||||
<div :class="{ 'zoom-menu': true, 'regular-zoom-menu': !isDemo, 'demo-zoom-menu': isDemo, expanded: !sidebarMenuCollapsed }">
|
||||
<button @click="zoomToFit" class="button-white" :title="$locale.baseText('nodeView.zoomToFit')">
|
||||
<font-awesome-icon icon="expand"/>
|
||||
|
@ -114,7 +147,7 @@ import {
|
|||
} from 'jsplumb';
|
||||
import { MessageBoxInputData } from 'element-ui/types/message-box';
|
||||
import { jsPlumb, OnConnectionBindInfo } from 'jsplumb';
|
||||
import { MODAL_CANCEL, MODAL_CLOSE, MODAL_CONFIRMED, NODE_NAME_PREFIX, NODE_OUTPUT_DEFAULT_KEY, PLACEHOLDER_EMPTY_WORKFLOW_ID, START_NODE_TYPE, VIEWS, WEBHOOK_NODE_TYPE, WORKFLOW_OPEN_MODAL_KEY } from '@/constants';
|
||||
import { MODAL_CANCEL, MODAL_CLOSE, MODAL_CONFIRMED, NODE_NAME_PREFIX, NODE_OUTPUT_DEFAULT_KEY, PLACEHOLDER_EMPTY_WORKFLOW_ID, START_NODE_TYPE, STICKY_NODE_TYPE, VIEWS, WEBHOOK_NODE_TYPE, WORKFLOW_OPEN_MODAL_KEY } from '@/constants';
|
||||
import { copyPaste } from '@/components/mixins/copyPaste';
|
||||
import { externalHooks } from '@/components/mixins/externalHooks';
|
||||
import { genericHelpers } from '@/components/mixins/genericHelpers';
|
||||
|
@ -133,6 +166,7 @@ import Node from '@/components/Node.vue';
|
|||
import NodeCreator from '@/components/NodeCreator/NodeCreator.vue';
|
||||
import NodeSettings from '@/components/NodeSettings.vue';
|
||||
import RunData from '@/components/RunData.vue';
|
||||
import Sticky from '@/components/Sticky.vue';
|
||||
|
||||
import * as CanvasHelpers from './canvasHelpers';
|
||||
|
||||
|
@ -177,6 +211,11 @@ import {
|
|||
import '../plugins/N8nCustomConnectorType';
|
||||
import '../plugins/PlusEndpointType';
|
||||
|
||||
interface AddNodeOptions {
|
||||
position?: XYPosition;
|
||||
dragAndDrop?: boolean;
|
||||
}
|
||||
|
||||
export default mixins(
|
||||
copyPaste,
|
||||
externalHooks,
|
||||
|
@ -198,6 +237,7 @@ export default mixins(
|
|||
NodeCreator,
|
||||
NodeSettings,
|
||||
RunData,
|
||||
Sticky,
|
||||
},
|
||||
errorCaptured: (err, vm, info) => {
|
||||
console.error('errorCaptured'); // eslint-disable-line no-console
|
||||
|
@ -331,6 +371,8 @@ export default mixins(
|
|||
},
|
||||
data () {
|
||||
return {
|
||||
GRID_SIZE: CanvasHelpers.GRID_SIZE,
|
||||
STICKY_NODE_TYPE,
|
||||
createNodeActive: false,
|
||||
instance: jsPlumb.getInstance(),
|
||||
lastSelectedConnection: null as null | Connection,
|
||||
|
@ -697,7 +739,7 @@ export default mixins(
|
|||
this.ctrlKeyPressed = true;
|
||||
} else if (e.key === 'F2' && !this.isReadOnly) {
|
||||
const lastSelectedNode = this.lastSelectedNode;
|
||||
if (lastSelectedNode !== null) {
|
||||
if (lastSelectedNode !== null && lastSelectedNode.type !== STICKY_NODE_TYPE) {
|
||||
this.callDebounced('renameNodePrompt', { debounceTime: 1500 }, lastSelectedNode.name);
|
||||
}
|
||||
} else if ((e.key === '=' || e.key === '+') && !this.isCtrlKeyPressed(e)) {
|
||||
|
@ -764,6 +806,9 @@ export default mixins(
|
|||
const lastSelectedNode = this.lastSelectedNode;
|
||||
|
||||
if (lastSelectedNode !== null) {
|
||||
if (lastSelectedNode.type === STICKY_NODE_TYPE && this.isReadOnly) {
|
||||
return;
|
||||
}
|
||||
this.$store.commit('setActiveNode', lastSelectedNode.name);
|
||||
}
|
||||
} else if (e.key === 'ArrowRight' && e.shiftKey === true) {
|
||||
|
@ -1227,6 +1272,27 @@ export default mixins(
|
|||
this.createNodeActive = false;
|
||||
},
|
||||
|
||||
onDragOver(event: DragEvent) {
|
||||
event.preventDefault();
|
||||
},
|
||||
|
||||
onDrop(event: DragEvent) {
|
||||
if (!event.dataTransfer) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nodeTypeName = event.dataTransfer.getData('nodeTypeName');
|
||||
if (nodeTypeName) {
|
||||
const mousePosition = this.getMousePositionWithinNodeView(event);
|
||||
|
||||
this.addNodeButton(nodeTypeName, {
|
||||
position: [mousePosition[0] - CanvasHelpers.NODE_SIZE / 2, mousePosition[1] - CanvasHelpers.NODE_SIZE / 2],
|
||||
dragAndDrop: true,
|
||||
});
|
||||
this.createNodeActive = false;
|
||||
}
|
||||
},
|
||||
|
||||
nodeDeselectedByName (nodeName: string) {
|
||||
const node = this.$store.getters.getNodeByName(nodeName);
|
||||
if (node) {
|
||||
|
@ -1267,7 +1333,7 @@ export default mixins(
|
|||
duration: 0,
|
||||
});
|
||||
},
|
||||
async injectNode (nodeTypeName: string) {
|
||||
async injectNode (nodeTypeName: string, options: AddNodeOptions = {}) {
|
||||
const nodeTypeData: INodeTypeDescription | null = this.$store.getters.nodeType(nodeTypeName);
|
||||
|
||||
if (nodeTypeData === null) {
|
||||
|
@ -1297,7 +1363,10 @@ export default mixins(
|
|||
|
||||
// when pulling new connection from node or injecting into a connection
|
||||
const lastSelectedNode = this.lastSelectedNode;
|
||||
if (lastSelectedNode) {
|
||||
|
||||
if (options.position) {
|
||||
newNodeData.position = CanvasHelpers.getNewNodePosition(this.nodes, options.position);
|
||||
} else if (lastSelectedNode) {
|
||||
const lastSelectedConnection = this.lastSelectedConnection;
|
||||
if (lastSelectedConnection) { // set when injecting into a connection
|
||||
const [diffX] = CanvasHelpers.getConnectorLengths(lastSelectedConnection);
|
||||
|
@ -1308,10 +1377,12 @@ export default mixins(
|
|||
|
||||
// set when pulling connections
|
||||
if (this.newNodeInsertPosition) {
|
||||
newNodeData.position = CanvasHelpers.getNewNodePosition(this.nodes, [this.newNodeInsertPosition[0] + CanvasHelpers.GRID_SIZE, this.newNodeInsertPosition[1] - CanvasHelpers.NODE_SIZE / 2]);
|
||||
newNodeData.position = CanvasHelpers.getNewNodePosition(this.nodes, [
|
||||
this.newNodeInsertPosition[0] + CanvasHelpers.GRID_SIZE,
|
||||
this.newNodeInsertPosition[1] - CanvasHelpers.NODE_SIZE / 2,
|
||||
]);
|
||||
this.newNodeInsertPosition = null;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
let yOffset = 0;
|
||||
|
||||
if (lastSelectedConnection) {
|
||||
|
@ -1352,14 +1423,22 @@ export default mixins(
|
|||
|
||||
this.$store.commit('setStateDirty', true);
|
||||
|
||||
if (nodeTypeName === STICKY_NODE_TYPE) {
|
||||
this.$telemetry.trackNodesPanel('nodeView.addSticky', { workflow_id: this.$store.getters.workflowId });
|
||||
} else {
|
||||
this.$externalHooks().run('nodeView.addNodeButton', { nodeTypeName });
|
||||
this.$telemetry.trackNodesPanel('nodeView.addNodeButton', { node_type: nodeTypeName, workflow_id: this.$store.getters.workflowId });
|
||||
this.$telemetry.trackNodesPanel('nodeView.addNodeButton', {
|
||||
node_type: nodeTypeName,
|
||||
workflow_id: this.$store.getters.workflowId,
|
||||
drag_and_drop: options.dragAndDrop,
|
||||
} as IDataObject);
|
||||
}
|
||||
|
||||
// Automatically deselect all nodes and select the current one and also active
|
||||
// current node
|
||||
this.deselectAllNodes();
|
||||
setTimeout(() => {
|
||||
this.nodeSelectedByName(newNodeData.name, true);
|
||||
this.nodeSelectedByName(newNodeData.name, nodeTypeName !== STICKY_NODE_TYPE);
|
||||
});
|
||||
|
||||
return newNodeData;
|
||||
|
@ -1396,7 +1475,7 @@ export default mixins(
|
|||
|
||||
this.__addConnection(connectionData, true);
|
||||
},
|
||||
async addNodeButton (nodeTypeName: string) {
|
||||
async addNodeButton (nodeTypeName: string, options: AddNodeOptions = {}) {
|
||||
if (this.editAllowedCheck() === false) {
|
||||
return;
|
||||
}
|
||||
|
@ -1405,7 +1484,7 @@ export default mixins(
|
|||
const lastSelectedNode = this.lastSelectedNode;
|
||||
const lastSelectedNodeOutputIndex = this.$store.getters.lastSelectedNodeOutputIndex;
|
||||
|
||||
const newNodeData = await this.injectNode(nodeTypeName);
|
||||
const newNodeData = await this.injectNode(nodeTypeName, options);
|
||||
if (!newNodeData) {
|
||||
return;
|
||||
}
|
||||
|
@ -2131,8 +2210,12 @@ export default mixins(
|
|||
}
|
||||
}
|
||||
|
||||
if(node.type === STICKY_NODE_TYPE) {
|
||||
this.$telemetry.track('User deleted workflow note', { workflow_id: this.$store.getters.workflowId });
|
||||
} else {
|
||||
this.$externalHooks().run('node.deleteNode', { node });
|
||||
this.$telemetry.track('User deleted node', { node_type: node.type, workflow_id: this.$store.getters.workflowId });
|
||||
}
|
||||
|
||||
let waitForNewConnection = false;
|
||||
// connect nodes before/after deleted node
|
||||
|
@ -2826,6 +2909,28 @@ export default mixins(
|
|||
bottom: 10px;
|
||||
}
|
||||
|
||||
.node-buttons-wrapper {
|
||||
position: fixed;
|
||||
width: 150px;
|
||||
height: 200px;
|
||||
top: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
|
||||
.add-sticky-button {
|
||||
margin-top: var(--spacing-2xs);
|
||||
opacity: 0;
|
||||
transition: .1s;
|
||||
transition-timing-function: linear;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.add-sticky-button {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.node-creator-button {
|
||||
position: fixed;
|
||||
text-align: center;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { getStyleTokenValue } from "@/components/helpers";
|
||||
import { NODE_OUTPUT_DEFAULT_KEY, START_NODE_TYPE } from "@/constants";
|
||||
import { getStyleTokenValue, isNumber } from "@/components/helpers";
|
||||
import { NODE_OUTPUT_DEFAULT_KEY, START_NODE_TYPE, STICKY_NODE_TYPE } from "@/constants";
|
||||
import { IBounds, INodeUi, IZoomConfig, XYPosition } from "@/Interface";
|
||||
import { Connection, Endpoint, Overlay, OverlaySpec, PaintStyle } from "jsplumb";
|
||||
import {
|
||||
|
@ -217,17 +217,22 @@ export const getLeftmostTopNode = (nodes: INodeUi[]): INodeUi => {
|
|||
|
||||
export const getWorkflowCorners = (nodes: INodeUi[]): IBounds => {
|
||||
return nodes.reduce((accu: IBounds, node: INodeUi) => {
|
||||
if (node.position[0] < accu.minX) {
|
||||
accu.minX = node.position[0];
|
||||
const xOffset = node.type === STICKY_NODE_TYPE && isNumber(node.parameters.width) ? node.parameters.width : NODE_SIZE;
|
||||
const yOffset = node.type === STICKY_NODE_TYPE && isNumber(node.parameters.height) ? node.parameters.height : NODE_SIZE;
|
||||
const x = node.position[0];
|
||||
const y = node.position[1];
|
||||
|
||||
if (x < accu.minX) {
|
||||
accu.minX = x;
|
||||
}
|
||||
if (node.position[1] < accu.minY) {
|
||||
accu.minY = node.position[1];
|
||||
if (y < accu.minY) {
|
||||
accu.minY = y;
|
||||
}
|
||||
if (node.position[0] > accu.maxX) {
|
||||
accu.maxX = node.position[0];
|
||||
if ((x + xOffset) > accu.maxX) {
|
||||
accu.maxX = x + xOffset;
|
||||
}
|
||||
if (node.position[1] > accu.maxY) {
|
||||
accu.maxY = node.position[1];
|
||||
if ((y + yOffset) > accu.maxY) {
|
||||
accu.maxY = y + yOffset;
|
||||
}
|
||||
|
||||
return accu;
|
||||
|
@ -592,6 +597,7 @@ export const getZoomToFit = (nodes: INodeUi[], addComponentPadding = true): {off
|
|||
const {minX, minY, maxX, maxY} = getWorkflowCorners(nodes);
|
||||
const sidebarWidth = addComponentPadding? SIDEBAR_WIDTH: 0;
|
||||
const headerHeight = addComponentPadding? HEADER_HEIGHT: 0;
|
||||
const footerHeight = addComponentPadding? 200: 100;
|
||||
|
||||
const PADDING = NODE_SIZE * 4;
|
||||
|
||||
|
@ -605,10 +611,10 @@ export const getZoomToFit = (nodes: INodeUi[], addComponentPadding = true): {off
|
|||
|
||||
const zoomLevel = Math.min(scaleX, scaleY, 1);
|
||||
let xOffset = (minX * -1) * zoomLevel + sidebarWidth; // find top right corner
|
||||
xOffset += (editorWidth - sidebarWidth - (maxX - minX + NODE_SIZE) * zoomLevel) / 2; // add padding to center workflow
|
||||
xOffset += (editorWidth - sidebarWidth - (maxX - minX) * zoomLevel) / 2; // add padding to center workflow
|
||||
|
||||
let yOffset = (minY * -1) * zoomLevel + headerHeight; // find top right corner
|
||||
yOffset += (editorHeight - headerHeight - (maxY - minY + NODE_SIZE * 2) * zoomLevel) / 2; // add padding to center workflow
|
||||
yOffset += (editorHeight - headerHeight - (maxY - minY + footerHeight) * zoomLevel) / 2; // add padding to center workflow
|
||||
|
||||
return {
|
||||
zoomLevel,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n-node-dev",
|
||||
"version": "0.52.0",
|
||||
"version": "0.54.0",
|
||||
"description": "CLI to simplify n8n credentials/node development",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"homepage": "https://n8n.io",
|
||||
|
@ -61,8 +61,8 @@
|
|||
"change-case": "^4.1.1",
|
||||
"copyfiles": "^2.1.1",
|
||||
"inquirer": "^7.0.1",
|
||||
"n8n-core": "~0.113.0",
|
||||
"n8n-workflow": "~0.95.0",
|
||||
"n8n-core": "~0.115.0",
|
||||
"n8n-workflow": "~0.97.0",
|
||||
"oauth-1.0a": "^2.2.6",
|
||||
"replace-in-file": "^6.0.0",
|
||||
"request": "^2.88.2",
|
||||
|
|
|
@ -31,6 +31,29 @@ export class Aws implements ICredentialType {
|
|||
password: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Temporary Security Credentials',
|
||||
name: 'temporaryCredentials',
|
||||
description: 'Support for temporary credentials from AWS STS',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
displayName: 'Session Token',
|
||||
name: 'sessionToken',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
temporaryCredentials: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
typeOptions: {
|
||||
password: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Custom Endpoints',
|
||||
name: 'customEndpoints',
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import {
|
||||
IAuthenticateBearer,
|
||||
IAuthenticateQueryAuth,
|
||||
ICredentialTestRequest,
|
||||
ICredentialType,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
||||
export class SlackApi implements ICredentialType {
|
||||
name = 'slackApi';
|
||||
displayName = 'Slack API';
|
||||
|
@ -17,4 +19,26 @@ export class SlackApi implements ICredentialType {
|
|||
required: true,
|
||||
},
|
||||
];
|
||||
authenticate: IAuthenticateBearer = {
|
||||
type: 'bearer',
|
||||
properties: {
|
||||
tokenPropertyName: 'accessToken',
|
||||
},
|
||||
};
|
||||
test: ICredentialTestRequest = {
|
||||
request: {
|
||||
baseURL: 'https://slack.com',
|
||||
url: '/api/users.profile.get',
|
||||
},
|
||||
rules: [
|
||||
{
|
||||
type: 'responseSuccessBody',
|
||||
properties: {
|
||||
key: 'error',
|
||||
value: 'invalid_auth',
|
||||
message: 'Invalid access token',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
|
|
@ -128,7 +128,7 @@ const postalAddressesFields: INodeProperties[] = [
|
|||
displayName: 'Location',
|
||||
name: 'location',
|
||||
type: 'fixedCollection',
|
||||
default: '',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Location Fields',
|
||||
|
|
|
@ -56,7 +56,6 @@ export const accountContactFields: INodeProperties[] = [
|
|||
],
|
||||
},
|
||||
},
|
||||
description: 'Account ID',
|
||||
},
|
||||
{
|
||||
displayName: 'Contact ID',
|
||||
|
@ -74,7 +73,6 @@ export const accountContactFields: INodeProperties[] = [
|
|||
],
|
||||
},
|
||||
},
|
||||
description: 'Contact ID',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
|
|
|
@ -51,7 +51,6 @@ export const contactListFields: INodeProperties[] = [
|
|||
],
|
||||
},
|
||||
},
|
||||
description: 'List ID',
|
||||
},
|
||||
{
|
||||
displayName: 'Contact ID',
|
||||
|
@ -69,7 +68,6 @@ export const contactListFields: INodeProperties[] = [
|
|||
],
|
||||
},
|
||||
},
|
||||
description: 'Contact ID',
|
||||
},
|
||||
|
||||
// ----------------------------------
|
||||
|
@ -91,7 +89,6 @@ export const contactListFields: INodeProperties[] = [
|
|||
],
|
||||
},
|
||||
},
|
||||
description: 'List ID',
|
||||
},
|
||||
{
|
||||
displayName: 'Contact ID',
|
||||
|
@ -109,6 +106,5 @@ export const contactListFields: INodeProperties[] = [
|
|||
],
|
||||
},
|
||||
},
|
||||
description: 'Contact ID',
|
||||
},
|
||||
];
|
||||
|
|
|
@ -54,7 +54,6 @@ export const contactTagFields: INodeProperties[] = [
|
|||
],
|
||||
},
|
||||
},
|
||||
description: 'Tag ID',
|
||||
},
|
||||
{
|
||||
displayName: 'Contact ID',
|
||||
|
@ -72,7 +71,6 @@ export const contactTagFields: INodeProperties[] = [
|
|||
],
|
||||
},
|
||||
},
|
||||
description: 'Contact ID',
|
||||
},
|
||||
// ----------------------------------
|
||||
// contactTag:delete
|
||||
|
|
|
@ -73,7 +73,7 @@ export const ecomOrderFields: INodeProperties[] = [
|
|||
],
|
||||
},
|
||||
},
|
||||
description: 'The id of the order in the external service. ONLY REQUIRED IF EXTERNALCHECKOUTID NOT INCLUDED',
|
||||
description: 'The id of the order in the external service. ONLY REQUIRED IF EXTERNALCHECKOUTID NOT INCLUDED.',
|
||||
},
|
||||
{
|
||||
displayName: 'External checkout ID',
|
||||
|
@ -437,7 +437,7 @@ export const ecomOrderFields: INodeProperties[] = [
|
|||
name: 'externalid',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'The id of the order in the external service. ONLY REQUIRED IF EXTERNALCHECKOUTID NOT INCLUDED',
|
||||
description: 'The id of the order in the external service. ONLY REQUIRED IF EXTERNALCHECKOUTID NOT INCLUDED.',
|
||||
},
|
||||
{
|
||||
displayName: 'External checkout ID',
|
||||
|
|
|
@ -155,7 +155,7 @@ export class Affinity implements INodeType {
|
|||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
const returnData: IDataObject[] = [];
|
||||
const length = items.length as unknown as number;
|
||||
const length = items.length;
|
||||
let responseData;
|
||||
const qs: IDataObject = {};
|
||||
const resource = this.getNodeParameter('resource', 0) as string;
|
||||
|
|
|
@ -183,6 +183,7 @@ export const companyFields: INodeProperties[] = [
|
|||
],
|
||||
},
|
||||
},
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-default-wrong-for-simplify
|
||||
default: false,
|
||||
description: 'Return a simplified version of the response instead of the raw data.',
|
||||
},
|
||||
|
@ -206,7 +207,7 @@ export const companyFields: INodeProperties[] = [
|
|||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
default: {},
|
||||
placeholder: 'Add Condition',
|
||||
options: [
|
||||
{
|
||||
|
@ -319,7 +320,6 @@ export const companyFields: INodeProperties[] = [
|
|||
},
|
||||
},
|
||||
default: '',
|
||||
description: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
|
@ -388,7 +388,6 @@ export const companyFields: INodeProperties[] = [
|
|||
name: 'jsonParameters',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
|
@ -600,7 +599,6 @@ export const companyFields: INodeProperties[] = [
|
|||
name: 'customProperties',
|
||||
type: 'fixedCollection',
|
||||
default: {},
|
||||
description: 'Custom Properties',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
|
@ -686,7 +684,6 @@ export const companyFields: INodeProperties[] = [
|
|||
name: 'jsonParameters',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
|
@ -898,7 +895,6 @@ export const companyFields: INodeProperties[] = [
|
|||
name: 'customProperties',
|
||||
type: 'fixedCollection',
|
||||
default: {},
|
||||
description: 'Custom Properties',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
|
|
|
@ -183,6 +183,7 @@ export const contactFields: INodeProperties[] = [
|
|||
],
|
||||
},
|
||||
},
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-default-wrong-for-simplify
|
||||
default: false,
|
||||
description: 'Return a simplified version of the response instead of the raw data.',
|
||||
},
|
||||
|
@ -206,7 +207,7 @@ export const contactFields: INodeProperties[] = [
|
|||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
default: {},
|
||||
placeholder: 'Add Condition',
|
||||
options: [
|
||||
{
|
||||
|
@ -319,7 +320,6 @@ export const contactFields: INodeProperties[] = [
|
|||
},
|
||||
},
|
||||
default: '',
|
||||
description: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
|
@ -389,7 +389,6 @@ export const contactFields: INodeProperties[] = [
|
|||
name: 'jsonParameters',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
|
@ -539,7 +538,6 @@ export const contactFields: INodeProperties[] = [
|
|||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'Email',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -769,7 +767,6 @@ export const contactFields: INodeProperties[] = [
|
|||
name: 'customProperties',
|
||||
type: 'fixedCollection',
|
||||
default: {},
|
||||
description: 'Custom Properties',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
|
@ -855,7 +852,6 @@ export const contactFields: INodeProperties[] = [
|
|||
name: 'jsonParameters',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
|
@ -1003,7 +999,6 @@ export const contactFields: INodeProperties[] = [
|
|||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'Email',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -1233,7 +1228,6 @@ export const contactFields: INodeProperties[] = [
|
|||
name: 'customProperties',
|
||||
type: 'fixedCollection',
|
||||
default: {},
|
||||
description: 'Custom Properties',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
|
|
|
@ -231,7 +231,6 @@ export const dealFields: INodeProperties[] = [
|
|||
name: 'jsonParameters',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
|
@ -302,7 +301,6 @@ export const dealFields: INodeProperties[] = [
|
|||
name: 'customData',
|
||||
type: 'fixedCollection',
|
||||
default: {},
|
||||
description: 'Custom Data',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
|
@ -381,7 +379,6 @@ export const dealFields: INodeProperties[] = [
|
|||
name: 'jsonParameters',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
|
@ -482,7 +479,6 @@ export const dealFields: INodeProperties[] = [
|
|||
name: 'customData',
|
||||
type: 'fixedCollection',
|
||||
default: {},
|
||||
description: 'Custom Data',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
|
|
|
@ -433,7 +433,7 @@ export class Airtable implements INodeType {
|
|||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Comma separated list of fields to ignore.',
|
||||
description: 'Comma-separated list of fields to ignore.',
|
||||
},
|
||||
{
|
||||
displayName: 'Typecast',
|
||||
|
|
|
@ -137,7 +137,7 @@ export class Automizy implements INodeType {
|
|||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
const returnData: IDataObject[] = [];
|
||||
const length = (items.length as unknown) as number;
|
||||
const length = items.length;
|
||||
const qs: IDataObject = {};
|
||||
let responseData;
|
||||
const resource = this.getNodeParameter('resource', 0) as string;
|
||||
|
|
|
@ -110,7 +110,7 @@ export const contactFields: INodeProperties[] = [
|
|||
displayName: 'Custom Fields',
|
||||
name: 'customFieldsUi',
|
||||
type: 'fixedCollection',
|
||||
default: '',
|
||||
default: {},
|
||||
placeholder: 'Add Custom Field',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
|
@ -394,7 +394,7 @@ export const contactFields: INodeProperties[] = [
|
|||
displayName: 'Custom Fields',
|
||||
name: 'customFieldsUi',
|
||||
type: 'fixedCollection',
|
||||
default: '',
|
||||
default: {},
|
||||
placeholder: 'Add Custom Field',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
|
|
|
@ -153,7 +153,7 @@ export class Autopilot implements INodeType {
|
|||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
const returnData: IDataObject[] = [];
|
||||
const length = (items.length as unknown) as number;
|
||||
const length = items.length;
|
||||
const qs: IDataObject = {};
|
||||
let responseData;
|
||||
const resource = this.getNodeParameter('resource', 0) as string;
|
||||
|
|
|
@ -91,7 +91,7 @@ export const contactFields: INodeProperties[] = [
|
|||
displayName: 'Custom Fields',
|
||||
name: 'customFieldsUi',
|
||||
type: 'fixedCollection',
|
||||
default: '',
|
||||
default: {},
|
||||
placeholder: 'Add Custom Field',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
|
|
|
@ -46,8 +46,13 @@ export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | I
|
|||
|
||||
// Sign AWS API request with the user credentials
|
||||
const signOpts = { headers: headers || {}, host: endpoint.host, method, path, body } as Request;
|
||||
sign(signOpts, { accessKeyId: `${credentials.accessKeyId}`.trim(), secretAccessKey: `${credentials.secretAccessKey}`.trim() });
|
||||
const securityHeaders = {
|
||||
accessKeyId: `${credentials.accessKeyId}`.trim(),
|
||||
secretAccessKey: `${credentials.secretAccessKey}`.trim(),
|
||||
sessionToken: credentials.temporaryCredentials ? `${credentials.sessionToken}`.trim() : undefined,
|
||||
};
|
||||
|
||||
sign(signOpts, securityHeaders);
|
||||
|
||||
const options: OptionsWithUri = {
|
||||
headers: signOpts.headers,
|
||||
|
|
|
@ -40,7 +40,11 @@ export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | I
|
|||
|
||||
// Concatenate path and instantiate URL object so it parses correctly query strings
|
||||
const endpoint = new URL(getEndpointForService(service, credentials) + path);
|
||||
|
||||
const securityHeaders = {
|
||||
accessKeyId: `${credentials.accessKeyId}`.trim(),
|
||||
secretAccessKey: `${credentials.secretAccessKey}`.trim(),
|
||||
sessionToken: credentials.temporaryCredentials ? `${credentials.sessionToken}`.trim() : undefined,
|
||||
};
|
||||
const options = sign({
|
||||
// @ts-ignore
|
||||
uri: endpoint,
|
||||
|
@ -50,10 +54,7 @@ export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | I
|
|||
path: '/',
|
||||
headers: { ...headers },
|
||||
body: JSON.stringify(body),
|
||||
}, {
|
||||
accessKeyId: credentials.accessKeyId,
|
||||
secretAccessKey: credentials.secretAccessKey,
|
||||
});
|
||||
}, securityHeaders);
|
||||
|
||||
try {
|
||||
return JSON.parse(await this.helpers.request!(options));
|
||||
|
|
|
@ -172,10 +172,10 @@ export const itemFields: INodeProperties[] = [
|
|||
{
|
||||
displayName: 'Expression Attribute Values',
|
||||
name: 'eavUi',
|
||||
description: 'Substitution tokens for attribute names in an expression. Only needed when the parameter "condition expression" is set',
|
||||
description: 'Substitution tokens for attribute names in an expression. Only needed when the parameter "condition expression" is set.',
|
||||
placeholder: 'Add Attribute Value',
|
||||
type: 'fixedCollection',
|
||||
default: '',
|
||||
default: {},
|
||||
required: true,
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
|
@ -223,14 +223,14 @@ export const itemFields: INodeProperties[] = [
|
|||
name: 'conditionExpression',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'A condition that must be satisfied in order for a conditional upsert to succeed. <a href="https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html">View details</a>',
|
||||
description: 'A condition that must be satisfied in order for a conditional upsert to succeed. <a href="https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html">View details</a>.',
|
||||
},
|
||||
{
|
||||
displayName: 'Expression Attribute Names',
|
||||
name: 'eanUi',
|
||||
placeholder: 'Add Expression',
|
||||
type: 'fixedCollection',
|
||||
default: '',
|
||||
default: {},
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
|
@ -254,7 +254,7 @@ export const itemFields: INodeProperties[] = [
|
|||
],
|
||||
},
|
||||
],
|
||||
description: 'One or more substitution tokens for attribute names in an expression. <a href="https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html">View details</a>',
|
||||
description: 'One or more substitution tokens for attribute names in an expression. <a href="https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html">View details</a>.',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -401,7 +401,7 @@ export const itemFields: INodeProperties[] = [
|
|||
name: 'eanUi',
|
||||
placeholder: 'Add Expression',
|
||||
type: 'fixedCollection',
|
||||
default: '',
|
||||
default: {},
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
|
@ -425,15 +425,15 @@ export const itemFields: INodeProperties[] = [
|
|||
],
|
||||
},
|
||||
],
|
||||
description: 'One or more substitution tokens for attribute names in an expression. Check <a href="https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html">Info</a>',
|
||||
description: 'One or more substitution tokens for attribute names in an expression. Check <a href="https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html">Info</a>.',
|
||||
},
|
||||
{
|
||||
displayName: 'Expression Attribute Values',
|
||||
name: 'expressionAttributeUi',
|
||||
description: 'Substitution tokens for attribute names in an expression. Only needed when the parameter "condition expression" is set',
|
||||
description: 'Substitution tokens for attribute names in an expression. Only needed when the parameter "condition expression" is set.',
|
||||
placeholder: 'Add Attribute Value',
|
||||
type: 'fixedCollection',
|
||||
default: '',
|
||||
default: {},
|
||||
required: true,
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
|
@ -624,7 +624,7 @@ export const itemFields: INodeProperties[] = [
|
|||
name: 'eanUi',
|
||||
placeholder: 'Add Expression',
|
||||
type: 'fixedCollection',
|
||||
default: '',
|
||||
default: {},
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
|
@ -648,7 +648,7 @@ export const itemFields: INodeProperties[] = [
|
|||
],
|
||||
},
|
||||
],
|
||||
description: 'One or more substitution tokens for attribute names in an expression. <a href="https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html">View details</a>',
|
||||
description: 'One or more substitution tokens for attribute names in an expression. <a href="https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html">View details</a>.',
|
||||
},
|
||||
{
|
||||
displayName: 'Read Type',
|
||||
|
@ -665,7 +665,7 @@ export const itemFields: INodeProperties[] = [
|
|||
},
|
||||
],
|
||||
default: 'eventuallyConsistentRead',
|
||||
description: 'Type of read to perform on the table. <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ReadConsistency.html">View details</a>',
|
||||
description: 'Type of read to perform on the table. <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ReadConsistency.html">View details</a>.',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -688,7 +688,7 @@ export const itemFields: INodeProperties[] = [
|
|||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'Whether to do an scan or query. Check <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-query-scan.html" >differences</a>',
|
||||
description: 'Whether to do an scan or query. Check <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-query-scan.html" >differences</a>.',
|
||||
},
|
||||
{
|
||||
displayName: 'Filter Expression',
|
||||
|
@ -733,7 +733,7 @@ export const itemFields: INodeProperties[] = [
|
|||
description: 'Substitution tokens for attribute names in an expression',
|
||||
placeholder: 'Add Attribute Value',
|
||||
type: 'fixedCollection',
|
||||
default: '',
|
||||
default: {},
|
||||
required: true,
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
|
@ -924,14 +924,14 @@ export const itemFields: INodeProperties[] = [
|
|||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Text that contains conditions that DynamoDB applies after the Query operation, but before the data is returned. Items that do not satisfy the FilterExpression criteria are not returned',
|
||||
description: 'Text that contains conditions that DynamoDB applies after the Query operation, but before the data is returned. Items that do not satisfy the FilterExpression criteria are not returned.',
|
||||
},
|
||||
{
|
||||
displayName: 'Expression Attribute Names',
|
||||
name: 'eanUi',
|
||||
placeholder: 'Add Expression',
|
||||
type: 'fixedCollection',
|
||||
default: '',
|
||||
default: {},
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
|
@ -955,7 +955,7 @@ export const itemFields: INodeProperties[] = [
|
|||
],
|
||||
},
|
||||
],
|
||||
description: 'One or more substitution tokens for attribute names in an expression. Check <a href="https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html">Info</a>',
|
||||
description: 'One or more substitution tokens for attribute names in an expression. Check <a href="https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html">Info</a>.',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -36,8 +36,13 @@ export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | I
|
|||
|
||||
// Sign AWS API request with the user credentials
|
||||
const signOpts = { headers: headers || {}, host: endpoint.host, method, path, body } as Request;
|
||||
sign(signOpts, { accessKeyId: `${credentials.accessKeyId}`.trim(), secretAccessKey: `${credentials.secretAccessKey}`.trim() });
|
||||
const securityHeaders = {
|
||||
accessKeyId: `${credentials.accessKeyId}`.trim(),
|
||||
secretAccessKey: `${credentials.secretAccessKey}`.trim(),
|
||||
sessionToken: credentials.temporaryCredentials ? `${credentials.sessionToken}`.trim() : undefined,
|
||||
};
|
||||
|
||||
sign(signOpts, securityHeaders);
|
||||
|
||||
const options: OptionsWithUri = {
|
||||
headers: signOpts.headers,
|
||||
|
|
|
@ -205,7 +205,7 @@ export class AwsRekognition implements INodeType {
|
|||
displayName: 'Regions of Interest',
|
||||
name: 'regionsOfInterestUi',
|
||||
type: 'fixedCollection',
|
||||
default: '',
|
||||
default: {},
|
||||
placeholder: 'Add Region of Interest',
|
||||
displayOptions: {
|
||||
show: {
|
||||
|
@ -272,7 +272,7 @@ export class AwsRekognition implements INodeType {
|
|||
displayName: 'Word Filter',
|
||||
name: 'wordFilterUi',
|
||||
type: 'collection',
|
||||
default: '',
|
||||
default: {},
|
||||
placeholder: 'Add Word Filter',
|
||||
displayOptions: {
|
||||
show: {
|
||||
|
|
|
@ -43,8 +43,13 @@ export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | I
|
|||
|
||||
// Sign AWS API request with the user credentials
|
||||
const signOpts = {headers: headers || {}, host: endpoint.host, method, path, body} as Request;
|
||||
const securityHeaders = {
|
||||
accessKeyId: `${credentials.accessKeyId}`.trim(),
|
||||
secretAccessKey: `${credentials.secretAccessKey}`.trim(),
|
||||
sessionToken: credentials.temporaryCredentials ? `${credentials.sessionToken}`.trim() : undefined,
|
||||
};
|
||||
|
||||
sign(signOpts, { accessKeyId: `${credentials.accessKeyId}`.trim(), secretAccessKey: `${credentials.secretAccessKey}`.trim()});
|
||||
sign(signOpts, securityHeaders);
|
||||
|
||||
const options: OptionsWithUri = {
|
||||
headers: signOpts.headers,
|
||||
|
|
|
@ -22,6 +22,7 @@ import {
|
|||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
JsonObject,
|
||||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
@ -632,7 +633,7 @@ export class AwsS3 implements INodeType {
|
|||
}
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push({ error: error.message });
|
||||
returnData.push({ error: (error as JsonObject).message });
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
|
|
|
@ -348,7 +348,7 @@ export const bucketFields: INodeProperties[] = [
|
|||
name: 'startAfter',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'StartAfter is where you want Amazon S3 to start listing from. Amazon S3 starts listing after this specified key',
|
||||
description: 'StartAfter is where you want Amazon S3 to start listing from. Amazon S3 starts listing after this specified key.',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -671,7 +671,7 @@ export const fileFields: INodeProperties[] = [
|
|||
name: 'tagsUi',
|
||||
placeholder: 'Add Tag',
|
||||
type: 'fixedCollection',
|
||||
default: '',
|
||||
default: {},
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
|
@ -695,14 +695,12 @@ export const fileFields: INodeProperties[] = [
|
|||
name: 'key',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -27,7 +27,7 @@ import {
|
|||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject, NodeApiError, NodeOperationError,
|
||||
IDataObject, JsonObject, NodeApiError, NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions | IWebhookFunctions, service: string, method: string, path: string, body?: string | Buffer, query: IDataObject = {}, headers?: object, option: IDataObject = {}, region?: string): Promise<any> { // tslint:disable-line:no-any
|
||||
|
@ -37,9 +37,13 @@ export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | I
|
|||
|
||||
// Sign AWS API request with the user credentials
|
||||
const signOpts = {headers: headers || {}, host: endpoint.host, method, path: `${endpoint.pathname}?${queryToString(query).replace(/\+/g, '%2B')}`, body} as Request;
|
||||
const securityHeaders = {
|
||||
accessKeyId: `${credentials.accessKeyId}`.trim(),
|
||||
secretAccessKey: `${credentials.secretAccessKey}`.trim(),
|
||||
sessionToken: credentials.temporaryCredentials ? `${credentials.sessionToken}`.trim() : undefined,
|
||||
};
|
||||
|
||||
|
||||
sign(signOpts, { accessKeyId: `${credentials.accessKeyId}`.trim(), secretAccessKey: `${credentials.secretAccessKey}`.trim()});
|
||||
sign(signOpts, securityHeaders);
|
||||
|
||||
const options: OptionsWithUri = {
|
||||
headers: signOpts.headers,
|
||||
|
@ -55,7 +59,7 @@ export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | I
|
|||
try {
|
||||
return await this.helpers.request!(options);
|
||||
} catch (error) {
|
||||
throw new NodeApiError(this.getNode(), error);
|
||||
throw new NodeApiError(this.getNode(), (error as JsonObject));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,13 @@ export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | I
|
|||
// Sign AWS API request with the user credentials
|
||||
|
||||
const signOpts = { headers: headers || {}, host: endpoint.host, method, path, body } as Request;
|
||||
sign(signOpts, { accessKeyId: `${credentials.accessKeyId}`.trim(), secretAccessKey: `${credentials.secretAccessKey}`.trim() });
|
||||
const securityHeaders = {
|
||||
accessKeyId: `${credentials.accessKeyId}`.trim(),
|
||||
secretAccessKey: `${credentials.secretAccessKey}`.trim(),
|
||||
sessionToken: credentials.temporaryCredentials ? `${credentials.sessionToken}`.trim() : undefined,
|
||||
};
|
||||
|
||||
sign(signOpts, securityHeaders);
|
||||
|
||||
const options: OptionsWithUri = {
|
||||
headers: signOpts.headers,
|
||||
|
|
|
@ -55,7 +55,6 @@ export class AwsTextract implements INodeType {
|
|||
},
|
||||
],
|
||||
default: 'analyzeExpense',
|
||||
description: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Input Data Field Name',
|
||||
|
@ -70,7 +69,7 @@ export class AwsTextract implements INodeType {
|
|||
},
|
||||
},
|
||||
required: true,
|
||||
description: 'The name of the input field containing the binary file data to be uploaded. Supported file types: PNG, JPEG',
|
||||
description: 'The name of the input field containing the binary file data to be uploaded. Supported file types: PNG, JPEG.',
|
||||
},
|
||||
{
|
||||
displayName: 'Simplify Response',
|
||||
|
|
|
@ -49,8 +49,13 @@ export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | I
|
|||
|
||||
// Sign AWS API request with the user credentials
|
||||
const signOpts = { headers: headers || {}, host: endpoint.host, method, path, body } as Request;
|
||||
sign(signOpts, { accessKeyId: `${credentials.accessKeyId}`.trim(), secretAccessKey: `${credentials.secretAccessKey}`.trim() });
|
||||
const securityHeaders = {
|
||||
accessKeyId: `${credentials.accessKeyId}`.trim(),
|
||||
secretAccessKey: `${credentials.secretAccessKey}`.trim(),
|
||||
sessionToken: credentials.temporaryCredentials ? `${credentials.sessionToken}`.trim() : undefined,
|
||||
};
|
||||
|
||||
sign(signOpts, securityHeaders);
|
||||
|
||||
const options: OptionsWithUri = {
|
||||
headers: signOpts.headers,
|
||||
|
@ -131,7 +136,13 @@ export async function validateCrendetials(this: ICredentialTestFunctions, decryp
|
|||
|
||||
// Sign AWS API request with the user credentials
|
||||
const signOpts = { host: endpoint.host, method: 'POST', path: '?Action=GetCallerIdentity&Version=2011-06-15' } as Request;
|
||||
sign(signOpts, { accessKeyId: `${credentials.accessKeyId}`.trim(), secretAccessKey: `${credentials.secretAccessKey}`.trim() });
|
||||
const securityHeaders = {
|
||||
accessKeyId: `${credentials.accessKeyId}`.trim(),
|
||||
secretAccessKey: `${credentials.secretAccessKey}`.trim(),
|
||||
sessionToken: credentials.temporaryCredentials ? `${credentials.sessionToken}`.trim() : undefined,
|
||||
};
|
||||
|
||||
sign(signOpts, securityHeaders);
|
||||
|
||||
const options: OptionsWithUri = {
|
||||
headers: signOpts.headers,
|
||||
|
|
|
@ -49,8 +49,13 @@ export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | I
|
|||
|
||||
// Sign AWS API request with the user credentials
|
||||
const signOpts = { headers: headers || {}, host: endpoint.host, method, path, body } as Request;
|
||||
sign(signOpts, { accessKeyId: `${credentials.accessKeyId}`.trim(), secretAccessKey: `${credentials.secretAccessKey}`.trim() });
|
||||
const securityHeaders = {
|
||||
accessKeyId: `${credentials.accessKeyId}`.trim(),
|
||||
secretAccessKey: `${credentials.secretAccessKey}`.trim(),
|
||||
sessionToken: credentials.temporaryCredentials ? `${credentials.sessionToken}`.trim() : undefined,
|
||||
};
|
||||
|
||||
sign(signOpts, securityHeaders);
|
||||
|
||||
const options: OptionsWithUri = {
|
||||
headers: signOpts.headers,
|
||||
|
|
|
@ -28,7 +28,6 @@ export const descriptions: INodeProperties[] = [
|
|||
},
|
||||
],
|
||||
default: 'get',
|
||||
description: '',
|
||||
},
|
||||
...get.description,
|
||||
];
|
||||
|
|
|
@ -50,7 +50,7 @@ export const createEmployeeSharedDescription = (sync = false): INodeProperties[]
|
|||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'United States',
|
||||
description: 'The name of the country. Must exist in the BambooHr country list',
|
||||
description: 'The name of the country. Must exist in the BambooHr country list.',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -49,7 +49,6 @@ export const descriptions: INodeProperties[] = [
|
|||
},
|
||||
],
|
||||
default: 'create',
|
||||
description: '',
|
||||
},
|
||||
...create.description,
|
||||
...get.description,
|
||||
|
|
|
@ -50,7 +50,7 @@ export const updateEmployeeSharedDescription = (sync = false): INodeProperties[]
|
|||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'United States',
|
||||
description: 'The name of the country. Must exist in the BambooHr country list',
|
||||
description: 'The name of the country. Must exist in the BambooHr country list.',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -54,7 +54,7 @@ export const employeeDocumentUploadDescription: EmployeeDocumentProperties = [
|
|||
},
|
||||
},
|
||||
required: true,
|
||||
description: 'The name of the input field containing the binary file data to be uploaded. Supported file types: PNG, JPEG',
|
||||
description: 'The name of the input field containing the binary file data to be uploaded. Supported file types: PNG, JPEG.',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
|
|
|
@ -56,7 +56,6 @@ export const descriptions: INodeProperties[] = [
|
|||
},
|
||||
],
|
||||
default: 'delete',
|
||||
description: '',
|
||||
},
|
||||
...del.description,
|
||||
...download.description,
|
||||
|
|
|
@ -17,7 +17,7 @@ export const fileUploadDescription: INodeProperties[] = [
|
|||
},
|
||||
},
|
||||
required: true,
|
||||
description: 'The name of the input field containing the binary file data to be uploaded. Supported file types: PNG, JPEG',
|
||||
description: 'The name of the input field containing the binary file data to be uploaded. Supported file types: PNG, JPEG.',
|
||||
},
|
||||
{
|
||||
displayName: 'Category Name/ID',
|
||||
|
|
|
@ -113,7 +113,7 @@ export class Bannerbear implements INodeType {
|
|||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
const returnData: IDataObject[] = [];
|
||||
const length = items.length as unknown as number;
|
||||
const length = items.length;
|
||||
let responseData;
|
||||
const qs: IDataObject = {};
|
||||
const resource = this.getNodeParameter('resource', 0) as string;
|
||||
|
|
|
@ -297,37 +297,37 @@ export const operationFields: INodeProperties[] = [
|
|||
{
|
||||
name: 'Date Equal',
|
||||
value: 'date_equal',
|
||||
description: 'Field is date. Format: \'YYYY-MM-DD\'',
|
||||
description: 'Field is date. Format: \'YYYY-MM-DD\'.',
|
||||
},
|
||||
{
|
||||
name: 'Date Not Equal',
|
||||
value: 'date_not_equal',
|
||||
description: 'Field is not date. Format: \'YYYY-MM-DD\'',
|
||||
description: 'Field is not date. Format: \'YYYY-MM-DD\'.',
|
||||
},
|
||||
{
|
||||
name: 'Date Equals Today',
|
||||
value: 'date_equals_today',
|
||||
description: 'Field is today. Format: string',
|
||||
description: 'Field is today. Format: string.',
|
||||
},
|
||||
{
|
||||
name: 'Date Equals Month',
|
||||
value: 'date_equals_month',
|
||||
description: 'Field in this month. Format: string',
|
||||
description: 'Field in this month. Format: string.',
|
||||
},
|
||||
{
|
||||
name: 'Date Equals Year',
|
||||
value: 'date_equals_year',
|
||||
description: 'Field in this year. Format: string',
|
||||
description: 'Field in this year. Format: string.',
|
||||
},
|
||||
{
|
||||
name: 'Date Before Date',
|
||||
value: 'date_before',
|
||||
description: 'Field before this date. Format: \'YYYY-MM-DD\'',
|
||||
description: 'Field before this date. Format: \'YYYY-MM-DD\'.',
|
||||
},
|
||||
{
|
||||
name: 'Date After Date',
|
||||
value: 'date_after',
|
||||
description: 'Field after this date. Format: \'YYYY-MM-DD\'',
|
||||
description: 'Field after this date. Format: \'YYYY-MM-DD\'.',
|
||||
},
|
||||
{
|
||||
name: 'Filename Contains',
|
||||
|
|
|
@ -174,7 +174,6 @@ export class Beeminder implements INodeType {
|
|||
name: 'datapointId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Datapoint id',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
|
@ -207,7 +206,6 @@ export class Beeminder implements INodeType {
|
|||
name: 'comment',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Comment',
|
||||
},
|
||||
{
|
||||
displayName: 'Timestamp',
|
||||
|
@ -284,7 +282,6 @@ export class Beeminder implements INodeType {
|
|||
name: 'comment',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Comment',
|
||||
},
|
||||
{
|
||||
displayName: 'Timestamp',
|
||||
|
@ -326,7 +323,7 @@ export class Beeminder implements INodeType {
|
|||
|
||||
const items = this.getInputData();
|
||||
const returnData: IDataObject[] = [];
|
||||
const length = items.length as unknown as number;
|
||||
const length = items.length;
|
||||
const timezone = this.getTimezone();
|
||||
|
||||
const resource = this.getNodeParameter('resource', 0) as string;
|
||||
|
|
|
@ -134,7 +134,7 @@ export class Bitly implements INodeType {
|
|||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
const returnData: IDataObject[] = [];
|
||||
const length = items.length as unknown as number;
|
||||
const length = items.length;
|
||||
const qs: IDataObject = {};
|
||||
let responseData;
|
||||
const resource = this.getNodeParameter('resource', 0) as string;
|
||||
|
|
|
@ -80,7 +80,7 @@ export class Box implements INodeType {
|
|||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
const returnData: IDataObject[] = [];
|
||||
const length = items.length as unknown as number;
|
||||
const length = items.length;
|
||||
const qs: IDataObject = {};
|
||||
let responseData;
|
||||
const timezone = this.getTimezone();
|
||||
|
|
|
@ -77,7 +77,6 @@ export const fileFields: INodeProperties[] = [
|
|||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'File ID',
|
||||
},
|
||||
{
|
||||
displayName: 'Parent ID',
|
||||
|
@ -94,7 +93,7 @@ export const fileFields: INodeProperties[] = [
|
|||
],
|
||||
},
|
||||
},
|
||||
description: 'The ID of folder to copy the file to. If not defined will be copied to the root folder',
|
||||
description: 'The ID of folder to copy the file to. If not defined will be copied to the root folder.',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
|
@ -176,7 +175,6 @@ export const fileFields: INodeProperties[] = [
|
|||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'File ID',
|
||||
},
|
||||
{
|
||||
displayName: 'Binary Property',
|
||||
|
@ -327,7 +325,7 @@ export const fileFields: INodeProperties[] = [
|
|||
name: 'contet_types',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: `Limits search results to items with the given content types. Content types are defined as a comma separated lists of Box recognized content types.`,
|
||||
description: 'Limits search results to items with the given content types. Content types are defined as a comma-separated lists of Box recognized content types.',
|
||||
},
|
||||
{
|
||||
displayName: 'Created At Range',
|
||||
|
@ -396,7 +394,7 @@ export const fileFields: INodeProperties[] = [
|
|||
name: 'ancestor_folder_ids',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: `Limits search results to items within the given list of folders. Folders are defined as a comma separated lists of folder IDs.`,
|
||||
description: 'Limits search results to items within the given list of folders. Folders are defined as a comma-separated lists of folder IDs.',
|
||||
},
|
||||
{
|
||||
displayName: 'Scope',
|
||||
|
@ -421,7 +419,7 @@ export const fileFields: INodeProperties[] = [
|
|||
type: 'string',
|
||||
default: '',
|
||||
placeholder: '1000000,5000000',
|
||||
description: `Limits search results to items within a given file size range. File size ranges are defined as comma separated byte sizes.`,
|
||||
description: 'Limits search results to items within a given file size range. File size ranges are defined as comma-separated byte sizes.',
|
||||
},
|
||||
{
|
||||
displayName: 'Sort',
|
||||
|
@ -492,7 +490,7 @@ export const fileFields: INodeProperties[] = [
|
|||
name: 'owner_user_ids',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: `Limits search results to items owned by the given list of owners. Owners are defined as a comma separated list of user IDs.`,
|
||||
description: 'Limits search results to items owned by the given list of owners. Owners are defined as a comma-separated list of user IDs.',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -830,6 +828,6 @@ export const fileFields: INodeProperties[] = [
|
|||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'ID of the parent folder that will contain the file. If not it will be uploaded to the root folder',
|
||||
description: 'ID of the parent folder that will contain the file. If not it will be uploaded to the root folder.',
|
||||
},
|
||||
];
|
||||
|
|
|
@ -89,7 +89,7 @@ export const folderFields: INodeProperties[] = [
|
|||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'ID of the folder you want to create the new folder in. if not defined it will be created on the root folder',
|
||||
description: 'ID of the folder you want to create the new folder in. if not defined it will be created on the root folder.',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
|
@ -155,7 +155,6 @@ export const folderFields: INodeProperties[] = [
|
|||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Folder ID',
|
||||
},
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
@ -176,7 +175,6 @@ export const folderFields: INodeProperties[] = [
|
|||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Folder ID',
|
||||
},
|
||||
{
|
||||
displayName: 'Recursive',
|
||||
|
@ -279,7 +277,7 @@ export const folderFields: INodeProperties[] = [
|
|||
name: 'contet_types',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: `Limits search results to items with the given content types. Content types are defined as a comma separated lists of Box recognized content types.`,
|
||||
description: 'Limits search results to items with the given content types. Content types are defined as a comma-separated lists of Box recognized content types.',
|
||||
},
|
||||
{
|
||||
displayName: 'Created At Range',
|
||||
|
@ -348,7 +346,7 @@ export const folderFields: INodeProperties[] = [
|
|||
name: 'ancestor_folder_ids',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: `Limits search results to items within the given list of folders. Folders are defined as a comma separated lists of folder IDs.`,
|
||||
description: 'Limits search results to items within the given list of folders. Folders are defined as a comma-separated lists of folder IDs.',
|
||||
},
|
||||
{
|
||||
displayName: 'Scope',
|
||||
|
@ -373,7 +371,7 @@ export const folderFields: INodeProperties[] = [
|
|||
type: 'string',
|
||||
default: '',
|
||||
placeholder: '1000000,5000000',
|
||||
description: `Limits search results to items within a given file size range. File size ranges are defined as comma separated byte sizes.`,
|
||||
description: 'Limits search results to items within a given file size range. File size ranges are defined as comma-separated byte sizes.',
|
||||
},
|
||||
{
|
||||
displayName: 'Sort',
|
||||
|
@ -444,7 +442,7 @@ export const folderFields: INodeProperties[] = [
|
|||
name: 'owner_user_ids',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: `Limits search results to items owned by the given list of owners. Owners are defined as a comma separated list of user IDs.`,
|
||||
description: 'Limits search results to items owned by the given list of owners. Owners are defined as a comma-separated list of user IDs.',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -703,7 +701,6 @@ export const folderFields: INodeProperties[] = [
|
|||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Folder ID',
|
||||
},
|
||||
{
|
||||
displayName: 'Update Fields',
|
||||
|
|
|
@ -161,7 +161,7 @@ export class Brandfetch implements INodeType {
|
|||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
const length = items.length as unknown as number;
|
||||
const length = items.length;
|
||||
|
||||
const operation = this.getNodeParameter('operation', 0) as string;
|
||||
const responseData = [];
|
||||
|
|
|
@ -297,7 +297,6 @@ export const objectFields: INodeProperties[] = [
|
|||
name: 'jsonParameters',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
|
@ -471,7 +470,7 @@ export const objectFields: INodeProperties[] = [
|
|||
},
|
||||
},
|
||||
placeholder: `[ { "key": "name", "constraint_type": "text contains", "value": "cafe" } , { "key": "address", "constraint_type": "geographic_search", "value": { "range":10, "origin_address":"New York" } } ]`,
|
||||
description: 'Refine the list that is returned by the Data API with search constraints, exactly as you define a search in Bubble. See <a href="https://manual.bubble.io/core-resources/api/data-api#search-constraints">link</a>',
|
||||
description: 'Refine the list that is returned by the Data API with search constraints, exactly as you define a search in Bubble. See <a href="https://manual.bubble.io/core-resources/api/data-api#search-constraints">link</a>.',
|
||||
},
|
||||
{
|
||||
displayName: 'Sort',
|
||||
|
@ -492,7 +491,7 @@ export const objectFields: INodeProperties[] = [
|
|||
name: 'sort_field',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: `Specify the field to use for sorting. Either use a fielddefined for the current typeor use <code>_random_sorting</code> to get the entries in a random order`,
|
||||
description: 'Specify the field to use for sorting. Either use a fielddefined for the current typeor use <code>_random_sorting</code> to get the entries in a random order.',
|
||||
},
|
||||
{
|
||||
displayName: 'Descending',
|
||||
|
|
|
@ -1 +1 @@
|
|||
<svg height="2500" viewBox="7.4 0 344.6 360" width="2342" xmlns="http://www.w3.org/2000/svg"><g fill="#676b74"><path d="M313.8 360H45.5c-21 0-38.1-17.1-38.1-38.1V53.5c0-21 17.1-38.1 38.1-38.1h268.3c21 0 38.1 17.1 38.1 38.1v268.3c.1 21.1-17 38.2-38.1 38.2zM45.5 36.5c-9.4 0-17 7.6-17 17v268.3c0 9.4 7.6 17 17 17h268.3c9.4 0 17-7.6 17-17V53.5c0-9.4-7.6-17-17-17z"/><path d="M256.6 72.4c-4.5 0-8.1-3.6-8.1-8.1V8.1c0-4.5 3.6-8.1 8.1-8.1s8.1 3.6 8.1 8.1v56.1c0 4.5-3.6 8.2-8.1 8.2zm-154.7 0c-4.5 0-8.1-3.6-8.1-8.1V8.1c0-4.5 3.6-8.1 8.1-8.1s8.1 3.6 8.1 8.1v56.1c.1 4.5-3.6 8.2-8.1 8.2zm87.5 181.4c-33.6 0-60.9-27.3-60.9-60.9s27.3-60.9 60.9-60.9c15.2 0 29.7 5.6 40.9 15.8 1.4 1.2 1.5 3.4.2 4.7-1.2 1.4-3.4 1.5-4.7.2-10-9.1-22.9-14.1-36.4-14.1-29.9 0-54.2 24.3-54.2 54.2s24.3 54.2 54.2 54.2c13.5 0 26.4-5 36.4-14.1 1.4-1.2 3.5-1.1 4.7.2 1.2 1.4 1.1 3.5-.2 4.7-11.2 10.4-25.7 16-40.9 16z"/></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 525.8 535.73"><defs><style>.cls-1{fill:none;}.cls-2{fill:#006bff;}.cls-3{fill:#0ae9ef;}</style></defs><g id="Layer_2" data-name="Layer 2"><g id="Logo_assets" data-name="Logo assets"><g id="Brand_mark" data-name="Brand mark"><path class="cls-1" d="M443.74,337.62l-27.16,47.05a139.52,139.52,0,0,1-120.82,69.75H241.43a139.52,139.52,0,0,1-120.82-69.75L93.45,337.62a139.52,139.52,0,0,1,0-139.51l27.16-47.05A139.53,139.53,0,0,1,241.43,81.3h54.33a139.53,139.53,0,0,1,120.82,69.76l27.16,47.05a139.23,139.23,0,0,1,8.55,17.55c0,.12.09.23.13.35a102.15,102.15,0,0,0,44.33-18.24c0-.14-.08-.28-.13-.43a237.8,237.8,0,0,0-33.29-67.58,240.67,240.67,0,0,0-52-53.48A239.3,239.3,0,0,0,98.65,437.08a239.43,239.43,0,0,0,398-98.69c.05-.15.09-.29.13-.43a102.15,102.15,0,0,0-44.33-18.24c0,.12-.09.23-.13.35A139.23,139.23,0,0,1,443.74,337.62Z"/><path class="cls-2" d="M360.4,347.4c-17,15.09-38.21,33.87-76.78,33.87h-23c-27.88,0-53.23-10.12-71.37-28.49-17.72-17.94-27.48-42.5-27.48-69.16V252.11c0-26.66,9.76-51.22,27.48-69.16,18.14-18.37,43.49-28.49,71.37-28.49h23c38.57,0,59.76,18.78,76.78,33.87,17.65,15.65,32.9,29.16,73.52,29.16a116.05,116.05,0,0,0,18.5-1.48c0-.12-.08-.23-.13-.35a139.23,139.23,0,0,0-8.55-17.55l-27.16-47.05A139.53,139.53,0,0,0,295.76,81.3H241.43a139.53,139.53,0,0,0-120.82,69.76L93.45,198.11a139.52,139.52,0,0,0,0,139.51l27.16,47.05a139.52,139.52,0,0,0,120.82,69.75h54.33a139.52,139.52,0,0,0,120.82-69.75l27.16-47.05a139.23,139.23,0,0,0,8.55-17.55c0-.12.09-.23.13-.35a116.05,116.05,0,0,0-18.5-1.48C393.3,318.24,378.05,331.75,360.4,347.4Z"/><path class="cls-2" d="M283.62,183h-23c-42.42,0-70.3,30.3-70.3,69.09v31.51c0,38.79,27.88,69.09,70.3,69.09h23c61.82,0,57-63,150.3-63a144.19,144.19,0,0,1,26.37,2.41,139.36,139.36,0,0,0,0-48.46,143.32,143.32,0,0,1-26.37,2.42C340.59,246.05,345.44,183,283.62,183Z"/><path class="cls-2" d="M513.91,315.13a130.21,130.21,0,0,0-53.62-23c0,.16-.05.32-.08.47a138.46,138.46,0,0,1-7.79,27.16A102.15,102.15,0,0,1,496.75,338c0,.14-.08.28-.13.43A237.8,237.8,0,0,1,463.33,406a240.67,240.67,0,0,1-52,53.48A239.3,239.3,0,0,1,98.65,98.65a239.43,239.43,0,0,1,398,98.69c.05.15.09.29.13.43A102.15,102.15,0,0,1,452.42,216a139.36,139.36,0,0,1,7.8,27.18c0,.15,0,.3.07.44a129.94,129.94,0,0,0,53.62-23c15.29-11.31,12.33-24.09,10-31.65C490.22,79.52,388.33,0,267.86,0,119.93,0,0,119.93,0,267.86S119.93,535.73,267.86,535.73c120.47,0,222.36-79.52,256-188.94C526.24,339.23,529.2,326.45,513.91,315.13Z"/><path class="cls-3" d="M452.42,216a116.05,116.05,0,0,1-18.5,1.48c-40.62,0-55.87-13.51-73.52-29.16-17-15.09-38.21-33.87-76.78-33.87h-23c-27.88,0-53.23,10.12-71.37,28.49-17.72,17.94-27.48,42.5-27.48,69.16v31.51c0,26.66,9.76,51.22,27.48,69.16,18.14,18.37,43.49,28.49,71.37,28.49h23c38.57,0,59.76-18.78,76.78-33.87,17.65-15.65,32.9-29.16,73.52-29.16a116.05,116.05,0,0,1,18.5,1.48,138.46,138.46,0,0,0,7.79-27.16c0-.15.06-.31.08-.47a144.19,144.19,0,0,0-26.37-2.41c-93.33,0-88.48,63-150.3,63h-23c-42.42,0-70.3-30.3-70.3-69.09V252.11c0-38.79,27.88-69.09,70.3-69.09h23c61.82,0,57,63,150.3,63a143.32,143.32,0,0,0,26.37-2.42c0-.14,0-.29-.07-.44A139.36,139.36,0,0,0,452.42,216Z"/><path class="cls-3" d="M452.42,216a116.05,116.05,0,0,1-18.5,1.48c-40.62,0-55.87-13.51-73.52-29.16-17-15.09-38.21-33.87-76.78-33.87h-23c-27.88,0-53.23,10.12-71.37,28.49-17.72,17.94-27.48,42.5-27.48,69.16v31.51c0,26.66,9.76,51.22,27.48,69.16,18.14,18.37,43.49,28.49,71.37,28.49h23c38.57,0,59.76-18.78,76.78-33.87,17.65-15.65,32.9-29.16,73.52-29.16a116.05,116.05,0,0,1,18.5,1.48,138.46,138.46,0,0,0,7.79-27.16c0-.15.06-.31.08-.47a144.19,144.19,0,0,0-26.37-2.41c-93.33,0-88.48,63-150.3,63h-23c-42.42,0-70.3-30.3-70.3-69.09V252.11c0-38.79,27.88-69.09,70.3-69.09h23c61.82,0,57,63,150.3,63a143.32,143.32,0,0,0,26.37-2.42c0-.14,0-.29-.07-.44A139.36,139.36,0,0,0,452.42,216Z"/></g></g></g></svg>
|
||||
|
|
Before Width: | Height: | Size: 891 B After Width: | Height: | Size: 3.7 KiB |
|
@ -61,7 +61,7 @@ export class CircleCi implements INodeType {
|
|||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
const returnData: IDataObject[] = [];
|
||||
const length = items.length as unknown as number;
|
||||
const length = items.length;
|
||||
const qs: IDataObject = {};
|
||||
let responseData;
|
||||
const resource = this.getNodeParameter('resource', 0) as string;
|
||||
|
|
|
@ -164,7 +164,7 @@ export function getActionInheritedProperties(): INodeProperties[] {
|
|||
name: 'iconUrl',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Optional icon to be shown on the action in conjunction with the title. Supports data URI in version 1.2+',
|
||||
description: 'Optional icon to be shown on the action in conjunction with the title. Supports data URI in version 1.2+.',
|
||||
},
|
||||
{
|
||||
displayName: 'Style',
|
||||
|
@ -205,7 +205,7 @@ export function getTextBlockProperties(): INodeProperties[] {
|
|||
},
|
||||
},
|
||||
required: true,
|
||||
description: 'Text to display. A subset of markdown is supported (https://aka.ms/ACTextFeatures)',
|
||||
description: 'Text to display. A subset of markdown is supported (https://aka.ms/ACTextFeatures).',
|
||||
},
|
||||
{
|
||||
displayName: 'Color',
|
||||
|
@ -407,7 +407,7 @@ export function getTextBlockProperties(): INodeProperties[] {
|
|||
},
|
||||
},
|
||||
default: true,
|
||||
description: 'If true, allow text to wrap. Otherwise, text is clipped',
|
||||
description: 'If true, allow text to wrap. Otherwise, text is clipped.',
|
||||
},
|
||||
{
|
||||
displayName: 'Height',
|
||||
|
@ -537,7 +537,7 @@ export function getInputTextProperties(): INodeProperties[] {
|
|||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Unique identifier for the value. Used to identify collected input when the Submit action is performed',
|
||||
description: 'Unique identifier for the value. Used to identify collected input when the Submit action is performed.',
|
||||
},
|
||||
{
|
||||
displayName: 'Is Multiline',
|
||||
|
@ -579,7 +579,7 @@ export function getInputTextProperties(): INodeProperties[] {
|
|||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Description of the input desired. Displayed when no text has been input',
|
||||
description: 'Description of the input desired. Displayed when no text has been input.',
|
||||
},
|
||||
{
|
||||
displayName: 'Regex',
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue