Add Storyblok Node (#1125)

*  Add Storyblok node

* 🔨 Minor changes

*  Small improvements

*  Improvements

*  Prepare Storyblok Node for release

Co-authored-by: Tanay Pant <tanaypant@protonmail.com>
Co-authored-by: ricardo <ricardoespinoza105@gmail.com>
This commit is contained in:
Jan 2020-11-04 10:53:33 +01:00 committed by GitHub
parent 638e688f25
commit 28b0c87136
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 1274 additions and 0 deletions

View file

@ -0,0 +1,18 @@
import {
ICredentialType,
NodePropertyTypes,
} from 'n8n-workflow';
export class StoryblokContentApi implements ICredentialType {
name = 'storyblokContentApi';
displayName = 'Storyblok Content API';
documentationUrl = 'storyblok';
properties = [
{
displayName: 'API Key',
name: 'apiKey',
type: 'string' as NodePropertyTypes,
default: '',
},
];
}

View file

@ -0,0 +1,18 @@
import {
ICredentialType,
NodePropertyTypes,
} from 'n8n-workflow';
export class StoryblokManagementApi implements ICredentialType {
name = 'storyblokManagementApi';
displayName = 'Storyblok Management API';
documentationUrl = 'storyblok';
properties = [
{
displayName: 'Personal Access Token',
name: 'accessToken',
type: 'string' as NodePropertyTypes,
default: '',
},
];
}

View file

@ -0,0 +1,92 @@
import {
OptionsWithUri,
} from 'request';
import {
IExecuteFunctions,
IExecuteSingleFunctions,
IHookFunctions,
ILoadOptionsFunctions,
} from 'n8n-core';
import {
IDataObject,
} from 'n8n-workflow';
export async function storyblokApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
const authenticationMethod = this.getNodeParameter('source', 0) as string;
let options: OptionsWithUri = {
headers: {
'Content-Type': 'application/json',
},
method,
qs,
body,
uri: '',
json: true,
};
options = Object.assign({}, options, option);
if (Object.keys(options.body).length === 0) {
delete options.body;
}
if (authenticationMethod === 'contentApi') {
const credentials = this.getCredentials('storyblokContentApi') as IDataObject;
options.uri = `https://api.storyblok.com${resource}`;
Object.assign(options.qs, { token: credentials.apiKey });
} else {
const credentials = this.getCredentials('storyblokManagementApi') as IDataObject;
options.uri = `https://mapi.storyblok.com${resource}`;
Object.assign(options.headers, { 'Authorization': credentials.accessToken });
}
try {
return this.helpers.request!(options);
} catch (error) {
if (error.response && error.response.body && error.response.body.message) {
// Try to return the error prettier
const errorBody = error.response.body;
throw new Error(`Storyblok error response [${error.statusCode}]: ${errorBody.message}`);
}
// Expected error data did not get returned so throw the actual error
throw error;
}
}
export async function storyblokApiRequestAllItems(this: IHookFunctions | ILoadOptionsFunctions | IExecuteFunctions, propertyName: string, method: string, resource: string, body: any = {}, query: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
const returnData: IDataObject[] = [];
let responseData;
query.per_page = 100;
query.page = 1;
do {
responseData = await storyblokApiRequest.call(this, method, resource, body, query);
query.page++;
returnData.push.apply(returnData, responseData[propertyName]);
} while (
responseData[propertyName].length !== 0
);
return returnData;
}
export function validateJSON(json: string | undefined): any { // tslint:disable-line:no-any
let result;
try {
result = JSON.parse(json!);
} catch (exception) {
result = undefined;
}
return result;
}

View file

@ -0,0 +1,143 @@
import {
INodeProperties,
} from 'n8n-workflow';
export const storyContentOperations = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
displayOptions: {
show: {
source: [
'contentApi',
],
resource: [
'story',
],
},
},
options: [
{
name: 'Get',
value: 'get',
description: 'Get a story',
},
{
name: 'Get All',
value: 'getAll',
description: 'Get all stories',
},
],
default: 'get',
description: 'The operation to perform.',
},
] as INodeProperties[];
export const storyContentFields = [
/* -------------------------------------------------------------------------- */
/* story:get */
/* -------------------------------------------------------------------------- */
{
displayName: 'Identifier',
name: 'identifier',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
source: [
'contentApi',
],
resource: [
'story',
],
operation: [
'get',
],
},
},
description: 'The ID or slug of the story to get.',
},
/* -------------------------------------------------------------------------- */
/* story:getAll */
/* -------------------------------------------------------------------------- */
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
displayOptions: {
show: {
source: [
'contentApi',
],
resource: [
'story',
],
operation: [
'getAll',
],
},
},
default: false,
description: 'Returns a list of your user contacts.',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
displayOptions: {
show: {
source: [
'contentApi',
],
resource: [
'story',
],
operation: [
'getAll',
],
returnAll: [
false,
],
},
},
typeOptions: {
minValue: 1,
maxValue: 100,
},
default: 50,
description: 'How many results to return.',
},
{
displayName: 'Filters',
name: 'filters',
type: 'collection',
placeholder: 'Add Filter',
default: {},
displayOptions: {
show: {
source: [
'contentApi',
],
resource: [
'story',
],
operation: [
'getAll',
],
},
},
options: [
{
displayName: 'Starts With',
name: 'starts_with',
type: 'string',
default: '',
description: 'Filter by slug.',
},
],
},
] as INodeProperties[];

View file

@ -0,0 +1,647 @@
import {
INodeProperties,
} from 'n8n-workflow';
export const storyManagementOperations = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
displayOptions: {
show: {
source: [
'managementApi',
],
resource: [
'story',
],
},
},
options: [
// {
// name: 'Create',
// value: 'create',
// description: 'Create a story',
// },
{
name: 'Delete',
value: 'delete',
description: 'Delete a story',
},
{
name: 'Get',
value: 'get',
description: 'Get a story',
},
{
name: 'Get All',
value: 'getAll',
description: 'Get all stories',
},
{
name: 'Publish',
value: 'publish',
description: 'Publish a story',
},
{
name: 'Unpublish',
value: 'unpublish',
description: 'Unpublish a story',
},
],
default: 'get',
description: 'The operation to perform.',
},
] as INodeProperties[];
export const storyManagementFields = [
// /* -------------------------------------------------------------------------- */
// /* story:create */
// /* -------------------------------------------------------------------------- */
// {
// displayName: 'Space ID',
// name: 'space',
// type: 'options',
// typeOptions: {
// loadOptionsMethod: 'getSpaces',
// },
// default: '',
// required: true,
// displayOptions: {
// show: {
// source: [
// 'managementApi',
// ],
// resource: [
// 'story',
// ],
// operation: [
// 'create',
// ],
// },
// },
// description: 'The name of the space.',
// },
// {
// displayName: 'Name',
// name: 'name',
// type: 'string',
// default: '',
// required: true,
// displayOptions: {
// show: {
// source: [
// 'managementApi',
// ],
// resource: [
// 'story',
// ],
// operation: [
// 'create',
// ],
// },
// },
// description: 'The name you give this story.',
// },
// {
// displayName: 'Slug',
// name: 'slug',
// type: 'string',
// default: '',
// required: true,
// displayOptions: {
// show: {
// source: [
// 'managementApi',
// ],
// resource: [
// 'story',
// ],
// operation: [
// 'create',
// ],
// },
// },
// description: 'The slug/path you give this story.',
// },
// {
// displayName: 'JSON Parameters',
// name: 'jsonParameters',
// type: 'boolean',
// default: false,
// description: '',
// displayOptions: {
// show: {
// source: [
// 'managementApi',
// ],
// resource: [
// 'story',
// ],
// operation: [
// 'create',
// ],
// },
// },
// },
// {
// displayName: 'Additional Fields',
// name: 'additionalFields',
// type: 'collection',
// placeholder: 'Add Field',
// displayOptions: {
// show: {
// source: [
// 'managementApi',
// ],
// resource: [
// 'story',
// ],
// operation: [
// 'create',
// ],
// },
// },
// default: {},
// options: [
// {
// displayName: 'Content',
// name: 'contentUi',
// type: 'fixedCollection',
// description: 'Add Content',
// typeOptions: {
// multipleValues: false,
// },
// displayOptions: {
// show: {
// '/jsonParameters': [
// false,
// ],
// },
// },
// placeholder: 'Add Content',
// default: '',
// options: [
// {
// displayName: 'Content Data',
// name: 'contentValue',
// values: [
// {
// displayName: 'Component',
// name: 'component',
// type: 'options',
// typeOptions: {
// loadOptionsMethod: 'getComponents',
// loadOptionsDependsOn: [
// 'space',
// ],
// },
// default: '',
// },
// {
// displayName: 'Elements',
// name: 'elementUi',
// type: 'fixedCollection',
// description: 'Add Body',
// typeOptions: {
// multipleValues: true,
// },
// placeholder: 'Add Element',
// default: '',
// options: [
// {
// displayName: 'Element',
// name: 'elementValues',
// values: [
// {
// displayName: 'Component',
// name: 'component',
// type: 'options',
// typeOptions: {
// loadOptionsMethod: 'getComponents',
// loadOptionsDependsOn: [
// 'space',
// ],
// },
// default: '',
// },
// {
// displayName: 'Element Data',
// name: 'dataUi',
// type: 'fixedCollection',
// description: 'Add Data',
// typeOptions: {
// multipleValues: true,
// },
// placeholder: 'Add Data',
// default: '',
// options: [
// {
// displayName: 'Data',
// name: 'dataValues',
// values: [
// {
// displayName: 'Key',
// name: 'key',
// type: 'string',
// default: '',
// },
// {
// displayName: 'Value',
// name: 'value',
// type: 'string',
// default: '',
// },
// ],
// },
// ],
// },
// ],
// },
// ],
// },
// ],
// },
// ],
// },
// {
// displayName: 'Content (JSON)',
// name: 'contentJson',
// type: 'string',
// displayOptions: {
// show: {
// '/jsonParameters': [
// true,
// ],
// },
// },
// default: '',
// },
// {
// displayName: 'Parent ID',
// name: 'parentId',
// type: 'string',
// default: '',
// description: 'Parent story/folder numeric ID.',
// },
// {
// displayName: 'Path',
// name: 'path',
// type: 'string',
// default: '',
// description: 'Given real path, used in the preview editor.',
// },
// {
// displayName: 'Is Startpage',
// name: 'isStartpage',
// type: 'boolean',
// default: false,
// description: 'Is startpage of current folder.',
// },
// {
// displayName: 'First Published At',
// name: 'firstPublishedAt',
// type: 'dateTime',
// default: '',
// description: 'First publishing date.',
// },
// ],
// },
/* -------------------------------------------------------------------------- */
/* story:delete */
/* -------------------------------------------------------------------------- */
{
displayName: 'Space ID',
name: 'space',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getSpaces',
},
default: '',
required: true,
displayOptions: {
show: {
source: [
'managementApi',
],
resource: [
'story',
],
operation: [
'delete',
],
},
},
description: 'The name of the space.',
},
{
displayName: 'Story ID',
name: 'storyId',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
source: [
'managementApi',
],
resource: [
'story',
],
operation: [
'delete',
],
},
},
description: 'Numeric ID of the story.',
},
/* -------------------------------------------------------------------------- */
/* story:get */
/* -------------------------------------------------------------------------- */
{
displayName: 'Space ID',
name: 'space',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getSpaces',
},
default: '',
required: true,
displayOptions: {
show: {
source: [
'managementApi',
],
resource: [
'story',
],
operation: [
'get',
],
},
},
description: 'The name of the space.',
},
{
displayName: 'Story ID',
name: 'storyId',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
source: [
'managementApi',
],
resource: [
'story',
],
operation: [
'get',
],
},
},
description: 'Numeric ID of the story.',
},
/* -------------------------------------------------------------------------- */
/* story:getAll */
/* -------------------------------------------------------------------------- */
{
displayName: 'Space ID',
name: 'space',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getSpaces',
},
default: '',
required: true,
displayOptions: {
show: {
source: [
'managementApi',
],
resource: [
'story',
],
operation: [
'getAll',
],
},
},
description: 'The name of the space',
},
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
displayOptions: {
show: {
source: [
'managementApi',
],
resource: [
'story',
],
operation: [
'getAll',
],
},
},
default: false,
description: 'Returns a list of your user contacts.',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
displayOptions: {
show: {
source: [
'managementApi',
],
resource: [
'story',
],
operation: [
'getAll',
],
returnAll: [
false,
],
},
},
typeOptions: {
minValue: 1,
maxValue: 100,
},
default: 50,
description: 'How many results to return.',
},
{
displayName: 'Filters',
name: 'filters',
type: 'collection',
placeholder: 'Add Filter',
default: {},
displayOptions: {
show: {
source: [
'managementApi',
],
resource: [
'story',
],
operation: [
'getAll',
],
},
},
options: [
{
displayName: 'Starts With',
name: 'starts_with',
type: 'string',
default: '',
description: 'Filter by slug.',
},
],
},
/* -------------------------------------------------------------------------- */
/* story:publish */
/* -------------------------------------------------------------------------- */
{
displayName: 'Space ID',
name: 'space',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getSpaces',
},
default: '',
required: true,
displayOptions: {
show: {
source: [
'managementApi',
],
resource: [
'story',
],
operation: [
'publish',
],
},
},
description: 'The name of the space.',
},
{
displayName: 'Story ID',
name: 'storyId',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
source: [
'managementApi',
],
resource: [
'story',
],
operation: [
'publish',
],
},
},
description: 'Numeric ID of the story.',
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
placeholder: 'Add Option',
displayOptions: {
show: {
source: [
'managementApi',
],
resource: [
'story',
],
operation: [
'publish',
],
},
},
default: {},
options: [
{
displayName: 'Release ID',
name: 'releaseId',
type: 'string',
default: '',
description: 'Numeric ID of release.',
},
{
displayName: 'Language',
name: 'language',
type: 'string',
default: '',
description: 'Language code to publish the story individually (must be enabled in the space settings).',
},
],
},
/* -------------------------------------------------------------------------- */
/* story:unpublish */
/* -------------------------------------------------------------------------- */
{
displayName: 'Space ID',
name: 'space',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getSpaces',
},
default: '',
required: true,
displayOptions: {
show: {
source: [
'managementApi',
],
resource: [
'story',
],
operation: [
'unpublish',
],
},
},
description: 'The name of the space.',
},
{
displayName: 'Story ID',
name: 'storyId',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
source: [
'managementApi',
],
resource: [
'story',
],
operation: [
'unpublish',
],
},
},
description: 'Numeric ID of the story.',
},
] as INodeProperties[];

View file

@ -0,0 +1,334 @@
import {
IExecuteFunctions,
} from 'n8n-core';
import {
IDataObject,
ILoadOptionsFunctions,
INodeExecutionData,
INodePropertyOptions,
INodeType,
INodeTypeDescription,
} from 'n8n-workflow';
import {
storyblokApiRequest,
storyblokApiRequestAllItems,
validateJSON,
} from './GenericFunctions';
import {
storyContentFields,
storyContentOperations,
} from './StoryContentDescription';
import {
storyManagementFields,
storyManagementOperations,
} from './StoryManagementDescription';
import { v4 as uuidv4 } from 'uuid';
export class Storyblok implements INodeType {
description: INodeTypeDescription = {
displayName: 'Storyblok',
name: 'storyblok',
icon: 'file:storyblok.svg',
group: ['output'],
version: 1,
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Consume Storyblok API',
defaults: {
name: 'Storyblok',
color: '#09b3af',
},
inputs: ['main'],
outputs: ['main'],
credentials: [
{
name: 'storyblokContentApi',
required: true,
displayOptions: {
show: {
source: [
'contentApi',
],
},
},
},
{
name: 'storyblokManagementApi',
required: true,
displayOptions: {
show: {
source: [
'managementApi',
],
},
},
},
],
properties: [
{
displayName: 'Source',
name: 'source',
type: 'options',
default: 'contentApi',
description: 'Pick where your data comes from, Content or Management API',
options: [
{
name: 'Content API',
value: 'contentApi',
},
{
name: 'Management API',
value: 'managementApi',
},
],
},
// Resources: Content API
{
displayName: 'Resource',
name: 'resource',
type: 'options',
options: [
{
name: 'Story',
value: 'story',
},
],
default: 'story',
description: 'Resource to consume.',
displayOptions: {
show: {
source: [
'contentApi',
],
},
},
},
// Resources: Management API
{
displayName: 'Resource',
name: 'resource',
type: 'options',
options: [
{
name: 'Story',
value: 'story',
},
],
default: 'story',
description: 'Resource to consume.',
displayOptions: {
show: {
source: [
'managementApi',
],
},
},
},
// Content API - Story
...storyContentOperations,
...storyContentFields,
// Management API - Story
...storyManagementOperations,
...storyManagementFields,
],
};
methods = {
loadOptions: {
async getSpaces(
this: ILoadOptionsFunctions,
): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const { spaces } = await storyblokApiRequest.call(
this,
'GET',
'/v1/spaces',
);
for (const space of spaces) {
returnData.push({
name: space.name,
value: space.id,
});
}
return returnData;
},
async getComponents(
this: ILoadOptionsFunctions,
): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const space = this.getCurrentNodeParameter('space') as string;
const { components } = await storyblokApiRequest.call(
this,
'GET',
`/v1/spaces/${space}/components`,
);
for (const component of components) {
returnData.push({
name: `${component.name} ${(component.is_root ? '(root)' : '')}`,
value: component.name,
});
}
return returnData;
},
},
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
const returnData: IDataObject[] = [];
const length = items.length as unknown as number;
const qs: IDataObject = {};
let responseData;
const source = this.getNodeParameter('source', 0) as string;
const resource = this.getNodeParameter('resource', 0) as string;
const operation = this.getNodeParameter('operation', 0) as string;
for (let i = 0; i < length; i++) {
if (source === 'contentApi') {
if (resource === 'story') {
if (operation === 'get') {
const identifier = this.getNodeParameter('identifier', i) as string;
responseData = await storyblokApiRequest.call(this, 'GET', `/v1/cdn/stories/${identifier}`);
responseData = responseData.story;
}
if (operation === 'getAll') {
const filters = this.getNodeParameter('filters', i) as string;
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
Object.assign(qs, filters);
if (returnAll) {
responseData = await storyblokApiRequestAllItems.call(this, 'stories', 'GET', '/v1/cdn/stories', {}, qs);
} else {
const limit = this.getNodeParameter('limit', i) as number;
qs.per_page = limit;
responseData = await storyblokApiRequest.call(this, 'GET', `/v1/cdn/stories`, {}, qs);
responseData = responseData.stories;
}
}
}
}
if (source === 'managementApi') {
if (resource === 'story') {
// if (operation === 'create') {
// const space = this.getNodeParameter('space', i) as string;
// const name = this.getNodeParameter('name', i) as string;
// const slug = this.getNodeParameter('slug', i) as string;
// const jsonParameters = this.getNodeParameter('jsonParameters', i) as boolean;
// const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
// const body: IDataObject = {
// name,
// slug,
// };
// if (jsonParameters) {
// if (additionalFields.contentJson) {
// const json = validateJSON(additionalFields.contentJson as string);
// body.content = json;
// }
// } else {
// if (additionalFields.contentUi) {
// const contentValue = (additionalFields.contentUi as IDataObject).contentValue as IDataObject;
// const content: { component: string, body: IDataObject[] } = { component: '', body: [] };
// if (contentValue) {
// content.component = contentValue.component as string;
// const elementValues = (contentValue.elementUi as IDataObject).elementValues as IDataObject[];
// for (const elementValue of elementValues) {
// const body: IDataObject = {};
// body._uid = uuidv4();
// body.component = elementValue.component;
// if (elementValue.dataUi) {
// const dataValues = (elementValue.dataUi as IDataObject).dataValues as IDataObject[];
// for (const dataValue of dataValues) {
// body[dataValue.key as string] = dataValue.value;
// }
// }
// content.body.push(body);
// }
// }
// body.content = content;
// }
// }
// if (additionalFields.parentId) {
// body.parent_id = additionalFields.parentId as string;
// }
// if (additionalFields.path) {
// body.path = additionalFields.path as string;
// }
// if (additionalFields.isStartpage) {
// body.is_startpage = additionalFields.isStartpage as string;
// }
// if (additionalFields.firstPublishedAt) {
// body.first_published_at = additionalFields.firstPublishedAt as string;
// }
// responseData = await storyblokApiRequest.call(this, 'POST', `/v1/spaces/${space}/stories`, { story: body });
// responseData = responseData.story;
// }
if (operation === 'delete') {
const space = this.getNodeParameter('space', i) as string;
const storyId = this.getNodeParameter('storyId', i) as string;
responseData = await storyblokApiRequest.call(this, 'DELETE', `/v1/spaces/${space}/stories/${storyId}`);
responseData = responseData.story;
}
if (operation === 'get') {
const space = this.getNodeParameter('space', i) as string;
const storyId = this.getNodeParameter('storyId', i) as string;
responseData = await storyblokApiRequest.call(this, 'GET', `/v1/spaces/${space}/stories/${storyId}`);
responseData = responseData.story;
}
if (operation === 'getAll') {
const space = this.getNodeParameter('space', i) as string;
const filters = this.getNodeParameter('filters', i) as string;
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
Object.assign(qs, filters);
if (returnAll) {
responseData = await storyblokApiRequestAllItems.call(this, 'stories', 'GET', `/v1/spaces/${space}/stories`, {}, qs);
} else {
const limit = this.getNodeParameter('limit', i) as number;
qs.per_page = limit;
responseData = await storyblokApiRequest.call(this, 'GET', `/v1/spaces/${space}/stories`, {}, qs);
responseData = responseData.stories;
}
}
if (operation === 'publish') {
const space = this.getNodeParameter('space', i) as string;
const storyId = this.getNodeParameter('storyId', i) as string;
const options = this.getNodeParameter('options', i) as IDataObject;
const query: IDataObject = {};
// Not sure if these two options work
if (options.releaseId) {
query.release_id = options.releaseId as string;
}
if (options.language) {
query.lang = options.language as string;
}
responseData = await storyblokApiRequest.call(this, 'GET', `/v1/spaces/${space}/stories/${storyId}/publish`, {}, query);
responseData = responseData.story;
}
if (operation === 'unpublish') {
const space = this.getNodeParameter('space', i) as string;
const storyId = this.getNodeParameter('storyId', i) as string;
responseData = await storyblokApiRequest.call(this, 'GET', `/v1/spaces/${space}/stories/${storyId}/unpublish`);
responseData = responseData.story;
}
}
}
if (Array.isArray(responseData)) {
returnData.push.apply(returnData, responseData as IDataObject[]);
} else {
returnData.push(responseData as IDataObject);
}
}
return [this.helpers.returnJsonArray(returnData)];
}
}

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="87px" height="103px" viewBox="0 0 87 103" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 51.3 (57544) - http://www.bohemiancoding.com/sketch -->
<title>colored-standalone</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="colored-standalone" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="logo" fill="#09B3AF" fill-rule="nonzero">
<path d="M51.8,49 L31,49 L31,59 L51.3,59 C52.5,59 53.6,58.5 54.5,57.7 C55.3,56.9 55.8,55.7 55.8,54.2 C55.8290035,52.9130708 55.4433501,51.6509323 54.7,50.6 C53.9,49.6 53,49 51.8,49 Z M52.5,36.9 C53.4,36.3 53.8,35 53.8,33.3 C53.8,31.8 53.4,30.7 52.7,30 C52,29.4 51.1,29 50.1,29 L31,29 L31,38 L49.7,38 C50.7,38 51.7,37.5 52.5,36.9 Z" id="Shape"></path>
<path d="M83,0 L4.5,0 C2,0 0,2 0,4.4 L0,83 C0,85.4 2,86.9 4.4,86.9 L16,86.9 L16,102.6 L30.4,87 L83,87 C85.4,87 86.9,85.5 86.9,83 L86.9,4.5 C86.9,2.1 85.4,0.1 82.9,0.1 L83,0 Z M69.8,63.7 C68.8,65.5 67.3,67 65.5,68.1 C63.6,69.3 61.5,70.4 59.1,70.9 C56.7,71.5 54.1,72 51.4,72 L16,72 L16,16 L56.2,16 C58.2,16 59.9,16.4 61.5,17.3 C63,18.1 64.4,19.2 65.5,20.5 C67.7403434,23.2320077 68.9444137,26.6671496 68.9,30.2 C68.9,32.8 68.2,35.3 66.9,37.7 C65.5522265,40.1140117 63.4421536,42.0130773 60.9,43.1 C64.1,44 66.6,45.6 68.5,47.9 C70.3,50.3 71.2,53.4 71.2,57.3 C71.2,59.8 70.7,61.9 69.7,63.7 L69.8,63.7 Z" id="Shape"></path>
</g>
<g id="logo" fill="#09B3AF" fill-rule="nonzero">
<path d="M51.8,49 L31,49 L31,59 L51.3,59 C52.5,59 53.6,58.5 54.5,57.7 C55.3,56.9 55.8,55.7 55.8,54.2 C55.8290035,52.9130708 55.4433501,51.6509323 54.7,50.6 C53.9,49.6 53,49 51.8,49 Z M52.5,36.9 C53.4,36.3 53.8,35 53.8,33.3 C53.8,31.8 53.4,30.7 52.7,30 C52,29.4 51.1,29 50.1,29 L31,29 L31,38 L49.7,38 C50.7,38 51.7,37.5 52.5,36.9 Z" id="Shape"></path>
<path d="M83,0 L4.5,0 C2,0 0,2 0,4.4 L0,83 C0,85.4 2,86.9 4.4,86.9 L16,86.9 L16,102.6 L30.4,87 L83,87 C85.4,87 86.9,85.5 86.9,83 L86.9,4.5 C86.9,2.1 85.4,0.1 82.9,0.1 L83,0 Z M69.8,63.7 C68.8,65.5 67.3,67 65.5,68.1 C63.6,69.3 61.5,70.4 59.1,70.9 C56.7,71.5 54.1,72 51.4,72 L16,72 L16,16 L56.2,16 C58.2,16 59.9,16.4 61.5,17.3 C63,18.1 64.4,19.2 65.5,20.5 C67.7403434,23.2320077 68.9444137,26.6671496 68.9,30.2 C68.9,32.8 68.2,35.3 66.9,37.7 C65.5522265,40.1140117 63.4421536,42.0130773 60.9,43.1 C64.1,44 66.6,45.6 68.5,47.9 C70.3,50.3 71.2,53.4 71.2,57.3 C71.2,59.8 70.7,61.9 69.7,63.7 L69.8,63.7 Z" id="Shape"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -170,6 +170,10 @@
"dist/credentials/StravaOAuth2Api.credentials.js", "dist/credentials/StravaOAuth2Api.credentials.js",
"dist/credentials/StripeApi.credentials.js", "dist/credentials/StripeApi.credentials.js",
"dist/credentials/Sftp.credentials.js", "dist/credentials/Sftp.credentials.js",
"dist/credentials/Signl4Api.credentials.js",
"dist/credentials/SpotifyOAuth2Api.credentials.js",
"dist/credentials/StoryblokContentApi.credentials.js",
"dist/credentials/StoryblokManagementApi.credentials.js",
"dist/credentials/SurveyMonkeyApi.credentials.js", "dist/credentials/SurveyMonkeyApi.credentials.js",
"dist/credentials/SurveyMonkeyOAuth2Api.credentials.js", "dist/credentials/SurveyMonkeyOAuth2Api.credentials.js",
"dist/credentials/TaigaCloudApi.credentials.js", "dist/credentials/TaigaCloudApi.credentials.js",
@ -371,6 +375,7 @@
"dist/nodes/SpreadsheetFile.node.js", "dist/nodes/SpreadsheetFile.node.js",
"dist/nodes/SseTrigger.node.js", "dist/nodes/SseTrigger.node.js",
"dist/nodes/Start.node.js", "dist/nodes/Start.node.js",
"dist/nodes/Storyblok/Storyblok.node.js",
"dist/nodes/Strava/Strava.node.js", "dist/nodes/Strava/Strava.node.js",
"dist/nodes/Strava/StravaTrigger.node.js", "dist/nodes/Strava/StravaTrigger.node.js",
"dist/nodes/Stripe/StripeTrigger.node.js", "dist/nodes/Stripe/StripeTrigger.node.js",