diff --git a/.talismanrc b/.talismanrc index 345ca2f2..a3a987ae 100644 --- a/.talismanrc +++ b/.talismanrc @@ -1,5 +1,10 @@ +scopeconfig: + - scope: node fileignoreconfig: -- filename: .github/workflows/secrets-scan.yml - ignore_detectors: - - filecontent + - filename: package-lock.json + ignore_detectors: + - filecontent + - filename: .github/workflows/secrets-scan.yml + ignore_detectors: + - filecontent version: "1.0" \ No newline at end of file diff --git a/__test__/uiLocation.test.ts b/__test__/uiLocation.test.ts index 20177485..1c743a50 100644 --- a/__test__/uiLocation.test.ts +++ b/__test__/uiLocation.test.ts @@ -310,7 +310,7 @@ describe("UI Location", () => { const config = await uiLocation.getConfig(); expect(config).toEqual({}); expect(postRobotSendToParentMock).toHaveBeenLastCalledWith( - "getConfig" + "getConfig", {"context": {"extensionUID": "extension_uid", "installationUID": "installation_uid"}} ); }); }); diff --git a/package-lock.json b/package-lock.json index 6b31b370..6506f14c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,19 @@ { "name": "@contentstack/app-sdk", - "version": "2.3.2", + "version": "2.3.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@contentstack/app-sdk", - "version": "2.3.2", + "version": "2.3.3", "license": "MIT", "dependencies": { "axios": "^1.7.9", "jsonfile": "^6.1.0", "loader-utils": "^3.2.1", "post-robot": "^8.0.31", + "rxjs": "^7.8.1", "ssri": "^12.0.0", "wolfy87-eventemitter": "^5.2.9" }, @@ -2968,9 +2969,9 @@ } }, "node_modules/@types/ws": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", - "integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", "dev": true, "dependencies": { "@types/node": "*" @@ -3775,23 +3776,24 @@ } }, "node_modules/axios": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.3.tgz", - "integrity": "sha512-iP4DebzoNlP/YN2dpwCgb8zoCmhtkajzS48JvwmkSkXvPI3DHc7m+XYL5tGnSlJtR6nImXZmdCuN5aP8dh1d8A==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", + "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", "dependencies": { "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", + "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "node_modules/axios/node_modules/form-data": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", - "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { @@ -4079,15 +4081,6 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/body-parser/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/body-parser/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -4116,9 +4109,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "dependencies": { "balanced-match": "^1.0.0", @@ -4203,9 +4196,9 @@ "dev": true }, "node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "dev": true, "engines": { "node": ">= 0.8" @@ -4557,17 +4550,17 @@ } }, "node_modules/compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", "dev": true, "dependencies": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", + "bytes": "3.1.2", + "compressible": "~2.0.18", "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", "vary": "~1.1.2" }, "engines": { @@ -4589,6 +4582,35 @@ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, + "node_modules/compression/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -6107,14 +6129,16 @@ } }, "node_modules/form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.4.tgz", + "integrity": "sha512-f0cRzm6dkyVYV3nPoooP8XlccPQukegwhAnpoLcXy+X+A8KfpGOoXwDr9FLZd3wzgLaBGQBE3lY93Zm/i1JvIQ==", "dev": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.35" }, "engines": { "node": ">= 6" @@ -9021,6 +9045,16 @@ "node": ">=6" } }, + "node_modules/launch-editor": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.11.1.tgz", + "integrity": "sha512-SEET7oNfgSaB6Ym0jufAdCeo3meJVeCaaDyzRygy0xsp2BFKCprcfHljTq4QkzTLUxEKkFK6OK4811YM2oSrRg==", + "dev": true, + "dependencies": { + "picocolors": "^1.1.1", + "shell-quote": "^1.8.3" + } + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -9881,9 +9915,9 @@ } }, "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", "dev": true, "engines": { "node": ">= 0.8" @@ -10117,9 +10151,9 @@ } }, "node_modules/picocolors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", - "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true }, "node_modules/picomatch": { @@ -10505,15 +10539,6 @@ "node": ">= 0.8" } }, - "node_modules/raw-body/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/react": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", @@ -10850,10 +10875,9 @@ } }, "node_modules/rxjs": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz", - "integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==", - "dev": true, + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "dependencies": { "tslib": "^2.1.0" } @@ -10861,8 +10885,7 @@ "node_modules/rxjs/node_modules/tslib": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "dev": true + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" }, "node_modules/safe-buffer": { "version": "5.1.2", @@ -11180,6 +11203,18 @@ "node": ">=8" } }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/shiki": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.10.1.tgz", @@ -12102,9 +12137,9 @@ } }, "node_modules/typedoc/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "dependencies": { "balanced-match": "^1.0.0" @@ -12514,9 +12549,9 @@ } }, "node_modules/webpack-dev-server": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.11.1.tgz", - "integrity": "sha512-lILVz9tAUy1zGFwieuaQtYiadImb5M3d+H+L1zDYalYoDl0cksAB1UNyuE5MMWJrG6zR1tXkCP2fitl7yoUJiw==", + "version": "4.15.2", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz", + "integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==", "dev": true, "dependencies": { "@types/bonjour": "^3.5.9", @@ -12525,7 +12560,7 @@ "@types/serve-index": "^1.9.1", "@types/serve-static": "^1.13.10", "@types/sockjs": "^0.3.33", - "@types/ws": "^8.5.1", + "@types/ws": "^8.5.5", "ansi-html-community": "^0.0.8", "bonjour-service": "^1.0.11", "chokidar": "^3.5.3", @@ -12538,6 +12573,7 @@ "html-entities": "^2.3.2", "http-proxy-middleware": "^2.0.3", "ipaddr.js": "^2.0.1", + "launch-editor": "^2.6.0", "open": "^8.0.9", "p-retry": "^4.5.0", "rimraf": "^3.0.2", @@ -12546,8 +12582,8 @@ "serve-index": "^1.9.1", "sockjs": "^0.3.24", "spdy": "^4.0.2", - "webpack-dev-middleware": "^5.3.1", - "ws": "^8.4.2" + "webpack-dev-middleware": "^5.3.4", + "ws": "^8.13.0" }, "bin": { "webpack-dev-server": "bin/webpack-dev-server.js" @@ -12563,6 +12599,9 @@ "webpack": "^4.37.0 || ^5.0.0" }, "peerDependenciesMeta": { + "webpack": { + "optional": true + }, "webpack-cli": { "optional": true } diff --git a/package.json b/package.json index 5fa7d7c6..3af26daa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@contentstack/app-sdk", - "version": "2.3.2", + "version": "2.3.3", "types": "dist/src/index.d.ts", "description": "The Contentstack App SDK allows you to customize your Contentstack applications.", "main": "dist/index.js", @@ -62,6 +62,7 @@ "jsonfile": "^6.1.0", "loader-utils": "^3.2.1", "post-robot": "^8.0.31", + "rxjs": "^7.8.1", "ssri": "^12.0.0", "wolfy87-eventemitter": "^5.2.9" }, diff --git a/src/RTE/types.tsx b/src/RTE/types.tsx index d119c099..ab750b0f 100644 --- a/src/RTE/types.tsx +++ b/src/RTE/types.tsx @@ -13,6 +13,7 @@ import { } from "slate"; import { RTEPlugin } from "./index"; +import UiLocation from "../uiLocation"; declare interface TransformOptions { at?: Location; @@ -48,7 +49,7 @@ export declare interface IRteParam { voids?: boolean; } ) => Point | undefined; - + sdk: UiLocation; isPointEqual: (point: Point, another: Point) => boolean; }; @@ -140,6 +141,7 @@ export declare interface IRteParam { getVariable: (name: string, defaultValue: any) => T; setVariable: (name: string, value: T) => void; getConfig: () => { [key: string]: T }; + sdk: UiLocation; } export declare type IRteParamWithPreventDefault = { @@ -199,7 +201,7 @@ export declare interface IRteElementType { children: Array; } -type IDynamicFunction = ( +export type IDynamicFunction = ( element: IRteElementType ) => | Exclude diff --git a/src/index.ts b/src/index.ts index b36ddecc..3b4b7061 100755 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,10 @@ import postRobot from "post-robot"; +import { InitializationData } from "./types"; +import { IRteParam } from "./RTE/types"; +import { PluginDefinition, PluginBuilder, registerPlugins } from "./rtePlugin"; import UiLocation from "./uiLocation"; import { version } from "../package.json"; -import { InitializationData } from "./types"; postRobot.CONFIG.LOG_LEVEL = "error"; @@ -43,6 +45,55 @@ class ContentstackAppSDK { .catch((e: Error) => Promise.reject(e)); } + /** + * Registers RTE plugins with the Contentstack platform. + * This method is the primary entry point for defining and registering custom RTE plugins + * built using the PluginBuilder pattern. It returns a function that the Contentstack + * platform will invoke at runtime, providing the necessary context. + * + * @example + * // In your plugin's entry file (e.g., src/index.ts): + * import ContentstackAppSDK, { PluginBuilder } from '@contentstack/app-sdk'; + * + * const MyCustomPlugin = new PluginBuilder("my-plugin-id") + * .title("My Plugin") + * .icon() + * .elementType("block") + * .display("toolbar") + * .render(()=>{return }) + * .on("exec", (rte: IRteParam) => { + * // Access SDK via rte.sdk if needed: + * const sdk = rte.sdk; + * // ... plugin execution logic ... + * }) + * .build(); + * + * export default ContentstackAppSDK.registerRTEPlugins( + * MyCustomPlugin + * ); + * + * @param {...PluginDefinition} pluginDefinitions - One or more plugin definitions created using the `PluginBuilder`. + * Each `PluginDefinition` describes the plugin's configuration, callbacks, and any child plugins. + * @returns {Promise<{ __isPluginBuilder__: boolean; version: string; plugins: (context: RTEContext, rte: IRteParam) => Promise<{ [key: string]: RTEPlugin; }>; }>} + * A Promise that resolves to an object containing: + * - `__isPluginBuilder__`: A boolean flag indicating this is a builder-based plugin export. + * - `version`: The version of the SDK that registered the plugins. + * - `plugins`: An asynchronous function. This function is designed to be invoked by the + * Contentstack platform loader, providing the `context` (initialization data) and + * the `rte` instance. When called, it materializes and returns a map of the + * registered `RTEPlugin` instances, keyed by their IDs. + */ + + static async registerRTEPlugins(...pluginDefinitions: PluginDefinition[]) { + return { + __isPluginBuilder__: true, + version, + plugins: (context: InitializationData, rte: IRteParam) => { + return registerPlugins(...pluginDefinitions)(context, rte); + } + }; + } + /** * Version of Contentstack App SDK. */ @@ -52,4 +103,11 @@ class ContentstackAppSDK { } export default ContentstackAppSDK; -module.exports = ContentstackAppSDK; +export { PluginBuilder }; + +// CommonJS compatibility +if (typeof module !== 'undefined' && module.exports) { + module.exports = ContentstackAppSDK; + module.exports.default = ContentstackAppSDK; + module.exports.PluginBuilder = PluginBuilder; +} \ No newline at end of file diff --git a/src/rtePlugin.ts b/src/rtePlugin.ts new file mode 100644 index 00000000..1d3a5f29 --- /dev/null +++ b/src/rtePlugin.ts @@ -0,0 +1,171 @@ +import { RTEPlugin as Plugin, rtePluginInitializer } from "./RTE"; +import { + IConfig, + IDisplayOnOptions, + IDynamicFunction, + IElementTypeOptions, + IOnFunction, + IRteElementType, + IRteParam, +} from "./RTE/types"; +import { InitializationData } from "./types"; +import UiLocation from "./uiLocation"; + +type PluginConfigCallback = (sdk: UiLocation) => Promise | IConfig; + +interface PluginDefinition { + id: string; + config: Partial; + callbacks: Partial; + asyncConfigCallback?: PluginConfigCallback; + childBuilders: PluginBuilder[]; +} + +class PluginBuilder { + private id: string; + private _config: Partial = {}; + private _callbacks: Partial = {}; + private _asyncConfigCallback?: PluginConfigCallback; + private _childBuilders: PluginBuilder[] = []; + + constructor(id: string) { + this.id = id; + this._config.title = id; + } + + title(title: string): PluginBuilder { + this._config.title = title; + return this; + } + icon(icon: React.ReactElement | null): PluginBuilder { + this._config.icon = icon; + return this; + } + display(display: IDisplayOnOptions | IDisplayOnOptions[]): PluginBuilder { + this._config.display = display; + return this; + } + elementType( + elementType: + | IElementTypeOptions + | IElementTypeOptions[] + | IDynamicFunction + ): PluginBuilder { + this._config.elementType = elementType; + return this; + } + render(renderFn: (element: React.ReactElement, attrs: { [key: string]: any }, path: number[], rte: IRteParam) => React.ReactElement): PluginBuilder { + this._config.render = renderFn; + return this; + } + shouldOverride( + shouldOverrideFn: (element: IRteElementType) => boolean + ): PluginBuilder { + this._config.shouldOverride = shouldOverrideFn; + return this; + } + on( + type: T, + callback: IOnFunction[T] + ): PluginBuilder { + this._callbacks[type] = callback; + return this; + } + configure(callback: PluginConfigCallback): PluginBuilder { + this._asyncConfigCallback = callback; + return this; + } + addPlugins(...builders: PluginBuilder[]): PluginBuilder { + this._childBuilders.push(...builders); + return this; + } + + /** + * Builds and returns a definition of the RTE Plugin, ready to be materialized + * into a concrete RTEPlugin instance later when the SDK and Plugin Factory are available. + * This method no longer performs the actual creation of RTEPlugin instances. + */ + build(): PluginDefinition { + return { + id: this.id, + config: this._config, + callbacks: this._callbacks, + asyncConfigCallback: this._asyncConfigCallback, + childBuilders: this._childBuilders, + }; + } +} + +async function materializePlugin( + pluginDef: PluginDefinition, + sdk: UiLocation +): Promise { + let finalConfig: Partial = { ...pluginDef.config }; + if (pluginDef.asyncConfigCallback) { + const dynamicConfig = await Promise.resolve( + pluginDef.asyncConfigCallback(sdk) + ); + finalConfig = { ...finalConfig, ...dynamicConfig }; + } + + const plugin = rtePluginInitializer( + pluginDef.id, + (rte: IRteParam | void) => { + return finalConfig; + } + ); + + Object.entries(pluginDef.callbacks).forEach(([type, callback]) => { + plugin.on(type as keyof IOnFunction, callback); + }); + + if (pluginDef.childBuilders.length > 0) { + const childPlugins = await Promise.all( + pluginDef.childBuilders.map((childBuilder) => + materializePlugin(childBuilder.build(), sdk) + ) + ); + plugin.addPlugins(...childPlugins); + } + + return plugin; +} + +function registerPlugins( + ...pluginDefinitions: PluginDefinition[] +): ( + context: InitializationData, + rte: IRteParam +) => Promise<{ [key: string]: Plugin }> { + const definitionsToProcess = [...pluginDefinitions]; + const plugins = async (context: InitializationData, rte: IRteParam) => { + try { + const sdk = new UiLocation(context); + const materializedPlugins: { [key: string]: Plugin } = {}; + for (const def of definitionsToProcess) { + const pluginInstance = await materializePlugin(def, sdk); + materializedPlugins[def.id] = pluginInstance; + } + rte.sdk = sdk; + return materializedPlugins; + } catch (err) { + console.error("Error during plugin registration:", err); + throw err; + } + }; + return plugins; +} + +export { + IConfig, + IDisplayOnOptions, + IDynamicFunction, + IElementTypeOptions, + IOnFunction, + IRteElementType, + IRteParam, + Plugin, + PluginBuilder, + PluginDefinition, + registerPlugins +}; \ No newline at end of file diff --git a/src/uiLocation.ts b/src/uiLocation.ts index 5507b495..1a9ccf0a 100755 --- a/src/uiLocation.ts +++ b/src/uiLocation.ts @@ -431,7 +431,7 @@ class UiLocation { return Promise.resolve(this.config); } return this.postRobot - .sendToParent("getConfig") + .sendToParent("getConfig", {context:{installationUID:this.installationUID, extensionUID:this.locationUID}}) .then(onData) .catch(onError); }; @@ -485,7 +485,7 @@ class UiLocation { api = (url: string, option?: RequestInit): Promise => dispatchApiRequest(url, option) as Promise; - + /** * Method used to create an adapter for management sdk. */ @@ -519,4 +519,4 @@ class UiLocation { } } -export default UiLocation; +export default UiLocation; \ No newline at end of file diff --git a/src/utils/adapter.ts b/src/utils/adapter.ts index b96fb4c3..82637c77 100644 --- a/src/utils/adapter.ts +++ b/src/utils/adapter.ts @@ -14,10 +14,17 @@ import { axiosToFetchResponse, fetchToAxiosConfig } from "./utils"; * @returns A function that takes AxiosRequestConfig and returns a promise. */ export const dispatchAdapter = - (postRobot: typeof PostRobot) => (config: AxiosRequestConfig) => { + (postRobot: typeof PostRobot) => + ( + config: AxiosRequestConfig, + context?: { installationUID: string; extensionUID: string } + ) => { return new Promise((resolve, reject) => { postRobot - .sendToParent("apiAdapter", config) + .sendToParent("apiAdapter", { + data: config, + extension: context, + }) .then((event: unknown) => { const { data: response } = event as { data: AxiosResponse }; @@ -56,12 +63,14 @@ export const dispatchAdapter = */ export const dispatchApiRequest = async ( url: string, - options?: RequestInit + options?: RequestInit, + context?: { installationUID: string; extensionUID: string } ): Promise => { try { const config = fetchToAxiosConfig(url, options); const axiosResponse = (await dispatchAdapter(PostRobot)( - config + config, + context )) as AxiosResponse; return axiosToFetchResponse(axiosResponse); @@ -70,8 +79,7 @@ export const dispatchApiRequest = async ( return new Response(err.response?.data, { status: err.response.status, statusText: err.response.statusText, - headers: err.response.headers - + headers: err.response.headers, }); } return new Response(err.stack, { @@ -81,3 +89,4 @@ export const dispatchApiRequest = async ( }); } }; +