mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
✨ Add Notion Node (#1811)
* ✨ Notion Node * ⚡ Improvements * test * ⚡ Improvements * ⚡ Improvements * ⚡ Add missing url field * Remove linter errors * ⚡ Add filters * ⚡ Add and & or filters * ⚡ Improvements * ⚡ Improvements * ⚡ Improvements * ⚡ Improvements * ⚡ Improvements * ⚡ Improvements * ⚡ Fixed style on some options * ⚡ Improvements * ⚡ Improvements * ⚡ Improvements * ⚡ Improvements * ⚡ Improvements * ⚡ improvements * ⚡ Rename DatabasePage -> Search to Get All * ⚡ Improvements * ⚡ Minor improvements Co-authored-by: ricardo <ricardoespinoza105@gmail.com>
This commit is contained in:
parent
72436bd03e
commit
ed6f2c5b8a
18
packages/nodes-base/credentials/NotionApi.credentials.ts
Normal file
18
packages/nodes-base/credentials/NotionApi.credentials.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export class NotionApi implements ICredentialType {
|
||||
name = 'notionApi';
|
||||
displayName = 'Notion API';
|
||||
documentationUrl = 'notion';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'API Key',
|
||||
name: 'apiKey',
|
||||
type: 'string' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
];
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
import {
|
||||
ICredentialType,
|
||||
NodePropertyTypes,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export class NotionOAuth2Api implements ICredentialType {
|
||||
name = 'notionOAuth2Api';
|
||||
extends = [
|
||||
'oAuth2Api',
|
||||
];
|
||||
displayName = 'Notion OAuth2 API';
|
||||
documentationUrl = 'notion';
|
||||
properties = [
|
||||
{
|
||||
displayName: 'Authorization URL',
|
||||
name: 'authUrl',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'https://api.notion.com/v1/oauth/authorize',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Access Token URL',
|
||||
name: 'accessTokenUrl',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'https://api.notion.com/v1/oauth/token',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Scope',
|
||||
name: 'scope',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Auth URI Query Parameters',
|
||||
name: 'authQueryParameters',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Authentication',
|
||||
name: 'authentication',
|
||||
type: 'hidden' as NodePropertyTypes,
|
||||
default: 'header',
|
||||
},
|
||||
];
|
||||
}
|
|
@ -66,6 +66,8 @@ export async function jiraSoftwareCloudApiRequest(this: IHookFunctions | IExecut
|
|||
delete options.qs;
|
||||
}
|
||||
|
||||
console.log(options);
|
||||
|
||||
try {
|
||||
return await this.helpers.request!(options);
|
||||
} catch (error) {
|
||||
|
|
123
packages/nodes-base/nodes/Notion/BlockDescription.ts
Normal file
123
packages/nodes-base/nodes/Notion/BlockDescription.ts
Normal file
|
@ -0,0 +1,123 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
blocks,
|
||||
} from './Blocks';
|
||||
|
||||
export const blockOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'block',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Append',
|
||||
value: 'append',
|
||||
description: 'Append a block',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all children blocks',
|
||||
},
|
||||
],
|
||||
default: 'append',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const blockFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* block:append */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Block ID',
|
||||
name: 'blockId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'block',
|
||||
],
|
||||
operation: [
|
||||
'append',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: `The ID of block. A page it is also considered a block. Hence, a Page ID can be used as well.`,
|
||||
},
|
||||
...blocks('block', 'append'),
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* block:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Block ID',
|
||||
name: 'blockId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'block',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'block',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'If all results should be returned or only up to a given limit.',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'block',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 100,
|
||||
},
|
||||
default: 50,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
] as INodeProperties[];
|
571
packages/nodes-base/nodes/Notion/Blocks.ts
Normal file
571
packages/nodes-base/nodes/Notion/Blocks.ts
Normal file
|
@ -0,0 +1,571 @@
|
|||
import {
|
||||
IDisplayOptions,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
const colors = [
|
||||
{
|
||||
name: 'Default',
|
||||
value: 'default',
|
||||
},
|
||||
{
|
||||
name: 'Gray',
|
||||
value: 'gray',
|
||||
},
|
||||
{
|
||||
name: 'Brown',
|
||||
value: 'brown',
|
||||
},
|
||||
{
|
||||
name: 'Orange',
|
||||
value: 'orange',
|
||||
},
|
||||
{
|
||||
name: 'Yellow',
|
||||
value: 'yellow',
|
||||
},
|
||||
{
|
||||
name: 'Green',
|
||||
value: 'green',
|
||||
},
|
||||
{
|
||||
name: 'Blue',
|
||||
value: 'blue',
|
||||
},
|
||||
{
|
||||
name: 'Purple',
|
||||
value: 'purple',
|
||||
},
|
||||
{
|
||||
name: 'Pink',
|
||||
value: 'pink',
|
||||
},
|
||||
{
|
||||
name: 'Red',
|
||||
value: 'red',
|
||||
},
|
||||
{
|
||||
name: 'Gray Background',
|
||||
value: 'gray_background',
|
||||
},
|
||||
{
|
||||
name: 'Brown Background',
|
||||
value: 'brown_background',
|
||||
},
|
||||
{
|
||||
name: 'Orange Background',
|
||||
value: 'orange_background',
|
||||
},
|
||||
{
|
||||
name: 'Yellow Background',
|
||||
value: 'yellow_background',
|
||||
},
|
||||
{
|
||||
name: 'Green Background',
|
||||
value: 'green_background',
|
||||
},
|
||||
{
|
||||
name: 'Blue Background',
|
||||
value: 'blue_background',
|
||||
},
|
||||
{
|
||||
name: 'Purple Background',
|
||||
value: 'purple_background',
|
||||
},
|
||||
{
|
||||
name: 'Pink Background',
|
||||
value: 'pink_background',
|
||||
},
|
||||
{
|
||||
name: 'Red Background',
|
||||
value: 'red_background',
|
||||
},
|
||||
];
|
||||
|
||||
const annotation = [
|
||||
{
|
||||
displayName: 'Annotations',
|
||||
name: 'annotationUi',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Annotation',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Bold',
|
||||
name: 'bold',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether the text is bolded.',
|
||||
},
|
||||
{
|
||||
displayName: 'Italic',
|
||||
name: 'italic',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether the text is italicized.',
|
||||
},
|
||||
{
|
||||
displayName: 'Strikethrough',
|
||||
name: 'strikethrough',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether the text is struck through.',
|
||||
},
|
||||
{
|
||||
displayName: 'Underline',
|
||||
name: 'underline',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether the text is underlined.',
|
||||
},
|
||||
{
|
||||
displayName: 'Code',
|
||||
name: 'code',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether the text is code style.',
|
||||
},
|
||||
{
|
||||
displayName: 'Color',
|
||||
name: 'color',
|
||||
type: 'options',
|
||||
options: colors,
|
||||
default: '',
|
||||
description: 'Color of the text.',
|
||||
},
|
||||
],
|
||||
description: 'All annotations that apply to this rich text.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
const typeMention = [
|
||||
{
|
||||
displayName: 'Type',
|
||||
name: 'mentionType',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
textType: [
|
||||
'mention',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Database',
|
||||
value: 'database',
|
||||
},
|
||||
{
|
||||
name: 'Date',
|
||||
value: 'date',
|
||||
},
|
||||
{
|
||||
name: 'Page',
|
||||
value: 'page',
|
||||
},
|
||||
{
|
||||
name: 'User',
|
||||
value: 'user',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description: `An inline mention of a user, page, database, or date. In the app these are</br>
|
||||
created by typing @ followed by the name of a user, page, database, or a date.`,
|
||||
},
|
||||
{
|
||||
displayName: 'User ID',
|
||||
name: 'user',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getUsers',
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
mentionType: [
|
||||
'user',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'The id of the user being mentioned.',
|
||||
},
|
||||
{
|
||||
displayName: 'Page ID',
|
||||
name: 'page',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
mentionType: [
|
||||
'page',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'The id of the page being mentioned.',
|
||||
},
|
||||
{
|
||||
displayName: 'Database ID',
|
||||
name: 'database',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getDatabases',
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
mentionType: [
|
||||
'database',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'The id of the database being mentioned.',
|
||||
},
|
||||
{
|
||||
displayName: 'Range',
|
||||
name: 'range',
|
||||
displayOptions: {
|
||||
show: {
|
||||
mentionType: [
|
||||
'date',
|
||||
],
|
||||
},
|
||||
},
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Weather or not you want to define a date range.',
|
||||
},
|
||||
{
|
||||
displayName: 'Date',
|
||||
name: 'date',
|
||||
displayOptions: {
|
||||
show: {
|
||||
mentionType: [
|
||||
'date',
|
||||
],
|
||||
range: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: 'An ISO 8601 format date, with optional time.',
|
||||
},
|
||||
{
|
||||
displayName: 'Date Start',
|
||||
name: 'dateStart',
|
||||
displayOptions: {
|
||||
show: {
|
||||
mentionType: [
|
||||
'date',
|
||||
],
|
||||
range: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
},
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: 'An ISO 8601 format date, with optional time.',
|
||||
},
|
||||
{
|
||||
displayName: 'Date End',
|
||||
name: 'dateEnd',
|
||||
displayOptions: {
|
||||
show: {
|
||||
range: [
|
||||
true,
|
||||
],
|
||||
mentionType: [
|
||||
'date',
|
||||
],
|
||||
},
|
||||
},
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: `An ISO 8601 formatted date, with optional time. Represents the end of a date range.`,
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
const typeEquation = [
|
||||
{
|
||||
displayName: 'Expression',
|
||||
name: 'expression',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
textType: [
|
||||
'equation',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: '',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
const typeText = [
|
||||
{
|
||||
displayName: 'Text',
|
||||
name: 'text',
|
||||
displayOptions: {
|
||||
show: {
|
||||
textType: [
|
||||
'text',
|
||||
],
|
||||
},
|
||||
},
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: `Text content. This field contains the actual content</br>
|
||||
of your text and is probably the field you'll use most often.`,
|
||||
},
|
||||
{
|
||||
displayName: 'Is Link',
|
||||
name: 'isLink',
|
||||
displayOptions: {
|
||||
show: {
|
||||
textType: [
|
||||
'text',
|
||||
],
|
||||
},
|
||||
},
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
displayName: 'Text Link',
|
||||
name: 'textLink',
|
||||
displayOptions: {
|
||||
show: {
|
||||
textType: [
|
||||
'text',
|
||||
],
|
||||
isLink: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
},
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'The URL that this link points to.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const text = (displayOptions: IDisplayOptions) => [
|
||||
{
|
||||
displayName: 'Text',
|
||||
name: 'text',
|
||||
placeholder: 'Add Text',
|
||||
type: 'fixedCollection',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
displayOptions,
|
||||
options: [
|
||||
{
|
||||
name: 'text',
|
||||
displayName: 'Text',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Type',
|
||||
name: 'textType',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Equation',
|
||||
value: 'equation',
|
||||
},
|
||||
{
|
||||
name: 'Mention',
|
||||
value: 'mention',
|
||||
},
|
||||
{
|
||||
name: 'Text',
|
||||
value: 'text',
|
||||
},
|
||||
],
|
||||
default: 'text',
|
||||
description: '',
|
||||
},
|
||||
...typeText,
|
||||
...typeMention,
|
||||
...typeEquation,
|
||||
|
||||
...annotation,
|
||||
],
|
||||
},
|
||||
],
|
||||
description: 'Rich text in the block.',
|
||||
}] as INodeProperties[];
|
||||
|
||||
|
||||
const todo = (type: string) => [{
|
||||
displayName: 'Checked',
|
||||
name: 'checked',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
type,
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'Whether the to_do is checked or not.',
|
||||
}] as INodeProperties[];
|
||||
|
||||
const title = (type: string) => [{
|
||||
displayName: 'Title',
|
||||
name: 'title',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
type,
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'Plain text of page title.',
|
||||
}] as INodeProperties[];
|
||||
|
||||
const richText = (displayOptions: IDisplayOptions) => [
|
||||
{
|
||||
displayName: 'Rich Text',
|
||||
name: 'richText',
|
||||
type: 'boolean',
|
||||
displayOptions,
|
||||
default: false,
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
const textContent = (displayOptions: IDisplayOptions) => [
|
||||
{
|
||||
displayName: 'Text',
|
||||
name: 'textContent',
|
||||
type: 'string',
|
||||
displayOptions,
|
||||
default: '',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
const block = (blockType: string) => {
|
||||
const data: INodeProperties[] = [];
|
||||
switch (blockType) {
|
||||
case 'to_do':
|
||||
data.push(...todo(blockType));
|
||||
data.push(...richText({
|
||||
show: {
|
||||
type: [
|
||||
blockType,
|
||||
],
|
||||
},
|
||||
}));
|
||||
data.push(...textContent({
|
||||
show: {
|
||||
type: [
|
||||
blockType,
|
||||
],
|
||||
richText: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
}));
|
||||
data.push(...text({
|
||||
show: {
|
||||
type: [
|
||||
blockType,
|
||||
],
|
||||
richText: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
}));
|
||||
break;
|
||||
case 'child_page':
|
||||
data.push(...title(blockType));
|
||||
break;
|
||||
default:
|
||||
data.push(...richText({
|
||||
show: {
|
||||
type: [
|
||||
blockType,
|
||||
],
|
||||
},
|
||||
}));
|
||||
data.push(...textContent({
|
||||
show: {
|
||||
type: [
|
||||
blockType,
|
||||
],
|
||||
richText: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
}));
|
||||
data.push(...text({
|
||||
show: {
|
||||
type: [
|
||||
blockType,
|
||||
],
|
||||
richText: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
}));
|
||||
break;
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
export const blocks = (resource: string, operation: string) => [{
|
||||
displayName: 'Blocks',
|
||||
name: 'blockUi',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
resource,
|
||||
],
|
||||
operation: [
|
||||
operation,
|
||||
],
|
||||
},
|
||||
},
|
||||
placeholder: 'Add Block',
|
||||
options: [
|
||||
{
|
||||
name: 'blockValues',
|
||||
displayName: 'Block',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Type',
|
||||
name: 'type',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getBlockTypes',
|
||||
},
|
||||
description: 'Type of block',
|
||||
default: 'paragraph',
|
||||
},
|
||||
...block('paragraph'),
|
||||
...block('heading_1'),
|
||||
...block('heading_2'),
|
||||
...block('heading_3'),
|
||||
...block('toggle'),
|
||||
...block('to_do'),
|
||||
...block('child_page'),
|
||||
...block('bulleted_list_item'),
|
||||
...block('numbered_list_item'),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
100
packages/nodes-base/nodes/Notion/DatabaseDescription.ts
Normal file
100
packages/nodes-base/nodes/Notion/DatabaseDescription.ts
Normal file
|
@ -0,0 +1,100 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const databaseOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'database',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Get a database',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all databases',
|
||||
},
|
||||
],
|
||||
default: 'get',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const databaseFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* database:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Database ID',
|
||||
name: 'databaseId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'database',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* database:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'database',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'If all results should be returned or only up to a given limit.',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'database',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 100,
|
||||
},
|
||||
default: 50,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
] as INodeProperties[];
|
994
packages/nodes-base/nodes/Notion/DatabasePageDescription.ts
Normal file
994
packages/nodes-base/nodes/Notion/DatabasePageDescription.ts
Normal file
|
@ -0,0 +1,994 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
blocks,
|
||||
text,
|
||||
} from './Blocks';
|
||||
|
||||
import {
|
||||
filters,
|
||||
} from './Filters';
|
||||
|
||||
export const databasePageOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'databasePage',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create a pages in a database',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all pages in a database',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
description: 'Update pages in a database',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const databasePageFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* databasePage:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Database ID',
|
||||
name: 'databaseId',
|
||||
type: 'options',
|
||||
default: '',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getDatabases',
|
||||
},
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'databasePage',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'The ID of the database that this databasePage belongs to.',
|
||||
},
|
||||
{
|
||||
displayName: 'Simple',
|
||||
name: 'simple',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'databasePage',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: true,
|
||||
description: 'When set to true a simplify version of the response will be used else the raw data.',
|
||||
},
|
||||
{
|
||||
displayName: 'Properties',
|
||||
name: 'propertiesUi',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'databasePage',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
placeholder: 'Add Property',
|
||||
options: [
|
||||
{
|
||||
name: 'propertyValues',
|
||||
displayName: 'Property',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Key',
|
||||
name: 'key',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getDatabaseProperties',
|
||||
loadOptionsDependsOn: [
|
||||
'databaseId',
|
||||
],
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Type',
|
||||
name: 'type',
|
||||
type: 'hidden',
|
||||
default: '={{$parameter["&key"].split("|")[1]}}',
|
||||
},
|
||||
{
|
||||
displayName: 'Title',
|
||||
name: 'title',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'title',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Rich Text',
|
||||
name: 'richText',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'rich_text',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
displayName: 'Text',
|
||||
name: 'textContent',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'rich_text',
|
||||
],
|
||||
richText: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
...text({
|
||||
show: {
|
||||
type: [
|
||||
'rich_text',
|
||||
],
|
||||
richText: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
}),
|
||||
{
|
||||
displayName: 'Phone Number',
|
||||
name: 'phoneValue',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'phone_number',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: `Phone number. No structure is enforced.`,
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'multiSelectValue',
|
||||
type: 'multiOptions',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getPropertySelectValues',
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'multi_select',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: [],
|
||||
description: `Name of the options you want to set.
|
||||
Multiples can be defined separated by comma.`,
|
||||
},
|
||||
{
|
||||
displayName: 'Option',
|
||||
name: 'selectValue',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getPropertySelectValues',
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'select',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: `Name of the option you want to set.`,
|
||||
},
|
||||
{
|
||||
displayName: 'Email',
|
||||
name: 'emailValue',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'email',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Email address.',
|
||||
},
|
||||
{
|
||||
displayName: 'URL',
|
||||
name: 'urlValue',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'url',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Web address.',
|
||||
},
|
||||
{
|
||||
displayName: 'User IDs',
|
||||
name: 'peopleValue',
|
||||
type: 'multiOptions',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getUsers',
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'people',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: [],
|
||||
description: 'List of users. Multiples can be defined separated by comma.',
|
||||
},
|
||||
{
|
||||
displayName: 'Relation IDs',
|
||||
name: 'relationValue',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'relation',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: [],
|
||||
description: 'List of databases that belong to another database. Multiples can be defined separated by comma.',
|
||||
},
|
||||
{
|
||||
displayName: 'Checked',
|
||||
name: 'checkboxValue',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'checkbox',
|
||||
],
|
||||
},
|
||||
},
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: `
|
||||
Whether or not the checkbox is checked.</br>
|
||||
true represents checked.</br>
|
||||
false represents unchecked.
|
||||
`,
|
||||
},
|
||||
{
|
||||
displayName: 'Number',
|
||||
name: 'numberValue',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'number',
|
||||
],
|
||||
},
|
||||
},
|
||||
type: 'number',
|
||||
default: 0,
|
||||
description: 'Number value.',
|
||||
},
|
||||
{
|
||||
displayName: 'Range',
|
||||
name: 'range',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'date',
|
||||
],
|
||||
},
|
||||
},
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Weather or not you want to define a date range.',
|
||||
},
|
||||
{
|
||||
displayName: 'Date',
|
||||
name: 'date',
|
||||
displayOptions: {
|
||||
show: {
|
||||
range: [
|
||||
false,
|
||||
],
|
||||
type: [
|
||||
'date',
|
||||
],
|
||||
},
|
||||
},
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: 'An ISO 8601 format date, with optional time.',
|
||||
},
|
||||
{
|
||||
displayName: 'Date Start',
|
||||
name: 'dateStart',
|
||||
displayOptions: {
|
||||
show: {
|
||||
range: [
|
||||
true,
|
||||
],
|
||||
type: [
|
||||
'date',
|
||||
],
|
||||
},
|
||||
},
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: 'An ISO 8601 format date, with optional time.',
|
||||
},
|
||||
{
|
||||
displayName: 'Date End',
|
||||
name: 'dateEnd',
|
||||
displayOptions: {
|
||||
show: {
|
||||
range: [
|
||||
true,
|
||||
],
|
||||
type: [
|
||||
'date',
|
||||
],
|
||||
},
|
||||
},
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: `
|
||||
An ISO 8601 formatted date, with optional time. Represents the end of a date range.`,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
...blocks('databasePage', 'create'),
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* databasePage:update */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Page ID',
|
||||
name: 'pageId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'databasePage',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'The ID of the databasePage to update.',
|
||||
},
|
||||
{
|
||||
displayName: 'Simple',
|
||||
name: 'simple',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'databasePage',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: true,
|
||||
description: 'When set to true a simplify version of the response will be used else the raw data.',
|
||||
},
|
||||
{
|
||||
displayName: 'Properties',
|
||||
name: 'propertiesUi',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'databasePage',
|
||||
],
|
||||
operation: [
|
||||
'update',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
placeholder: 'Add Property',
|
||||
options: [
|
||||
{
|
||||
name: 'propertyValues',
|
||||
displayName: 'Property',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Key',
|
||||
name: 'key',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getDatabaseIdFromPage',
|
||||
loadOptionsDependsOn: [
|
||||
'pageId',
|
||||
],
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Type',
|
||||
name: 'type',
|
||||
type: 'hidden',
|
||||
default: '={{$parameter["&key"].split("|")[1]}}',
|
||||
},
|
||||
{
|
||||
displayName: 'Title',
|
||||
name: 'title',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'title',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Rich Text',
|
||||
name: 'richText',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'rich_text',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
displayName: 'Text',
|
||||
name: 'textContent',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'rich_text',
|
||||
],
|
||||
richText: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
...text({
|
||||
show: {
|
||||
type: [
|
||||
'rich_text',
|
||||
],
|
||||
richText: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
}),
|
||||
{
|
||||
displayName: 'Phone Number',
|
||||
name: 'phoneValue',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'phone_number',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: `Phone number. No structure is enforced.`,
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'multiSelectValue',
|
||||
type: 'multiOptions',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getDatabaseOptionsFromPage',
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'multi_select',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: [],
|
||||
description: `Name of the options you want to set.
|
||||
Multiples can be defined separated by comma.`,
|
||||
},
|
||||
{
|
||||
displayName: 'Option',
|
||||
name: 'selectValue',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getDatabaseOptionsFromPage',
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'select',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: `Name of the option you want to set.`,
|
||||
},
|
||||
{
|
||||
displayName: 'Email',
|
||||
name: 'emailValue',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'email',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Email address.',
|
||||
},
|
||||
{
|
||||
displayName: 'URL',
|
||||
name: 'urlValue',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'url',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Web address.',
|
||||
},
|
||||
{
|
||||
displayName: 'User IDs',
|
||||
name: 'peopleValue',
|
||||
type: 'multiOptions',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getUsers',
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'people',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: [],
|
||||
description: 'List of users. Multiples can be defined separated by comma.',
|
||||
},
|
||||
{
|
||||
displayName: 'Relation IDs',
|
||||
name: 'relationValue',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'relation',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: [],
|
||||
description: 'List of databases that belong to another database. Multiples can be defined separated by comma.',
|
||||
},
|
||||
{
|
||||
displayName: 'Checked',
|
||||
name: 'checkboxValue',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'checkbox',
|
||||
],
|
||||
},
|
||||
},
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: `
|
||||
Whether or not the checkbox is checked.</br>
|
||||
true represents checked.</br>
|
||||
false represents unchecked.
|
||||
`,
|
||||
},
|
||||
{
|
||||
displayName: 'Number',
|
||||
name: 'numberValue',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'number',
|
||||
],
|
||||
},
|
||||
},
|
||||
type: 'number',
|
||||
default: 0,
|
||||
description: 'Number value.',
|
||||
},
|
||||
{
|
||||
displayName: 'Range',
|
||||
name: 'range',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'date',
|
||||
],
|
||||
},
|
||||
},
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Weather or not you want to define a date range.',
|
||||
},
|
||||
{
|
||||
displayName: 'Date',
|
||||
name: 'date',
|
||||
displayOptions: {
|
||||
show: {
|
||||
range: [
|
||||
false,
|
||||
],
|
||||
type: [
|
||||
'date',
|
||||
],
|
||||
},
|
||||
},
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: 'An ISO 8601 format date, with optional time.',
|
||||
},
|
||||
{
|
||||
displayName: 'Date Start',
|
||||
name: 'dateStart',
|
||||
displayOptions: {
|
||||
show: {
|
||||
range: [
|
||||
true,
|
||||
],
|
||||
type: [
|
||||
'date',
|
||||
],
|
||||
},
|
||||
},
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: 'An ISO 8601 format date, with optional time.',
|
||||
},
|
||||
{
|
||||
displayName: 'Date End',
|
||||
name: 'dateEnd',
|
||||
displayOptions: {
|
||||
show: {
|
||||
range: [
|
||||
true,
|
||||
],
|
||||
type: [
|
||||
'date',
|
||||
],
|
||||
},
|
||||
},
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: `
|
||||
An ISO 8601 formatted date, with optional time. Represents the end of a date range.`,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* databasePage:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Database ID',
|
||||
name: 'databaseId',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getDatabases',
|
||||
},
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'databasePage',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'databasePage',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'If all results should be returned or only up to a given limit.',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'databasePage',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 100,
|
||||
},
|
||||
default: 50,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
{
|
||||
displayName: 'Simple',
|
||||
name: 'simple',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'databasePage',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: true,
|
||||
description: 'When set to true a simplify version of the response will be used else the raw data.',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: [
|
||||
'databasePage',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
placeholder: 'Add Field',
|
||||
options: [
|
||||
{
|
||||
displayName: 'Filters',
|
||||
name: 'filter',
|
||||
placeholder: 'Add Filter',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: false,
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Single Condition',
|
||||
name: 'singleCondition',
|
||||
values: [
|
||||
...filters,
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Multiple Condition',
|
||||
name: 'multipleCondition',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Condition',
|
||||
name: 'condition',
|
||||
placeholder: 'Add Condition',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'OR',
|
||||
name: 'or',
|
||||
values: [
|
||||
...filters,
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'AND',
|
||||
name: 'and',
|
||||
values: [
|
||||
...filters,
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Sort',
|
||||
name: 'sort',
|
||||
placeholder: 'Add Sort',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Sort',
|
||||
name: 'sortValue',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Timestamp',
|
||||
name: 'timestamp',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: `Whether or not to use the record's timestamp to sort the response.`,
|
||||
},
|
||||
{
|
||||
displayName: 'Property Name',
|
||||
name: 'key',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
timestamp: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getFilterProperties',
|
||||
loadOptionsDependsOn: [
|
||||
'datatabaseId',
|
||||
],
|
||||
},
|
||||
default: '',
|
||||
description: 'The name of the property to filter by.',
|
||||
},
|
||||
{
|
||||
displayName: 'Property Name',
|
||||
name: 'key',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Created Time',
|
||||
value: 'created_time',
|
||||
},
|
||||
{
|
||||
name: 'Last Edited Time',
|
||||
value: 'last_edited_time',
|
||||
},
|
||||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
timestamp: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'The name of the property to filter by.',
|
||||
},
|
||||
{
|
||||
displayName: 'Type',
|
||||
name: 'type',
|
||||
type: 'hidden',
|
||||
displayOptions: {
|
||||
show: {
|
||||
timestamp: [
|
||||
true,
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '={{$parameter["&key"].split("|")[1]}}',
|
||||
},
|
||||
{
|
||||
displayName: 'Direction',
|
||||
name: 'direction',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Ascending',
|
||||
value: 'ascending',
|
||||
},
|
||||
{
|
||||
name: 'Descending',
|
||||
value: 'descending',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description: 'The direction to sort.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
] as INodeProperties[];
|
371
packages/nodes-base/nodes/Notion/Filters.ts
Normal file
371
packages/nodes-base/nodes/Notion/Filters.ts
Normal file
|
@ -0,0 +1,371 @@
|
|||
import {
|
||||
getConditions
|
||||
} from './GenericFunctions';
|
||||
|
||||
export const filters = [{
|
||||
displayName: 'Property Name',
|
||||
name: 'key',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getFilterProperties',
|
||||
loadOptionsDependsOn: [
|
||||
'datatabaseId',
|
||||
],
|
||||
},
|
||||
default: '',
|
||||
description: 'The name of the property to filter by.',
|
||||
},
|
||||
{
|
||||
displayName: 'Type',
|
||||
name: 'type',
|
||||
type: 'hidden',
|
||||
default: '={{$parameter["&key"].split("|")[1]}}',
|
||||
},
|
||||
...getConditions(),
|
||||
{
|
||||
displayName: 'Title',
|
||||
name: 'titleValue',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'title',
|
||||
],
|
||||
},
|
||||
hide: {
|
||||
condition: [
|
||||
'is_empty',
|
||||
'is_not_empty',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Text',
|
||||
name: 'richTextValue',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'rich_text',
|
||||
],
|
||||
},
|
||||
hide: {
|
||||
condition: [
|
||||
'is_empty',
|
||||
'is_not_empty',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Phone Number',
|
||||
name: 'phoneNumberValue',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'phone_number',
|
||||
],
|
||||
},
|
||||
hide: {
|
||||
condition: [
|
||||
'is_empty',
|
||||
'is_not_empty',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: `Phone number. No structure is enforced.`,
|
||||
},
|
||||
{
|
||||
displayName: 'Option',
|
||||
name: 'multiSelectValue',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getPropertySelectValues',
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'multi_select',
|
||||
],
|
||||
},
|
||||
hide: {
|
||||
condition: [
|
||||
'is_empty',
|
||||
'is_not_empty',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: [],
|
||||
description: `Name of the options you want to set.
|
||||
Multiples can be defined separated by comma.`,
|
||||
},
|
||||
{
|
||||
displayName: 'Option',
|
||||
name: 'selectValue',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getPropertySelectValues',
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'select',
|
||||
],
|
||||
},
|
||||
hide: {
|
||||
condition: [
|
||||
'is_empty',
|
||||
'is_not_empty',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: `Name of the option you want to set.`,
|
||||
},
|
||||
{
|
||||
displayName: 'Email',
|
||||
name: 'emailValue',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'email',
|
||||
],
|
||||
},
|
||||
hide: {
|
||||
condition: [
|
||||
'is_empty',
|
||||
'is_not_empty',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Email address.',
|
||||
},
|
||||
{
|
||||
displayName: 'URL',
|
||||
name: 'urlValue',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'url',
|
||||
],
|
||||
},
|
||||
hide: {
|
||||
condition: [
|
||||
'is_empty',
|
||||
'is_not_empty',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'Web address.',
|
||||
},
|
||||
{
|
||||
displayName: 'User ID',
|
||||
name: 'peopleValue',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getUsers',
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'people',
|
||||
],
|
||||
},
|
||||
hide: {
|
||||
condition: [
|
||||
'is_empty',
|
||||
'is_not_empty',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'List of users. Multiples can be defined separated by comma.',
|
||||
},
|
||||
{
|
||||
displayName: 'User ID',
|
||||
name: 'createdByValue',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getUsers',
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'created_by',
|
||||
],
|
||||
},
|
||||
hide: {
|
||||
condition: [
|
||||
'is_empty',
|
||||
'is_not_empty',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'List of users. Multiples can be defined separated by comma.',
|
||||
},
|
||||
{
|
||||
displayName: 'User ID',
|
||||
name: 'lastEditedByValue',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getUsers',
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'last_edited_by',
|
||||
],
|
||||
},
|
||||
hide: {
|
||||
condition: [
|
||||
'is_empty',
|
||||
'is_not_empty',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
description: 'List of users. Multiples can be defined separated by comma.',
|
||||
},
|
||||
{
|
||||
displayName: 'Relation ID',
|
||||
name: 'relationValue',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'relation',
|
||||
],
|
||||
},
|
||||
hide: {
|
||||
condition: [
|
||||
'is_empty',
|
||||
'is_not_empty',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Checked',
|
||||
name: 'checkboxValue',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'checkbox',
|
||||
],
|
||||
},
|
||||
},
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: `Whether or not the checkbox is checked.</br>
|
||||
true represents checked.</br>
|
||||
false represents unchecked.`,
|
||||
},
|
||||
{
|
||||
displayName: 'Number',
|
||||
name: 'numberValue',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'number',
|
||||
],
|
||||
},
|
||||
hide: {
|
||||
condition: [
|
||||
'is_empty',
|
||||
'is_not_empty',
|
||||
],
|
||||
},
|
||||
},
|
||||
type: 'number',
|
||||
default: 0,
|
||||
description: 'Number value.',
|
||||
},
|
||||
{
|
||||
displayName: 'Date',
|
||||
name: 'date',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'date',
|
||||
],
|
||||
},
|
||||
hide: {
|
||||
condition: [
|
||||
'is_empty',
|
||||
'is_not_empty',
|
||||
'past_week',
|
||||
'past_month',
|
||||
'past_year',
|
||||
'next_week',
|
||||
'next_month',
|
||||
'next_year',
|
||||
],
|
||||
},
|
||||
},
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: 'An ISO 8601 format date, with optional time.',
|
||||
},
|
||||
{
|
||||
displayName: 'Created Time',
|
||||
name: 'createdTimeValue',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'created_time',
|
||||
],
|
||||
},
|
||||
hide: {
|
||||
condition: [
|
||||
'is_empty',
|
||||
'is_not_empty',
|
||||
'past_week',
|
||||
'past_month',
|
||||
'past_year',
|
||||
'next_week',
|
||||
'next_month',
|
||||
'next_year',
|
||||
],
|
||||
},
|
||||
},
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: 'An ISO 8601 format date, with optional time.',
|
||||
},
|
||||
{
|
||||
displayName: 'Last Edited Time',
|
||||
name: 'lastEditedTime',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
'last_edited_time',
|
||||
],
|
||||
},
|
||||
hide: {
|
||||
condition: [
|
||||
'is_empty',
|
||||
'is_not_empty',
|
||||
'past_week',
|
||||
'past_month',
|
||||
'past_year',
|
||||
'next_week',
|
||||
'next_month',
|
||||
'next_year',
|
||||
],
|
||||
},
|
||||
},
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: 'An ISO 8601 format date, with optional time.',
|
||||
}];
|
542
packages/nodes-base/nodes/Notion/GenericFunctions.ts
Normal file
542
packages/nodes-base/nodes/Notion/GenericFunctions.ts
Normal file
|
@ -0,0 +1,542 @@
|
|||
import {
|
||||
OptionsWithUri,
|
||||
} from 'request';
|
||||
|
||||
import {
|
||||
IExecuteFunctions,
|
||||
IExecuteSingleFunctions,
|
||||
IHookFunctions,
|
||||
ILoadOptionsFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
IDisplayOptions,
|
||||
INodeProperties,
|
||||
IPollFunctions,
|
||||
NodeApiError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
camelCase,
|
||||
capitalCase,
|
||||
} from 'change-case';
|
||||
|
||||
import * as moment from 'moment-timezone';
|
||||
|
||||
export async function notionApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IPollFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||
|
||||
try {
|
||||
let options: OptionsWithUri = {
|
||||
headers: {
|
||||
'Notion-Version': '2021-05-13',
|
||||
},
|
||||
method,
|
||||
qs,
|
||||
body,
|
||||
uri: uri || `https://api.notion.com/v1${resource}`,
|
||||
json: true,
|
||||
};
|
||||
|
||||
options = Object.assign({}, options, option);
|
||||
const credentials = this.getCredentials('notionApi') as IDataObject;
|
||||
options!.headers!['Authorization'] = `Bearer ${credentials.apiKey}`;
|
||||
return this.helpers.request!(options);
|
||||
|
||||
} catch (error) {
|
||||
throw new NodeApiError(this.getNode(), error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function notionApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions | IPollFunctions, propertyName: string, method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
|
||||
|
||||
const returnData: IDataObject[] = [];
|
||||
|
||||
let responseData;
|
||||
|
||||
do {
|
||||
responseData = await notionApiRequest.call(this, method, endpoint, body, query);
|
||||
const { next_cursor } = responseData;
|
||||
query['start_cursor'] = next_cursor;
|
||||
body['start_cursor'] = next_cursor;
|
||||
returnData.push.apply(returnData, responseData[propertyName]);
|
||||
if (query.limit && query.limit <= returnData.length) {
|
||||
return returnData;
|
||||
}
|
||||
} while (
|
||||
responseData.has_more !== false
|
||||
);
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
export function getBlockTypes() {
|
||||
return [
|
||||
{
|
||||
name: 'Paragraph',
|
||||
value: 'paragraph',
|
||||
},
|
||||
{
|
||||
name: 'Heading 1',
|
||||
value: 'heading_1',
|
||||
},
|
||||
{
|
||||
name: 'Heading 2',
|
||||
value: 'heading_2',
|
||||
},
|
||||
{
|
||||
name: 'Heading 3',
|
||||
value: 'heading_3',
|
||||
},
|
||||
{
|
||||
name: 'Toggle',
|
||||
value: 'toggle',
|
||||
},
|
||||
{
|
||||
name: 'To-Do',
|
||||
value: 'to_do',
|
||||
},
|
||||
// {
|
||||
// name: 'Child Page',
|
||||
// value: 'child_page',
|
||||
// },
|
||||
{
|
||||
name: 'Bulleted List Item',
|
||||
value: 'bulleted_list_item',
|
||||
},
|
||||
{
|
||||
name: 'Numbered List Item',
|
||||
value: 'numbered_list_item',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function textContent(content: string) {
|
||||
return {
|
||||
text: {
|
||||
content,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function formatTitle(content: string) {
|
||||
return {
|
||||
title: [
|
||||
textContent(content),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
export function formatText(content: string) {
|
||||
return {
|
||||
text: [
|
||||
textContent(content),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function getLink(text: { textLink: string, isLink: boolean }) {
|
||||
if (text.isLink === true) {
|
||||
return {
|
||||
link: {
|
||||
url: text.textLink,
|
||||
},
|
||||
};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
function getTexts(texts: [{ textType: string, text: string, isLink: boolean, range: boolean, textLink: string, mentionType: string, dateStart: string, dateEnd: string, date: string, annotationUi: IDataObject, expression: string }]) {
|
||||
const results = [];
|
||||
for (const text of texts) {
|
||||
if (text.textType === 'text') {
|
||||
results.push({
|
||||
type: 'text',
|
||||
text: {
|
||||
content: text.text,
|
||||
...getLink(text),
|
||||
},
|
||||
annotations: text.annotationUi,
|
||||
});
|
||||
} else if (text.textType === 'mention') {
|
||||
if (text.mentionType === 'date') {
|
||||
results.push({
|
||||
type: 'mention',
|
||||
mention: {
|
||||
type: text.mentionType,
|
||||
[text.mentionType]: (text.range === true)
|
||||
? { start: text.dateStart, end: text.dateEnd }
|
||||
: { start: text.date, end: null },
|
||||
},
|
||||
annotations: text.annotationUi,
|
||||
});
|
||||
} else {
|
||||
//@ts-ignore
|
||||
results.push({
|
||||
type: 'mention',
|
||||
mention: {
|
||||
type: text.mentionType,
|
||||
//@ts-ignore
|
||||
[text.mentionType]: { id: text[text.mentionType] as string },
|
||||
},
|
||||
annotations: text.annotationUi,
|
||||
});
|
||||
}
|
||||
} else if (text.textType === 'equation') {
|
||||
results.push({
|
||||
type: 'equation',
|
||||
equation: {
|
||||
expression: text.expression,
|
||||
},
|
||||
annotations: text.annotationUi,
|
||||
});
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
export function formatBlocks(blocks: IDataObject[]) {
|
||||
const results = [];
|
||||
for (const block of blocks) {
|
||||
results.push({
|
||||
object: 'block',
|
||||
type: block.type,
|
||||
[block.type as string]: {
|
||||
...(block.type === 'to_do') ? { checked: block.checked } : { checked: false },
|
||||
//@ts-expect-error
|
||||
// tslint:disable-next-line: no-any
|
||||
text: (block.richText === false) ? formatText(block.textContent).text : getTexts(block.text.text as any || []),
|
||||
},
|
||||
});
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
// tslint:disable-next-line: no-any
|
||||
function getPropertyKeyValue(value: any, type: string, timezone: string) {
|
||||
let result = {};
|
||||
switch (type) {
|
||||
case 'rich_text':
|
||||
if (value.richText === false) {
|
||||
result = { rich_text: [{ text: { content: value.textContent } }] };
|
||||
} else {
|
||||
result = { rich_text: getTexts(value.text.text) };
|
||||
}
|
||||
break;
|
||||
case 'title':
|
||||
result = { title: [{ text: { content: value.title } }] };
|
||||
break;
|
||||
case 'number':
|
||||
result = { type: 'number', number: value.numberValue };
|
||||
break;
|
||||
case 'url':
|
||||
result = { type: 'url', url: value.urlValue };
|
||||
break;
|
||||
case 'checkbox':
|
||||
result = { type: 'checkbox', checkbox: value.checkboxValue };
|
||||
break;
|
||||
case 'relation':
|
||||
result = {
|
||||
// tslint:disable-next-line: no-any
|
||||
type: 'relation', relation: (value.relationValue).reduce((acc: [], cur: any) => {
|
||||
return acc.concat(cur.split(',').map((relation: string) => ({ id: relation })));
|
||||
}, []),
|
||||
};
|
||||
break;
|
||||
case 'multi_select':
|
||||
result = {
|
||||
// tslint:disable-next-line: no-any
|
||||
type: 'multi_select', multi_select: value.multiSelectValue.filter((id: any) => id !== null).map((option: string) => ({ id: option })),
|
||||
};
|
||||
break;
|
||||
case 'email':
|
||||
result = {
|
||||
type: 'email', email: value.emailValue,
|
||||
};
|
||||
break;
|
||||
case 'people':
|
||||
result = {
|
||||
type: 'people', people: value.peopleValue.map((option: string) => ({ id: option })),
|
||||
};
|
||||
break;
|
||||
case 'phone_number':
|
||||
result = {
|
||||
type: 'phone_number', phone_number: value.phoneValue,
|
||||
};
|
||||
break;
|
||||
case 'select':
|
||||
result = {
|
||||
type: 'select', select: { id: value.selectValue },
|
||||
};
|
||||
break;
|
||||
case 'date':
|
||||
if (value.range === true) {
|
||||
result = {
|
||||
type: 'date', date: { start: moment.tz(value.dateStart, timezone).utc().format(), end: moment.tz(value.dateEnd, timezone).utc().format() },
|
||||
};
|
||||
} else {
|
||||
result = {
|
||||
type: 'date', date: { start: moment.tz(value.date, timezone).utc().format(), end: null },
|
||||
};
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function getNameAndType(key: string) {
|
||||
const [name, type] = key.split('|');
|
||||
return {
|
||||
name,
|
||||
type,
|
||||
};
|
||||
}
|
||||
|
||||
export function mapProperties(properties: IDataObject[], timezone: string) {
|
||||
return properties.reduce((obj, value) => Object.assign(obj, {
|
||||
[`${(value.key as string).split('|')[0]}`]: getPropertyKeyValue(value, (value.key as string).split('|')[1], timezone),
|
||||
}), {});
|
||||
}
|
||||
|
||||
export function mapSorting(data: [{ key: string, type: string, direction: string, timestamp: boolean }]) {
|
||||
return data.map((sort) => {
|
||||
return {
|
||||
direction: sort.direction,
|
||||
[(sort.timestamp) ? 'timestamp' : 'property']: sort.key.split('|')[0],
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function mapFilters(filters: IDataObject[], timezone: string) {
|
||||
// tslint:disable-next-line: no-any
|
||||
return filters.reduce((obj, value: { [key: string]: any }) => {
|
||||
let key = getNameAndType(value.key).type;
|
||||
let valuePropertyName = value[`${camelCase(key)}Value`];
|
||||
if (['is_empty', 'is_not_empty'].includes(value.condition as string)) {
|
||||
valuePropertyName = true;
|
||||
} else if (['past_week', 'past_month', 'past_year', 'next_week', 'next_month', 'next_year'].includes(value.condition as string)) {
|
||||
valuePropertyName = {};
|
||||
}
|
||||
if (key === 'rich_text') {
|
||||
key = 'text';
|
||||
} else if (key === 'phone_number') {
|
||||
key = 'phone';
|
||||
} else if (key === 'date') {
|
||||
valuePropertyName = (valuePropertyName !== undefined && !Object.keys(valuePropertyName).length) ? {} : moment.tz(value.date, timezone).utc().format();
|
||||
}
|
||||
return Object.assign(obj, {
|
||||
['property']: getNameAndType(value.key).name,
|
||||
[key]: { [`${value.condition}`]: valuePropertyName },
|
||||
});
|
||||
}, {});
|
||||
}
|
||||
|
||||
// tslint:disable-next-line: no-any
|
||||
export function simplifyProperties(properties: any) {
|
||||
// tslint:disable-next-line: no-any
|
||||
const results: any = {};
|
||||
for (const key of Object.keys(properties)) {
|
||||
const type = (properties[key] as IDataObject).type as string;
|
||||
if (['text'].includes(properties[key].type)) {
|
||||
const texts = properties[key].text.map((e: { plain_text: string }) => e.plain_text || {}).join('');
|
||||
results[`${key}`] = texts;
|
||||
} else if (['url', 'created_time', 'checkbox', 'number', 'last_edited_time', 'email', 'phone_number', 'date'].includes(properties[key].type)) {
|
||||
// tslint:disable-next-line: no-any
|
||||
results[`${key}`] = properties[key][type] as any;
|
||||
} else if (['title'].includes(properties[key].type)) {
|
||||
if (Array.isArray(properties[key][type]) && properties[key][type].length !== 0) {
|
||||
results[`${key}`] = properties[key][type][0].plain_text;
|
||||
} else {
|
||||
results[`${key}`] = '';
|
||||
}
|
||||
} else if (['created_by', 'last_edited_by', 'select'].includes(properties[key].type)) {
|
||||
results[`${key}`] = properties[key][type].name;
|
||||
} else if (['people'].includes(properties[key].type)) {
|
||||
if (Array.isArray(properties[key][type])) {
|
||||
// tslint:disable-next-line: no-any
|
||||
results[`${key}`] = properties[key][type].map((person: any) => person.person.email || {});
|
||||
} else {
|
||||
results[`${key}`] = properties[key][type];
|
||||
}
|
||||
} else if (['multi_select'].includes(properties[key].type)) {
|
||||
if (Array.isArray(properties[key][type])) {
|
||||
results[`${key}`] = properties[key][type].map((e: IDataObject) => e.name || {});
|
||||
} else {
|
||||
results[`${key}`] = properties[key][type].options.map((e: IDataObject) => e.name || {});
|
||||
}
|
||||
} else if (['relation'].includes(properties[key].type)) {
|
||||
if (Array.isArray(properties[key][type])) {
|
||||
results[`${key}`] = properties[key][type].map((e: IDataObject) => e.id || {});
|
||||
} else {
|
||||
results[`${key}`] = properties[key][type].database_id;
|
||||
}
|
||||
} else if (['formula'].includes(properties[key].type)) {
|
||||
results[`${key}`] = properties[key][type][properties[key][type].type];
|
||||
|
||||
} else if (['rollup'].includes(properties[key].type)) {
|
||||
//TODO figure how to resolve rollup field type
|
||||
// results[`${key}`] = properties[key][type][properties[key][type].type];
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
// tslint:disable-next-line: no-any
|
||||
export function simplifyObjects(objects: any) {
|
||||
if (!Array.isArray(objects)) {
|
||||
objects = [objects];
|
||||
}
|
||||
const results: IDataObject[] = [];
|
||||
for (const { object, id, properties, parent, title } of objects) {
|
||||
if (object === 'page' && (parent.type === 'page_id' || parent.type === 'workspace')) {
|
||||
results.push({
|
||||
id,
|
||||
title: properties.title.title[0].plain_text,
|
||||
});
|
||||
} else if (object === 'page' && parent.type === 'database_id') {
|
||||
results.push({
|
||||
id,
|
||||
...simplifyProperties(properties),
|
||||
});
|
||||
} else if (object === 'database') {
|
||||
results.push({
|
||||
id,
|
||||
title: title[0].plain_text,
|
||||
});
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
export function getFormattedChildren(children: IDataObject[]) {
|
||||
const results: IDataObject[] = [];
|
||||
for (const child of children) {
|
||||
const type = child.type;
|
||||
results.push({ [`${type}`]: child, object: 'block', type });
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
export function getConditions() {
|
||||
|
||||
const elements: INodeProperties[] = [];
|
||||
|
||||
const types: { [key: string]: string } = {
|
||||
title: 'rich_text',
|
||||
rich_text: 'rich_text',
|
||||
number: 'number',
|
||||
checkbox: 'checkbox',
|
||||
select: 'select',
|
||||
multi_select: 'multi_select',
|
||||
date: 'date',
|
||||
people: 'people',
|
||||
files: 'files',
|
||||
url: 'rich_text',
|
||||
email: 'rich_text',
|
||||
phone_number: 'rich_text',
|
||||
relation: 'relation',
|
||||
//formula: 'formula',
|
||||
created_by: 'people',
|
||||
created_time: 'date',
|
||||
last_edited_by: 'people',
|
||||
last_edited_time: 'date',
|
||||
};
|
||||
|
||||
const typeConditions: { [key: string]: string[] } = {
|
||||
rich_text: [
|
||||
'equals',
|
||||
'does_not_equal',
|
||||
'contains',
|
||||
'does_not_contain',
|
||||
'starts_with',
|
||||
'ends_with',
|
||||
'is_empty',
|
||||
'is_not_empty',
|
||||
],
|
||||
number: [
|
||||
'equals',
|
||||
'does_not_equal',
|
||||
'grater_than',
|
||||
'less_than',
|
||||
'greater_than_or_equal_to',
|
||||
'less_than_or_equal_to',
|
||||
'is_empty',
|
||||
'is_not_empty',
|
||||
],
|
||||
checkbox: [
|
||||
'equals',
|
||||
'does_not_equal',
|
||||
],
|
||||
select: [
|
||||
'equals',
|
||||
'does_not_equal',
|
||||
'is_empty',
|
||||
'is_not_empty',
|
||||
],
|
||||
multi_select: [
|
||||
'contains',
|
||||
'does_not_equal',
|
||||
'is_empty',
|
||||
'is_not_empty',
|
||||
],
|
||||
date: [
|
||||
'equals',
|
||||
'before',
|
||||
'after',
|
||||
'on_or_before',
|
||||
'is_empty',
|
||||
'is_not_empty',
|
||||
'on_or_after',
|
||||
'past_week',
|
||||
'past_month',
|
||||
'past_year',
|
||||
'next_week',
|
||||
'next_month',
|
||||
'next_year',
|
||||
],
|
||||
people: [
|
||||
'contains',
|
||||
'does_not_contain',
|
||||
'is_empty',
|
||||
'is_not_empty',
|
||||
],
|
||||
files: [
|
||||
'is_empty',
|
||||
'is_not_empty',
|
||||
],
|
||||
relation: [
|
||||
'contains',
|
||||
'does_not_contain',
|
||||
'is_empty',
|
||||
'is_not_empty',
|
||||
],
|
||||
formula: [
|
||||
'contains',
|
||||
'does_not_contain',
|
||||
'is_empty',
|
||||
'is_not_empty',
|
||||
],
|
||||
};
|
||||
|
||||
for (const type of Object.keys(types)) {
|
||||
elements.push(
|
||||
{
|
||||
displayName: 'Condition',
|
||||
name: 'condition',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
type: [
|
||||
type,
|
||||
],
|
||||
},
|
||||
} as IDisplayOptions,
|
||||
options: (typeConditions[types[type]] as string[]).map((type: string) => ({ name: capitalCase(type), value: type })),
|
||||
default: '',
|
||||
description: 'The value of the property to filter by.',
|
||||
} as INodeProperties,
|
||||
);
|
||||
}
|
||||
return elements;
|
||||
}
|
514
packages/nodes-base/nodes/Notion/Notion.node.ts
Normal file
514
packages/nodes-base/nodes/Notion/Notion.node.ts
Normal file
|
@ -0,0 +1,514 @@
|
|||
import {
|
||||
IExecuteFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
ILoadOptionsFunctions,
|
||||
INodeExecutionData,
|
||||
INodePropertyOptions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
formatBlocks,
|
||||
formatTitle,
|
||||
getBlockTypes,
|
||||
mapFilters,
|
||||
mapProperties,
|
||||
mapSorting,
|
||||
notionApiRequest,
|
||||
notionApiRequestAllItems,
|
||||
simplifyObjects,
|
||||
} from './GenericFunctions';
|
||||
|
||||
import {
|
||||
databaseFields,
|
||||
databaseOperations,
|
||||
} from './DatabaseDescription';
|
||||
|
||||
import {
|
||||
userFields,
|
||||
userOperations,
|
||||
} from './UserDescription';
|
||||
|
||||
import {
|
||||
pageFields,
|
||||
pageOperations,
|
||||
} from './PageDescription';
|
||||
|
||||
import {
|
||||
blockFields,
|
||||
blockOperations,
|
||||
} from './BlockDescription';
|
||||
|
||||
import {
|
||||
databasePageFields,
|
||||
databasePageOperations,
|
||||
} from './DatabasePageDescription';
|
||||
|
||||
export class Notion implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Notion (Beta)',
|
||||
name: 'notion',
|
||||
icon: 'file:notion.svg',
|
||||
group: ['output'],
|
||||
version: 1,
|
||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||
description: 'Consume Notion API (Beta)',
|
||||
defaults: {
|
||||
name: 'Notion',
|
||||
color: '#000000',
|
||||
},
|
||||
inputs: ['main'],
|
||||
outputs: ['main'],
|
||||
credentials: [
|
||||
{
|
||||
name: 'notionApi',
|
||||
required: true,
|
||||
// displayOptions: {
|
||||
// show: {
|
||||
// authentication: [
|
||||
// 'apiKey',
|
||||
// ],
|
||||
// },
|
||||
// },
|
||||
},
|
||||
// {
|
||||
// name: 'notionOAuth2Api',
|
||||
// required: true,
|
||||
// displayOptions: {
|
||||
// show: {
|
||||
// authentication: [
|
||||
// 'oAuth2',
|
||||
// ],
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
],
|
||||
properties: [
|
||||
// {
|
||||
// displayName: 'Authentication',
|
||||
// name: 'authentication',
|
||||
// type: 'options',
|
||||
// options: [
|
||||
// {
|
||||
// name: 'API Key',
|
||||
// value: 'apiKey',
|
||||
// },
|
||||
// {
|
||||
// name: 'OAuth2',
|
||||
// value: 'oAuth2',
|
||||
// },
|
||||
// ],
|
||||
// default: 'apiKey',
|
||||
// description: 'The resource to operate on.',
|
||||
// },
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Block',
|
||||
value: 'block',
|
||||
},
|
||||
{
|
||||
name: 'Database',
|
||||
value: 'database',
|
||||
},
|
||||
{
|
||||
name: 'Database Page',
|
||||
value: 'databasePage',
|
||||
},
|
||||
{
|
||||
name: 'Page',
|
||||
value: 'page',
|
||||
},
|
||||
{
|
||||
name: 'User',
|
||||
value: 'user',
|
||||
},
|
||||
],
|
||||
default: 'page',
|
||||
description: 'Resource to consume.',
|
||||
},
|
||||
...blockOperations,
|
||||
...blockFields,
|
||||
...databaseOperations,
|
||||
...databaseFields,
|
||||
...databasePageOperations,
|
||||
...databasePageFields,
|
||||
...pageOperations,
|
||||
...pageFields,
|
||||
...userOperations,
|
||||
...userFields,
|
||||
],
|
||||
};
|
||||
|
||||
methods = {
|
||||
loadOptions: {
|
||||
async getDatabases(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
const body: IDataObject = {
|
||||
page_size: 100,
|
||||
filter: { property: 'object', value: 'database' },
|
||||
};
|
||||
const databases = await notionApiRequestAllItems.call(this, 'results', 'POST', `/search`, body);
|
||||
for (const database of databases) {
|
||||
returnData.push({
|
||||
name: database.title[0].plain_text,
|
||||
value: database.id,
|
||||
});
|
||||
}
|
||||
returnData.sort((a, b) => {
|
||||
if (a.name.toLocaleLowerCase() < b.name.toLocaleLowerCase()) { return -1; }
|
||||
if (a.name.toLocaleLowerCase() > b.name.toLocaleLowerCase()) { return 1; }
|
||||
return 0;
|
||||
});
|
||||
return returnData;
|
||||
},
|
||||
async getDatabaseProperties(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
const databaseId = this.getCurrentNodeParameter('databaseId') as string;
|
||||
const { properties } = await notionApiRequest.call(this, 'GET', `/databases/${databaseId}`);
|
||||
for (const key of Object.keys(properties)) {
|
||||
//remove parameters that cannot be set from the API.
|
||||
if (!['created_time', 'last_edited_time', 'created_by', 'last_edited_by', 'formula', 'files'].includes(properties[key].type)) {
|
||||
returnData.push({
|
||||
name: `${key} - (${properties[key].type})`,
|
||||
value: `${key}|${properties[key].type}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
returnData.sort((a, b) => {
|
||||
if (a.name.toLocaleLowerCase() < b.name.toLocaleLowerCase()) { return -1; }
|
||||
if (a.name.toLocaleLowerCase() > b.name.toLocaleLowerCase()) { return 1; }
|
||||
return 0;
|
||||
});
|
||||
return returnData;
|
||||
},
|
||||
async getFilterProperties(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
const databaseId = this.getCurrentNodeParameter('databaseId') as string;
|
||||
const { properties } = await notionApiRequest.call(this, 'GET', `/databases/${databaseId}`);
|
||||
for (const key of Object.keys(properties)) {
|
||||
returnData.push({
|
||||
name: `${key} - (${properties[key].type})`,
|
||||
value: `${key}|${properties[key].type}`,
|
||||
});
|
||||
}
|
||||
returnData.sort((a, b) => {
|
||||
if (a.name.toLocaleLowerCase() < b.name.toLocaleLowerCase()) { return -1; }
|
||||
if (a.name.toLocaleLowerCase() > b.name.toLocaleLowerCase()) { return 1; }
|
||||
return 0;
|
||||
});
|
||||
return returnData;
|
||||
},
|
||||
async getBlockTypes(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
return getBlockTypes();
|
||||
},
|
||||
async getPropertySelectValues(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const [name, type] = (this.getCurrentNodeParameter('&key') as string).split('|');
|
||||
const databaseId = this.getCurrentNodeParameter('databaseId') as string;
|
||||
const { properties } = await notionApiRequest.call(this, 'GET', `/databases/${databaseId}`);
|
||||
return (properties[name][type].options).map((option: IDataObject) => ({ name: option.name, value: option.id }));
|
||||
},
|
||||
async getUsers(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
const users = await notionApiRequestAllItems.call(this, 'results', 'GET', '/users');
|
||||
for (const user of users) {
|
||||
returnData.push({
|
||||
name: user.name,
|
||||
value: user.id,
|
||||
});
|
||||
}
|
||||
return returnData;
|
||||
},
|
||||
async getDatabaseIdFromPage(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
const pageId = this.getCurrentNodeParameter('pageId') as string;
|
||||
const { parent: { database_id: databaseId } } = await notionApiRequest.call(this, 'GET', `/pages/${pageId}`);
|
||||
const { properties } = await notionApiRequest.call(this, 'GET', `/databases/${databaseId}`);
|
||||
for (const key of Object.keys(properties)) {
|
||||
//remove parameters that cannot be set from the API.
|
||||
if (!['created_time', 'last_edited_time', 'created_by', 'last_edited_by', 'formula', 'files'].includes(properties[key].type)) {
|
||||
returnData.push({
|
||||
name: `${key} - (${properties[key].type})`,
|
||||
value: `${key}|${properties[key].type}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
returnData.sort((a, b) => {
|
||||
if (a.name.toLocaleLowerCase() < b.name.toLocaleLowerCase()) { return -1; }
|
||||
if (a.name.toLocaleLowerCase() > b.name.toLocaleLowerCase()) { return 1; }
|
||||
return 0;
|
||||
});
|
||||
return returnData;
|
||||
},
|
||||
|
||||
async getDatabaseOptionsFromPage(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const pageId = this.getCurrentNodeParameter('pageId') as string;
|
||||
const [name, type] = (this.getCurrentNodeParameter('&key') as string).split('|');
|
||||
const { parent: { database_id: databaseId } } = await notionApiRequest.call(this, 'GET', `/pages/${pageId}`);
|
||||
const { properties } = await notionApiRequest.call(this, 'GET', `/databases/${databaseId}`);
|
||||
return (properties[name][type].options).map((option: IDataObject) => ({ name: option.name, value: option.id }));
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
const returnData: IDataObject[] = [];
|
||||
const length = items.length as unknown as number;
|
||||
let responseData;
|
||||
const qs: IDataObject = {};
|
||||
const timezone = this.getTimezone();
|
||||
|
||||
const resource = this.getNodeParameter('resource', 0) as string;
|
||||
const operation = this.getNodeParameter('operation', 0) as string;
|
||||
|
||||
if (resource === 'block') {
|
||||
|
||||
if (operation === 'append') {
|
||||
for (let i = 0; i < length; i++) {
|
||||
const blockId = this.getNodeParameter('blockId', i) as string;
|
||||
const body: IDataObject = {
|
||||
children: formatBlocks(this.getNodeParameter('blockUi.blockValues', i, []) as IDataObject[]),
|
||||
};
|
||||
const block = await notionApiRequest.call(this, 'PATCH', `/blocks/${blockId}/children`, body);
|
||||
returnData.push(block);
|
||||
}
|
||||
}
|
||||
|
||||
if (operation === 'getAll') {
|
||||
for (let i = 0; i < length; i++) {
|
||||
const blockId = this.getNodeParameter('blockId', i) as string;
|
||||
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
if (returnAll) {
|
||||
responseData = await notionApiRequestAllItems.call(this, 'results', 'GET', `/blocks/${blockId}/children`, {});
|
||||
} else {
|
||||
qs.page_size = this.getNodeParameter('limit', i) as number;
|
||||
responseData = await notionApiRequest.call(this, 'GET', `/blocks/${blockId}/children`, {});
|
||||
responseData = responseData.results;
|
||||
}
|
||||
returnData.push.apply(returnData, responseData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (resource === 'database') {
|
||||
|
||||
if (operation === 'get') {
|
||||
for (let i = 0; i < length; i++) {
|
||||
const databaseId = this.getNodeParameter('databaseId', i) as string;
|
||||
responseData = await notionApiRequest.call(this, 'GET', `/databases/${databaseId}`);
|
||||
returnData.push(responseData);
|
||||
}
|
||||
}
|
||||
|
||||
if (operation === 'getAll') {
|
||||
for (let i = 0; i < length; i++) {
|
||||
const body: IDataObject = {
|
||||
page_size: 100,
|
||||
filter: { property: 'object', value: 'database' },
|
||||
};
|
||||
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
if (returnAll) {
|
||||
responseData = await notionApiRequestAllItems.call(this, 'results', 'POST', `/search`, body);
|
||||
} else {
|
||||
body['page_size'] = this.getNodeParameter('limit', i) as number;
|
||||
responseData = await notionApiRequest.call(this, 'POST', `/search`, body);
|
||||
responseData = responseData.results;
|
||||
}
|
||||
returnData.push.apply(returnData, responseData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (resource === 'databasePage') {
|
||||
|
||||
if (operation === 'create') {
|
||||
for (let i = 0; i < length; i++) {
|
||||
const simple = this.getNodeParameter('simple', i) as boolean;
|
||||
// tslint:disable-next-line: no-any
|
||||
const body: { [key: string]: any } = {
|
||||
parent: {},
|
||||
properties: {},
|
||||
};
|
||||
body.parent['database_id'] = this.getNodeParameter('databaseId', i) as string;
|
||||
const properties = this.getNodeParameter('propertiesUi.propertyValues', i, []) as IDataObject[];
|
||||
if (properties.length !== 0) {
|
||||
body.properties = mapProperties(properties, timezone) as IDataObject;
|
||||
}
|
||||
body.children = formatBlocks(this.getNodeParameter('blockUi.blockValues', i, []) as IDataObject[]);
|
||||
responseData = await notionApiRequest.call(this, 'POST', '/pages', body);
|
||||
if (simple === true) {
|
||||
responseData = simplifyObjects(responseData);
|
||||
}
|
||||
returnData.push.apply(returnData, Array.isArray(responseData) ? responseData : [responseData]);
|
||||
}
|
||||
}
|
||||
|
||||
if (operation === 'getAll') {
|
||||
for (let i = 0; i < length; i++) {
|
||||
const simple = this.getNodeParameter('simple', 0) as boolean;
|
||||
const databaseId = this.getNodeParameter('databaseId', i) as string;
|
||||
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
const filters = this.getNodeParameter('options.filter', i, {}) as IDataObject;
|
||||
const sort = this.getNodeParameter('options.sort.sortValue', i, []) as IDataObject[];
|
||||
const body: IDataObject = {
|
||||
filter: {},
|
||||
};
|
||||
if (filters.singleCondition) {
|
||||
body['filter'] = mapFilters([filters.singleCondition] as IDataObject[], timezone);
|
||||
}
|
||||
if (filters.multipleCondition) {
|
||||
const { or, and } = (filters.multipleCondition as IDataObject).condition as IDataObject;
|
||||
if (Array.isArray(or) && or.length !== 0) {
|
||||
Object.assign(body.filter, { or: (or as IDataObject[]).map((data) => mapFilters([data], timezone)) });
|
||||
}
|
||||
if (Array.isArray(and) && and.length !== 0) {
|
||||
Object.assign(body.filter, { and: (and as IDataObject[]).map((data) => mapFilters([data], timezone)) });
|
||||
}
|
||||
}
|
||||
if (!Object.keys(body.filter as IDataObject).length) {
|
||||
delete body.filter;
|
||||
}
|
||||
if (sort) {
|
||||
//@ts-expect-error
|
||||
body['sorts'] = mapSorting(sort);
|
||||
}
|
||||
if (returnAll) {
|
||||
responseData = await notionApiRequestAllItems.call(this, 'results', 'POST', `/databases/${databaseId}/query`, body, {});
|
||||
} else {
|
||||
body.page_size = this.getNodeParameter('limit', i) as number;
|
||||
responseData = await notionApiRequest.call(this, 'POST', `/databases/${databaseId}/query`, body, qs);
|
||||
responseData = responseData.results;
|
||||
}
|
||||
if (simple === true) {
|
||||
responseData = simplifyObjects(responseData);
|
||||
}
|
||||
returnData.push.apply(returnData, responseData);
|
||||
}
|
||||
}
|
||||
|
||||
if (operation === 'update') {
|
||||
for (let i = 0; i < length; i++) {
|
||||
const pageId = this.getNodeParameter('pageId', i) as string;
|
||||
const simple = this.getNodeParameter('simple', i) as boolean;
|
||||
const properties = this.getNodeParameter('propertiesUi.propertyValues', i, []) as IDataObject[];
|
||||
// tslint:disable-next-line: no-any
|
||||
const body: { [key: string]: any } = {
|
||||
properties: {},
|
||||
};
|
||||
if (properties.length !== 0) {
|
||||
body.properties = mapProperties(properties, timezone) as IDataObject;
|
||||
}
|
||||
responseData = await notionApiRequest.call(this, 'PATCH', `/pages/${pageId}`, body);
|
||||
if (simple === true) {
|
||||
responseData = simplifyObjects(responseData);
|
||||
}
|
||||
returnData.push.apply(returnData, Array.isArray(responseData) ? responseData : [responseData]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (resource === 'user') {
|
||||
|
||||
if (operation === 'get') {
|
||||
for (let i = 0; i < length; i++) {
|
||||
const userId = this.getNodeParameter('userId', i) as string;
|
||||
responseData = await notionApiRequest.call(this, 'GET', `/users/${userId}`);
|
||||
returnData.push(responseData);
|
||||
}
|
||||
}
|
||||
if (operation === 'getAll') {
|
||||
for (let i = 0; i < length; i++) {
|
||||
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
if (returnAll) {
|
||||
responseData = await notionApiRequestAllItems.call(this, 'results', 'GET', '/users');
|
||||
} else {
|
||||
qs.limit = this.getNodeParameter('limit', i) as number;
|
||||
responseData = await notionApiRequestAllItems.call(this, 'results', 'GET', '/users');
|
||||
responseData = responseData.splice(0, qs.limit);
|
||||
}
|
||||
returnData.push.apply(returnData, responseData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (resource === 'page') {
|
||||
|
||||
if (operation === 'create') {
|
||||
for (let i = 0; i < length; i++) {
|
||||
const simple = this.getNodeParameter('simple', i) as boolean;
|
||||
// tslint:disable-next-line: no-any
|
||||
const body: { [key: string]: any } = {
|
||||
parent: {},
|
||||
properties: {},
|
||||
};
|
||||
body.parent['page_id'] = this.getNodeParameter('pageId', i) as string;
|
||||
body.properties = formatTitle(this.getNodeParameter('title', i) as string);
|
||||
body.children = formatBlocks(this.getNodeParameter('blockUi.blockValues', i, []) as IDataObject[]);
|
||||
responseData = await notionApiRequest.call(this, 'POST', '/pages', body);
|
||||
if (simple === true) {
|
||||
responseData = simplifyObjects(responseData);
|
||||
}
|
||||
returnData.push.apply(returnData, Array.isArray(responseData) ? responseData : [responseData]);
|
||||
}
|
||||
}
|
||||
|
||||
if (operation === 'get') {
|
||||
for (let i = 0; i < length; i++) {
|
||||
const pageId = this.getNodeParameter('pageId', i) as string;
|
||||
const simple = this.getNodeParameter('simple', i) as boolean;
|
||||
responseData = await notionApiRequest.call(this, 'GET', `/pages/${pageId}`);
|
||||
if (simple === true) {
|
||||
responseData = simplifyObjects(responseData);
|
||||
}
|
||||
returnData.push.apply(returnData, Array.isArray(responseData) ? responseData : [responseData]);
|
||||
}
|
||||
}
|
||||
|
||||
if (operation === 'search') {
|
||||
for (let i = 0; i < length; i++) {
|
||||
const text = this.getNodeParameter('text', i) as string;
|
||||
const options = this.getNodeParameter('options', i) as IDataObject;
|
||||
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
const simple = this.getNodeParameter('simple', i) as boolean;
|
||||
const body: IDataObject = {};
|
||||
|
||||
if (text) {
|
||||
body['query'] = text;
|
||||
}
|
||||
|
||||
if (options.filter) {
|
||||
const filter = (options.filter as IDataObject || {}).filters as IDataObject[] || [];
|
||||
body['filter'] = filter;
|
||||
}
|
||||
|
||||
if (options.sort) {
|
||||
const sort = (options.sort as IDataObject || {}).sortValue as IDataObject || {};
|
||||
body['sort'] = sort;
|
||||
}
|
||||
if (returnAll) {
|
||||
responseData = await notionApiRequestAllItems.call(this, 'results', 'POST', '/search', body);
|
||||
} else {
|
||||
qs.limit = this.getNodeParameter('limit', i) as number;
|
||||
responseData = await notionApiRequestAllItems.call(this, 'results', 'POST', '/search', body);
|
||||
responseData = responseData.splice(0, qs.limit);
|
||||
}
|
||||
|
||||
if (simple === true) {
|
||||
responseData = simplifyObjects(responseData);
|
||||
}
|
||||
|
||||
returnData.push.apply(returnData, responseData);
|
||||
}
|
||||
}
|
||||
}
|
||||
return [this.helpers.returnJsonArray(returnData)];
|
||||
}
|
||||
}
|
188
packages/nodes-base/nodes/Notion/NotionTrigger.node.ts
Normal file
188
packages/nodes-base/nodes/Notion/NotionTrigger.node.ts
Normal file
|
@ -0,0 +1,188 @@
|
|||
import {
|
||||
IPollFunctions,
|
||||
} from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
ILoadOptionsFunctions,
|
||||
INodeExecutionData,
|
||||
INodePropertyOptions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
notionApiRequest,
|
||||
simplifyObjects,
|
||||
} from './GenericFunctions';
|
||||
|
||||
import * as moment from 'moment';
|
||||
|
||||
export class NotionTrigger implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Notion Trigger (Beta)',
|
||||
name: 'notionTrigger',
|
||||
icon: 'file:notion.svg',
|
||||
group: ['trigger'],
|
||||
version: 1,
|
||||
description: 'Starts the workflow when Notion events occur',
|
||||
subtitle: '={{$parameter["event"]}}',
|
||||
defaults: {
|
||||
name: 'Notion Trigger',
|
||||
color: '#000000',
|
||||
},
|
||||
credentials: [
|
||||
{
|
||||
name: 'notionApi',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
polling: true,
|
||||
inputs: [],
|
||||
outputs: ['main'],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Event',
|
||||
name: 'event',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Page Added to Database',
|
||||
value: 'pageAddedToDatabase',
|
||||
},
|
||||
// {
|
||||
// name: 'Record Updated',
|
||||
// value: 'recordUpdated',
|
||||
// },
|
||||
],
|
||||
required: true,
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Database',
|
||||
name: 'databaseId',
|
||||
type: 'options',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getDatabases',
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
event: [
|
||||
'pageAddedToDatabase',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
required: true,
|
||||
description: 'The ID of this database.',
|
||||
},
|
||||
{
|
||||
displayName: 'Simple',
|
||||
name: 'simple',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
event: [
|
||||
'pageAddedToDatabase',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: true,
|
||||
description: 'When set to true a simplify version of the response will be used else the raw data.',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
methods = {
|
||||
loadOptions: {
|
||||
async getDatabases(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
const { results: databases } = await notionApiRequest.call(this, 'POST', `/search`, { page_size: 100, filter: { property: 'object', value: 'database' } });
|
||||
for (const database of databases) {
|
||||
returnData.push({
|
||||
name: database.title[0].plain_text,
|
||||
value: database.id,
|
||||
});
|
||||
}
|
||||
returnData.sort((a, b) => {
|
||||
if (a.name.toLocaleLowerCase() < b.name.toLocaleLowerCase()) { return -1; }
|
||||
if (a.name.toLocaleLowerCase() > b.name.toLocaleLowerCase()) { return 1; }
|
||||
return 0;
|
||||
});
|
||||
return returnData;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
async poll(this: IPollFunctions): Promise<INodeExecutionData[][] | null> {
|
||||
const webhookData = this.getWorkflowStaticData('node');
|
||||
const databaseId = this.getNodeParameter('databaseId') as string;
|
||||
const event = this.getNodeParameter('event') as string;
|
||||
const simple = this.getNodeParameter('simple') as boolean;
|
||||
|
||||
const now = moment().utc().format();
|
||||
|
||||
const startDate = webhookData.lastTimeChecked as string || now;
|
||||
|
||||
const endDate = now;
|
||||
|
||||
webhookData.lastTimeChecked = endDate;
|
||||
|
||||
const sortProperty = (event === 'pageAddedToDatabase') ? 'created_time' : 'last_edited_time';
|
||||
|
||||
const body: IDataObject = {
|
||||
page_size: 1,
|
||||
sorts: [
|
||||
{
|
||||
timestamp: sortProperty,
|
||||
direction: 'descending',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
let records: IDataObject[] = [];
|
||||
|
||||
let hasMore = true;
|
||||
|
||||
//get last record
|
||||
let { results: data } = await notionApiRequest.call(this, 'POST', `/databases/${databaseId}/query`, body);
|
||||
|
||||
if (this.getMode() === 'manual') {
|
||||
if (simple === true) {
|
||||
data = simplifyObjects(data);
|
||||
}
|
||||
if (Array.isArray(data) && data.length) {
|
||||
return [this.helpers.returnJsonArray(data)];
|
||||
}
|
||||
}
|
||||
|
||||
// if something changed after the last check
|
||||
if (Object.keys(data[0]).length !== 0 && webhookData.lastRecordProccesed !== data[0].id) {
|
||||
do {
|
||||
body.page_size = 10;
|
||||
const { results, has_more, next_cursor } = await notionApiRequest.call(this, 'POST', `/databases/${databaseId}/query`, body);
|
||||
records.push.apply(records, results);
|
||||
hasMore = has_more;
|
||||
if (next_cursor !== null) {
|
||||
body['start_cursor'] = next_cursor;
|
||||
}
|
||||
} while (!moment(records[records.length - 1][sortProperty] as string).isSameOrBefore(startDate) && hasMore === true);
|
||||
|
||||
if (this.getMode() !== 'manual') {
|
||||
records = records.filter((record: IDataObject) => moment(record[sortProperty] as string).isBetween(startDate, endDate));
|
||||
}
|
||||
|
||||
if (simple === true) {
|
||||
records = simplifyObjects(records);
|
||||
}
|
||||
|
||||
webhookData.lastRecordProccesed = data[0].id;
|
||||
|
||||
if (Array.isArray(records) && records.length) {
|
||||
return [this.helpers.returnJsonArray(records)];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
332
packages/nodes-base/nodes/Notion/PageDescription.ts
Normal file
332
packages/nodes-base/nodes/Notion/PageDescription.ts
Normal file
|
@ -0,0 +1,332 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
blocks,
|
||||
} from './Blocks';
|
||||
|
||||
export const pageOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'page',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create a page',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Get a page',
|
||||
},
|
||||
{
|
||||
name: 'Search',
|
||||
value: 'search',
|
||||
description: 'Text search of pages',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const pageFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* page:create */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Parent Page ID',
|
||||
name: 'pageId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'page',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'The ID of the parent page that this child page belongs to.',
|
||||
},
|
||||
{
|
||||
displayName: 'Title',
|
||||
name: 'title',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'page',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'Page title. Appears at the top of the page and can be found via Quick Find.',
|
||||
},
|
||||
{
|
||||
displayName: 'Simple',
|
||||
name: 'simple',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'page',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: true,
|
||||
description: 'When set to true a simplify version of the response will be used else the raw data.',
|
||||
},
|
||||
...blocks('page', 'create'),
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* page:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Page ID',
|
||||
name: 'pageId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'page',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Simple',
|
||||
name: 'simple',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'page',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: true,
|
||||
description: 'When set to true a simplify version of the response will be used else the raw data.',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* page:search */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Search Text',
|
||||
name: 'text',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'page',
|
||||
],
|
||||
operation: [
|
||||
'search',
|
||||
],
|
||||
},
|
||||
},
|
||||
description: 'The text to search for.',
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'page',
|
||||
],
|
||||
operation: [
|
||||
'search',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'If all results should be returned or only up to a given limit.',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'page',
|
||||
],
|
||||
operation: [
|
||||
'search',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 100,
|
||||
},
|
||||
default: 50,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
{
|
||||
displayName: 'Simple',
|
||||
name: 'simple',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'page',
|
||||
],
|
||||
operation: [
|
||||
'search',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: true,
|
||||
description: 'When set to true a simplify version of the response will be used else the raw data.',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'page',
|
||||
],
|
||||
operation: [
|
||||
'search',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
placeholder: 'Add Field',
|
||||
options: [
|
||||
{
|
||||
displayName: 'Filters',
|
||||
name: 'filter',
|
||||
placeholder: 'Add Filter',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: false,
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Filter',
|
||||
name: 'filters',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Property',
|
||||
name: 'property',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Object',
|
||||
value: 'object',
|
||||
},
|
||||
],
|
||||
default: 'object',
|
||||
description: 'The name of the property to filter by.',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'value',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Database',
|
||||
value: 'database',
|
||||
},
|
||||
{
|
||||
name: 'Page',
|
||||
value: 'page',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description: 'The value of the property to filter by.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Sort',
|
||||
name: 'sort',
|
||||
placeholder: 'Add Sort',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: false,
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Sort',
|
||||
name: 'sortValue',
|
||||
values: [
|
||||
{
|
||||
displayName: 'Direction',
|
||||
name: 'direction',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Ascending',
|
||||
value: 'ascending',
|
||||
},
|
||||
{
|
||||
name: 'Descending',
|
||||
value: 'descending',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description: 'The direction to sort.',
|
||||
},
|
||||
{
|
||||
displayName: 'Timestamp',
|
||||
name: 'timestamp',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Last Edited Time',
|
||||
value: 'last_edited_time',
|
||||
},
|
||||
],
|
||||
default: 'last_edited_time',
|
||||
description: `The name of the timestamp to sort against.`,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
] as INodeProperties[];
|
100
packages/nodes-base/nodes/Notion/UserDescription.ts
Normal file
100
packages/nodes-base/nodes/Notion/UserDescription.ts
Normal file
|
@ -0,0 +1,100 @@
|
|||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export const userOperations = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'user',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Get a user',
|
||||
},
|
||||
{
|
||||
name: 'Get All',
|
||||
value: 'getAll',
|
||||
description: 'Get all users',
|
||||
},
|
||||
],
|
||||
default: 'get',
|
||||
description: 'The operation to perform.',
|
||||
},
|
||||
] as INodeProperties[];
|
||||
|
||||
export const userFields = [
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* user:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'User ID',
|
||||
name: 'userId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'user',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* user:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'user',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'If all results should be returned or only up to a given limit.',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'user',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 100,
|
||||
},
|
||||
default: 50,
|
||||
description: 'How many results to return.',
|
||||
},
|
||||
] as INodeProperties[];
|
1
packages/nodes-base/nodes/Notion/notion.svg
Normal file
1
packages/nodes-base/nodes/Notion/notion.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg height="2500" width="2500" xmlns="http://www.w3.org/2000/svg" viewBox="12 0.18999999999999906 487.619 510.941"><path d="M96.085 91.118c15.81 12.845 21.741 11.865 51.43 9.884l279.888-16.806c5.936 0 1-5.922-.98-6.906L379.94 43.686c-8.907-6.915-20.773-14.834-43.516-12.853L65.408 50.6c-9.884.98-11.858 5.922-7.922 9.883zm16.804 65.228v294.491c0 15.827 7.909 21.748 25.71 20.769l307.597-17.799c17.81-.979 19.794-11.865 19.794-24.722V136.57c0-12.836-4.938-19.758-15.84-18.77l-321.442 18.77c-11.863.997-15.82 6.931-15.82 19.776zm303.659 15.797c1.972 8.903 0 17.798-8.92 18.799l-14.82 2.953v217.412c-12.868 6.916-24.734 10.87-34.622 10.87-15.831 0-19.796-4.945-31.654-19.76l-96.944-152.19v147.248l30.677 6.922s0 17.78-24.75 17.78l-68.23 3.958c-1.982-3.958 0-13.832 6.921-15.81l17.805-4.935V210.7l-24.721-1.981c-1.983-8.903 2.955-21.74 16.812-22.736l73.195-4.934 100.889 154.171V198.836l-25.723-2.952c-1.974-10.884 5.927-18.787 15.819-19.767zM42.653 23.919l281.9-20.76c34.618-2.969 43.525-.98 65.283 14.825l89.986 63.247c14.848 10.876 19.797 13.837 19.797 25.693v346.883c0 21.74-7.92 34.597-35.608 36.564L136.64 510.14c-20.785.991-30.677-1.971-41.562-15.815l-66.267-85.978C16.938 392.52 12 380.68 12 366.828V58.495c0-17.778 7.922-32.608 30.653-34.576z" fill-rule="evenodd"/></svg>
|
After Width: | Height: | Size: 1.2 KiB |
|
@ -171,6 +171,8 @@
|
|||
"dist/credentials/NasaApi.credentials.js",
|
||||
"dist/credentials/NextCloudApi.credentials.js",
|
||||
"dist/credentials/NextCloudOAuth2Api.credentials.js",
|
||||
"dist/credentials/NotionApi.credentials.js",
|
||||
"dist/credentials/NotionOAuth2Api.credentials.js",
|
||||
"dist/credentials/OAuth1Api.credentials.js",
|
||||
"dist/credentials/OAuth2Api.credentials.js",
|
||||
"dist/credentials/OpenWeatherMapApi.credentials.js",
|
||||
|
@ -449,6 +451,8 @@
|
|||
"dist/nodes/Nasa/Nasa.node.js",
|
||||
"dist/nodes/NextCloud/NextCloud.node.js",
|
||||
"dist/nodes/NoOp.node.js",
|
||||
"dist/nodes/Notion/Notion.node.js",
|
||||
"dist/nodes/Notion/NotionTrigger.node.js",
|
||||
"dist/nodes/OpenThesaurus/OpenThesaurus.node.js",
|
||||
"dist/nodes/OpenWeatherMap.node.js",
|
||||
"dist/nodes/Orbit/Orbit.node.js",
|
||||
|
|
Loading…
Reference in a new issue