diff --git a/.github/workflows/jira.yml b/.github/workflows/jira.yml index caa4bbdf..250abc76 100644 --- a/.github/workflows/jira.yml +++ b/.github/workflows/jira.yml @@ -21,7 +21,7 @@ jobs: project: ${{ secrets.JIRA_PROJECT }} issuetype: ${{ secrets.JIRA_ISSUE_TYPE }} summary: | - ${{ github.event.pull_request.title }} + Snyk | Vulnerability | ${{ github.event.repository.name }} | ${{ github.event.pull_request.title }} description: | PR: ${{ github.event.pull_request.html_url }} diff --git a/LICENSE b/LICENSE index 797fccfa..46e33814 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022-2024 Contentstack +Copyright (c) 2022-2025 Contentstack Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/__test__/data/fileField.json b/__test__/data/fileField.json index 6f1327de..4d9c8227 100644 --- a/__test__/data/fileField.json +++ b/__test__/data/fileField.json @@ -9,52 +9,58 @@ }, "value": [ { - "uid": "blt78cdb7bdceb00332", - "created_at": "2019-05-27T06:25:46.929Z", - "updated_at": "2019-05-27T06:25:46.929Z", - "created_by": "asssa", - "updated_by": "asssa", - "content_type": "image/png", - "file_size": "49385", - "tags": [], - "filename": "asdasdasd(1).png", - "url": "https://images.contentstack.io/v3/assets/blxxx/blt78cdb7bdceb00332/sadsadsASS/asdasdasd(1).png", - "ACL": [], - "is_dir": false, - "_version": 1, - "title": "asdasdasd(1).png" + "file": { + "uid": "blt78cdb7bdceb00332", + "created_at": "2019-05-27T06:25:46.929Z", + "updated_at": "2019-05-27T06:25:46.929Z", + "created_by": "asssa", + "updated_by": "asssa", + "content_type": "image/png", + "file_size": "49385", + "tags": [], + "filename": "asdasdasd(1).png", + "url": "https://images.contentstack.io/v3/assets/blxxx/blt78cdb7bdceb00332/sadsadsASS/asdasdasd(1).png", + "ACL": [], + "is_dir": false, + "_version": 1, + "title": "asdasdasd(1).png" + } }, { - "uid": "bltd40e27d1918972a4", - "created_at": "2019-05-27T06:22:42.426Z", - "updated_at": "2019-05-27T06:22:42.426Z", - "created_by": "asssa", - "updated_by": "asssa", - "content_type": "image/png", - "file_size": "21748", - "tags": [], - "filename": "MVMT_Rallye_Green_Sandstone_3.png", - "url": "https://images.contentstack.io/v3/assets/blxxx/bltd40e27d1918972a4/5ceb8232231227de413bab12/MVMT_Rallye_Green_Sandstone_3.png", - "ACL": [], - "is_dir": false, - "_version": 1, - "title": "MVMT_Rallye_Green_Sandstone_3.png" + "file": { + "uid": "bltd40e27d1918972a4", + "created_at": "2019-05-27T06:22:42.426Z", + "updated_at": "2019-05-27T06:22:42.426Z", + "created_by": "asssa", + "updated_by": "asssa", + "content_type": "image/png", + "file_size": "21748", + "tags": [], + "filename": "MVMT_Rallye_Green_Sandstone_3.png", + "url": "https://images.contentstack.io/v3/assets/blxxx/bltd40e27d1918972a4/5ceb8232231227de413bab12/MVMT_Rallye_Green_Sandstone_3.png", + "ACL": [], + "is_dir": false, + "_version": 1, + "title": "MVMT_Rallye_Green_Sandstone_3.png" + } }, { - "uid": "blt236db5d7d0d2c2d1", - "created_at": "2019-05-27T06:22:39.763Z", - "updated_at": "2019-05-27T06:22:39.763Z", - "created_by": "asssa", - "updated_by": "asssa", - "content_type": "image/png", - "file_size": "23804", - "tags": [], - "filename": "MVMT_Rallye_Green_Sandstone_2.png", - "url": "https://images.contentstack.io/v3/assets/blxxx/blt236db5d7d0d2c2d1/5ceb822f065d29344df9fd2d/MVMT_Rallye_Green_Sandstone_2.png", - "ACL": [], - "is_dir": false, - "_version": 1, - "title": "MVMT_Rallye_Green_Sandstone_2.png" + "file": { + "uid": "blt236db5d7d0d2c2d1", + "created_at": "2019-05-27T06:22:39.763Z", + "updated_at": "2019-05-27T06:22:39.763Z", + "created_by": "asssa", + "updated_by": "asssa", + "content_type": "image/png", + "file_size": "23804", + "tags": [], + "filename": "MVMT_Rallye_Green_Sandstone_2.png", + "url": "https://images.contentstack.io/v3/assets/blxxx/blt236db5d7d0d2c2d1/5ceb822f065d29344df9fd2d/MVMT_Rallye_Green_Sandstone_2.png", + "ACL": [], + "is_dir": false, + "_version": 1, + "title": "MVMT_Rallye_Green_Sandstone_2.png" + } } ], "entry": { diff --git a/__test__/field.test.ts b/__test__/field.test.ts index b488b2cb..afb58825 100644 --- a/__test__/field.test.ts +++ b/__test__/field.test.ts @@ -72,9 +72,8 @@ describe("Field", () => { }); it("setFocus", () => { - field.setFocus() + field.setFocus(); expect(connection.sendToParent).toHaveBeenCalledWith("focus"); - }); }); @@ -131,7 +130,7 @@ describe("Field", () => { singleFileField.getData() ); expect( - fileFieldData.multiple.value.map((file) => file.uid) + fileFieldData.multiple.value.map(({ file }) => file.uid) ).toEqual(multipleFileField.getData()); }); diff --git a/__test__/fieldModifierLocation/field.test.ts b/__test__/fieldModifierLocation/field.test.ts index 1497f7b6..747d711c 100644 --- a/__test__/fieldModifierLocation/field.test.ts +++ b/__test__/fieldModifierLocation/field.test.ts @@ -125,7 +125,7 @@ describe("Field", () => { singleFileField.getData() ); expect( - fileFieldData.multiple.value.map((file) => file.uid) + fileFieldData.multiple.value.map(({ file }) => file.uid) ).toEqual(multipleFileField.getData()); }); diff --git a/__test__/organizationFullPage.test.ts b/__test__/organizationFullPage.test.ts new file mode 100644 index 00000000..89131606 --- /dev/null +++ b/__test__/organizationFullPage.test.ts @@ -0,0 +1,36 @@ +import { IOrgFullPageLocationInitData, LocationType } from "../src/types"; +import { OrganizationDetails } from "../src/types/organization.types"; + +const mockData: IOrgFullPageLocationInitData = { + type: LocationType.ORGANIZATION_FULL_PAGE, + app_id: "app_id", + installation_uid: "installation_uid", + extension_uid: "extension_uid", + region: "NA", + stack: {} as any, + user: {} as any, + currentBranch: "currentBranch", + organization: {} as OrganizationDetails, +}; +const organizationFullPage = { + currentOrganization: mockData.organization, +}; + +afterEach(() => { + jest.clearAllMocks(); +}); + +test("should return organization details", () => { + expect(organizationFullPage.currentOrganization).toBe(mockData.organization); +}); + +test("should handle missing organization details", () => { + const invalidData: IOrgFullPageLocationInitData = { + ...mockData, + organization: null as any, // check missing organization details + }; + const invalidOrganizationFullPage = { + currentOrganization: invalidData.organization, + }; + expect(invalidOrganizationFullPage.currentOrganization).toBeNull(); +}); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 7a729fb1..1048ac8e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@contentstack/app-sdk", - "version": "2.2.0", + "version": "2.3.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@contentstack/app-sdk", - "version": "2.2.0", + "version": "2.3.0", "license": "MIT", "dependencies": { "jsonfile": "^6.1.0", diff --git a/package.json b/package.json index 833d88d7..28fba15e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@contentstack/app-sdk", - "version": "2.2.0", + "version": "2.3.0", "types": "dist/src/index.d.ts", "description": "The Contentstack App SDK allows you to customize your Contentstack applications.", "main": "dist/index.js", diff --git a/src/ContentTypeSidebarWidget.ts b/src/ContentTypeSidebarWidget.ts new file mode 100644 index 00000000..8e490d0d --- /dev/null +++ b/src/ContentTypeSidebarWidget.ts @@ -0,0 +1,71 @@ +import EventEmitter from "wolfy87-eventemitter"; +import postRobot from "post-robot"; +import { IContentTypeSidebarInitData } from "./types"; +import { ContentType } from "./types/stack.types"; +import { GenericObjectType } from "./types/common.types"; + +/** Class representing a Content type Sidebar UI Location from Contentstack UI. */ + +class ContentTypeSidebarWidget { + /** + * @hideconstructor + */ + + currentContentType: ContentType; + _emitter: EventEmitter; + _connection: typeof postRobot; + _changedData?: GenericObjectType; + + constructor( + initializationData: IContentTypeSidebarInitData, + connection: typeof postRobot, + emitter: EventEmitter + ) { + this.currentContentType = initializationData.currentContentType; + + this._emitter = emitter; + + this._connection = connection; + + const thisContentType = this; + + this._emitter.on("contentTypeSave", (event: { data: ContentType }) => { + thisContentType.currentContentType = event.data; + }); + + this.getData = this.getData.bind(this); + this.onSave = this.onSave.bind(this); + } + + /** + * Get the current content type data. + * @returns {ContentTypeData} The current content type data. + */ + getData(): ContentType { + return this.currentContentType; + } + + /** + * Executes the provided callback function every time a content type is saved. + * @param {function} callback - The function to be called when a content type is saved. + * @param {ContentType} arg0 - The content type data passed as an argument to the callback function when a content type is saved. + */ + onSave(callback: (arg0: ContentType) => void) { + const contentTypeObj = this; + if (callback && typeof callback === "function") { + contentTypeObj._emitter.on( + "contentTypeSave", + (event: { data: ContentType }) => { + callback(event.data); + } + ); + this._emitter.emitEvent("_eventRegistration", [ + { name: "contentTypeSave" }, + ]); + } else { + throw Error("Callback must be a function"); + } + } +} + +export default ContentTypeSidebarWidget; diff --git a/src/field.ts b/src/field.ts index 62d3fd27..23b8939e 100755 --- a/src/field.ts +++ b/src/field.ts @@ -3,7 +3,6 @@ import postRobot from "post-robot"; import { IFieldInitData, IFieldModifierLocationInitData } from "./types"; import { GenericObjectType } from "./types/common.types"; import { Schema } from "./types/stack.types"; -import EventRegistry from "./EventRegistry"; const excludedDataTypesForSetField = [ "file", @@ -21,7 +20,7 @@ function separateResolvedData(field: Field, value: GenericObjectType) { resolvedData = value; unResolvedData = field.schema.multiple === true - ? value.map((file: any) => file.uid) + ? value.map(({ file }: GenericObjectType) => file?.uid) : value.uid; } else if (field.schema.multiple === true) { resolvedData = []; diff --git a/src/fieldModifierLocation/field.ts b/src/fieldModifierLocation/field.ts index fdc881fd..5b43640c 100644 --- a/src/fieldModifierLocation/field.ts +++ b/src/fieldModifierLocation/field.ts @@ -24,7 +24,7 @@ function separateResolvedData( resolvedData = value; unResolvedData = field.schema.multiple === true - ? value.map((file: any) => file.uid) + ? value.map(({ file }: GenericObjectType) => file?.uid) : value.uid; } else if (field.schema.multiple === true) { resolvedData = []; diff --git a/src/types.ts b/src/types.ts index ccc68e7a..25110f98 100644 --- a/src/types.ts +++ b/src/types.ts @@ -6,6 +6,7 @@ import Stack from "./stack"; import { GenericObjectType } from "./types/common.types"; import { Entry } from "./types/entry.types"; import { Asset, ContentType, Schema, StackDetail } from "./types/stack.types"; +import { OrganizationDetails } from "./types/organization.types"; import { User } from "./types/user.types"; import Window from "./window"; @@ -67,6 +68,10 @@ export declare interface IAppConfigWidget { stack: Stack; } +export declare interface IOrgFullPageLocation { + currentOrganization: OrganizationDetails; +} + export enum DashboardWidth { FULL_WIDTH = "full_width", HALF_WIDTH = "half_width", @@ -81,6 +86,8 @@ export enum LocationType { FULL_PAGE_LOCATION = "FULL_PAGE_LOCATION", RTE = "RTE", WIDGET = "WIDGET", + CONTENT_TYPE_SIDEBAR_WIDGET = "CONTENT_TYPE_SIDEBAR_WIDGET", + ORGANIZATION_FULL_PAGE = "ORGANIZATION_FULL_PAGE", } // Init data @@ -97,6 +104,12 @@ declare interface ICommonInitData { manifest?: Manifest; } +export declare interface IOrgFullPageLocationInitData extends ICommonInitData { + organization: OrganizationDetails; + config?: GenericObjectType; + type: LocationType.ORGANIZATION_FULL_PAGE; +} + export declare interface IDashboardInitData extends ICommonInitData { dashboard_width: DashboardWidth; config?: GenericObjectType; @@ -148,6 +161,12 @@ export declare interface IAssetSidebarInitData extends ICommonInitData { type: LocationType.ASSET_SIDEBAR_WIDGET; } +export declare interface IContentTypeSidebarInitData extends ICommonInitData { + config?: GenericObjectType; + currentContentType: ContentType; + type: LocationType.CONTENT_TYPE_SIDEBAR_WIDGET; +} + export declare interface IFieldModifierLocationInitData extends ICommonInitData { changedData: Entry; @@ -194,7 +213,9 @@ export type InitializationData = | IFieldModifierLocationInitData | IFullPageLocationInitData | IRTEInitData - | ISidebarInitData; + | ISidebarInitData + | IContentTypeSidebarInitData + | IOrgFullPageLocationInitData; /** * installation details API response diff --git a/src/types/organization.types.ts b/src/types/organization.types.ts new file mode 100644 index 00000000..16ee79aa --- /dev/null +++ b/src/types/organization.types.ts @@ -0,0 +1,12 @@ +import { GenericObjectType } from "./common.types"; + +export declare interface OrganizationDetails { + uid: string; + name: string; + owner_uid: string; + expires_on: string; + enabled: boolean; + created_at: string; + updated_at: string; + tags: string[]; +} \ No newline at end of file diff --git a/src/uiLocation.ts b/src/uiLocation.ts index da230cc7..22cb122c 100755 --- a/src/uiLocation.ts +++ b/src/uiLocation.ts @@ -2,6 +2,7 @@ import postRobot from "post-robot"; import EventEmitter from "wolfy87-eventemitter"; import AssetSidebarWidget from "./AssetSidebarWidget"; +import ContentTypeSidebarWidget from "./ContentTypeSidebarWidget"; import { IRTEPluginInitializer } from "./RTE/types"; import { AppConfig } from "./appConfig"; import Entry from "./entry"; @@ -26,6 +27,7 @@ import { LocationType, Manifest, Region, + IOrgFullPageLocation, } from "./types"; import { GenericObjectType, RequestOption } from "./types/common.types"; import { User } from "./types/user.types"; @@ -126,6 +128,8 @@ class UiLocation { AssetSidebarWidget: AssetSidebarWidget | null; FullPage: IFullPageLocation | null; FieldModifierLocation: IFieldModifierLocation | null; + ContentTypeSidebarWidget: ContentTypeSidebarWidget | null; + OrganizationFullPage: IOrgFullPageLocation | null; }; constructor(initData: InitializationData) { @@ -172,6 +176,8 @@ class UiLocation { AssetSidebarWidget: null, FullPage: null, FieldModifierLocation: null, + ContentTypeSidebarWidget: null, + OrganizationFullPage: null }; window["postRobot"] = postRobot; @@ -279,6 +285,23 @@ class UiLocation { break; } + case LocationType.ORGANIZATION_FULL_PAGE: { + this.location.OrganizationFullPage = { + currentOrganization: initializationData.organization, + }; + break; + } + + case LocationType.CONTENT_TYPE_SIDEBAR_WIDGET: { + this.location.ContentTypeSidebarWidget = + new ContentTypeSidebarWidget( + initializationData, + postRobot, + emitter + ); + break; + } + case LocationType.FIELD: default: { initializationData.self = true; @@ -366,6 +389,12 @@ class UiLocation { { data: event.data.data }, ]); } + + if (event.data.name === "contentTypeSave") { + emitter.emitEvent("contentTypeSave", [ + { data: event.data.data }, + ]); + } }); } catch (err) { console.error("Extension Event", err);