diff --git a/NotionApp.ts b/NotionApp.ts index 0b34803..aa77b54 100644 --- a/NotionApp.ts +++ b/NotionApp.ts @@ -7,7 +7,7 @@ import { ILogger, IModify, IPersistence, - IRead + IRead, } from "@rocket.chat/apps-engine/definition/accessors"; import { App } from "@rocket.chat/apps-engine/definition/App"; import { IAppInfo } from "@rocket.chat/apps-engine/definition/metadata"; @@ -80,7 +80,21 @@ export class NotionApp extends App { context: UIActionButtonContext.MESSAGE_BOX_ACTION, }; + const sendToPageButton: IUIActionButtonDescriptor = { + actionId: ActionButton.SEND_TO_PAGE_MESSAGE_ACTION, + labelI18n: ActionButton.SEND_TO_PAGE_MESSAGE_ACTION_LABEL, + context: UIActionButtonContext.MESSAGE_ACTION, + }; + + const sendToNewPageButton: IUIActionButtonDescriptor = { + actionId: ActionButton.SEND_TO_NEW_PAGE_MESSAGE_ACTION, + labelI18n: ActionButton.SEND_TO_NEW_PAGE_MESSAGE_ACTION_LABEL, + context: UIActionButtonContext.MESSAGE_ACTION, + }; + configurationExtend.ui.registerButton(commentOnPagesButton); + configurationExtend.ui.registerButton(sendToPageButton); + configurationExtend.ui.registerButton(sendToNewPageButton); } public getOAuth2Client(): OAuth2Client { diff --git a/app.json b/app.json index e7c3ca8..2af5242 100644 --- a/app.json +++ b/app.json @@ -46,6 +46,9 @@ }, { "name": "room.write" + }, + { + "name": "message.read" } ] } \ No newline at end of file diff --git a/definition/handlers/IHandler.ts b/definition/handlers/IHandler.ts index 8b51c1b..7dc857b 100644 --- a/definition/handlers/IHandler.ts +++ b/definition/handlers/IHandler.ts @@ -9,6 +9,7 @@ export interface IHandler extends Omit { commentOnPages(): Promise; createNotionPageOrRecord(): Promise; changeNotionWorkspace(): Promise; + shareNotionPage(): Promise; } export type IHanderParams = Omit; diff --git a/definition/lib/INotion.ts b/definition/lib/INotion.ts index 6f76626..45df08b 100644 --- a/definition/lib/INotion.ts +++ b/definition/lib/INotion.ts @@ -4,6 +4,9 @@ import { INotionUser, ITokenInfo } from "../authorization/IOAuth2Storage"; import { ClientError, Error } from "../../errors/Error"; import { NotionObjectTypes, NotionOwnerType } from "../../enum/Notion"; import { RichText } from "@tryfabric/martian/build/src/notion"; +import { RecordPropertyType } from "../../enum/modals/common/NotionProperties"; +import { Modals } from "../../enum/modals/common/Modals"; +import { IMessageAttachmentField } from "@rocket.chat/apps-engine/definition/messages"; export interface INotion { baseUrl: string; @@ -46,7 +49,38 @@ export interface INotionSDK extends INotion { token: string, page: IPage, prop: IPageProperties - ): Promise; + ): Promise<(INotionPage & { pageId: string }) | Error>; + retrieveDatabase( + token: string, + database_id: string + ): Promise; + createRecord( + token: string, + database: IDatabase, + properties: object + ): Promise< + | { + fields: Array; + url: string; + pageId: string; + } + | Error + >; + retrievePage( + token: string, + pageId: string + ): Promise<(IPage & { url: string }) | Error>; + appendMessageBlock( + token: string, + message: string, + blockId: string + ): Promise; + searchDatabases(token: string): Promise | Error>; + queryDatabasePages( + token: string, + databaseId: string + ): Promise> | Error>; + retrievePageContent(token: string, pageId: string): Promise; } export interface IParentPage { @@ -114,3 +148,40 @@ export interface IPageProperties { export interface INotionPage extends INotionDatabase { title: string; } + +export interface IDatabaseProperties { + title: IDatabaseTitle; + additionalProperties?: IDatabaseAddProperties; +} + +export interface IDatabaseTitle { + id: string; + type: NotionObjectTypes.TITLE; + name: string; +} + +export interface IDatabaseAddProperties { + [key: string]: { + id: string; + name: string; + type: RecordPropertyType; + config?: IDatabasePropertiesConfig; + }; +} + +interface IDatabasePropertiesConfig { + [Modals.OPTIONS]?: Array<{ + id: string; + color: string; + name: string; + }>; + + [Modals.GROUPS]?: Array<{ + id: string; + color: string; + name: string; + options_ids: Array; + }>; + + [NotionObjectTypes.EXPRESSION]?: string; +} diff --git a/definition/ui-kit/Element/IDatePickerElement.ts b/definition/ui-kit/Element/IDatePickerElement.ts new file mode 100644 index 0000000..fe59bb3 --- /dev/null +++ b/definition/ui-kit/Element/IDatePickerElement.ts @@ -0,0 +1,6 @@ +import { DatePickerElement } from "@rocket.chat/ui-kit"; + +export type DatePickerElementParam = Omit< + DatePickerElement, + "type" | "placeholder" | "appId" | "blockId" | "actionId" +> & { text?: string }; diff --git a/definition/ui-kit/Element/IElementBuilder.ts b/definition/ui-kit/Element/IElementBuilder.ts index cc0c00c..22f3d6c 100644 --- a/definition/ui-kit/Element/IElementBuilder.ts +++ b/definition/ui-kit/Element/IElementBuilder.ts @@ -1,4 +1,3 @@ -import { ButtonStyle } from "@rocket.chat/apps-engine/definition/uikit"; import { ButtonElement, ImageElement, @@ -6,6 +5,8 @@ import { PlainTextInputElement, OverflowElement, Option, + DatePickerElement, + MultiStaticSelectElement, } from "@rocket.chat/ui-kit"; import { ButtonParam } from "./IButtonElement"; import { ImageParam } from "./IImageElement"; @@ -15,6 +16,8 @@ import { } from "./IStaticSelectElement"; import { PlainTextInputParam } from "./IPlainTextInputElement"; import { OverflowElementParam } from "./IOverflowElement"; +import { DatePickerElementParam } from "./IDatePickerElement"; +import { MultiStaticSelectElementParam } from "./IMultiStaticSelectElement"; export interface IElementBuilder { addButton( @@ -35,6 +38,14 @@ export interface IElementBuilder { param: OverflowElementParam, interaction: ElementInteractionParam ): OverflowElement; + createDatePicker( + param: DatePickerElementParam, + interaction: ElementInteractionParam + ): DatePickerElement; + createMultiStaticSelect( + param: MultiStaticSelectElementParam, + interaction: ElementInteractionParam + ): MultiStaticSelectElement; } export type ElementInteractionParam = { blockId: string; actionId: string }; diff --git a/definition/ui-kit/Element/IMultiStaticSelectElement.ts b/definition/ui-kit/Element/IMultiStaticSelectElement.ts new file mode 100644 index 0000000..f0d8b7b --- /dev/null +++ b/definition/ui-kit/Element/IMultiStaticSelectElement.ts @@ -0,0 +1,7 @@ +import { MultiStaticSelectElement } from "@rocket.chat/ui-kit"; +export type MultiStaticSelectElementParam = Omit< + MultiStaticSelectElement, + "type" | "blockId" | "actionId" | "placeholder" | "appId" +> & { + text: string; +}; diff --git a/enum/CommandParam.ts b/enum/CommandParam.ts index 6f219b9..25bb462 100644 --- a/enum/CommandParam.ts +++ b/enum/CommandParam.ts @@ -6,6 +6,9 @@ export enum CommandParam { COMMENT = "comment", WORKSPACE = "workspace", WS = "ws", + SHARE = "share", + VIEW = "view", + VIEW_PAGE = "vp", } export enum SubCommandParam { diff --git a/enum/Notion.ts b/enum/Notion.ts index a27b2d2..d57c444 100644 --- a/enum/Notion.ts +++ b/enum/Notion.ts @@ -21,6 +21,7 @@ export enum NotionApi { COMMENTS = `https://api.notion.com/v1/comments`, USERS = `https://api.notion.com/v1/users`, PAGES = `https://api.notion.com/v1/pages`, + BLOCKS = `https://api.notion.com/v1/blocks`, } export enum Notion { @@ -45,4 +46,9 @@ export enum NotionObjectTypes { TITLE = "title", INFO = "info", NAME = "name", + PROPERTIES = "properties", + ID = "id", + DATABASE = "database", + ICON = "icon", + EMOJI = "emoji", } diff --git a/enum/messages.ts b/enum/messages.ts index 0cf8ba6..33a6b86 100644 --- a/enum/messages.ts +++ b/enum/messages.ts @@ -4,7 +4,8 @@ export enum Messages { • use \`/notion comment\` to comment on notion page • use \`/notion create\` to create page or record • use \`/notion create db\` to create database - • use \`/notion workspace\` to change workspace + • use \`/notion workspace\` to change workspace + • use \`/notion share\` to share pages `, HELPER_TEXT = `:wave: Need some help with \`/notion\`?`, diff --git a/enum/modals/NotionPage.ts b/enum/modals/NotionPage.ts new file mode 100644 index 0000000..9befc49 --- /dev/null +++ b/enum/modals/NotionPage.ts @@ -0,0 +1,11 @@ +export enum NotionPage { + VIEW_ID = "notion-view-page-view-id", + ACTION_ID = "notion-view-page-action-id", + VIEW_ACTION = "notion-view-page-view-action-id", + VIEW_BLOCK = "notion-view-page-view-block-id", + CANCEL_ACTION = "notion-view-page-cancel-action-id", + CANCEL_BLOCK = "notion-view-page-cancel-block-id", + VIEW = "View", + CANCEL = "Cancel", + TITLE = "View Notion Page", +} diff --git a/enum/modals/NotionPageOrRecord.ts b/enum/modals/NotionPageOrRecord.ts index 2d10323..0122b49 100644 --- a/enum/modals/NotionPageOrRecord.ts +++ b/enum/modals/NotionPageOrRecord.ts @@ -13,7 +13,5 @@ export enum NotionPageOrRecord { TITLE_ACTION = "title-page-or-record-action-id", CHANGE_DATABASE_TEXT = "Change Database", CHANGE_DATABASE_ACTION = "create-page-or-record-change-database-action-id", - ADD_PROPERTY_ACTION = "add-property-create-page-or-record-action-id", - ADD_PROPERTY_BLOCK = "add-property-create-page-or-record-action-id", - ADD_PROPERTY_BUTTON_TEXT = "Add Property", + PROPERTY_SELECTED_BLOCK_ELEMENT = "property-selected-element-create-page-or-record-block-id", } diff --git a/enum/modals/NotionTable.ts b/enum/modals/NotionTable.ts new file mode 100644 index 0000000..69a3402 --- /dev/null +++ b/enum/modals/NotionTable.ts @@ -0,0 +1,11 @@ +export enum NotionTable { + VIEW_ID = "notion-view-table-view-id", + ACTION_ID = "notion-view-table-action-id", + VIEW_ACTION = "notion-view-table-view-action-id", + VIEW_BLOCK = "notion-view-table-view-block-id", + CANCEL_ACTION = "notion-view-table-cancel-action-id", + CANCEL_BLOCK = "notion-view-table-cancel-block-id", + VIEW = "View", + CANCEL = "Cancel", + TITLE = "View Notion Table", +} diff --git a/enum/modals/SendMessagePage.ts b/enum/modals/SendMessagePage.ts new file mode 100644 index 0000000..9f2ee64 --- /dev/null +++ b/enum/modals/SendMessagePage.ts @@ -0,0 +1,11 @@ +export enum SendMessagePage { + VIEW_ID = "notion-send-message-page-view-id", + ACTION_ID = "notion-send-message-page-action-id", + SEND_ACTION = "notion-send-message-page-send-action-id", + SEND_BLOCK = "notion-send-message-page-send-block-id", + CANCEL_ACTION = "notion-send-message-page-cancel-action-id", + CANCEL_BLOCK = "notion-send-message-page-cancel-block-id", + SEND = "Send", + CANCEL = "Cancel", + TITLE = "Send To Notion Page", +} diff --git a/enum/modals/SharePage.ts b/enum/modals/SharePage.ts new file mode 100644 index 0000000..1767f70 --- /dev/null +++ b/enum/modals/SharePage.ts @@ -0,0 +1,14 @@ +export enum SharePage { + VIEW_ID = "notion-share-page-view-id", + ACTION_ID = "notion-share-page-action-id", + SHARE_ACTION = "notion-share-page-share-action-id", + SHARE_BLOCK = "notion-share-page-share-block-id", + CLOSE_ACTION = "notion-share-page-close-action-id", + CLOSE_BLOCK = "notion-share-page-close-block-id", + SHARE = "Share", + CLOSE = "Close", + TITLE = "Share Notion Page", + VIEW = "Click to View", + VIEW_PAGE_ACTION = "notion-share-page-view-page-action-id", + VIEW_PAGE_BLOCK = "notion-share-page-view-page-block-id", +} diff --git a/enum/modals/common/ActionButtons.ts b/enum/modals/common/ActionButtons.ts index e34a5cf..c4c7698 100644 --- a/enum/modals/common/ActionButtons.ts +++ b/enum/modals/common/ActionButtons.ts @@ -1,4 +1,8 @@ export enum ActionButton { COMMENT_ON_PAGES_MESSAGE_BOX_ACTION = "comment-on-pages-message-box-action", COMMENT_ON_PAGES_MESSAGE_BOX_ACTION_LABEL = "CommentOnPagesLabel", + SEND_TO_PAGE_MESSAGE_ACTION = "send-to-page-message-action", + SEND_TO_PAGE_MESSAGE_ACTION_LABEL = "SendToPageLabel", + SEND_TO_NEW_PAGE_MESSAGE_ACTION = "send-to-new-page-message-action", + SEND_TO_NEW_PAGE_MESSAGE_ACTION_LABEL = "SendToNewPageLabel", } \ No newline at end of file diff --git a/enum/modals/common/Modals.ts b/enum/modals/common/Modals.ts index 8c3265b..ec0db2e 100644 --- a/enum/modals/common/Modals.ts +++ b/enum/modals/common/Modals.ts @@ -10,4 +10,8 @@ export enum Modals { MISSING = "missing", VIEWERROR = "viewError", DATA = "data", + ADDITIONAL_PROP = "additionalProperties", + GROUPS = "groups", + PROPERTY = "property", + VALUE = "value", } diff --git a/enum/modals/common/NotionProperties.ts b/enum/modals/common/NotionProperties.ts index a8ec4c0..38b1f7a 100644 --- a/enum/modals/common/NotionProperties.ts +++ b/enum/modals/common/NotionProperties.ts @@ -99,3 +99,33 @@ export enum MissingPropertyMessage { EXPRESSION = "Expression is required", OPTION_NAME = "Option Name is required", } + +export type RecordPropertyType = Exclude< + PropertyTypeValue, + | PropertyTypeValue.CREATED_TIME + | PropertyTypeValue.CREATED_BY + | PropertyTypeValue.LAST_EDITED_TIME + | PropertyTypeValue.LAST_EDITED_BY +>; + +export enum NotSupportedPropertyType { + RELATION = "relation", + ROLLUP = "rollup", +} + +export const NotSupportedPropertyTypes = [ + PropertyTypeValue.CREATED_TIME.toString(), + PropertyTypeValue.CREATED_BY.toString(), + PropertyTypeValue.LAST_EDITED_TIME.toString(), + PropertyTypeValue.LAST_EDITED_BY.toString(), + NotSupportedPropertyType.RELATION.toString(), + NotSupportedPropertyType.ROLLUP.toString(), + PropertyTypeValue.FILES.toString(), +]; + +export enum CheckboxEnum { + TRUE = "true", + FALSE = "false", + YES = "Yes", + NO = "No", +} diff --git a/enum/modals/common/PageContent.ts b/enum/modals/common/PageContent.ts new file mode 100644 index 0000000..8d09061 --- /dev/null +++ b/enum/modals/common/PageContent.ts @@ -0,0 +1,35 @@ +export enum PageContent { + BOOKMARK = "bookmark", + BREADCRUMB = "breadcrumb", + BULLET_LIST_ITEM = "bulleted_list_item", + CALLOUT = "callout", + CHILD_PAGE = "child_page", + CHILD_DATABASE = "child_database", + CODE = "code", + COLUMN = "column", + COLUMN_LIST = "column_list", + DIVIDER = "divider", + EMBED = "embed", + EQUATION = "equation", + FILE = "file", + HEADING_1 = "heading_1", + HEADING_2 = "heading_2", + HEADING_3 = "heading_3", + IMAGE = "image", + LINK_PREVIEW = "link_preview", + NUMBER_LIST_ITEM = "numbered_list_item", + PARAGRAPH = "paragraph", + PDF = "pdf", + QUOTE = "quote", + SYNCED_BLOCK = "synced_block", + TABLE = "table", + TABLE_OF_CONTENTS = "table_of_contents", + TODO = "to_do", + TOGGLE_BLOCKS = "toggle", + VIDEO = "video", + // Mention Left +} + +export enum FileType { + EXTERNAL = "external", +} diff --git a/enum/modals/common/SearchDatabaseComponent.ts b/enum/modals/common/SearchDatabaseComponent.ts new file mode 100644 index 0000000..359fec6 --- /dev/null +++ b/enum/modals/common/SearchDatabaseComponent.ts @@ -0,0 +1,6 @@ +export enum SearchDatabaseComponent { + PLACEHOLDER = "Select Database", + BLOCK_ID = "search-database-component-block-id", + ACTION_ID = "search-database-component-action-id", + LABEL = "Database Name *", +} diff --git a/i18n/en.json b/i18n/en.json index 0d11fef..df5eb48 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -6,5 +6,7 @@ "CredentialsSettings": "Authorization Settings", "NotionCommandParams": "connect | disconnect | workspace | create | schema | comment", "NotionCommandDescription": "Create Notion pages and database from Rocket.Chat", - "CommentOnPagesLabel": "💬 Comment on Page" + "CommentOnPagesLabel": "💬 Comment on Page", + "SendToPageLabel": "📝 Send to Page", + "SendToNewPageLabel": "📢 Send to New Page" } diff --git a/package-lock.json b/package-lock.json index 66d6f65..ce02d3a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,7 +5,8 @@ "packages": { "": { "dependencies": { - "@tryfabric/martian": "^1.2.4" + "@tryfabric/martian": "^1.2.4", + "table": "^6.8.1" }, "devDependencies": { "@rocket.chat/apps-engine": "^1.39.1", @@ -159,6 +160,29 @@ "node": ">=6.0" } }, + "node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, "node_modules/ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -180,6 +204,14 @@ "sprintf-js": "~1.0.2" } }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "engines": { + "node": ">=8" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -359,6 +391,11 @@ "node": ">=0.3.1" } }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -386,6 +423,11 @@ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, "node_modules/form-data": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", @@ -540,6 +582,14 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, "node_modules/is-hexadecimal": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", @@ -585,6 +635,11 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, "node_modules/katex": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/katex/-/katex-0.12.0.tgz", @@ -602,6 +657,11 @@ "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", "dev": true }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==" + }, "node_modules/longest-streak": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-2.0.4.tgz", @@ -994,6 +1054,14 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "engines": { + "node": ">=6" + } + }, "node_modules/remark-gfm": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-1.0.0.tgz", @@ -1040,6 +1108,14 @@ "node": ">=0.10" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.2", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", @@ -1066,6 +1142,52 @@ "semver": "bin/semver" } }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/slice-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -1081,6 +1203,30 @@ "node": "*" } }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -1105,6 +1251,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/table": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz", + "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==", + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -1231,6 +1392,14 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/uuid": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", diff --git a/package.json b/package.json index 455374f..2f73d46 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "typescript": "^4.0.5" }, "dependencies": { - "@tryfabric/martian": "^1.2.4" + "@tryfabric/martian": "^1.2.4", + "table": "^6.8.1" } } diff --git a/src/commands/CommandUtility.ts b/src/commands/CommandUtility.ts index eb5b083..5c74f5c 100644 --- a/src/commands/CommandUtility.ts +++ b/src/commands/CommandUtility.ts @@ -119,6 +119,18 @@ export class CommandUtility implements ICommandUtility { await handler.changeNotionWorkspace(); break; } + case CommandParam.SHARE: { + await handler.shareNotionPage(); + break; + } + case CommandParam.VIEW : { + await handler.viewNotionTable(); + break; + } + case CommandParam.VIEW_PAGE: { + await handler.viewNotionPage(); + break; + } case CommandParam.HELP: default: { await sendHelperNotification( diff --git a/src/handlers/ExecuteActionButtonHandler.ts b/src/handlers/ExecuteActionButtonHandler.ts index ea47998..96d4076 100644 --- a/src/handlers/ExecuteActionButtonHandler.ts +++ b/src/handlers/ExecuteActionButtonHandler.ts @@ -11,6 +11,7 @@ import { import { NotionApp } from "../../NotionApp"; import { ActionButton } from "../../enum/modals/common/ActionButtons"; import { Handler } from "./Handler"; +import { IMessage } from "@rocket.chat/apps-engine/definition/messages"; export class ExecuteActionButtonHandler { private context: UIKitActionButtonInteractionContext; @@ -26,7 +27,7 @@ export class ExecuteActionButtonHandler { } public async handleActions(): Promise { - const { actionId, user, room, triggerId } = + const { actionId, user, room, triggerId, message } = this.context.getInteractionData(); const handler = new Handler({ @@ -45,6 +46,14 @@ export class ExecuteActionButtonHandler { await handler.commentOnPages(); break; } + case ActionButton.SEND_TO_NEW_PAGE_MESSAGE_ACTION: { + await handler.createNotionPageOrRecord(false, message); + break; + } + case ActionButton.SEND_TO_PAGE_MESSAGE_ACTION: { + await handler.sendToNotionPage(message as IMessage); + break; + } } return this.context.getInteractionResponder().successResponse(); diff --git a/src/handlers/ExecuteBlockActionHandler.ts b/src/handlers/ExecuteBlockActionHandler.ts index 165b842..85d37ab 100644 --- a/src/handlers/ExecuteBlockActionHandler.ts +++ b/src/handlers/ExecuteBlockActionHandler.ts @@ -19,15 +19,24 @@ import { ModalInteractionStorage } from "../storage/ModalInteraction"; import { createDatabaseModal } from "../modals/createDatabaseModal"; import { OAuth2Storage } from "../authorization/OAuth2Storage"; import { Error } from "../../errors/Error"; -import { sendNotificationWithConnectBlock } from "../helper/message"; -import { PropertyTypeValue } from "../../enum/modals/common/NotionProperties"; +import { + sendNotification, + sendNotificationWithConnectBlock, +} from "../helper/message"; +import { + NotSupportedPropertyTypes, + PropertyTypeValue, +} from "../../enum/modals/common/NotionProperties"; import { Modals } from "../../enum/modals/common/Modals"; import { getDuplicatePropertyNameViewErrors } from "../helper/getDuplicatePropNameViewError"; import { SearchPage } from "../../enum/modals/common/SearchPageComponent"; import { createCommentContextualBar } from "../modals/createCommentContextualBar"; import { CommentPage } from "../../enum/modals/CommentPage"; -import { NotionObjectTypes } from "../../enum/Notion"; -import { ITokenInfo } from "../../definition/authorization/IOAuth2Storage"; +import { NotionObjectTypes, NotionOwnerType } from "../../enum/Notion"; +import { + INotionUser, + ITokenInfo, +} from "../../definition/authorization/IOAuth2Storage"; import { ICommentInfo, IDatabase, @@ -41,6 +50,11 @@ import { createPageOrRecordModal } from "../modals/createPageOrRecordModal"; import { NotionPageOrRecord } from "../../enum/modals/NotionPageOrRecord"; import { NotionWorkspace } from "../../enum/modals/NotionWorkspace"; import { changeWorkspaceModal } from "../modals/changeWorkspaceModal"; +import { getPropertiesIdsObject } from "../helper/getPropertiesIdsObject"; +import { SharePage } from "../../enum/modals/SharePage"; +import { NotionTable } from "../../enum/modals/NotionTable"; +import { SendMessagePage } from "../../enum/modals/SendMessagePage"; +import { NotionPage } from "../../enum/modals/NotionPage"; export class ExecuteBlockActionHandler { private context: UIKitBlockInteractionContext; @@ -157,6 +171,20 @@ export class ExecuteBlockActionHandler { ); break; } + case NotionPage.ACTION_ID: + case SendMessagePage.ACTION_ID: + case SharePage.ACTION_ID: { + return this.handleSelectPageAction(); + break; + } + case NotionTable.ACTION_ID: { + return this.handleSelectDatabaseAction(); + break; + } + case SharePage.VIEW_PAGE_ACTION: { + return this.handleViewPage(oAuth2Storage, room as IRoom); + break; + } default: { // Property Type Select Action const propertyTypeSelected = @@ -860,6 +888,28 @@ export class ExecuteBlockActionHandler { } const database = Object as IDatabase; + + const databaseId = database.parent.database_id; + const { NotionSdk } = this.app.getUtils(); + const { access_token } = tokenInfo; + + const properties = await NotionSdk.retrieveDatabase( + access_token, + databaseId + ); + + if (properties instanceof Error) { + this.app.getLogger().error(properties.message); + return this.context.getInteractionResponder().errorResponse(); + } + + await modalInteraction.storeInputElementState( + SearchPageAndDatabase.ACTION_ID, + properties + ); + + await this.storeAllElements(properties, tokenInfo, modalInteraction); + const modal = await createPageOrRecordModal( this.app, user, @@ -888,7 +938,6 @@ export class ExecuteBlockActionHandler { roomInteractionStorage: RoomInteractionStorage ): Promise { const { value, user, triggerId } = this.context.getInteractionData(); - const tokenInfo = await oAuth2Storage.getCurrentWorkspace(user.id); const roomId = await roomInteractionStorage.getInteractionRoomId(); const room = (await this.read.getRoomReader().getById(roomId)) as IRoom; @@ -925,4 +974,93 @@ export class ExecuteBlockActionHandler { .getInteractionResponder() .updateModalViewResponse(modal); } + + private async storeAllElements( + properties, + tokenInfo: ITokenInfo, + modalInteraction: ModalInteractionStorage + ) { + const result: Array = []; + + for (const [property] of Object.entries(properties)) { + const propertyObject: object = properties[property]; + const propertyType: string = + propertyObject?.[NotionObjectTypes.TYPE]; + const propertyId: string = propertyObject?.[NotionObjectTypes.ID]; + + if (!propertyType.includes(NotionObjectTypes.TITLE.toString())) { + if (!NotSupportedPropertyTypes.includes(propertyType)) { + result.push({ + value: uuid(), + object: propertyObject, + }); + } + } + } + + await modalInteraction.updateInteractionActionId(result); + } + + private async handleSelectPageAction(): Promise { + return this.removeViewError(); + } + + private async handleSelectDatabaseAction(): Promise { + return this.removeViewError(); + } + + private async removeViewError(): Promise { + const { value, container } = this.context.getInteractionData(); + + return this.context.getInteractionResponder().viewErrorResponse({ + viewId: container.id, + errors: {}, + }); + } + + private async handleViewPage( + oAuth2Storage: OAuth2Storage, + room: IRoom + ): Promise { + const { user, value } = this.context.getInteractionData(); + const { NotionSdk } = this.app.getUtils(); + + const tokenInfo = await oAuth2Storage.getCurrentWorkspace(user.id); + + if (!tokenInfo) { + await sendNotificationWithConnectBlock( + this.app, + user, + this.read, + this.modify, + room + ); + return this.context.getInteractionResponder().errorResponse(); + } + + if (!value) { + return this.context.getInteractionResponder().errorResponse(); + } + + const { access_token } = tokenInfo; + const pageInfo: IPage & { url: string } = JSON.parse(value); + const { parent, name, url } = pageInfo; + const pageId = parent.page_id; + + const pageMrkdwn = await NotionSdk.retrievePageContent( + access_token, + pageId + ); + + if (pageMrkdwn instanceof Error) { + return this.context.getInteractionResponder().errorResponse(); + } + + const message = `# ${name}\n` + pageMrkdwn; + await sendNotification(this.read, this.modify, user, room, { + message: message, + }); + + return this.context.getInteractionResponder().successResponse(); + } } diff --git a/src/handlers/ExecuteViewClosedHandler.ts b/src/handlers/ExecuteViewClosedHandler.ts index bd95e24..ffecf04 100644 --- a/src/handlers/ExecuteViewClosedHandler.ts +++ b/src/handlers/ExecuteViewClosedHandler.ts @@ -16,6 +16,10 @@ import { OAuth2Storage } from "../authorization/OAuth2Storage"; import { ITokenInfo } from "../../definition/authorization/IOAuth2Storage"; import { CommentPage } from "../../enum/modals/CommentPage"; import { NotionPageOrRecord } from "../../enum/modals/NotionPageOrRecord"; +import { SearchPageAndDatabase } from "../../enum/modals/common/SearchPageAndDatabaseComponent"; +import { NotionObjectTypes } from "../../enum/Notion"; +import { PropertyTypeValue } from "../../enum/modals/common/NotionProperties"; +import { ActionButton } from "../../enum/modals/common/ActionButtons"; export class ExecuteViewClosedHandler { private context: UIKitViewCloseInteractionContext; @@ -75,6 +79,16 @@ export class ExecuteViewClosedHandler { await Promise.all([ modalInteraction.clearPagesOrDatabase(workspace_id), + modalInteraction.clearInputElementState( + SearchPageAndDatabase.ACTION_ID + ), + modalInteraction.clearAllInteractionActionId(), + modalInteraction.clearInputElementState( + PropertyTypeValue.PEOPLE + ), + modalInteraction.clearInputElementState( + ActionButton.SEND_TO_NEW_PAGE_MESSAGE_ACTION + ), ]); break; diff --git a/src/handlers/ExecuteViewSubmitHandler.ts b/src/handlers/ExecuteViewSubmitHandler.ts index 92c8d1a..0bdc1aa 100644 --- a/src/handlers/ExecuteViewSubmitHandler.ts +++ b/src/handlers/ExecuteViewSubmitHandler.ts @@ -1,4 +1,6 @@ import { + BlockType, + ButtonStyle, IUIKitResponse, UIKitViewSubmitInteractionContext, } from "@rocket.chat/apps-engine/definition/uikit"; @@ -14,12 +16,14 @@ import { ModalInteractionStorage } from "../storage/ModalInteraction"; import { clearAllInteraction } from "../helper/clearInteractions"; import { OAuth2Storage } from "../authorization/OAuth2Storage"; import { + sendMessage, + sendMessageWithAttachments, sendNotification, sendNotificationWithAttachments, sendNotificationWithConnectBlock, } from "../helper/message"; import { RoomInteractionStorage } from "../storage/RoomInteraction"; -import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; +import { IRoom, RoomType } from "@rocket.chat/apps-engine/definition/rooms"; import { getNotionDatabaseObject } from "../helper/getNotionDatabaseObject"; import { Error } from "../../errors/Error"; import { Modals } from "../../enum/modals/common/Modals"; @@ -33,6 +37,24 @@ import { IDatabase, IPage } from "../../definition/lib/INotion"; import { SearchPageAndDatabase } from "../../enum/modals/common/SearchPageAndDatabaseComponent"; import { NotionWorkspace } from "../../enum/modals/NotionWorkspace"; import { getConnectPreview } from "../helper/getConnectLayout"; +import { getTitleProperty } from "../helper/getTitleProperty"; +import { markdownToRichText } from "@tryfabric/martian"; +import { + CheckboxEnum, + PropertyTypeValue, +} from "../../enum/modals/common/NotionProperties"; +import { Block } from "@rocket.chat/ui-kit"; +import { SharePage } from "../../enum/modals/SharePage"; +import { SearchPage } from "../../enum/modals/common/SearchPageComponent"; +import { ActionButton } from "../../enum/modals/common/ActionButtons"; +import { getCredentials } from "../helper/getCredential"; +import { ICredential } from "../../definition/authorization/ICredential"; +import { SendMessagePage } from "../../enum/modals/SendMessagePage"; +import { NotionTable } from "../../enum/modals/NotionTable"; +import { SearchDatabaseComponent } from "../../enum/modals/common/SearchDatabaseComponent"; +import { table } from "table"; +import { NotionPage } from "../../enum/modals/NotionPage"; +import { ButtonInSectionComponent } from "../modals/common/buttonInSectionComponent"; export class ExecuteViewSubmitHandler { private context: UIKitViewSubmitInteractionContext; @@ -96,6 +118,38 @@ export class ExecuteViewSubmitHandler { ); break; } + case SharePage.VIEW_ID: { + return this.handleSharePage( + room, + oAuth2Storage, + modalInteraction + ); + break; + } + case SendMessagePage.VIEW_ID: { + return this.handleSendMessagePage( + room, + oAuth2Storage, + modalInteraction + ); + break; + } + case NotionTable.VIEW_ID: { + return this.handleViewTable( + room, + oAuth2Storage, + modalInteraction + ); + break; + } + case NotionPage.VIEW_ID: { + return this.handleViewNotionPage( + room, + oAuth2Storage, + modalInteraction + ); + break; + } default: { } } @@ -221,7 +275,7 @@ export class ExecuteViewSubmitHandler { modalInteraction: ModalInteractionStorage ): Promise { const { user, view } = this.context.getInteractionData(); - const { state } = view; + const { state, blocks } = view; const tokenInfo = await oAuth2Storage.getCurrentWorkspace(user.id); @@ -237,15 +291,64 @@ export class ExecuteViewSubmitHandler { } // handle missing properties later - - const Object: IPage | IDatabase = JSON.parse( + const pageSelectState: string | undefined = state?.[SearchPageAndDatabase.BLOCK_ID]?.[ SearchPageAndDatabase.ACTION_ID - ] - ); + ]; + + const missingPropObject = {}; - const { parent } = Object; + const title: string | undefined = + state?.[NotionPageOrRecord.TITLE_BLOCK]?.[ + NotionPageOrRecord.TITLE_ACTION + ]; + + if (!title) { + if (!pageSelectState) { + missingPropObject[NotionPageOrRecord.TITLE_ACTION] = + "Please Provide a Title"; + } else { + const titleBlockDatabaseSelected = blocks[2] as Block; + let titleViewError; + + if (titleBlockDatabaseSelected.type == BlockType.INPUT) { + if ( + titleBlockDatabaseSelected.element.type === + "plain_text_input" + ) { + titleViewError = titleBlockDatabaseSelected?.[ + "label" + ]?.[NotionObjectTypes.TEXT] as string; + } else { + const titleBlock = blocks[3] as Block; + titleViewError = titleBlock?.["label"]?.[ + NotionObjectTypes.TEXT + ] as string; + } + } + + missingPropObject[ + NotionPageOrRecord.TITLE_ACTION + ] = `Please Provide ${titleViewError}`; + } + } + if (!pageSelectState) { + missingPropObject[SearchPageAndDatabase.ACTION_ID] = + "Please Select a Page or Database"; + } + + if (Object.keys(missingPropObject).length) { + return this.context.getInteractionResponder().viewErrorResponse({ + viewId: view.id, + errors: missingPropObject, + }); + } + + const Objects: IPage | IDatabase = JSON.parse( + pageSelectState as string + ); + const { parent } = Objects; const parentType: string = parent.type; if (parentType.includes(NotionObjectTypes.PAGE_ID)) { @@ -254,11 +357,17 @@ export class ExecuteViewSubmitHandler { room, oAuth2Storage, modalInteraction, - Object as IPage + Objects as IPage ); } - return this.handleCreationOfRecord(); + return this.handleCreationOfRecord( + tokenInfo, + room, + oAuth2Storage, + modalInteraction, + Objects as IDatabase + ); } private async handleCreationOfPage( @@ -288,8 +397,62 @@ export class ExecuteViewSubmitHandler { this.app.getLogger().error(createdPage.message); message = `🚫 Something went wrong while creating page in **${workspace_name}**.`; } else { - const { name, link, title } = createdPage; + const { name, link, title, pageId } = createdPage; message = `✨ Your Page [**${title}**](${link}) is created successfully as a subpage in **${name}**.`; + + const preserveMessage = await modalInteraction.getInputElementState( + ActionButton.SEND_TO_NEW_PAGE_MESSAGE_ACTION + ); + + if (preserveMessage) { + const preserveMessageContext = preserveMessage as { + id: string; + text: string; + room: IRoom; + }; + + const { id, text } = preserveMessageContext; + + const appendBlock = await NotionSdk.appendMessageBlock( + access_token, + text, + pageId + ); + + if (appendBlock instanceof Error) { + this.app.getLogger().error(appendBlock.message); + message = `🚫 Something went wrong while appending message in **${workspace_name}**.`; + await sendNotification(this.read, this.modify, user, room, { + message, + }); + } else { + const { type, displayName } = preserveMessageContext.room; + const urlPath = + type === RoomType.CHANNEL + ? "channel" + : type === RoomType.PRIVATE_GROUP + ? "group" + : "direct"; + const { siteUrl } = (await getCredentials( + this.read, + this.modify, + user, + room + )) as ICredential; + + const messageLink = `${siteUrl}/${urlPath}/${displayName}?msg=${id}`; + const preserveText = `📝 Created New Page [**${title}**](${link}) and Preserved Following [Message](${messageLink}) `; + + await sendMessage( + this.read, + this.modify, + user, + room, + { message: preserveText }, + id + ); + } + } } await sendNotification(this.read, this.modify, user, room, { @@ -299,7 +462,123 @@ export class ExecuteViewSubmitHandler { return this.context.getInteractionResponder().successResponse(); } - private async handleCreationOfRecord(): Promise { + private async handleCreationOfRecord( + tokenInfo: ITokenInfo, + room: IRoom, + oAuth2Storage: OAuth2Storage, + modalInteraction: ModalInteractionStorage, + database: IDatabase + ): Promise { + const { view, user } = this.context.getInteractionData(); + const { state } = view; + const { NotionSdk } = this.app.getUtils(); + const { access_token, workspace_name, owner } = tokenInfo; + const username = owner.user.name; + + const properties = (await modalInteraction.getInputElementState( + SearchPageAndDatabase.ACTION_ID + )) as object; + + const propertyElements = + await modalInteraction.getAllInteractionActionId(); + + const data = await this.getPagePropParamObject( + state, + properties, + propertyElements + ); + + const createdRecord = await NotionSdk.createRecord( + access_token, + database, + data + ); + + let message: string; + + if (createdRecord instanceof Error) { + this.app.getLogger().error(createdRecord.message); + message = `🚫 Something went wrong while creating record in **${workspace_name}**.`; + await sendNotification(this.read, this.modify, user, room, { + message, + }); + } else { + const { info } = database; + const databasename = info.name; + const databaselink = info.link; + const title: string = + state?.[NotionPageOrRecord.TITLE_BLOCK]?.[ + NotionPageOrRecord.TITLE_ACTION + ]; + const { fields, url, pageId } = createdRecord; + + message = `✨ Created [**${title}**](${url}) in [**${databasename}**](${databaselink})`; + + const messageId = await sendMessageWithAttachments( + this.read, + this.modify, + user, + room, + { + message: message, + fields, + } + ); + + const preserveMessage = await modalInteraction.getInputElementState( + ActionButton.SEND_TO_NEW_PAGE_MESSAGE_ACTION + ); + + if (preserveMessage) { + const preserveMessageContext = preserveMessage as { + id: string; + text: string; + room: IRoom; + }; + const { id, text } = preserveMessageContext; + const appendBlock = await NotionSdk.appendMessageBlock( + access_token, + text, + pageId + ); + + if (appendBlock instanceof Error) { + this.app.getLogger().error(appendBlock.message); + message = `🚫 Something went wrong while appending message in **${workspace_name}**.`; + await sendNotification(this.read, this.modify, user, room, { + message, + }); + } else { + const { type, displayName } = preserveMessageContext.room; + const urlPath = + type === RoomType.CHANNEL + ? "channel" + : type === RoomType.PRIVATE_GROUP + ? "group" + : "direct"; + + const { siteUrl } = (await getCredentials( + this.read, + this.modify, + user, + room + )) as ICredential; + + const messageLink = `${siteUrl}/${urlPath}/${displayName}?msg=${id}`; + const preserveText = `📝 Created [**${title}**](${url}) Page and Preserved Following [Message](${messageLink}) `; + + await sendMessage( + this.read, + this.modify, + user, + room, + { message: preserveText }, + id + ); + } + } + } + return this.context.getInteractionResponder().successResponse(); } @@ -354,4 +633,450 @@ export class ExecuteViewSubmitHandler { await roomInteractionStorage.clearInteractionRoomId(); return this.context.getInteractionResponder().successResponse(); } + + private async getPagePropParamObject( + state: object | undefined, + properties: object, + propertyElements: { data: object[] } | undefined + ): Promise { + const title: string = + state?.[NotionPageOrRecord.TITLE_BLOCK]?.[ + NotionPageOrRecord.TITLE_ACTION + ]; + + const { label } = await getTitleProperty(properties); + + const data: object = { + [label]: { + [NotionObjectTypes.TITLE]: markdownToRichText(title), + }, + }; + + const propertyValues: object = + state?.[NotionPageOrRecord.PROPERTY_SELECTED_BLOCK_ELEMENT]; + + propertyElements?.data?.forEach((propertyInfo: object) => { + const propertyObject: object = + propertyInfo?.[NotionObjectTypes.OBJECT]; + const propertyName: string = + propertyObject?.[NotionObjectTypes.NAME]; + const propertyType: string = + propertyObject?.[NotionObjectTypes.TYPE]; + const actionId: string = propertyInfo?.[Modals.VALUE]; + const propertyValue: string | Array | undefined = + propertyValues?.[actionId]; + + switch (propertyType) { + case PropertyTypeValue.CHECKBOX: { + data[propertyName] = { + [PropertyTypeValue.CHECKBOX]: + propertyValue == CheckboxEnum.TRUE, + }; + break; + } + case PropertyTypeValue.TEXT: { + if (propertyValue) { + data[propertyName] = { + [PropertyTypeValue.TEXT]: markdownToRichText( + propertyValue as string + ), + }; + } + break; + } + case PropertyTypeValue.NUMBER: { + if (propertyValue) { + data[propertyName] = { + [PropertyTypeValue.NUMBER]: Number(propertyValue), + }; + } + + break; + } + case PropertyTypeValue.URL: { + data[propertyName] = { + [PropertyTypeValue.URL]: propertyValue + ? propertyValue + : null, + }; + break; + } + case PropertyTypeValue.EMAIL: { + if (propertyValue) { + data[propertyName] = { + [PropertyTypeValue.EMAIL]: propertyValue, + }; + } + + break; + } + case PropertyTypeValue.PHONE_NUMBER: { + if (propertyValue) { + data[propertyName] = { + [PropertyTypeValue.PHONE_NUMBER]: propertyValue, + }; + } + + break; + } + case PropertyTypeValue.DATE: { + if (propertyValue) { + data[propertyName] = { + [PropertyTypeValue.DATE]: { + start: propertyValue, + }, + }; + } + + break; + } + case PropertyTypeValue.SELECT: { + if (propertyValue) { + data[propertyName] = { + [PropertyTypeValue.SELECT]: { + name: propertyValue, + }, + }; + } + + break; + } + case PropertyTypeValue.PEOPLE: { + const people: Array = []; + if (propertyValue) { + (propertyValue as Array)?.forEach((element) => { + people.push(JSON.parse(element)); + }); + data[propertyName] = { + [PropertyTypeValue.PEOPLE]: people, + }; + } + break; + } + case PropertyTypeValue.MULTI_SELECT: { + if (propertyValue) { + const multiSelect: Array = []; + (propertyValue as Array)?.forEach((element) => { + multiSelect.push({ name: element }); + }); + data[propertyName] = { + [PropertyTypeValue.MULTI_SELECT]: multiSelect, + }; + } + break; + } + case "status": { + if (propertyValue) { + data[propertyName] = { + status: { + name: propertyValue, + }, + }; + } + + break; + } + } + }); + + return data; + } + + public async handleSharePage( + room: IRoom, + oAuth2Storage: OAuth2Storage, + modalInteraction: ModalInteractionStorage + ): Promise { + const { view, user } = this.context.getInteractionData(); + const { state } = view; + + const { NotionSdk } = this.app.getUtils(); + const tokenInfo = await oAuth2Storage.getCurrentWorkspace(user.id); + + if (!tokenInfo) { + await sendNotificationWithConnectBlock( + this.app, + user, + this.read, + this.modify, + room + ); + return this.context.getInteractionResponder().errorResponse(); + } + + const { workspace_name, owner, access_token } = tokenInfo; + const pageId: string | undefined = + state?.[SearchPage.BLOCK_ID]?.[SharePage.ACTION_ID]; + + if (!pageId) { + return this.context.getInteractionResponder().viewErrorResponse({ + viewId: view.id, + errors: { + [SharePage.ACTION_ID]: "Please Select a Page to Share", + }, + }); + } + + const pageInfo = await NotionSdk.retrievePage(access_token, pageId); + + if (pageInfo instanceof Error) { + return this.context.getInteractionResponder().errorResponse(); + } + + const { name, parent, url } = pageInfo; + + const message = `Sharing [**${name}**](${url}) from **${workspace_name}✨ **`; + const viewPage = ButtonInSectionComponent( + { + app: this.app, + buttonText: SharePage.VIEW, + value: JSON.stringify(pageInfo), + style: ButtonStyle.PRIMARY, + text: message, + }, + { + blockId: SharePage.VIEW_PAGE_BLOCK, + actionId: SharePage.VIEW_PAGE_ACTION, + } + ); + + await sendMessage(this.read, this.modify, user, room, { + blocks: [viewPage], + }); + + return this.context.getInteractionResponder().successResponse(); + } + + public async handleSendMessagePage( + room: IRoom, + oAuth2Storage: OAuth2Storage, + modalInteraction: ModalInteractionStorage + ): Promise { + const { view, user } = this.context.getInteractionData(); + const { state } = view; + + const { NotionSdk } = this.app.getUtils(); + const tokenInfo = await oAuth2Storage.getCurrentWorkspace(user.id); + + if (!tokenInfo) { + await sendNotificationWithConnectBlock( + this.app, + user, + this.read, + this.modify, + room + ); + return this.context.getInteractionResponder().errorResponse(); + } + + const { workspace_name, owner, access_token } = tokenInfo; + const pageId: string | undefined = + state?.[SearchPage.BLOCK_ID]?.[SendMessagePage.ACTION_ID]; + + if (!pageId) { + return this.context.getInteractionResponder().viewErrorResponse({ + viewId: view.id, + errors: { + [SendMessagePage.ACTION_ID]: + "Please Select a Page to Share", + }, + }); + } + + const preserveMessage = await modalInteraction.getInputElementState( + ActionButton.SEND_TO_PAGE_MESSAGE_ACTION + ); + + if (preserveMessage) { + const preserveMessageContext = preserveMessage as { + id: string; + text: string; + room: IRoom; + }; + const { id, text } = preserveMessageContext; + const appendBlock = await NotionSdk.appendMessageBlock( + access_token, + text, + pageId + ); + + if (appendBlock instanceof Error) { + this.app.getLogger().error(appendBlock.message); + const message = `🚫 Something went wrong while appending message in **${workspace_name}**.`; + await sendNotification(this.read, this.modify, user, room, { + message, + }); + } else { + const pageInfo = await NotionSdk.retrievePage( + access_token, + pageId + ); + + if (pageInfo instanceof Error) { + this.app.getLogger().error(pageInfo.message); + return this.context + .getInteractionResponder() + .errorResponse(); + } + + const { name, url } = pageInfo; + const { type, displayName } = preserveMessageContext.room; + + const urlPath = + type === RoomType.CHANNEL + ? "channel" + : type === RoomType.PRIVATE_GROUP + ? "group" + : "direct"; + + const { siteUrl } = (await getCredentials( + this.read, + this.modify, + user, + room + )) as ICredential; + + const messageLink = `${siteUrl}/${urlPath}/${displayName}?msg=${id}`; + const preserveText = `📝 Preserved Following [Message](${messageLink}) in [**${name}**](${url}) `; + + await sendMessage( + this.read, + this.modify, + user, + room, + { message: preserveText }, + id + ); + } + } + + return this.context.getInteractionResponder().successResponse(); + } + + public async handleViewTable( + room: IRoom, + oAuth2Storage: OAuth2Storage, + modalInteraction: ModalInteractionStorage + ): Promise { + const { view, user } = this.context.getInteractionData(); + const { state } = view; + + const { NotionSdk } = this.app.getUtils(); + const tokenInfo = await oAuth2Storage.getCurrentWorkspace(user.id); + + if (!tokenInfo) { + await sendNotificationWithConnectBlock( + this.app, + user, + this.read, + this.modify, + room + ); + return this.context.getInteractionResponder().errorResponse(); + } + + const databaseObject: string | undefined = + state?.[SearchDatabaseComponent.BLOCK_ID]?.[NotionTable.ACTION_ID]; + + if (!databaseObject) { + return this.context.getInteractionResponder().viewErrorResponse({ + viewId: view.id, + errors: { + [NotionTable.ACTION_ID]: "Please Select a Database to View", + }, + }); + } + const { workspace_name, owner, access_token } = tokenInfo; + const DatabaseInfo: IDatabase = JSON.parse(databaseObject); + const { parent, info } = DatabaseInfo; + const { database_id } = parent; + const { name } = info; + + const response = await NotionSdk.queryDatabasePages( + access_token, + database_id + ); + + if (response instanceof Error) { + return this.context.getInteractionResponder().errorResponse(); + } + + const tableString = table(response, { + columnDefault: { + verticalAlignment: "middle", + wrapWord: true, + truncate: 100, + }, + header: { + alignment: "center", + content: name, + }, + }); + + const tableText = `\`\`\`\n${tableString}\n\`\`\``; + + await sendNotification(this.read, this.modify, user, room, { + message: tableText, + }); + + return this.context.getInteractionResponder().successResponse(); + } + + private async handleViewNotionPage( + room: IRoom, + oAuth2Storage: OAuth2Storage, + modalInteraction: ModalInteractionStorage + ): Promise { + const { view, user } = this.context.getInteractionData(); + const { state } = view; + + const { NotionSdk } = this.app.getUtils(); + const tokenInfo = await oAuth2Storage.getCurrentWorkspace(user.id); + + if (!tokenInfo) { + await sendNotificationWithConnectBlock( + this.app, + user, + this.read, + this.modify, + room + ); + return this.context.getInteractionResponder().errorResponse(); + } + + const { workspace_name, owner, access_token } = tokenInfo; + const pageId: string | undefined = + state?.[SearchPage.BLOCK_ID]?.[NotionPage.ACTION_ID]; + + if (!pageId) { + return this.context.getInteractionResponder().viewErrorResponse({ + viewId: view.id, + errors: { + [NotionPage.ACTION_ID]: "Please Select a Page to View", + }, + }); + } + const pageInfo = await NotionSdk.retrievePage(access_token, pageId); + + if (pageInfo instanceof Error) { + return this.context.getInteractionResponder().errorResponse(); + } + + const pageMrkdwn = await NotionSdk.retrievePageContent( + access_token, + pageId + ); + + if (pageMrkdwn instanceof Error) { + return this.context.getInteractionResponder().errorResponse(); + } + + const { name, url } = pageInfo; + const message = `# ${name}\n` + pageMrkdwn; + await sendNotification(this.read, this.modify, user, room, { + message: message, + }); + + return this.context.getInteractionResponder().successResponse(); + } } diff --git a/src/handlers/Handler.ts b/src/handlers/Handler.ts index 0cbe083..4a39b2d 100644 --- a/src/handlers/Handler.ts +++ b/src/handlers/Handler.ts @@ -21,6 +21,20 @@ import { NotionPageOrRecord } from "../../enum/modals/NotionPageOrRecord"; import { createPageOrRecordModal } from "../modals/createPageOrRecordModal"; import { changeWorkspaceModal } from "../modals/changeWorkspaceModal"; import { NotionWorkspace } from "../../enum/modals/NotionWorkspace"; +import { SearchPageAndDatabase } from "../../enum/modals/common/SearchPageAndDatabaseComponent"; +import { NotionOwnerType } from "../../enum/Notion"; +import { PropertyTypeValue } from "../../enum/modals/common/NotionProperties"; +import { INotionUser } from "../../definition/authorization/IOAuth2Storage"; +import { sharePageModal } from "../modals/sharePageModal"; +import { SharePage } from "../../enum/modals/SharePage"; +import { IMessage } from "@rocket.chat/apps-engine/definition/messages"; +import { ActionButton } from "../../enum/modals/common/ActionButtons"; +import { SendMessagePage } from "../../enum/modals/SendMessagePage"; +import { sendMessagePageModal } from "../modals/sendMessagePageModal"; +import { NotionTable } from "../../enum/modals/NotionTable"; +import { viewNotionTableModal } from "../modals/viewNotionTableModal"; +import { NotionPage } from "../../enum/modals/NotionPage"; +import { viewNotionPageModal } from "../modals/viewNotionPageModal"; export class Handler implements IHandler { public app: NotionApp; @@ -178,7 +192,10 @@ export class Handler implements IHandler { } } - public async createNotionPageOrRecord(update?: boolean): Promise { + public async createNotionPageOrRecord( + update?: boolean, + message?: IMessage + ): Promise { const userId = this.sender.id; const roomId = this.room.id; const tokenInfo = await this.oAuth2Storage.getCurrentWorkspace(userId); @@ -202,11 +219,15 @@ export class Handler implements IHandler { NotionPageOrRecord.VIEW_ID ); - const { workspace_id } = tokenInfo; + const { workspace_id, access_token } = tokenInfo; await Promise.all([ this.roomInteractionStorage.storeInteractionRoomId(roomId), modalInteraction.clearPagesOrDatabase(workspace_id), + modalInteraction.clearInputElementState( + SearchPageAndDatabase.ACTION_ID + ), + modalInteraction.clearAllInteractionActionId(), ]); const modal = await createPageOrRecordModal( @@ -241,11 +262,44 @@ export class Handler implements IHandler { return; } + await modalInteraction.clearInputElementState( + ActionButton.SEND_TO_NEW_PAGE_MESSAGE_ACTION + ); + + if (message) { + await modalInteraction.storeInputElementState( + ActionButton.SEND_TO_NEW_PAGE_MESSAGE_ACTION, + message + ); + } + await this.modify .getUiController() .openSurfaceView(modal, { triggerId }, this.sender); } + const { NotionSdk } = this.app.getUtils(); + const users = await NotionSdk.retrieveAllUsers(access_token); + + if (users instanceof Error) { + this.app.getLogger().error(users.message); + } else { + let people: Array = []; + + users.forEach((person) => { + if (person.type.includes(NotionOwnerType.PERSON)) { + people.push(person as INotionUser); + } + }); + + await modalInteraction.storeInputElementState( + PropertyTypeValue.PEOPLE, + { + people, + } + ); + } + return; } @@ -294,4 +348,227 @@ export class Handler implements IHandler { .openSurfaceView(modal, { triggerId }, this.sender); } } -} + + public async shareNotionPage(): Promise { + const userId = this.sender.id; + const roomId = this.room.id; + const tokenInfo = await this.oAuth2Storage.getCurrentWorkspace(userId); + + if (!tokenInfo) { + await sendNotificationWithConnectBlock( + this.app, + this.sender, + this.read, + this.modify, + this.room + ); + return; + } + + const persistenceRead = this.read.getPersistenceReader(); + const modalInteraction = new ModalInteractionStorage( + this.persis, + persistenceRead, + userId, + SharePage.VIEW_ID + ); + + const { workspace_id } = tokenInfo; + + await modalInteraction.clearPagesOrDatabase(workspace_id); + await this.roomInteractionStorage.storeInteractionRoomId(roomId); + + const modal = await sharePageModal( + this.app, + this.sender, + this.read, + this.persis, + this.modify, + this.room, + modalInteraction, + tokenInfo + ); + + if (modal instanceof Error) { + // Something went Wrong Probably SearchPageComponent Couldn't Fetch the Pages + this.app.getLogger().error(modal.message); + return; + } + + const triggerId = this.triggerId; + + if (triggerId) { + await this.modify + .getUiController() + .openSurfaceView(modal, { triggerId }, this.sender); + } + } + + public async sendToNotionPage(message: IMessage): Promise { + const userId = this.sender.id; + const roomId = this.room.id; + const tokenInfo = await this.oAuth2Storage.getCurrentWorkspace(userId); + + if (!tokenInfo) { + await sendNotificationWithConnectBlock( + this.app, + this.sender, + this.read, + this.modify, + this.room + ); + return; + } + + const persistenceRead = this.read.getPersistenceReader(); + const modalInteraction = new ModalInteractionStorage( + this.persis, + persistenceRead, + userId, + SendMessagePage.VIEW_ID + ); + + const { workspace_id } = tokenInfo; + + await modalInteraction.clearPagesOrDatabase(workspace_id); + await this.roomInteractionStorage.storeInteractionRoomId(roomId); + + const modal = await sendMessagePageModal( + this.app, + this.sender, + this.read, + this.persis, + this.modify, + this.room, + modalInteraction, + tokenInfo + ); + + if (modal instanceof Error) { + // Something went Wrong Probably SearchPageComponent Couldn't Fetch the Pages + this.app.getLogger().error(modal.message); + return; + } + + const triggerId = this.triggerId; + + if (triggerId) { + await modalInteraction.storeInputElementState( + ActionButton.SEND_TO_PAGE_MESSAGE_ACTION, + message + ); + + await this.modify + .getUiController() + .openSurfaceView(modal, { triggerId }, this.sender); + } + } + + public async viewNotionTable(): Promise { + const userId = this.sender.id; + const roomId = this.room.id; + const tokenInfo = await this.oAuth2Storage.getCurrentWorkspace(userId); + + if (!tokenInfo) { + await sendNotificationWithConnectBlock( + this.app, + this.sender, + this.read, + this.modify, + this.room + ); + return; + } + await this.roomInteractionStorage.storeInteractionRoomId(roomId); + + const persistenceRead = this.read.getPersistenceReader(); + const modalInteraction = new ModalInteractionStorage( + this.persis, + persistenceRead, + userId, + NotionTable.VIEW_ID + ); + + const { workspace_id } = tokenInfo; + await modalInteraction.clearPagesOrDatabase(workspace_id); + + const modal = await viewNotionTableModal( + this.app, + this.sender, + this.read, + this.persis, + this.modify, + this.room, + modalInteraction, + tokenInfo + ); + + if (modal instanceof Error) { + // Something went Wrong Probably SearchPageComponent Couldn't Fetch the Pages + this.app.getLogger().error(modal.message); + return; + } + + const triggerId = this.triggerId; + + if (triggerId) { + await this.modify + .getUiController() + .openSurfaceView(modal, { triggerId }, this.sender); + } + } + + public async viewNotionPage(): Promise { + const userId = this.sender.id; + const roomId = this.room.id; + const tokenInfo = await this.oAuth2Storage.getCurrentWorkspace(userId); + + if (!tokenInfo) { + await sendNotificationWithConnectBlock( + this.app, + this.sender, + this.read, + this.modify, + this.room + ); + return; + } + await this.roomInteractionStorage.storeInteractionRoomId(roomId); + + const persistenceRead = this.read.getPersistenceReader(); + const modalInteraction = new ModalInteractionStorage( + this.persis, + persistenceRead, + userId, + NotionPage.VIEW_ID + ); + + const { workspace_id } = tokenInfo; + await modalInteraction.clearPagesOrDatabase(workspace_id); + + const modal = await viewNotionPageModal( + this.app, + this.sender, + this.read, + this.persis, + this.modify, + this.room, + modalInteraction, + tokenInfo + ); + + if (modal instanceof Error) { + // Something went Wrong Probably SearchPageComponent Couldn't Fetch the Pages + this.app.getLogger().error(modal.message); + return; + } + + const triggerId = this.triggerId; + + if (triggerId) { + await this.modify + .getUiController() + .openSurfaceView(modal, { triggerId }, this.sender); + } + } +} \ No newline at end of file diff --git a/src/helper/getPropertiesIdsObject.ts b/src/helper/getPropertiesIdsObject.ts new file mode 100644 index 0000000..10254bc --- /dev/null +++ b/src/helper/getPropertiesIdsObject.ts @@ -0,0 +1,21 @@ +import { NotionObjectTypes } from "../../enum/Notion"; +import { NotSupportedPropertyTypes } from "../../enum/modals/common/NotionProperties"; + +export function getPropertiesIdsObject(properties: object): { + [key: string]: boolean; +} { + let result = {}; + + for (const [property] of Object.entries(properties)) { + const propertyObject: object = properties[property]; + const propertyType: string = propertyObject?.[NotionObjectTypes.TYPE]; + const propertyId: string = propertyObject?.[NotionObjectTypes.ID]; + + if (!propertyType.includes(NotionObjectTypes.TITLE.toString())) { + if (!NotSupportedPropertyTypes.includes(propertyType)) { + result[propertyId] = false; + } + } + } + return result; +} diff --git a/src/helper/getPropertySelectedElement.ts b/src/helper/getPropertySelectedElement.ts new file mode 100644 index 0000000..d43f8cd --- /dev/null +++ b/src/helper/getPropertySelectedElement.ts @@ -0,0 +1,191 @@ +import { Block } from "@rocket.chat/ui-kit"; +import { NotionObjectTypes } from "../../enum/Notion"; +import { inputElementComponent } from "../modals/common/inputElementComponent"; +import { NotionApp } from "../../NotionApp"; +import { + CheckboxEnum, + PropertyTypeValue, +} from "../../enum/modals/common/NotionProperties"; +import { DropDownComponent } from "../modals/common/DropDownComponent"; +import { StaticSelectOptionsParam } from "../../definition/ui-kit/Element/IStaticSelectElement"; +import { DatePickerComponent } from "../modals/common/DatePickerComponent"; +import { Modals } from "../../enum/modals/common/Modals"; +import { MultiSelectComponent } from "../modals/common/MultiSelectComponent"; +import { ModalInteractionStorage } from "../storage/ModalInteraction"; +import { INotionUser } from "../../definition/authorization/IOAuth2Storage"; +import { NotionPageOrRecord } from "../../enum/modals/NotionPageOrRecord"; +export function getPropertySelectedElement( + app: NotionApp, + actionId: string, + item: object, + modalInteraction: ModalInteractionStorage, + allUsers?: object +): Block { + const propertyObject: object = item?.[NotionObjectTypes.OBJECT]; + const propertyType: string = propertyObject?.[NotionObjectTypes.TYPE]; + const propertyName: string = propertyObject?.[NotionObjectTypes.NAME]; + let block: Block; + switch (propertyType) { + case PropertyTypeValue.DATE: { + block = DatePickerComponent( + { + app, + label: propertyName, + }, + { + actionId, + blockId: NotionPageOrRecord.PROPERTY_SELECTED_BLOCK_ELEMENT, + } + ); + break; + } + case PropertyTypeValue.CHECKBOX: { + const placeholder: string = `Select ${propertyName}...`; + const options: StaticSelectOptionsParam = [ + { + text: CheckboxEnum.YES, + value: CheckboxEnum.TRUE, + }, + { + text: CheckboxEnum.NO, + value: CheckboxEnum.FALSE, + }, + ]; + + block = DropDownComponent( + { + app, + placeholder, + text: propertyName, + options, + }, + { + blockId: NotionPageOrRecord.PROPERTY_SELECTED_BLOCK_ELEMENT, + actionId, + } + ); + break; + } + case "status": + case PropertyTypeValue.SELECT: { + const optionsObject: object = propertyObject?.[propertyType]; + const selectOptions: Array<{ + id: string; + name: string; + color: string; + }> = optionsObject?.[Modals.OPTIONS]; + + const placeholder: string = `Select ${propertyName}...`; + const options: StaticSelectOptionsParam = selectOptions.map( + (option) => { + const { name } = option; + return { + text: name, + value: name, + }; + } + ); + + block = DropDownComponent( + { app, options, placeholder, text: propertyName }, + { + blockId: NotionPageOrRecord.PROPERTY_SELECTED_BLOCK_ELEMENT, + actionId, + } + ); + + break; + } + case PropertyTypeValue.MULTI_SELECT: { + const optionsObject: object = propertyObject?.[propertyType]; + const selectOptions: Array<{ + id: string; + name: string; + color: string; + }> = optionsObject?.[Modals.OPTIONS]; + + const placeholder: string = `Select ${propertyName}...`; + const options: StaticSelectOptionsParam = selectOptions.map( + (option) => { + const { name } = option; + return { + text: name, + value: name, + }; + } + ); + + block = MultiSelectComponent( + { + app, + placeholder, + label: propertyName, + options, + }, + { + blockId: NotionPageOrRecord.PROPERTY_SELECTED_BLOCK_ELEMENT, + actionId, + } + ); + + break; + } + case PropertyTypeValue.PEOPLE: { + const persons = allUsers as object; + const people: Array = + allUsers?.[PropertyTypeValue.PEOPLE]; + const options: StaticSelectOptionsParam = people.map((user) => { + const { object, id, name } = user; + return { + text: name as string, + value: JSON.stringify({ + object, + id, + }), + }; + }); + const placeholder = `Select ${propertyName}...`; + + block = MultiSelectComponent( + { + app, + options, + placeholder, + label: propertyName, + }, + { + blockId: NotionPageOrRecord.PROPERTY_SELECTED_BLOCK_ELEMENT, + actionId, + } + ); + + break; + } + default: { + const multiline: boolean = propertyType.includes( + PropertyTypeValue.TEXT + ) + ? true + : false; + + const placeholder: string = `Enter ${propertyName}...`; + + block = inputElementComponent( + { + app, + placeholder, + label: propertyName, + multiline, + }, + { + blockId: NotionPageOrRecord.PROPERTY_SELECTED_BLOCK_ELEMENT, + actionId, + } + ); + + break; + } + } + + return block; +} diff --git a/src/helper/getTitleProperty.ts b/src/helper/getTitleProperty.ts new file mode 100644 index 0000000..5251167 --- /dev/null +++ b/src/helper/getTitleProperty.ts @@ -0,0 +1,23 @@ +import { NotionObjectTypes } from "../../enum/Notion"; + +export async function getTitleProperty( + properties: object +): Promise<{ label: string; placeholder: string }> { + const columns = Object.keys(properties); + const firstColumn = columns[0]; + const lastColumn = columns[columns.length - 1]; + + // title at first position + if (properties[firstColumn]?.type == NotionObjectTypes.TITLE) { + return { + label: firstColumn, + placeholder: `Enter ${firstColumn}...`, + }; + } + + //title at last position + return { + label: lastColumn, + placeholder: `Enter ${lastColumn}...`, + }; +} diff --git a/src/helper/message.ts b/src/helper/message.ts index 7c5998a..7947f81 100644 --- a/src/helper/message.ts +++ b/src/helper/message.ts @@ -180,3 +180,74 @@ export async function sendHelperMessageOnInstall( await modify.getCreator().finish(previewBuilder); await modify.getCreator().finish(textMessageBuilder); } + +export async function sendMessageWithAttachments( + read: IRead, + modify: IModify, + user: IUser, + room: IRoom, + content: { + message?: string; + blocks?: Array; + fields: Array; + } +): Promise { + const { message, blocks, fields } = content; + const messageBuilder = modify + .getCreator() + .startMessage() + .setSender(user) + .setRoom(room) + .setGroupable(false); + + if (message) { + messageBuilder.setText(message); + } else if (blocks) { + messageBuilder.setBlocks(blocks); + } + + messageBuilder.setAttachments([ + { + color: "#000000", + fields, + }, + ]); + + return await modify.getCreator().finish(messageBuilder); +} + +export async function sendMessage( + read: IRead, + modify: IModify, + user: IUser, + room: IRoom, + content: { message?: string; blocks?: Array }, + threadId?: string, + attachment?: IMessageAttachment +): Promise { + const { message, blocks } = content; + const messageBuilder = modify + .getCreator() + .startMessage() + .setSender(user) + .setRoom(room) + .setGroupable(false) + .setParseUrls(true); + + if (message) { + messageBuilder.setText(message); + } else if (blocks) { + messageBuilder.setBlocks(blocks); + } + + if (threadId) { + messageBuilder.setThreadId(threadId); + } + + if (attachment) { + messageBuilder.addAttachment(attachment); + } + + await modify.getCreator().finish(messageBuilder); + return; +} diff --git a/src/lib/ElementBuilder.ts b/src/lib/ElementBuilder.ts index d7d7cd0..36e2bf6 100644 --- a/src/lib/ElementBuilder.ts +++ b/src/lib/ElementBuilder.ts @@ -11,6 +11,8 @@ import { PlainTextInputElement, OverflowElement, Option, + DatePickerElement, + MultiStaticSelectElement, } from "@rocket.chat/ui-kit"; import { ButtonParam } from "../../definition/ui-kit/Element/IButtonElement"; import { ImageParam } from "../../definition/ui-kit/Element/IImageElement"; @@ -20,6 +22,8 @@ import { } from "../../definition/ui-kit/Element/IStaticSelectElement"; import { PlainTextInputParam } from "../../definition/ui-kit/Element/IPlainTextInputElement"; import { OverflowElementParam } from "../../definition/ui-kit/Element/IOverflowElement"; +import { DatePickerElementParam } from "../../definition/ui-kit/Element/IDatePickerElement"; +import { MultiStaticSelectElementParam } from "../../definition/ui-kit/Element/IMultiStaticSelectElement"; export class ElementBuilder implements IElementBuilder { constructor(private readonly appId: string) {} @@ -159,4 +163,68 @@ export class ElementBuilder implements IElementBuilder { }; return overflow; } + + public createDatePicker( + param: DatePickerElementParam, + interaction: ElementInteractionParam + ): DatePickerElement { + const { initialDate, text, dispatchActionConfig } = param; + const { blockId, actionId } = interaction; + + const datePicker: DatePickerElement = { + type: BlockElementType.DATEPICKER, + ...(text + ? { + placeholder: { + type: TextObjectType.PLAIN_TEXT, + text, + }, + } + : undefined), + initialDate, + appId: this.appId, + blockId, + actionId, + dispatchActionConfig, + }; + + return datePicker; + } + + public createMultiStaticSelect( + param: MultiStaticSelectElementParam, + interaction: ElementInteractionParam + ): MultiStaticSelectElement { + const { + text, + options, + optionGroups, + maxSelectItems, + initialOption, + initialValue, + dispatchActionConfig, + confirm, + } = param; + const { blockId, actionId } = interaction; + + const multiStaticSelect: MultiStaticSelectElement = { + type: BlockElementType.MULTI_STATIC_SELECT, + placeholder: { + type: TextObjectType.PLAIN_TEXT, + text, + }, + options, + optionGroups, + maxSelectItems, + initialOption, + initialValue, + appId: this.appId, + blockId, + actionId, + dispatchActionConfig, + confirm, + }; + + return multiStaticSelect; + } } diff --git a/src/lib/NotionSDK.ts b/src/lib/NotionSDK.ts index 6b42495..4b55e75 100644 --- a/src/lib/NotionSDK.ts +++ b/src/lib/NotionSDK.ts @@ -27,6 +27,7 @@ import { ServerError, } from "../../errors/Error"; import { + Notion, NotionApi, NotionObjectTypes, NotionOwnerType, @@ -39,6 +40,15 @@ import { getMentionObject, getWhiteSpaceTextObject, } from "../helper/getNotionObject"; +import { Modals } from "../../enum/modals/common/Modals"; +import { + CheckboxEnum, + NotSupportedPropertyTypes, + PropertyTypeValue, +} from "../../enum/modals/common/NotionProperties"; +import { IMessageAttachmentField } from "@rocket.chat/apps-engine/definition/messages"; +import { FileType, PageContent } from "../../enum/modals/common/PageContent"; +import { TextObjectType } from "@rocket.chat/ui-kit"; export class NotionSDK implements INotionSDK { baseUrl: string; @@ -556,7 +566,7 @@ export class NotionSDK implements INotionSDK { token: string, page: IPage, prop: IPageProperties - ): Promise { + ): Promise<(INotionPage & { pageId: string }) | Error> { try { const { name, parent } = page; const { title } = prop; @@ -594,9 +604,1037 @@ export class NotionSDK implements INotionSDK { title, }; + const pageId: string = response?.data?.id; + + return { + ...result, + pageId, + }; + } catch (err) { + throw new AppsEngineException(err as string); + } + } + + public async retrieveDatabase( + token: string, + database_id: string + ): Promise { + try { + const response = await this.http.get( + NotionApi.CREATE_DATABASE + `/${database_id}`, + { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": NotionApi.CONTENT_TYPE, + "User-Agent": NotionApi.USER_AGENT, + "Notion-Version": this.NotionVersion, + }, + } + ); + + if (!response.statusCode.toString().startsWith("2")) { + return this.handleErrorResponse( + response.statusCode, + `Error While retrieving Database: `, + response.content + ); + } + + const database = response.data; + const properties = response.data?.[NotionObjectTypes.PROPERTIES]; + return properties; + } catch (err) { + throw new AppsEngineException(err as string); + } + } + + private async getRecordPropertyObject(item): Promise { + const properties: object = item?.[NotionObjectTypes.PROPERTIES]; + + let result = {}; + + for (const [property] of Object.entries(properties)) { + const propertyObject: object = properties[property]; + const propertyType: string = + propertyObject?.[NotionObjectTypes.TYPE]; + const propertyName: string = + propertyObject?.[NotionObjectTypes.NAME]; + const propertyId: string = propertyObject?.[NotionObjectTypes.ID]; + + if (propertyType.includes(NotionObjectTypes.TITLE.toString())) { + result[NotionObjectTypes.TITLE] = { + id: propertyId, + name: propertyName, + type: NotionObjectTypes.TITLE, + }; + } else { + const commonProperties = { + id: propertyId, + name: propertyName, + type: propertyType, + }; + + switch (propertyType) { + case PropertyTypeValue.MULTI_SELECT: + case PropertyTypeValue.SELECT: { + const options: Array<{ + id: string; + color: string; + name: string; + }> = propertyObject?.[propertyType]?.[Modals.OPTIONS]; + + result[Modals.ADDITIONAL_PROP][propertyName] = { + ...commonProperties, + config: { + [Modals.OPTIONS]: options, + }, + }; + break; + } + case PropertyTypeValue.FORMULA: { + const expression: string = + propertyObject?.[propertyType]?.[ + NotionObjectTypes.EXPRESSION + ]; + result[Modals.ADDITIONAL_PROP][propertyName] = { + ...commonProperties, + config: { + [NotionObjectTypes.EXPRESSION]: expression, + }, + }; + break; + } + case "status": { + const options: Array<{ + id: string; + color: string; + name: string; + }> = propertyObject?.[propertyType]?.[Modals.OPTIONS]; + + const groups: Array<{ + id: string; + name: string; + color: string; + options_ids: Array; + }> = propertyObject?.[propertyType]?.[Modals.GROUPS]; + + result[Modals.ADDITIONAL_PROP][propertyName] = { + ...commonProperties, + config: { + [Modals.OPTIONS]: options, + [Modals.GROUPS]: groups, + }, + }; + + break; + } + default: { + if (!NotSupportedPropertyTypes.includes(propertyType)) { + result[Modals.ADDITIONAL_PROP][propertyName] = { + ...commonProperties, + }; + } + break; + } + } + } + } + return result; + } + + public async createRecord( + token: string, + database: IDatabase, + properties: object + ): Promise< + | { + fields: Array; + url: string; + pageId: string; + } + | Error + > { + try { + const { parent } = database; + const data = { + parent, + properties, + }; + + const response = await this.http.post(NotionApi.PAGES, { + data, + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": NotionApi.CONTENT_TYPE, + "User-Agent": NotionApi.USER_AGENT, + "Notion-Version": this.NotionVersion, + }, + }); + + if (!response.statusCode.toString().startsWith("2")) { + return this.handleErrorResponse( + response.statusCode, + `Error While creating Record: `, + response.content + ); + } + const pageInfo = response.data; + const url: string = pageInfo?.url; + const pageId: string = pageInfo?.id; + + const prop = response.data?.[NotionObjectTypes.PROPERTIES]; + const fields = await this.getFieldsFromRecord(prop); + return { + fields, + url, + pageId, + }; + } catch (err) { + throw new AppsEngineException(err as string); + } + } + + private async getFieldsFromRecord(properties: object) { + const fields: Array = []; + const propertyKeys = Object.keys(properties); + + for (let index = 0; index < propertyKeys.length; ++index) { + const propertyObject: object = properties[propertyKeys[index]]; + const propertyType: string = + propertyObject?.[NotionObjectTypes.TYPE]; + + switch (propertyType) { + case PropertyTypeValue.CHECKBOX: { + const value: boolean = propertyObject?.[propertyType]; + fields.push({ + short: true, + title: propertyKeys[index], + value: value ? CheckboxEnum.TRUE : CheckboxEnum.FALSE, + }); + break; + } + case PropertyTypeValue.TEXT: { + const value = propertyObject?.[propertyType]; + if (value?.length) { + const markdown = await this.richTextToMarkdown(value); + fields.push({ + title: propertyKeys[index], + value: markdown, + }); + } + break; + } + case PropertyTypeValue.NUMBER: { + const value: number | null = propertyObject?.[propertyType]; + if (value) { + fields.push({ + short: true, + title: propertyKeys[index], + value: value.toString(), + }); + } + break; + } + case PropertyTypeValue.URL: { + const value: string | null = propertyObject?.[propertyType]; + if (value) { + fields.push({ + short: true, + title: propertyKeys[index], + value, + }); + } + + break; + } + case PropertyTypeValue.EMAIL: { + const value: string | null = propertyObject?.[propertyType]; + if (value) { + fields.push({ + short: true, + title: propertyKeys[index], + value, + }); + } + break; + } + case PropertyTypeValue.PHONE_NUMBER: { + const value: string | null = propertyObject?.[propertyType]; + if (value) { + fields.push({ + short: true, + title: propertyKeys[index], + value, + }); + } + break; + } + case PropertyTypeValue.DATE: { + const value: object | null = propertyObject?.[propertyType]; + if (value) { + const date = value?.["start"]; + fields.push({ + short: true, + title: propertyKeys[index], + value: date, + }); + } + + break; + } + case PropertyTypeValue.SELECT: { + const value: object | null = propertyObject?.[propertyType]; + if (value) { + const selectValue = value?.[NotionObjectTypes.NAME]; + fields.push({ + short: true, + title: propertyKeys[index], + value: selectValue, + }); + } + + break; + } + case PropertyTypeValue.PEOPLE: { + const value: Array | null = + propertyObject?.[propertyType]; + let fieldValue = ""; + if (value && value.length) { + const fullLength = value.length; + value.forEach((element, index) => { + const name: string = + element?.[NotionObjectTypes.NAME]; + fieldValue += `${name}`; + + if (index < fullLength - 1) { + fieldValue += ", "; + } + }); + + fields.push({ + short: true, + title: propertyKeys[index], + value: fieldValue, + }); + } + break; + } + case PropertyTypeValue.MULTI_SELECT: { + const value: Array<{ + id: string; + name: string; + color: string; + }> | null = propertyObject?.[propertyType]; + if (value && value.length) { + const fullLength = value.length; + let MultiSelectValue = ""; + value.forEach((element, index) => { + const name: string = + element?.[NotionObjectTypes.NAME]; + MultiSelectValue += `${name}`; + + if (index < fullLength - 1) { + MultiSelectValue += ", "; + } + }); + + fields.push({ + short: true, + title: propertyKeys[index], + value: MultiSelectValue, + }); + } + break; + } + case "status": { + const value: object | null = propertyObject?.[propertyType]; + if (value) { + const statusValue = value?.[NotionObjectTypes.NAME]; + fields.push({ + short: true, + title: propertyKeys[index], + value: statusValue, + }); + } + + break; + } + } + } + + return fields; + } + + public async retrievePage( + token: string, + pageId: string + ): Promise<(IPage & { url: string }) | Error> { + try { + const response = await this.http.get( + NotionApi.PAGES + `/${pageId}`, + { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": NotionApi.CONTENT_TYPE, + "User-Agent": NotionApi.USER_AGENT, + "Notion-Version": this.NotionVersion, + }, + } + ); + + if (!response.statusCode.toString().startsWith("2")) { + return this.handleErrorResponse( + response.statusCode, + `Error While retrieving Page: `, + response.content + ); + } + + const pageInfo = response.data; + const page = (await this.getPageObjectFromResults( + pageInfo + )) as IPage; + const url: string = pageInfo?.url; + + return { + ...page, + url, + }; + } catch (err) { + throw new AppsEngineException(err as string); + } + } + + public async appendMessageBlock( + token: string, + message: string, + blockId: string + ): Promise { + try { + const response = await this.http.patch( + NotionApi.BLOCKS + `/${blockId}/children`, + { + data: { + children: [ + { + type: "callout", + callout: { + rich_text: [ + { + type: "text", + text: { + content: message, + link: null, + }, + }, + ], + icon: { + emoji: "⭐", + }, + color: "default", + }, + }, + ], + }, + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": NotionApi.CONTENT_TYPE, + "User-Agent": NotionApi.USER_AGENT, + "Notion-Version": this.NotionVersion, + }, + } + ); + + if (!response.statusCode.toString().startsWith("2")) { + return this.handleErrorResponse( + response.statusCode, + `Error While Appending Message Block: `, + response.content + ); + } + + return true; + } catch (err) { + throw new AppsEngineException(err as string); + } + } + + public async searchDatabases( + token: string + ): Promise | Error> { + try { + const response = await this.http.post(NotionApi.SEARCH, { + data: { + filter: { + value: NotionObjectTypes.DATABASE, + property: NotionObjectTypes.PROPERTY, + }, + }, + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": NotionApi.CONTENT_TYPE, + "User-Agent": NotionApi.USER_AGENT, + "Notion-Version": this.NotionVersion, + }, + }); + + if (!response.statusCode.toString().startsWith("2")) { + return this.handleErrorResponse( + response.statusCode, + `Error While Searching Databases: `, + response.content + ); + } + + const { results } = response.data; + + const result: Array = []; + results.forEach(async (item) => { + const objectType: string = item?.[NotionObjectTypes.OBJECT]; + const databaseObject = await this.getDatabaseObjectFromResults( + item + ); + + result.push(databaseObject); + }); return result; } catch (err) { throw new AppsEngineException(err as string); } } + + public async queryDatabasePages( + token: string, + databaseId: string + ): Promise> | Error> { + try { + const response = await this.http.post( + NotionApi.CREATE_DATABASE + `/${databaseId}/query`, + { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": NotionApi.CONTENT_TYPE, + "User-Agent": NotionApi.USER_AGENT, + "Notion-Version": this.NotionVersion, + }, + } + ); + + if (!response.statusCode.toString().startsWith("2")) { + return this.handleErrorResponse( + response.statusCode, + `Error While quering Database Pages List: `, + response.content + ); + } + const results = response.data?.results; + return this.getDatabaseTable(results); + } catch (err) { + throw new AppsEngineException(err as string); + } + } + + private async getDatabaseTable( + results: Array + ): Promise>> { + const tableData: Array> = []; + + if (results?.length > 0) { + for (let i = 0; i <= results.length; i++) { + tableData.push([]); + } + + const properties = results[0]?.[NotionObjectTypes.PROPERTIES]; + let propertyKeys = Object.keys(properties); + + const supportedProperties = Object.values( + PropertyTypeValue + ) as string[]; + supportedProperties.push(NotionObjectTypes.TITLE); + supportedProperties.push("status"); + + propertyKeys = propertyKeys.filter((propKey) => { + return supportedProperties.includes( + properties[propKey]?.[NotionObjectTypes.TYPE] + ); + }); + + tableData[0].push(...propertyKeys); + + results?.forEach(async (item, index) => { + const propertyItem: object = + item?.[NotionObjectTypes.PROPERTIES]; + + propertyKeys.forEach(async (key) => { + const propertyValueObject = propertyItem?.[key]; + const type: string = + propertyValueObject?.[NotionObjectTypes.TYPE]; + switch (type) { + case NotionObjectTypes.TITLE: { + const richText = propertyValueObject?.[type]; + const markdown = + richText[0]?.[NotionObjectTypes.TEXT]?.[ + "content" + ] ?? ""; + tableData[index + 1].push(markdown); + break; + } + case PropertyTypeValue.CHECKBOX: { + const checkbox: boolean = + propertyValueObject?.[type]; + if (checkbox) { + tableData[index + 1].push("✅"); + } else { + tableData[index + 1].push("❎"); + } + break; + } + case PropertyTypeValue.TEXT: { + const richText = propertyValueObject?.[type]; + const markdown = + richText[0]?.[NotionObjectTypes.TEXT]?.[ + "content" + ] ?? ""; + tableData[index + 1].push(markdown); + break; + } + case PropertyTypeValue.NUMBER: { + const value: number | undefined = + propertyValueObject?.[type]; + if (value) { + tableData[index + 1].push(value.toString()); + } else { + tableData[index + 1].push(""); + } + + break; + } + case PropertyTypeValue.URL: { + const url: string | null = + propertyValueObject?.[type]; + + if (url) { + tableData[index + 1].push(url); + } else { + tableData[index + 1].push(""); + } + + break; + } + case PropertyTypeValue.EMAIL: { + const email: string | null = + propertyValueObject?.[type]; + + if (email) { + tableData[index + 1].push(email); + } else { + tableData[index + 1].push(""); + } + break; + } + case PropertyTypeValue.PHONE_NUMBER: { + const phoneNumber: string | null = + propertyValueObject?.[type]; + if (phoneNumber) { + tableData[index + 1].push(phoneNumber); + } else { + tableData[index + 1].push(""); + } + break; + } + case PropertyTypeValue.DATE: { + const date: object | null = + propertyValueObject?.[type]; + if (date) { + const value = date?.["start"]; + tableData[index + 1].push(value); + } else { + tableData[index + 1].push(""); + } + break; + } + case PropertyTypeValue.SELECT: { + const select: object | null = + propertyValueObject?.[type]; + + if (select) { + const selectValue = + select?.[NotionObjectTypes.NAME]; + tableData[index + 1].push(selectValue); + } else { + tableData[index + 1].push(""); + } + break; + } + case PropertyTypeValue.PEOPLE: { + const people: Array | null = + propertyValueObject?.[type]; + + let fieldValue = ""; + if (people && people.length) { + const fullLength = people.length; + people.forEach((element, eleindex) => { + const name: string = + element?.[NotionObjectTypes.NAME]; + fieldValue += `${name}`; + + if (eleindex < fullLength - 1) { + fieldValue += ", "; + } + }); + } + + tableData[index + 1].push(fieldValue); + break; + } + case PropertyTypeValue.MULTI_SELECT: { + const multiSelect: Array<{ + id: string; + name: string; + color: string; + }> | null = propertyValueObject?.[type]; + + let MultiSelectValue = ""; + + if (multiSelect && multiSelect.length) { + const fullLength = multiSelect.length; + multiSelect.forEach((element, index) => { + const name: string = + element?.[NotionObjectTypes.NAME]; + MultiSelectValue += `${name}`; + + if (index < fullLength - 1) { + MultiSelectValue += ", "; + } + }); + } + tableData[index + 1].push(MultiSelectValue); + break; + } + case "status": { + const status: object | null = + propertyValueObject?.[type]; + if (status) { + const statusValue = + status?.[NotionObjectTypes.NAME]; + tableData[index + 1].push(statusValue); + } else { + tableData[index + 1].push(""); + } + break; + } + case PropertyTypeValue.CREATED_BY: { + const createdBy = propertyValueObject?.[type]; + const name: string = + createdBy?.[NotionObjectTypes.NAME]; + tableData[index + 1].push(createdBy); + break; + } + case PropertyTypeValue.CREATED_TIME: { + const createdTime = propertyValueObject?.[type]; + tableData[index + 1].push(createdTime); + break; + } + case PropertyTypeValue.LAST_EDITED_TIME: { + const lastEditedTime = propertyValueObject?.[type]; + tableData[index + 1].push(lastEditedTime); + break; + } + case PropertyTypeValue.LAST_EDITED_BY: { + const lastEditedBy = propertyValueObject?.[type]; + const name: string = + lastEditedBy?.[NotionObjectTypes.NAME]; + tableData[index + 1].push(lastEditedBy); + break; + } + case PropertyTypeValue.FORMULA: { + const formula = propertyValueObject?.[type]; + const value: string | null = formula?.["string"]; + if (value) { + tableData[index + 1].push(value); + } else { + tableData[index + 1].push(""); + } + break; + } + case PropertyTypeValue.FILES: { + const files: Array | null = + propertyValueObject?.[type]; + let filesLink = ""; + + if (files && files.length) { + files.forEach((file, index) => { + const name: string = + file?.[NotionObjectTypes.NAME]; + const url: string = + file?.["file"]?.[PropertyTypeValue.URL]; + + filesLink += `${name}`; + + if (index < files.length - 1) { + filesLink += ", "; + } + }); + } + tableData[index + 1].push(filesLink); + break; + } + } + }); + }); + } + return tableData; + } + + public async retrievePageContent( + token: string, + pageId: string + ): Promise { + try { + const response = await this.http.get( + NotionApi.BLOCKS + `/${pageId}/children`, + { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": NotionApi.CONTENT_TYPE, + "User-Agent": NotionApi.USER_AGENT, + "Notion-Version": this.NotionVersion, + }, + } + ); + + if (!response.statusCode.toString().startsWith("2")) { + return this.handleErrorResponse( + response.statusCode, + `Error While retrieving Page Content: `, + response.content + ); + } + + const results: Array = response.data?.results; + return this.getPageContentInMarkdwn(results); + } catch (err) { + throw new AppsEngineException(err as string); + } + } + + private async getPageContentInMarkdwn(results): Promise { + let pageContent = ""; + + results?.forEach((result) => { + const type = result?.[NotionObjectTypes.TYPE]; + const propertyObject = result?.[type]; + switch (type) { + case PageContent.BOOKMARK: { + const bookmark: string | undefined = + propertyObject?.[PropertyTypeValue.URL]; + + if (bookmark) { + pageContent += `[bookmark](${bookmark})\n`; + } + + break; + } + case PageContent.BREADCRUMB: { + // not supported yet from notion + break; + } + case PageContent.NUMBER_LIST_ITEM: + case PageContent.BULLET_LIST_ITEM: { + const bulletList = propertyObject?.[PropertyTypeValue.TEXT]; + if (bulletList) { + const markdown = + bulletList[0]?.[TextObjectType.PLAIN_TEXT] ?? ""; + pageContent += `- ${markdown}\n`; + } + break; + } + case PageContent.CALLOUT: { + const callout = propertyObject?.[PropertyTypeValue.TEXT]; + const emoji = + propertyObject?.[NotionObjectTypes.ICON]?.[ + NotionObjectTypes.EMOJI + ]; + if (callout) { + const markdown = + callout[0]?.[TextObjectType.PLAIN_TEXT] ?? ""; + pageContent += + "> " + + `${emoji ? `${emoji} ` : ""}` + + `${markdown}\n`; + } + break; + } + case PageContent.CHILD_PAGE: { + break; + } + case PageContent.CHILD_DATABASE: { + break; + } + case PageContent.CODE: { + const code = propertyObject?.[PropertyTypeValue.TEXT]; + if (code) { + const markdown = + code[0]?.[TextObjectType.PLAIN_TEXT] ?? ""; + pageContent += `\`\`\`\n${markdown}\n\`\`\`\n`; + } + break; + } + case PageContent.COLUMN: { + break; + } + case PageContent.COLUMN_LIST: { + break; + } + case PageContent.DIVIDER: { + pageContent += `\n`; + break; + } + case PageContent.EMBED: { + break; + } + case PageContent.EQUATION: { + const expression = + propertyObject?.[NotionObjectTypes.EXPRESSION]; + if (expression) { + pageContent += `\\[${expression}\\]\n`; + } + break; + } + case PageContent.FILE: { + const file: string | undefined = + propertyObject?.[PropertyTypeValue.URL]; + if (file) { + pageContent += `[file](${file})\n`; + } + break; + } + case PageContent.HEADING_1: { + const heading1 = propertyObject?.[PropertyTypeValue.TEXT]; + + if (heading1) { + const markdown = + heading1[0]?.[NotionObjectTypes.TEXT]?.[ + "content" + ] ?? ""; + + pageContent += `## ${markdown}\n`; + } + break; + } + case PageContent.HEADING_2: { + const heading2 = propertyObject?.[PropertyTypeValue.TEXT]; + + if (heading2) { + const markdown = + heading2[0]?.[NotionObjectTypes.TEXT]?.[ + "content" + ] ?? ""; + + pageContent += `### ${markdown}\n`; + } + break; + } + case PageContent.HEADING_3: { + const heading3 = propertyObject?.[PropertyTypeValue.TEXT]; + + if (heading3) { + const markdown = + heading3[0]?.[NotionObjectTypes.TEXT]?.[ + "content" + ] ?? ""; + + pageContent += `#### ${markdown}\n`; + } + break; + } + case PageContent.IMAGE: { + const file: { url: string } | undefined = + propertyObject?.[PageContent.FILE]; + if (file) { + pageContent += `![file](${file.url})\n`; + } + break; + } + case PageContent.LINK_PREVIEW: { + break; + } + case PageContent.PARAGRAPH: { + const paragraph = propertyObject?.[PropertyTypeValue.TEXT]; + if (paragraph) { + const markdown = + paragraph[0]?.[TextObjectType.PLAIN_TEXT] ?? ""; + pageContent += `${markdown}\n`; + } + + break; + } + case PageContent.PDF: { + const pdf: { url: string } | undefined = + propertyObject?.[PageContent.FILE]; + + if (pdf) { + pageContent += `![pdf](${pdf.url})\n`; + } + + break; + } + case PageContent.QUOTE: { + const quote = propertyObject?.[PropertyTypeValue.TEXT]; + if (quote) { + const markdown = + quote[0]?.[NotionObjectTypes.TEXT]?.["content"] ?? + ""; + + pageContent += `> ${markdown}\n`; + } + break; + } + case PageContent.SYNCED_BLOCK: { + // not handling synced block for now + break; + } + case PageContent.TABLE: { + // not handling table for now + break; + } + case PageContent.TABLE_OF_CONTENTS: { + // not handling tableofcontents for now + break; + } + case PageContent.TODO: { + const todo = propertyObject?.[PropertyTypeValue.TEXT]; + if (todo) { + const markdown = + todo[0]?.[TextObjectType.PLAIN_TEXT] ?? undefined; + const checked: boolean = propertyObject?.["checked"]; + if (markdown) { + if (checked) { + pageContent += `- [x] ${markdown}\n`; + } else { + pageContent += `- [ ] ${markdown}\n`; + } + } + } + break; + } + case PageContent.TOGGLE_BLOCKS: { + break; + } + case PageContent.VIDEO: { + const video: { url: string } | undefined = + propertyObject?.[FileType.EXTERNAL] ?? + propertyObject?.[PageContent.FILE]; + + if (video) { + pageContent += `\n![video](${video.url})\n`; + } + + break; + } + } + }); + + return pageContent; + } } diff --git a/src/modals/common/DatePickerComponent.ts b/src/modals/common/DatePickerComponent.ts new file mode 100644 index 0000000..b6b51f9 --- /dev/null +++ b/src/modals/common/DatePickerComponent.ts @@ -0,0 +1,21 @@ +import { NotionApp } from "../../../NotionApp"; +import { ElementInteractionParam } from "../../../definition/ui-kit/Element/IElementBuilder"; + +export function DatePickerComponent( + { app, label }: { app: NotionApp; label: string }, + { blockId, actionId }: ElementInteractionParam +) { + const { elementBuilder, blockBuilder } = app.getUtils(); + const datePickerElement = elementBuilder.createDatePicker( + {}, + { blockId, actionId } + ); + + const inputBlock = blockBuilder.createInputBlock({ + text: label, + element: datePickerElement, + optional: false, + }); + + return inputBlock; +} diff --git a/src/modals/common/MultiSelectComponent.ts b/src/modals/common/MultiSelectComponent.ts new file mode 100644 index 0000000..c408424 --- /dev/null +++ b/src/modals/common/MultiSelectComponent.ts @@ -0,0 +1,33 @@ +import { NotionApp } from "../../../NotionApp"; +import { ElementInteractionParam } from "../../../definition/ui-kit/Element/IElementBuilder"; +import { StaticSelectOptionsParam } from "../../../definition/ui-kit/Element/IStaticSelectElement"; + +export function MultiSelectComponent( + { + app, + placeholder, + options, + label, + }: { + app: NotionApp; + placeholder: string; + options: StaticSelectOptionsParam; + label: string; + }, + { blockId, actionId }: ElementInteractionParam +) { + const { elementBuilder, blockBuilder } = app.getUtils(); + const dropDownOption = elementBuilder.createDropDownOptions(options); + const multiSelect = elementBuilder.createMultiStaticSelect( + { text: placeholder, options: dropDownOption }, + { blockId, actionId } + ); + + const inputBlock = blockBuilder.createInputBlock({ + text: label, + element: multiSelect, + optional: false, + }); + + return inputBlock; +} diff --git a/src/modals/common/searchDatabaseComponent.ts b/src/modals/common/searchDatabaseComponent.ts new file mode 100644 index 0000000..4b0335e --- /dev/null +++ b/src/modals/common/searchDatabaseComponent.ts @@ -0,0 +1,60 @@ +import { NotionApp } from "../../../NotionApp"; +import { InputBlock } from "@rocket.chat/ui-kit"; +import { Error } from "../../../errors/Error"; +import { StaticSelectOptionsParam } from "../../../definition/ui-kit/Element/IStaticSelectElement"; +import { ModalInteractionStorage } from "../../storage/ModalInteraction"; +import { IDatabase } from "../../../definition/lib/INotion"; +import { ITokenInfo } from "../../../definition/authorization/IOAuth2Storage"; +import { Modals } from "../../../enum/modals/common/Modals"; +import { NotionObjectTypes } from "../../../enum/Notion"; +import { SearchDatabaseComponent } from "../../../enum/modals/common/SearchDatabaseComponent"; +export async function searchDatabaseComponent( + app: NotionApp, + modalInteraction: ModalInteractionStorage, + tokenInfo: ITokenInfo, + actionId: string +): Promise { + const { NotionSdk, elementBuilder, blockBuilder } = app.getUtils(); + const { access_token, workspace_id } = tokenInfo; + let Databases; + Databases = await modalInteraction.getPagesOrDatabase(workspace_id); + if (!Databases) { + Databases = await NotionSdk.searchDatabases(access_token); + if (Databases instanceof Error) { + return Databases; + } + + await modalInteraction.storePagesOrDatabase(Databases, workspace_id); + } + const accesibleDatabase: Array = Databases; + + const options: StaticSelectOptionsParam = accesibleDatabase.map((item) => { + const info = NotionObjectTypes.INFO.toString(); + const name = NotionObjectTypes.NAME.toString(); + + const text: string = item?.[info]?.[name]; + const value = JSON.stringify(item); + + return { + text, + value, + }; + }); + + const dropDownOption = elementBuilder.createDropDownOptions(options); + const dropDown = elementBuilder.addDropDown( + { + placeholder: SearchDatabaseComponent.PLACEHOLDER, + options: dropDownOption, + dispatchActionConfig: [Modals.dispatchActionConfigOnSelect], + }, + { blockId: SearchDatabaseComponent.BLOCK_ID, actionId } + ); + const inputBlock = blockBuilder.createInputBlock({ + text: SearchDatabaseComponent.LABEL, + element: dropDown, + optional: false, + }); + + return inputBlock; +} diff --git a/src/modals/createPageOrRecordModal.ts b/src/modals/createPageOrRecordModal.ts index 85169f8..d6cd623 100644 --- a/src/modals/createPageOrRecordModal.ts +++ b/src/modals/createPageOrRecordModal.ts @@ -25,6 +25,12 @@ import { OverflowMenuComponent } from "./common/OverflowMenuComponent"; import { Modals } from "../../enum/modals/common/Modals"; import { IDatabase } from "../../definition/lib/INotion"; import { getSelectDatabaseLayout } from "../helper/getSelectDatabaseLayout"; +import { getTitleProperty } from "../helper/getTitleProperty"; +import { ButtonInSectionComponent } from "./common/buttonInSectionComponent"; +import { DropDownComponent } from "./common/DropDownComponent"; +import { NotionObjectTypes } from "../../enum/Notion"; +import { getPropertySelectedElement } from "../helper/getPropertySelectedElement"; +import { PropertyTypeValue } from "../../enum/modals/common/NotionProperties"; export async function createPageOrRecordModal( app: NotionApp, @@ -35,7 +41,8 @@ export async function createPageOrRecordModal( room: IRoom, modalInteraction: ModalInteractionStorage, tokenInfo: ITokenInfo, - parent?: IDatabase + parent?: IDatabase, + addPropertyAction?: boolean ): Promise { const { elementBuilder, blockBuilder } = app.getUtils(); const divider = blockBuilder.createDividerBlock(); @@ -43,6 +50,21 @@ export async function createPageOrRecordModal( const appId = app.getID(); const overFlowMenuText = [DatabaseModal.OVERFLOW_MENU_TEXT.toString()]; const overFlowMenuValue = [DatabaseModal.OVERFLOW_MENU_ACTION.toString()]; + let properties: object | undefined; + let addedProperty: { data: Array } | undefined; + let allUsers: object | undefined; + + if (parent) { + properties = await modalInteraction.getInputElementState( + SearchPageAndDatabase.ACTION_ID + ); + + addedProperty = await modalInteraction.getAllInteractionActionId(); + + allUsers = await modalInteraction.getInputElementState( + PropertyTypeValue.PEOPLE + ); + } if (parent) { overFlowMenuText.push( @@ -95,12 +117,21 @@ export async function createPageOrRecordModal( blocks.push(SelectedDatabaseLayout); } + let labelOfPageOrRecord = NotionPageOrRecord.TITLE_LABEL.toString(); + let placeholderOfPageOrRecord = + NotionPageOrRecord.TITLE_PLACEHOLDER.toString(); + + if (parent && properties) { + const { label, placeholder } = await getTitleProperty(properties); + labelOfPageOrRecord = `${label} *`; + placeholderOfPageOrRecord = placeholder; + } const titleOfPageOrRecordBlock = inputElementComponent( { app, - placeholder: NotionPageOrRecord.TITLE_PLACEHOLDER, - label: NotionPageOrRecord.TITLE_LABEL, + placeholder: placeholderOfPageOrRecord, + label: labelOfPageOrRecord, optional: false, }, { @@ -111,6 +142,30 @@ export async function createPageOrRecordModal( blocks.push(titleOfPageOrRecordBlock); + if (parent && addedProperty) { + const data = addedProperty.data; + data.forEach(async (item, index) => { + if (index === 0) { + blocks.push(divider); + } + + const selectedPropertyTypeElementActionId = item?.[Modals.VALUE]; + if (selectedPropertyTypeElementActionId) { + const PropertySelectedElement = getPropertySelectedElement( + app, + selectedPropertyTypeElementActionId, + item, + modalInteraction, + allUsers + ); + + blocks.push(PropertySelectedElement); + } + + blocks.push(divider); + }); + } + const submit = elementBuilder.addButton( { text: NotionPageOrRecord.CREATE, style: ButtonStyle.PRIMARY }, { diff --git a/src/modals/sendMessagePageModal.ts b/src/modals/sendMessagePageModal.ts new file mode 100644 index 0000000..045c26d --- /dev/null +++ b/src/modals/sendMessagePageModal.ts @@ -0,0 +1,77 @@ +import { + IModify, + IPersistence, + IRead, + IUIKitSurfaceViewParam, +} from "@rocket.chat/apps-engine/definition/accessors"; +import { ITokenInfo } from "../../definition/authorization/IOAuth2Storage"; +import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; +import { IUser } from "@rocket.chat/apps-engine/definition/users"; +import { NotionApp } from "../../NotionApp"; +import { Error } from "../../errors/Error"; +import { searchPageComponent } from "./common/searchPageComponent"; +import { ModalInteractionStorage } from "../storage/ModalInteraction"; +import { Block, TextObjectType } from "@rocket.chat/ui-kit"; +import { getConnectPreview } from "../helper/getConnectLayout"; +import { + ButtonStyle, + UIKitSurfaceType, +} from "@rocket.chat/apps-engine/definition/uikit"; +import { SendMessagePage } from "../../enum/modals/SendMessagePage"; + +export async function sendMessagePageModal( + app: NotionApp, + user: IUser, + read: IRead, + persistence: IPersistence, + modify: IModify, + room: IRoom, + modalInteraction: ModalInteractionStorage, + tokenInfo: ITokenInfo +): Promise { + const blocks: Block[] = []; + const { elementBuilder } = app.getUtils(); + const connectBlock = getConnectPreview(app.getID(), tokenInfo); + blocks.push(connectBlock); + + const searchForPageComponent = await searchPageComponent( + app, + modalInteraction, + tokenInfo, + SendMessagePage.ACTION_ID + ); + + if (searchForPageComponent instanceof Error) { + return searchForPageComponent; + } + + blocks.push(searchForPageComponent); + + const submit = elementBuilder.addButton( + { text: SendMessagePage.SEND, style: ButtonStyle.PRIMARY }, + { + actionId: SendMessagePage.SEND_ACTION, + blockId: SendMessagePage.SEND_BLOCK, + } + ); + + const close = elementBuilder.addButton( + { text: SendMessagePage.CANCEL, style: ButtonStyle.DANGER }, + { + actionId: SendMessagePage.CANCEL_ACTION, + blockId: SendMessagePage.CANCEL_BLOCK, + } + ); + + return { + id: SendMessagePage.VIEW_ID, + type: UIKitSurfaceType.MODAL, + title: { + type: TextObjectType.MRKDWN, + text: SendMessagePage.TITLE, + }, + blocks, + close, + submit, + }; +} diff --git a/src/modals/sharePageModal.ts b/src/modals/sharePageModal.ts new file mode 100644 index 0000000..0050e35 --- /dev/null +++ b/src/modals/sharePageModal.ts @@ -0,0 +1,77 @@ +import { + IModify, + IPersistence, + IRead, + IUIKitSurfaceViewParam, +} from "@rocket.chat/apps-engine/definition/accessors"; +import { ITokenInfo } from "../../definition/authorization/IOAuth2Storage"; +import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; +import { IUser } from "@rocket.chat/apps-engine/definition/users"; +import { NotionApp } from "../../NotionApp"; +import { Error } from "../../errors/Error"; +import { searchPageComponent } from "./common/searchPageComponent"; +import { ModalInteractionStorage } from "../storage/ModalInteraction"; +import { SharePage } from "../../enum/modals/SharePage"; +import { Block, TextObjectType } from "@rocket.chat/ui-kit"; +import { getConnectPreview } from "../helper/getConnectLayout"; +import { + ButtonStyle, + UIKitSurfaceType, +} from "@rocket.chat/apps-engine/definition/uikit"; + +export async function sharePageModal( + app: NotionApp, + user: IUser, + read: IRead, + persistence: IPersistence, + modify: IModify, + room: IRoom, + modalInteraction: ModalInteractionStorage, + tokenInfo: ITokenInfo +): Promise { + const blocks: Block[] = []; + const { elementBuilder } = app.getUtils(); + const connectBlock = getConnectPreview(app.getID(), tokenInfo); + blocks.push(connectBlock); + + const searchForPageComponent = await searchPageComponent( + app, + modalInteraction, + tokenInfo, + SharePage.ACTION_ID + ); + + if (searchForPageComponent instanceof Error) { + return searchForPageComponent; + } + + blocks.push(searchForPageComponent); + + const submit = elementBuilder.addButton( + { text: SharePage.SHARE, style: ButtonStyle.PRIMARY }, + { + actionId: SharePage.SHARE_ACTION, + blockId: SharePage.SHARE_BLOCK, + } + ); + + const close = elementBuilder.addButton( + { text: SharePage.CLOSE, style: ButtonStyle.DANGER }, + { + actionId: SharePage.CLOSE_ACTION, + blockId: SharePage.CLOSE_BLOCK, + } + ); + + return { + id: SharePage.VIEW_ID, + type: UIKitSurfaceType.MODAL, + title: { + type: TextObjectType.MRKDWN, + text: SharePage.TITLE, + }, + blocks, + close, + submit, + }; +} diff --git a/src/modals/viewNotionPageModal.ts b/src/modals/viewNotionPageModal.ts new file mode 100644 index 0000000..0d7897d --- /dev/null +++ b/src/modals/viewNotionPageModal.ts @@ -0,0 +1,77 @@ +import { + IModify, + IPersistence, + IRead, + IUIKitSurfaceViewParam, +} from "@rocket.chat/apps-engine/definition/accessors"; +import { ITokenInfo } from "../../definition/authorization/IOAuth2Storage"; +import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; +import { IUser } from "@rocket.chat/apps-engine/definition/users"; +import { NotionApp } from "../../NotionApp"; +import { Error } from "../../errors/Error"; +import { searchPageComponent } from "./common/searchPageComponent"; +import { ModalInteractionStorage } from "../storage/ModalInteraction"; +import { Block, TextObjectType } from "@rocket.chat/ui-kit"; +import { getConnectPreview } from "../helper/getConnectLayout"; +import { + ButtonStyle, + UIKitSurfaceType, +} from "@rocket.chat/apps-engine/definition/uikit"; +import { NotionPage } from "../../enum/modals/NotionPage"; + +export async function viewNotionPageModal( + app: NotionApp, + user: IUser, + read: IRead, + persistence: IPersistence, + modify: IModify, + room: IRoom, + modalInteraction: ModalInteractionStorage, + tokenInfo: ITokenInfo +): Promise { + const blocks: Block[] = []; + const { elementBuilder } = app.getUtils(); + const connectBlock = getConnectPreview(app.getID(), tokenInfo); + blocks.push(connectBlock); + + const searchForPageComponent = await searchPageComponent( + app, + modalInteraction, + tokenInfo, + NotionPage.ACTION_ID + ); + + if (searchForPageComponent instanceof Error) { + return searchForPageComponent; + } + + blocks.push(searchForPageComponent); + + const submit = elementBuilder.addButton( + { text: NotionPage.VIEW, style: ButtonStyle.PRIMARY }, + { + actionId: NotionPage.VIEW_ACTION, + blockId: NotionPage.VIEW_BLOCK, + } + ); + + const close = elementBuilder.addButton( + { text: NotionPage.CANCEL, style: ButtonStyle.DANGER }, + { + actionId: NotionPage.CANCEL_ACTION, + blockId: NotionPage.CANCEL_BLOCK, + } + ); + + return { + id: NotionPage.VIEW_ID, + type: UIKitSurfaceType.MODAL, + title: { + type: TextObjectType.MRKDWN, + text: NotionPage.TITLE, + }, + blocks, + close, + submit, + }; +} diff --git a/src/modals/viewNotionTableModal.ts b/src/modals/viewNotionTableModal.ts new file mode 100644 index 0000000..632794f --- /dev/null +++ b/src/modals/viewNotionTableModal.ts @@ -0,0 +1,80 @@ +import { + IModify, + IPersistence, + IRead, + IUIKitSurfaceViewParam, +} from "@rocket.chat/apps-engine/definition/accessors"; +import { ITokenInfo } from "../../definition/authorization/IOAuth2Storage"; +import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; +import { IUser } from "@rocket.chat/apps-engine/definition/users"; +import { NotionApp } from "../../NotionApp"; +import { Error } from "../../errors/Error"; +import { searchPageComponent } from "./common/searchPageComponent"; +import { ModalInteractionStorage } from "../storage/ModalInteraction"; +import { SharePage } from "../../enum/modals/SharePage"; +import { Block, TextObjectType } from "@rocket.chat/ui-kit"; +import { getConnectPreview } from "../helper/getConnectLayout"; +import { + ButtonStyle, + UIKitSurfaceType, +} from "@rocket.chat/apps-engine/definition/uikit"; +import { searchDatabaseComponent } from "./common/searchDatabaseComponent"; +import { SearchDatabaseComponent } from "../../enum/modals/common/SearchDatabaseComponent"; +import { NotionTable } from "../../enum/modals/NotionTable"; + +export async function viewNotionTableModal( + app: NotionApp, + user: IUser, + read: IRead, + persistence: IPersistence, + modify: IModify, + room: IRoom, + modalInteraction: ModalInteractionStorage, + tokenInfo: ITokenInfo +): Promise { + const blocks: Block[] = []; + const { elementBuilder } = app.getUtils(); + const connectBlock = getConnectPreview(app.getID(), tokenInfo); + blocks.push(connectBlock); + + const searchForDatabaseComponent = await searchDatabaseComponent( + app, + modalInteraction, + tokenInfo, + NotionTable.ACTION_ID + ); + + if (searchForDatabaseComponent instanceof Error) { + return searchForDatabaseComponent; + } + + blocks.push(searchForDatabaseComponent); + + const submit = elementBuilder.addButton( + { text: NotionTable.VIEW, style: ButtonStyle.PRIMARY }, + { + actionId: NotionTable.VIEW_ACTION, + blockId: NotionTable.VIEW_BLOCK, + } + ); + + const close = elementBuilder.addButton( + { text: NotionTable.CANCEL, style: ButtonStyle.DANGER }, + { + actionId: NotionTable.CANCEL_ACTION, + blockId: NotionTable.CANCEL_BLOCK, + } + ); + + return { + id: NotionTable.VIEW_ID, + type: UIKitSurfaceType.MODAL, + title: { + type: TextObjectType.MRKDWN, + text: NotionTable.TITLE, + }, + blocks, + close, + submit, + }; +}