diff --git a/definition/handlers/IHandler.ts b/definition/handlers/IHandler.ts index d989aa6..1270b81 100644 --- a/definition/handlers/IHandler.ts +++ b/definition/handlers/IHandler.ts @@ -6,6 +6,8 @@ export interface IHandler extends Omit { oAuth2Storage: OAuth2Storage; roomInteractionStorage: RoomInteractionStorage; createNotionDatabase(): Promise; + commentOnPages(): Promise; + createNotionPageOrRecord(): Promise; } export type IHanderParams = Omit; diff --git a/definition/lib/INotion.ts b/definition/lib/INotion.ts index 28e2fff..6f76626 100644 --- a/definition/lib/INotion.ts +++ b/definition/lib/INotion.ts @@ -39,6 +39,14 @@ export interface INotionSDK extends INotion { pageId: string, comment: string ): Promise; + searchPagesAndDatabases( + token: string + ): Promise | Error>; + createPage( + token: string, + page: IPage, + prop: IPageProperties + ): Promise; } export interface IParentPage { @@ -50,6 +58,16 @@ export interface IPage { parent: IParentPage; } +export interface IParentDatabase { + type: NotionObjectTypes.DATABASE_ID; + database_id: string; +} + +export interface IDatabase { + info: INotionDatabase; + parent: IParentDatabase; +} + export interface INotionDatabase { name: string; link: string; @@ -88,3 +106,11 @@ interface INotionBot { }; workspace_name: string; } + +export interface IPageProperties { + title: string; +} + +export interface INotionPage extends INotionDatabase { + title: string; +} diff --git a/enum/Notion.ts b/enum/Notion.ts index 25f60cb..428a868 100644 --- a/enum/Notion.ts +++ b/enum/Notion.ts @@ -19,6 +19,7 @@ export enum NotionApi { CREATE_DATABASE = `https://api.notion.com/v1/databases`, COMMENTS = `https://api.notion.com/v1/comments`, USERS = `https://api.notion.com/v1/users`, + PAGES = `https://api.notion.com/v1/pages`, } export enum Notion { @@ -38,4 +39,9 @@ export enum NotionObjectTypes { COMMENT = "comment", PARENT = "parent", MENTION = "mention", + DATABASE_ID = "database_id", + OBJECT = "object", + TITLE = "title", + INFO = "info", + NAME = "name", } diff --git a/enum/messages.ts b/enum/messages.ts index c01ff97..3937568 100644 --- a/enum/messages.ts +++ b/enum/messages.ts @@ -2,6 +2,7 @@ export enum Messages { HELPER_COMMANDS = `• use \`/notion connect\` to connect your workspace • use \`/notion disconnect\` to disconnect workspace • use \`/notion comment\` to comment on notion page + • use \`/notion create\` to create page or record • use \`/notion create database\` to create database `, HELPER_TEXT = `:wave: Need some help with \`/notion\`?`, diff --git a/enum/modals/NotionDatabase.ts b/enum/modals/NotionDatabase.ts index 0d301bd..fc75c66 100644 --- a/enum/modals/NotionDatabase.ts +++ b/enum/modals/NotionDatabase.ts @@ -47,4 +47,6 @@ export enum DatabaseModal { REMOVE_OPTION_BLOCK = "remove-option-notion-database-block-id", SELECT_PROPERTY_OPTION_NAME = "select-property-option-name-action", PROPERTY_TYPE_TITLE = "title", + OVERFLOW_MENU_ACTION = "create-notion-database-overflow-menu-action-id", + OVERFLOW_MENU_TEXT = "Create Database", } diff --git a/enum/modals/NotionPageOrRecord.ts b/enum/modals/NotionPageOrRecord.ts new file mode 100644 index 0000000..2d10323 --- /dev/null +++ b/enum/modals/NotionPageOrRecord.ts @@ -0,0 +1,19 @@ +export enum NotionPageOrRecord { + VIEW_ID = "notion-page-or-record-view-id", + TITLE = "Create Page or Record", + CREATE = "Create", + CREATE_ACTION = "create-page-or-record-action-id", + CREATE_BLOCK = "create-page-or-record-block-id", + CLOSE = "Close", + CLOSE_ACTION = "close-page-or-record-action-id", + CLOSE_BLOCK = "close-page-or-record-block-id", + TITLE_PLACEHOLDER = "Enter Title of Page or Record", + TITLE_LABEL = "Title *", + TITLE_BLOCK = "title-page-or-record-block-id", + 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", +} diff --git a/enum/modals/common/SearchPageAndDatabaseComponent.ts b/enum/modals/common/SearchPageAndDatabaseComponent.ts new file mode 100644 index 0000000..08aeeac --- /dev/null +++ b/enum/modals/common/SearchPageAndDatabaseComponent.ts @@ -0,0 +1,6 @@ +export enum SearchPageAndDatabase { + PLACEHOLDER = "Select a Page or Database", + BLOCK_ID = "search-page-and-database-component-block-id", + ACTION_ID = "search-page-and-database-component-action-id", + LABEL = "Page or Database Name *", +} diff --git a/src/commands/CommandUtility.ts b/src/commands/CommandUtility.ts index d13069a..6188cbf 100644 --- a/src/commands/CommandUtility.ts +++ b/src/commands/CommandUtility.ts @@ -110,6 +110,10 @@ export class CommandUtility implements ICommandUtility { await handler.commentOnPages(); break; } + case CommandParam.CREATE: { + await handler.createNotionPageOrRecord(); + break; + } case CommandParam.HELP: default: { await sendHelperNotification( diff --git a/src/handlers/ExecuteBlockActionHandler.ts b/src/handlers/ExecuteBlockActionHandler.ts index 6b9b3d2..4734625 100644 --- a/src/handlers/ExecuteBlockActionHandler.ts +++ b/src/handlers/ExecuteBlockActionHandler.ts @@ -28,7 +28,17 @@ import { createCommentContextualBar } from "../modals/createCommentContextualBar import { CommentPage } from "../../enum/modals/CommentPage"; import { NotionObjectTypes } from "../../enum/Notion"; import { ITokenInfo } from "../../definition/authorization/IOAuth2Storage"; -import { ICommentInfo } from "../../definition/lib/INotion"; +import { + ICommentInfo, + IDatabase, + IPage, + IParentDatabase, + IParentPage, +} from "../../definition/lib/INotion"; +import { SearchPageAndDatabase } from "../../enum/modals/common/SearchPageAndDatabaseComponent"; +import { Handler } from "./Handler"; +import { createPageOrRecordModal } from "../modals/createPageOrRecordModal"; +import { NotionPageOrRecord } from "../../enum/modals/NotionPageOrRecord"; export class ExecuteBlockActionHandler { private context: UIKitBlockInteractionContext; @@ -121,13 +131,22 @@ export class ExecuteBlockActionHandler { break; } case Modals.OVERFLOW_MENU_ACTION: { - return this.handleRefreshCommentAction( + return this.handleOverFlowMenuAction( modalInteraction, oAuth2Storage, roomInteractionStorage ); break; } + case SearchPageAndDatabase.ACTION_ID: { + return this.handleSearchPageAndDatabaseAction( + modalInteraction, + oAuth2Storage, + roomInteractionStorage + ); + + break; + } default: { // Property Type Select Action const propertyTypeSelected = @@ -741,4 +760,115 @@ export class ExecuteBlockActionHandler { .getInteractionResponder() .updateContextualBarViewResponse(contextualBar); } + + private async handleOverFlowMenuAction( + modalInteraction: ModalInteractionStorage, + oAuth2Storage: OAuth2Storage, + roomInteractionStorage: RoomInteractionStorage + ): Promise { + const { value, user, triggerId } = this.context.getInteractionData(); + + if (!value) { + return this.context.getInteractionResponder().errorResponse(); + } + + // Check if the value is pageId. if not then it is not a refresh comment action + const OverFlowActions = [ + DatabaseModal.OVERFLOW_MENU_ACTION.toString(), + NotionPageOrRecord.CHANGE_DATABASE_ACTION.toString(), + ]; + + if (!OverFlowActions.includes(value)) { + return this.handleRefreshCommentAction( + modalInteraction, + oAuth2Storage, + roomInteractionStorage + ); + } + + const roomId = await roomInteractionStorage.getInteractionRoomId(); + const room = (await this.read.getRoomReader().getById(roomId)) as IRoom; + + const handler = new Handler({ + app: this.app, + read: this.read, + modify: this.modify, + persis: this.persistence, + http: this.http, + sender: user, + room, + triggerId, + }); + + switch (value) { + case DatabaseModal.OVERFLOW_MENU_ACTION: { + await handler.createNotionDatabase(); + break; + } + case NotionPageOrRecord.CHANGE_DATABASE_ACTION: { + await handler.createNotionPageOrRecord(true); + break; + } + } + + return this.context.getInteractionResponder().successResponse(); + } + + private async handleSearchPageAndDatabaseAction( + modalInteraction: ModalInteractionStorage, + oAuth2Storage: OAuth2Storage, + roomInteractionStorage: RoomInteractionStorage + ): Promise { + const { value, user } = 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; + + if (!tokenInfo) { + await sendNotificationWithConnectBlock( + this.app, + user, + this.read, + this.modify, + room + ); + + return this.context.getInteractionResponder().errorResponse(); + } + + if (!value) { + return this.context.getInteractionResponder().errorResponse(); + } + + let Object: IPage | IDatabase = JSON.parse(value); + let parentObject: IParentPage | IParentDatabase = Object.parent; + + // update the modal if database is selected + if (parentObject.type.includes(NotionObjectTypes.PAGE_ID)) { + return this.context.getInteractionResponder().successResponse(); + } + + const database = Object as IDatabase; + const modal = await createPageOrRecordModal( + this.app, + user, + this.read, + this.persistence, + this.modify, + room, + modalInteraction, + tokenInfo, + database + ); + + if (modal instanceof Error) { + this.app.getLogger().error(modal.message); + return this.context.getInteractionResponder().errorResponse(); + } + + return this.context + .getInteractionResponder() + .updateModalViewResponse(modal); + } } diff --git a/src/handlers/ExecuteViewClosedHandler.ts b/src/handlers/ExecuteViewClosedHandler.ts index 0c24f29..bd95e24 100644 --- a/src/handlers/ExecuteViewClosedHandler.ts +++ b/src/handlers/ExecuteViewClosedHandler.ts @@ -15,7 +15,7 @@ import { ModalInteractionStorage } from "../storage/ModalInteraction"; import { OAuth2Storage } from "../authorization/OAuth2Storage"; import { ITokenInfo } from "../../definition/authorization/IOAuth2Storage"; import { CommentPage } from "../../enum/modals/CommentPage"; -import { SearchPage } from "../../enum/modals/common/SearchPageComponent"; +import { NotionPageOrRecord } from "../../enum/modals/NotionPageOrRecord"; export class ExecuteViewClosedHandler { private context: UIKitViewCloseInteractionContext; @@ -57,7 +57,6 @@ export class ExecuteViewClosedHandler { break; } case CommentPage.VIEW_ID: { - await Promise.all([ modalInteraction.clearInputElementState( CommentPage.COMMENT_INPUT_ACTION @@ -68,6 +67,18 @@ export class ExecuteViewClosedHandler { ]); break; } + case NotionPageOrRecord.VIEW_ID: { + const { workspace_id } = + (await oAuth2Storage.getCurrentWorkspace( + user.id + )) as ITokenInfo; + + await Promise.all([ + modalInteraction.clearPagesOrDatabase(workspace_id), + ]); + + break; + } default: { } } diff --git a/src/handlers/ExecuteViewSubmitHandler.ts b/src/handlers/ExecuteViewSubmitHandler.ts index cc6dfcb..cea2486 100644 --- a/src/handlers/ExecuteViewSubmitHandler.ts +++ b/src/handlers/ExecuteViewSubmitHandler.ts @@ -26,6 +26,11 @@ import { Modals } from "../../enum/modals/common/Modals"; import { handleMissingProperties } from "../helper/handleMissingProperties"; import { getDuplicatePropertyNameViewErrors } from "../helper/getDuplicatePropNameViewError"; import { IMessageAttachmentField } from "@rocket.chat/apps-engine/definition/messages"; +import { NotionPageOrRecord } from "../../enum/modals/NotionPageOrRecord"; +import { NotionObjectTypes } from "../../enum/Notion"; +import { ITokenInfo } from "../../definition/authorization/IOAuth2Storage"; +import { IDatabase, IPage } from "../../definition/lib/INotion"; +import { SearchPageAndDatabase } from "../../enum/modals/common/SearchPageAndDatabaseComponent"; export class ExecuteViewSubmitHandler { private context: UIKitViewSubmitInteractionContext; @@ -72,6 +77,14 @@ export class ExecuteViewSubmitHandler { ); break; } + case NotionPageOrRecord.VIEW_ID: { + return this.handleCreationOfPageOrRecord( + room, + oAuth2Storage, + modalInteraction + ); + break; + } default: { } } @@ -190,4 +203,92 @@ export class ExecuteViewSubmitHandler { return this.context.getInteractionResponder().successResponse(); } + + private async handleCreationOfPageOrRecord( + room: IRoom, + oAuth2Storage: OAuth2Storage, + modalInteraction: ModalInteractionStorage + ): Promise { + const { user, view } = this.context.getInteractionData(); + const { state } = view; + + const tokenInfo = await oAuth2Storage.getCurrentWorkspace(user.id); + + if (!tokenInfo) { + await sendNotificationWithConnectBlock( + this.app, + user, + this.read, + this.modify, + room + ); + return this.context.getInteractionResponder().errorResponse(); + } + + // handle missing properties later + + const Object: IPage | IDatabase = JSON.parse( + state?.[SearchPageAndDatabase.BLOCK_ID]?.[ + SearchPageAndDatabase.ACTION_ID + ] + ); + + const { parent } = Object; + + const parentType: string = parent.type; + + if (parentType.includes(NotionObjectTypes.PAGE_ID)) { + return this.handleCreationOfPage( + tokenInfo, + room, + oAuth2Storage, + modalInteraction, + Object as IPage + ); + } + + return this.handleCreationOfRecord(); + } + + private async handleCreationOfPage( + tokenInfo: ITokenInfo, + room: IRoom, + oAuth2Storage: OAuth2Storage, + modalInteraction: ModalInteractionStorage, + page: IPage + ): Promise { + const { NotionSdk } = this.app.getUtils(); + const { view, user } = this.context.getInteractionData(); + const { state } = view; + const { access_token, workspace_name } = tokenInfo; + + const title: string = + state?.[NotionPageOrRecord.TITLE_BLOCK]?.[ + NotionPageOrRecord.TITLE_ACTION + ]; + + const createdPage = await NotionSdk.createPage(access_token, page, { + title, + }); + + let message: string; + + if (createdPage instanceof Error) { + this.app.getLogger().error(createdPage.message); + message = `🚫 Something went wrong while creating page in **${workspace_name}**.`; + } else { + const { name, link, title } = createdPage; + message = `✨ Your Page [**${title}**](${link}) is created successfully as a subpage in **${name}**.`; + } + + await sendNotification(this.read, this.modify, user, room, { + message, + }); + + return this.context.getInteractionResponder().successResponse(); + } + + private async handleCreationOfRecord(): Promise { + return this.context.getInteractionResponder().successResponse(); + } } diff --git a/src/handlers/Handler.ts b/src/handlers/Handler.ts index a6f7068..b83a895 100644 --- a/src/handlers/Handler.ts +++ b/src/handlers/Handler.ts @@ -17,7 +17,8 @@ import { DatabaseModal } from "../../enum/modals/NotionDatabase"; import { sendNotificationWithConnectBlock } from "../helper/message"; import { CommentPage } from "../../enum/modals/CommentPage"; import { createCommentContextualBar } from "../modals/createCommentContextualBar"; -import { SearchPage } from "../../enum/modals/common/SearchPageComponent"; +import { NotionPageOrRecord } from "../../enum/modals/NotionPageOrRecord"; +import { createPageOrRecordModal } from "../modals/createPageOrRecordModal"; export class Handler implements IHandler { public app: NotionApp; @@ -174,4 +175,75 @@ export class Handler implements IHandler { ); } } + + public async createNotionPageOrRecord(update?: boolean): 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, + NotionPageOrRecord.VIEW_ID + ); + + const { workspace_id } = tokenInfo; + + await Promise.all([ + this.roomInteractionStorage.storeInteractionRoomId(roomId), + modalInteraction.clearPagesOrDatabase(workspace_id), + ]); + + const modal = await createPageOrRecordModal( + 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) { + if (update) { + await this.modify.getUiController().updateSurfaceView( + modal, + { + triggerId, + }, + this.sender + ); + + return; + } + + await this.modify + .getUiController() + .openSurfaceView(modal, { triggerId }, this.sender); + } + + return; + } } diff --git a/src/helper/getSelectDatabaseLayout.ts b/src/helper/getSelectDatabaseLayout.ts new file mode 100644 index 0000000..cb142cc --- /dev/null +++ b/src/helper/getSelectDatabaseLayout.ts @@ -0,0 +1,50 @@ +import { ITokenInfo } from "../../definition/authorization/IOAuth2Storage"; +import { PreviewBlockWithPreview, PreviewBlock } from "@rocket.chat/ui-kit"; +import { ElementBuilder } from "../lib/ElementBuilder"; +import { BlockBuilder } from "../lib/BlockBuilder"; +import { Notion } from "../../enum/Notion"; +import { INotionDatabase } from "../../definition/lib/INotion"; + +export function getSelectDatabaseLayout( + appId: string, + tokenInfo: ITokenInfo, + properties: INotionDatabase +): Exclude { + const { workspace_icon, owner } = tokenInfo; + const { name, avatar_url } = owner.user; + + const database_name = properties.name; + const database_url = properties.link; + + const elementBuilder = new ElementBuilder(appId); + const blockBuilder = new BlockBuilder(appId); + + const workspace_icon_url = workspace_icon?.startsWith("/") + ? `${Notion.WEBSITE_URL}${workspace_icon}` + : workspace_icon?.startsWith("http") + ? `${workspace_icon}` + : undefined; + const thumb = workspace_icon_url ? { url: workspace_icon_url } : undefined; + const title = [ + `**📋 Database Name**`, + `[**${database_name}**](${database_url})`, + ]; + const description = [""]; + const avatarElement = elementBuilder.addImage({ + imageUrl: avatar_url as string, + altText: name as string, + }); + const avatarName = `**${name}**`; + const footer = blockBuilder.createContextBlock({ + contextElements: [avatarElement, avatarName], + }); + + const connectPreview = blockBuilder.createPreviewBlock({ + title, + description, + footer, + thumb, + }); + + return connectPreview; +} diff --git a/src/lib/NotionSDK.ts b/src/lib/NotionSDK.ts index 47558d5..6b42495 100644 --- a/src/lib/NotionSDK.ts +++ b/src/lib/NotionSDK.ts @@ -1,10 +1,13 @@ import { ICommentInfo, ICommentObject, + IDatabase, INotionDatabase, + INotionPage, INotionSDK, INotionUserBot, IPage, + IPageProperties, } from "../../definition/lib/INotion"; import { HttpStatusCode, @@ -170,7 +173,7 @@ export class NotionSDK implements INotionSDK { private returnPage(name: string, page_id: string): IPage { return { - name, + name: `📄 ${name}`, parent: { type: NotionObjectTypes.PAGE_ID, page_id, @@ -481,4 +484,119 @@ export class NotionSDK implements INotionSDK { throw new AppsEngineException(err as string); } } + + public async searchPagesAndDatabases( + token: string + ): Promise | Error> { + try { + const response = await this.http.post(NotionApi.SEARCH, { + 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 Pages: `, + response.content + ); + } + + const { results } = response.data; + + const result: Array = []; + results.forEach(async (item) => { + const objectType: string = item?.[NotionObjectTypes.OBJECT]; + if (objectType.includes(NotionObjectTypes.PAGE)) { + const pageObject = await this.getPageObjectFromResults( + item + ); + + if (pageObject) { + result.push(pageObject); + } + } else { + const databaseObject = + await this.getDatabaseObjectFromResults(item); + + result.push(databaseObject); + } + }); + + return result; + } catch (err) { + throw new AppsEngineException(err as string); + } + } + + private async getDatabaseObjectFromResults(item): Promise { + const databaseNameTitleObject = item?.[NotionObjectTypes.TITLE]; + const name: string = databaseNameTitleObject.length + ? databaseNameTitleObject[0]?.plain_text + : "Untitled"; + const database_id: string = item.id; + + return { + info: { + name: `📚 ${name}`, + link: item?.url, + }, + parent: { + type: NotionObjectTypes.DATABASE_ID, + database_id, + }, + }; + } + + public async createPage( + token: string, + page: IPage, + prop: IPageProperties + ): Promise { + try { + const { name, parent } = page; + const { title } = prop; + + const data = { + parent, + properties: { + [NotionObjectTypes.TITLE]: { + [NotionObjectTypes.TITLE]: markdownToRichText(title), + }, + }, + }; + + 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 Page: `, + response.content + ); + } + + let result: INotionPage = { + link: response?.data?.url, + name, + title, + }; + + return result; + } catch (err) { + throw new AppsEngineException(err as string); + } + } } diff --git a/src/modals/common/searchPageOrDatabaseComponent.ts b/src/modals/common/searchPageOrDatabaseComponent.ts new file mode 100644 index 0000000..64daa30 --- /dev/null +++ b/src/modals/common/searchPageOrDatabaseComponent.ts @@ -0,0 +1,69 @@ +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, IPage } from "../../../definition/lib/INotion"; +import { ITokenInfo } from "../../../definition/authorization/IOAuth2Storage"; +import { Modals } from "../../../enum/modals/common/Modals"; +import { SearchPageAndDatabase } from "../../../enum/modals/common/SearchPageAndDatabaseComponent"; +import { NotionObjectTypes } from "../../../enum/Notion"; +export async function searchPageOrDatabaseComponent( + app: NotionApp, + modalInteraction: ModalInteractionStorage, + tokenInfo: ITokenInfo, + actionId: string +): Promise { + const { NotionSdk, elementBuilder, blockBuilder } = app.getUtils(); + const { access_token, workspace_id } = tokenInfo; + let pagesOrDatabases; + pagesOrDatabases = await modalInteraction.getPagesOrDatabase(workspace_id); + if (!pagesOrDatabases) { + pagesOrDatabases = await NotionSdk.searchPagesAndDatabases( + access_token + ); + if (pagesOrDatabases instanceof Error) { + return pagesOrDatabases; + } + + await modalInteraction.storePagesOrDatabase( + pagesOrDatabases, + workspace_id + ); + } + const accesiblePagesAndDatabase: Array = + pagesOrDatabases; + + const options: StaticSelectOptionsParam = accesiblePagesAndDatabase.map( + (item) => { + + const info = NotionObjectTypes.INFO.toString(); + const name = NotionObjectTypes.NAME.toString(); + + const text: string = item?.[name] ?? item?.[info]?.[name]; + const value = JSON.stringify(item); + + return { + text, + value, + }; + } + ); + + const dropDownOption = elementBuilder.createDropDownOptions(options); + const dropDown = elementBuilder.addDropDown( + { + placeholder: SearchPageAndDatabase.PLACEHOLDER, + options: dropDownOption, + dispatchActionConfig: [Modals.dispatchActionConfigOnSelect], + }, + { blockId: SearchPageAndDatabase.BLOCK_ID, actionId } + ); + const inputBlock = blockBuilder.createInputBlock({ + text: SearchPageAndDatabase.LABEL, + element: dropDown, + optional: false, + }); + + return inputBlock; +} diff --git a/src/modals/createPageOrRecordModal.ts b/src/modals/createPageOrRecordModal.ts new file mode 100644 index 0000000..85169f8 --- /dev/null +++ b/src/modals/createPageOrRecordModal.ts @@ -0,0 +1,141 @@ +import { IUser } from "@rocket.chat/apps-engine/definition/users"; +import { NotionApp } from "../../NotionApp"; +import { + IModify, + IPersistence, + IRead, + IUIKitSurfaceViewParam, +} from "@rocket.chat/apps-engine/definition/accessors"; +import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; +import { ModalInteractionStorage } from "../storage/ModalInteraction"; +import { ITokenInfo } from "../../definition/authorization/IOAuth2Storage"; +import { Error } from "../../errors/Error"; +import { NotionPageOrRecord } from "../../enum/modals/NotionPageOrRecord"; +import { + ButtonStyle, + UIKitSurfaceType, +} from "@rocket.chat/apps-engine/definition/uikit"; +import { TextObjectType, Block } from "@rocket.chat/ui-kit"; +import { getConnectPreview } from "../helper/getConnectLayout"; +import { inputElementComponent } from "./common/inputElementComponent"; +import { searchPageOrDatabaseComponent } from "./common/searchPageOrDatabaseComponent"; +import { SearchPageAndDatabase } from "../../enum/modals/common/SearchPageAndDatabaseComponent"; +import { DatabaseModal } from "../../enum/modals/NotionDatabase"; +import { OverflowMenuComponent } from "./common/OverflowMenuComponent"; +import { Modals } from "../../enum/modals/common/Modals"; +import { IDatabase } from "../../definition/lib/INotion"; +import { getSelectDatabaseLayout } from "../helper/getSelectDatabaseLayout"; + +export async function createPageOrRecordModal( + app: NotionApp, + user: IUser, + read: IRead, + persistence: IPersistence, + modify: IModify, + room: IRoom, + modalInteraction: ModalInteractionStorage, + tokenInfo: ITokenInfo, + parent?: IDatabase +): Promise { + const { elementBuilder, blockBuilder } = app.getUtils(); + const divider = blockBuilder.createDividerBlock(); + const blocks: Block[] = []; + const appId = app.getID(); + const overFlowMenuText = [DatabaseModal.OVERFLOW_MENU_TEXT.toString()]; + const overFlowMenuValue = [DatabaseModal.OVERFLOW_MENU_ACTION.toString()]; + + if (parent) { + overFlowMenuText.push( + NotionPageOrRecord.CHANGE_DATABASE_TEXT.toString() + ); + overFlowMenuValue.push( + NotionPageOrRecord.CHANGE_DATABASE_ACTION.toString() + ); + } + + const overflowMenu = await OverflowMenuComponent( + { + app, + text: overFlowMenuText, + value: overFlowMenuValue, + }, + { + blockId: Modals.OVERFLOW_MENU_BLOCK, + actionId: Modals.OVERFLOW_MENU_ACTION, + } + ); + + blocks.push(overflowMenu); + if (!parent) { + const connectBlock = getConnectPreview(appId, tokenInfo); + blocks.push(connectBlock); + } + + if (!parent) { + const SearchForPageOrDatabaseComponent = + await searchPageOrDatabaseComponent( + app, + modalInteraction, + tokenInfo, + SearchPageAndDatabase.ACTION_ID + ); + + if (SearchForPageOrDatabaseComponent instanceof Error) { + return SearchForPageOrDatabaseComponent; + } + + blocks.push(SearchForPageOrDatabaseComponent); + } else { + const { info } = parent; + const SelectedDatabaseLayout = getSelectDatabaseLayout( + appId, + tokenInfo, + info + ); + + blocks.push(SelectedDatabaseLayout); + } + + const titleOfPageOrRecordBlock = inputElementComponent( + { + app, + placeholder: NotionPageOrRecord.TITLE_PLACEHOLDER, + label: NotionPageOrRecord.TITLE_LABEL, + optional: false, + }, + { + blockId: NotionPageOrRecord.TITLE_BLOCK, + actionId: NotionPageOrRecord.TITLE_ACTION, + } + ); + + blocks.push(titleOfPageOrRecordBlock); + + const submit = elementBuilder.addButton( + { text: NotionPageOrRecord.CREATE, style: ButtonStyle.PRIMARY }, + { + actionId: NotionPageOrRecord.CREATE_ACTION, + blockId: NotionPageOrRecord.CREATE_BLOCK, + } + ); + + const close = elementBuilder.addButton( + { text: NotionPageOrRecord.CLOSE, style: ButtonStyle.DANGER }, + { + actionId: NotionPageOrRecord.CLOSE_ACTION, + blockId: NotionPageOrRecord.CLOSE_BLOCK, + } + ); + + return { + id: NotionPageOrRecord.VIEW_ID, + type: UIKitSurfaceType.MODAL, + title: { + type: TextObjectType.MRKDWN, + text: NotionPageOrRecord.TITLE, + }, + blocks, + close, + submit, + }; +}