diff --git a/gatsby-config.js b/gatsby-config.js index f08c7e23..e01e4c0f 100644 --- a/gatsby-config.js +++ b/gatsby-config.js @@ -37,6 +37,10 @@ module.exports = { title: "AEM Content Fragments Editor", path: "/services/aem-cf-editor/" }, + { + title: "Universal Editor", + path: "/services/aem-universal-editor/" + }, { title: "Adobe Commerce Admin", path: "https://developer.adobe.com/commerce/extensibility/admin-ui-sdk/" @@ -214,7 +218,41 @@ module.exports = { path: "/extension-manager/extension-developed-by-adobe/content-fragments-workflows" } ] - } + }, + { + title: "Extension Points", + path: "/services/aem-universal-editor/api/", + pages: [ + { + title: "Common Concepts", + path: "/services/aem-universal-editor/api/commons/" + }, + { + title: "Header Menu", + path: "/services/aem-universal-editor/api/header-menu/" + }, + { + title: "Modal Dialogs", + path: "/services/aem-universal-editor/api/modal/" + }, + { + title: "Custom panels in Properties Rail", + path: "/services/aem-universal-editor/api/properties-rails/" + }, + { + title: "Overriding Default Rendering of Item Types", + path: "/services/aem-universal-editor/api/item-types-renderers/" + }, + { + title: "Working with events", + path: "/services/aem-universal-editor/api/events/" + }, + { + title: "Retrieving Data from the Universal Editor", + path: "/services/aem-universal-editor/api/data/" + } + ] + }, ] }, plugins: [ diff --git a/src/pages/index.md b/src/pages/index.md index 5ca35162..f60fd6ab 100644 --- a/src/pages/index.md +++ b/src/pages/index.md @@ -98,6 +98,12 @@ Start building extensions for AEM Content Fragments editor +[Universal Editor](services/aem-universal-editor/) + +Start building extensions for the Universal Editor + + + [Adobe Commerce Admin](https://developer.adobe.com/commerce/extensibility/admin-ui-sdk/) Start building extensions for Adobe Commerce Admin diff --git a/src/pages/services/aem-universal-editor/api/commons/index.md b/src/pages/services/aem-universal-editor/api/commons/index.md new file mode 100644 index 00000000..ab385631 --- /dev/null +++ b/src/pages/services/aem-universal-editor/api/commons/index.md @@ -0,0 +1,100 @@ +--- +title: Universal Editor Extensibility +description: Learn how to customize Universal Editor +contributors: + - https://github.com/AdobeDocs/uix +--- + +# Common Concepts in Creating Extensions + +Understand the fundamentals required to develop an extension for the Universal Editor. + +## Extension Point + +Universal editor has an `universal-editor/ui/1` [extension point](https://developer.adobe.com/app-builder/docs/guides/extensions/) that allows you to extend its functionality. +To declare it to be used by your extension, you need to add the following configuration to your `app.config.yaml` at the +root of your extension: + +```yaml +extensions: + universal-editor/ui/1: + $include: src/universal-editor-ui-1/ext.config.yaml +``` +Here is an example of `ext.config.yaml` file: + +```yaml +operations: + view: + - type: web + impl: index.html +actions: actions +web: web-src +``` + +## Extension Registration + +Interaction between UI Extension and Universal Editor starts with the initialization process that includes extension's +capabilities registration so Universal Editor knows when to invoke the extension. Registration is done by `register` +method provided by `@adobe/uix-guest` library. This asynchronous method takes single object that describes extension +and returns object representing connection to the Universal Editor. + +Method `register` should be invoked after extension initialization page is loaded. + +Extension registration data must include: + +- `id` - string with random extension identifier. This identifier useful for debugging of interaction between Universal +Editor and extension and needed if extension provides custom UI. +- `methods` - objects with extension code exposed to the Universal Editor console. All methods are grouped into +namespaces that represents extension points provided by the Universal Editor. +Currently, the following **namespaces** are available: + - _headerMenu_, that allows to add buttons to the header of the Universal Editor; + - _rightPanel_, that allows to add custom content under the rails to the right panel of the Universal Editor; + - _canvas_, that allows to add custom renderer for data types + +```js +import { register } from "@adobe/uix-guest"; + +// ... + + const guestConnection = await register({ + id: "extension-id", + methods: { + headerMenu: { + getButtons() { + // .. + } + }, + rightPanel: { + getPanels() { + // .. + } + }, + canvas: { + getRenderers() { + // .. + } + } + } + }); +// ... +``` +## Building Extension UI + + +In cases where a UI Extension manages data or sends data to a remote service, the register method is the only one expected to be called. If the UI Extension includes its own interface, it should be presented on a separate page. If this interface needs data from the Universal Editor or needs to trigger any logic, it should establish a connection using the attach method. + +```js +import { attach } from "@adobe/uix-guest"; + +const guestConnection = await attach({ id: "extension-id" }); +const state = await connection.host.editorState.get(); +const token = await connection.sharedContext.get("token"); +const model = await connection.host.field.getModel(); +``` + +## Set up communication with Universal Editor + +Both `register` and `attach` function of `@adobe/uix-guest` returns same connection object that has `host` property and +expose API of Universal Editor exposed for UI Extensions. Through this api you can access data from the universal editor as well as send data to it. + +Check [this section](../data) to learn about common concepts on how to achive this diff --git a/src/pages/services/aem-universal-editor/api/data/index.md b/src/pages/services/aem-universal-editor/api/data/index.md new file mode 100644 index 00000000..0157d83c --- /dev/null +++ b/src/pages/services/aem-universal-editor/api/data/index.md @@ -0,0 +1,85 @@ +--- +title: Data exchange - Universal Editor Extensibility +description: Learn how to exchange data between Universal Editor and your extension +contributors: + - https://github.com/AdobeDocs/uix +--- + +# Learn how to exchange data between Universal Editor and your extension + +As we develop extensions for the Universal Editor, it's essential to facilitate data exchange between the editor and the extension. There are two main categories of data involved: +- User-specific information, like IMS organization. +- Editor-specific information, such as the current editor location. + +Below you can find the details on how to access these data categories. + +### Shared Context + +The shared context is a dataset that Universal Editor shares with UI Extensions. It's used to understand the context of +the user who is running Universal Editor. You can access the shared context through the `sharedContext` property of the +connection object. + +```js +import { attach } from "@adobe/uix-guest"; + + useEffect(() => { + (async () => { + const guestConnection = await attach({ id: extensionId }); + + setGuestConnection(guestConnection); + })(); + }, []); + + ... + +const context = guestConnection.sharedContext; +const hostAppLocale = context.get("locale"); +``` + +Available data in the shared context: + +| Key | Type | Description | +|--------|----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| locale | `string` | Locale string for globalization of current user | +| theme | `string` | Available [options](https://experienceleague.adobe.com/en/docs/experience-manager-cloud-service/content/overview/aem-cloud-service-on-unified-shell#changing-to-dark-theme): "light" or "dark". The theme selected by current user | +| orgId | `string` | IMS org ID | +| token | `string` | User token | +| authScheme | `string` | Auth schema that should be used during communication with host application | + + +### Editor State + +The editor state is a dataset that Universal Editor shares with UI Extensions. It's used to understand the current state +of the editor. You can access the editor state through the `editorState` property of the host object. + +Below is an example of how you can access editor state properties, e.g., connections. + +```js +import { attach } from "@adobe/uix-guest"; + +... + + useEffect(() => { + (async () => { + const guestConnection = await attach({ id: extensionId }); + + setGuestConnection(guestConnection); + })(); + }, []); + ... + +const editorState = await guestConnection.host.editorState.get(); +const {connections} = editorState; + +... + +``` +Available data in the editor state: + +| Key | Type | Sample | Description | +|-----|---------------------------|-----------------------------------------------------------------------|-------------| +| connections | `obj` | { "aemconnection": "aem:%auth_instance_url%" } | [Connections](https://experienceleague.adobe.com/en/docs/experience-manager-cloud-service/content/implementing/developing/universal-editor/getting-started#connections) which are used in the app are stored as `` tags in the page’s ``. | +| selected | `obj` | {fcb38012-c4c7-51a8-896c-79e76kjk: true} | Element that is currently edited. | +| editables | `[objects]` | Array[{id: '33661..", type: 'reference', resource: "urn:..., ..}, {}] | List of elements that might be edited. The editable element object includes [proper metadata](https://experienceleague.adobe.com/en/docs/experience-manager-cloud-service/content/implementing/developing/universal-editor/attributes-types). | +| location | `string` | "%locationString%" | The current page location | +| customTokens | `obj` | {"aemconnection":""} | Custom tokens available for connections | \ No newline at end of file diff --git a/src/pages/services/aem-universal-editor/api/events/index.md b/src/pages/services/aem-universal-editor/api/events/index.md new file mode 100644 index 00000000..69f6334c --- /dev/null +++ b/src/pages/services/aem-universal-editor/api/events/index.md @@ -0,0 +1,43 @@ +--- +title: Working with the events - Universal Editor Extensibility +description: Leveraging universal editor events within your application +contributors: + - https://github.com/AdobeDocs/uix +--- + +# Working with Events + +The Universal Editor sends defined events to remote applications. In case the remote application has no custom event listener for the sent event, a fallback event listener provided by the universal-editor-cors package is executed. + +### Working with Events + +The Universal Editor offers a list of events that extensions can subscribe to in order to respond to changes in content or the user interface. +Refer to this [document](https://experienceleague.adobe.com/en/docs/experience-manager-cloud-service/content/implementing/developing/universal-editor/events) for the available event list. + +For instance, here's an example of how to subscribe to the `aue:ui-select` event: + +```js + + useEffect(() => { + (async () => { + const guestConnection = await attach({id: extensionId}); + ... + await guestConnection.host.remoteApp.addEventListener('aue:ui-select', console.log('event recieved!')); + ... + })(); + }, []); +``` + +If your business logic requires sending an event to the Universal Editor, you can use the `dispatchEvent` method. +Here's an example of how to dispatch the `aue:ui-select` event: + +```js + useEffect(() => { + (async () => { + const guestConnection = await attach({id: extensionId}); + ... + await guestConnection.host.remoteApp.dispatchEvent('aue:ui-select', {data: 'some data'}); + ... + })(); + }, []); +``` \ No newline at end of file diff --git a/src/pages/services/aem-universal-editor/api/header-menu/header-menu-item-with-submenu.png b/src/pages/services/aem-universal-editor/api/header-menu/header-menu-item-with-submenu.png new file mode 100644 index 00000000..c01143d1 Binary files /dev/null and b/src/pages/services/aem-universal-editor/api/header-menu/header-menu-item-with-submenu.png differ diff --git a/src/pages/services/aem-universal-editor/api/header-menu/header-menu-item.png b/src/pages/services/aem-universal-editor/api/header-menu/header-menu-item.png new file mode 100644 index 00000000..3a4ce3ec Binary files /dev/null and b/src/pages/services/aem-universal-editor/api/header-menu/header-menu-item.png differ diff --git a/src/pages/services/aem-universal-editor/api/header-menu/index.md b/src/pages/services/aem-universal-editor/api/header-menu/index.md new file mode 100644 index 00000000..080fcaf3 --- /dev/null +++ b/src/pages/services/aem-universal-editor/api/header-menu/index.md @@ -0,0 +1,115 @@ +--- +title: Universal Editor Extensibility +description: Learn how to customize Universal Editor +contributors: + - https://github.com/AdobeDocs/uix +--- + +# Header Menu + +Header menu can be customized via methods defined in `headerMenu` namespace. + +You have the ability to: + +- create multiple buttons from single extension; +- implement drop-down menu buttons; +- use different [variations](https://spectrum.adobe.com/page/button/#Options) of buttons from React Spectrum; +- use any [icon](https://react-spectrum.adobe.com/react-spectrum/workflow-icons.html#available-icons) from React Spectrum; + +## Custom button with callback + +```js +import { register } from "@adobe/uix-guest"; + +// ... + +const guestConnection = await register({ + id: "my.company.extension-with-header-menu-button", + methods: { + headerMenu: { + getButtons() { + return [ + { + id: "my.company.export-button", + label: "Export", + icon: 'Export', + onClick: () => { + console.log('Export button has been pressed.'); + }, + }, + ]; + }, + }, + }, +}); +``` + +The `onClick` callback is invoked when a user clicks on the button. It does not receive any arguments. + +![Header menu item](./header-menu-item.png) + +## Custom button with sub menu + +```js +import { register } from "@adobe/uix-guest"; + +// ... + +const guestConnection = await register({ + id: "my.company.extension-with-header-menu-button", + methods: { + headerMenu: { + async getButtons() { + return [ + { + id: "my.company.export-button", + label: "Export", + icon: 'Export', + subItems: [ + { + id: 'xml', + label: 'XML', + onClick: async () => { + // ... + }, + }, + { + id: 'csv', + label: 'CSV', + onClick: async () => { + // ... + }, + }, + ], + }, + ]; + }, + }, + }, +}); +``` + +![Header menu item with submenu](./header-menu-item-with-submenu.png) + +## API Reference + +### Button API + +| Field | Type | Required | Description | +|----------|-----------------------------------------------------------------------------| ------ |-------------------------------------------------------------------------------------------------------------------------------| +| id | `string` | ✔️ | **Must be unique** across all extensions. Consider adding a vendor prefix to this field | +| label | `string` | ✔️ | Button label that will be visible on UI | +| icon | `string` | | Name of a [React-Spectrum workflow icon](https://react-spectrum.adobe.com/react-spectrum/workflow-icons.html#available-icons) | +| variant | `cta`
`primary`
`secondary`
`negative`
`action` | | The [visual style](https://spectrum.adobe.com/page/button/#Options) of the button | +| subItems | `array` | | A list with sub menu items. | +| onClick | `callback(): void` | ✔️ | A callback for a button `onClick` event | + +### Sub menu item API + +| Field | Type | Required | Description | +|----------|-------------------------------------------------------------------------| ------ |--------------------------------------------------------------------------------------------------------------| +| id | `string` | ✔️ | **Must be unique** across the current button sub menu | +| label | `string` | ✔️ | Button label that will be visible on UI | +| icon | `string` | | Name of a [React-Spectrum workflow icon](https://react-spectrum.adobe.com/react-spectrum/workflow-icons.html#available-icons) | +| onClick | `callback(): void` | ✔️ | A callback for a button `onClick` event | + diff --git a/src/pages/services/aem-universal-editor/api/index.md b/src/pages/services/aem-universal-editor/api/index.md new file mode 100644 index 00000000..71f48c83 --- /dev/null +++ b/src/pages/services/aem-universal-editor/api/index.md @@ -0,0 +1,40 @@ +--- +title: Universal Editor Extensibility +description: Learn how to customize Universal Editor +contributors: + - https://github.com/AdobeDocs/uix +--- + +# The Universal Editor Extension Points + +This section covers the utilization of existing extension points, extension registration, and common methods that can be used in any application that leverages extension points for service customization. + + + +[Common Concepts in Creating Extensions](commons) + +Learn about common concepts, extension registration, and methods that can be used in any extension + + + +[Header Menu](header-menu) + +Explore the ways to extend and customize Header Menu + + + +[Modal Dialogs](modal) + +Learn about modal host API methods that can be used in any extension + + + +[Custom data types renderers for properties rail](item-types-renderers) + +Learn how to customize the user interface of a data type field within the properties rail of Universal Editor. + + + +[Properties Rail](properties-rails) + +Explore the ways to extend and customize Properties Rail in Universal Editor. diff --git a/src/pages/services/aem-universal-editor/api/item-types-renderers/custom-renderer-field.png b/src/pages/services/aem-universal-editor/api/item-types-renderers/custom-renderer-field.png new file mode 100644 index 00000000..277a13d5 Binary files /dev/null and b/src/pages/services/aem-universal-editor/api/item-types-renderers/custom-renderer-field.png differ diff --git a/src/pages/services/aem-universal-editor/api/item-types-renderers/index.md b/src/pages/services/aem-universal-editor/api/item-types-renderers/index.md new file mode 100644 index 00000000..3178f46c --- /dev/null +++ b/src/pages/services/aem-universal-editor/api/item-types-renderers/index.md @@ -0,0 +1,180 @@ +--- +title: Custom item types renderers for properties rail - Universal Editor Extensibility +description: Discover how to personalize the user interface of a data type field within the properties rail of Universal Editor. +contributors: + - https://github.com/AdobeDocs/uix +--- + +# Custom element rendering for properties rail by data-type + +This feature allows third-party developer to build custom input UI for specific data types in the Universal Editor. +The custom UI is rendered in an iframe and replaces the standard UI for the field in the [properties rail](https://experienceleague.adobe.com/en/docs/experience-manager-cloud-service/content/sites/authoring/universal-editor/authoring#properties-rail). + +![](./custom-renderer-field.png) + +## Define override rules + +An UIX extension can define a custom renderer to replace the standard UI with an iframe, which then renders the custom UI provided by the extension. +Check this document to learn about available [data types for universal editor](https://experienceleague.adobe.com/en/docs/experience-manager-cloud-service/content/implementing/developing/universal-editor/attributes-types#item-types). +```js +function ExtensionRegistration() { + const init = async () => { + const guestConnection = await register({ + id: extensionId, + methods: { + canvas: { + getRenderers() { + return [ + { + dataType: "text", + url: '/#/renderer/1' + }, + { + dataType: "custom-type", + url: '/#/renderer/2', + }, + ]; + }, + }, + }, + }); + }; + init().catch(console.error); + + return IFrame for integration with Host (AEM)...; +} +``` + +## Implement custom rendering logic + +1. Define the router in the extension application to handle the custom rendering of the field. +```js +function App() { + return ( + + + + } /> + } + /> + } + /> + + + + ) + + // Methods + + // error handler on UI rendering failure + function onError(e, componentStack) {} + + // component to show if UI fails rendering + function fallbackComponent({ componentStack, error }) { + ... +} + +export default App +``` + +2. Implement custom field renderer + +```js +export default () => { + const [isLoading, setIsLoading] = useState(true); + const [connection, setConnection] = useState(); + const [model, setModel] = useState(); + const [value, setValue] = useState(); + const [error, setError] = useState(); + const [validationState, setValidationState] = useState(); + + const { rendererId } = useParams(); + if (!rendererId) { + console.error('Renderer id parameter is missed'); + return; + } + + useEffect(() => { + const init = async () => { + // connect to the host + const connection = await attach({ id: extensionId }); + setConnection(connection); + // get model + setModel(await connection.host.field.getModel()); + // get field value + setValue(await connection.host.field.getValue()); + // get field error + setError(await connection.host.field.getError()); + // get field validation state + setValidationState(await connection.host.field.getValidationState()); + setIsLoading(false); + }; + init().catch((e) => + console.log("Extension got the error during initialization:", e) + ); + }, []); + + const onChangeHandler = (v) => { + console.log("onChange on extension side", v); + connection.host.field.onChange(v); + }; + + return ( + + {!isLoading ? ( + <> + Content generated by the extension Renderer#{rendererId} + + + + + ) : ( + + + + )} + + ); +}; + +``` + +## Extension Registration Reference + +| Field | Type | Required | Description | +|-------------|----------|---------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| url | `string` | ✔️ | URL of the page to load in the iframe that will replace the original field. The URL must have the same origin as the extension declaring the rules for field replacement. | +| dataType | `string` | ✔️ | Value of `data-aue-type` | +| icon | `string` | | Icons to be displayed in the properties rail if data type is matched. | +## Field Reference + +```js +{ + await connection.host.field +} +``` +| Methods | Description | +|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `getModel` | Returns the model of the field. | +| `getValue` | Returns the value of the field. | +| `getError` | Returns the error of the field. | +| `getValidationState` | Returns the validation state of the field. | +| `onChange` | Sets the value of the field. | +| `setHeight` | Sets the height of the field. | \ No newline at end of file diff --git a/src/pages/services/aem-universal-editor/api/modal/index.md b/src/pages/services/aem-universal-editor/api/modal/index.md new file mode 100644 index 00000000..d1719aca --- /dev/null +++ b/src/pages/services/aem-universal-editor/api/modal/index.md @@ -0,0 +1,164 @@ +--- +title: Universal Editor Extensibility +description: Learn how to customize Universal Editor +contributors: + - https://github.com/AdobeDocs/uix +--- + +# Modal dialogs + +Describes basic methods for using modals dialogs within an extension. + +The Universal Editor (host instance) provides an API for showing modal dialogs with custom UI defined by an extension. These modals can be triggered by a click on the button or other events. Modal API is defined in the `modal` namespace. + +Content of the modal is rendered in an iframe with source defined by extension. Before showing modal you should create a page which renders Modal UI. This UI should use Adobe Spectrum UI library to provide consistent experience to the user. + +## An example of opening and closing a modal + +In order to display modal dialog extension must call `showUrl` method in `modal` namespace. + +```js +import { useEffect } from "react"; +import { Text } from "@adobe/react-spectrum" +import { extensionId } from "./Constants" +import { register } from "@adobe/uix-guest"; + +function ExtensionRegistration() { + useEffect(() => { + const init = async () => { + const guestConnection = await register({ + id: "my.company.extension-with-modal", + methods: { + headerMenu: { + getButtons() { + return [ + { + id: "example.button.actionWithSubItems", + label: "UIX Sub Items", + variant: "action", + icon: 'PublishCheck', // Spectrum workflow icon code from https://spectrum.adobe.com/page/icons/ + subItems: [ + { + id: 'modalDialog', + label: 'Modal Dialog', + icon: 'PublishSchedule', + onClick: async () => { + console.log('Button has been pressed.'); + url: "/index.html#/modal", // absolute or relative path + guestConnection.host.modal.showUrl({ + title: 'Modal Dialog: ', + url, + width: '900px', + }); + }, + }, + ], + } + ] + } + } + } + }); + } + init().catch(console.error) + }, []); + return IFrame for integration with Host... +} + +export default ExtensionRegistration +``` + +Modal may be closed by `close` method + +```js +import React, { useState, useEffect } from "react"; +import { attach } from "@adobe/uix-guest"; +import { + Provider, + Content, + defaultTheme, + Button +} from "@adobe/react-spectrum"; + +export default ModalComponent = () => { + const [guestConnection, setGuestConnection] = useState(null); + + + useEffect(() => { + (async () => { + const guestConnection = await attach({ id: "my.company.extension-with-modal" }); + + setGuestConnection(guestConnection); + })(); + }, []); + +... + const onCloseHandler = () => { + guestConnection.host.modal.close(); + }; + ... + return ( + + + ... + + + + ); + } + + +``` +![](./modal.png) + +## API Reference + +### Modal API Request Object + +The `modal.showUrl` and `modalInstance.set` methods accept a `ModalRequest` object. + +| Property | Type | Required | Default | Description | +|-----------|------------------------------------------|----------|---------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `url` | `string` | ✔️ | | URL of the page to load in the dialog frame. The URL must have the same origin as the extension making the modal request. | +| `title` | `string` | ✔️ | | Title of the modal to display. | +| `height` | `string`
`number`
`"auto"` | | `auto` | A number of pixels, a CSS value, or the string `auto`. The `auto` keyword will grow or shrink the modal to the height of the document in the iframe every time the guest document resizes, to a minimum of 20% and a maximum of 75% of window height. In fullscreen mode, this is ignored. | +| `width` | `string`
`number`
`"auto"` | | `auto` | A number of pixels, a CSS value, or the string `auto`. The `auto` keyword will grow or shrink the modal to the width of the document in the iframe every time the guest document resizes, to a minimum of 20% and a maximum of 75% of window width. In fullscreen mode, this is ignored. | +| `fullscreen` | `boolean` | | `false` | Display the dialog as large as possible. It will overlay most of the application, leaving small borders to indicate overlay. If true, any "width" and "height" parameters will be ignored. | +| `isDismissable` | `boolean` | | `true` | Show the dismiss button, so a user can close the modal no matter what state it is in. If an extension disables this, it must provide its own UI control which calls `modal.close()`. | +| `loading` | `boolean` | | `false` | Preserve the progress spinner that displays before the modal contents load. When `false` or unset, the modal will show a progress spinner until the guest in the iframe is connected, and then display the frame contents. If the `modal.showUrl()` call sets `{ loading: true }`, the spinner will continue displaying after the guest has connected, until the guest calls `modal.set({ loading: false })`. A modal which needs to do data fetching or layout adjustment after connecting should set `{ loading: true }` and then dismiss it from the modal when its UI is ready. | + +### Modal API + +The modal API is available to all extensions at the host.modal property. + +#### `modal.showUrl(request: ModalRequest): Promise` + +Any guest's GuestServer may call `modal.showUrl` to open a modal dialog. The provided `ModalRequest` object must have a `url` of the page to be displayed inside the modal. Typically this is another page or route in the calling extension app. + +If another modal is displaying and it belongs to a different extension, it rejects. + +#### `modal.close(): Promise` + +Close the current modal. If the current modal doesn't belong to the calling extension, it rejects. + +### Modal Instance API + +This API is returned to the GuestServer and also shared with the modal that loads inside the frame. A GuestServer loaded in a modal must call `set` instead of `showUrl`. + +#### `modal.set(request: ModalRequest): Promise` + +Modify the currently displaying modal. GuestUI frames running inside the modal may call this method to change their dimensions, or to change other parameters. + +#### `modal.close(): Promise` + +Close the modal. + +### Resizing + +When `height` and/or `width` are set to `"auto"`, the modal will attempt to resize whenever the displayed page changes size. This is to make the content of the iframe behave, as much as possible, like they're part of the content flow of the host application. + +- ⚠️ Resizes are detected every 100ms. +- ⚠️ Height is limited to between 20% and 75% of page height, and width is similarly limited. +- ⚠️ **If the page sets certain kinds of relative dimensions on its body elements, such as `99vh` or `101vmin`, it can cause slow expansion or contraction of the displayed frame.** Those measurements change as the outer document resizes the inner document, which triggers a loop. Remedy this by using other units to define the outside of your app. diff --git a/src/pages/services/aem-universal-editor/api/modal/modal.png b/src/pages/services/aem-universal-editor/api/modal/modal.png new file mode 100644 index 00000000..ef6c8650 Binary files /dev/null and b/src/pages/services/aem-universal-editor/api/modal/modal.png differ diff --git a/src/pages/services/aem-universal-editor/api/properties-rails/index.md b/src/pages/services/aem-universal-editor/api/properties-rails/index.md new file mode 100644 index 00000000..f5d1f2ec --- /dev/null +++ b/src/pages/services/aem-universal-editor/api/properties-rails/index.md @@ -0,0 +1,162 @@ +--- +title: Properties Rail - Universal Editor Extensibility +description: Explore the ways to extend and customize Properties Rail in Universal Editor +contributors: + - AdobeDocs/uix +--- + +# Properties Rail + +The [properties rail](https://experienceleague.adobe.com/en/docs/experience-manager-cloud-service/content/sites/authoring/universal-editor/authoring#properties-rail) is always present along the right side of [Universal Editor](../../). The extensibility feature allows adding new panels to it, ensuring seamless integration. + +![Properties Rail](properties-rail-initial.png) + +## Example of adding custom panels + +This code snippets demonstrate how to create a custom panel using [UIX SDK](https://github.com/adobe/uix-sdk) library and add it to the properties rail of the editor, enabling users to access and interact with the custom functionality seamlessly. + +```js +// App.js + +import { HashRouter as Router, Routes, Route } from "react-router-dom" +import ExtensionRegistration from "./ExtensionRegistration" +import RailContent from "./RailContent"; + +// ... + +function App() { + return ( + + + + } /> + } + /> + } + /> + + + + ) + // ... +} +``` + +```js +// ExtensionRegistration.js + +import { register } from "@adobe/uix-guest"; + +// ... + +function ExtensionRegistration() { + useEffect(() => { + const init = async () => { + const registrationConfig = { + id: extensionId, + methods: { + rightPanel: { + addRails() { + return [ + { + id: "my.company.panel_1", + header: "Last Changes", + url: '/#/rail/1', + icon: 'Export', + }, + { + id: "my.company.panel_2", + header: "Workflow", + url: '/#/rail/2', + hotkey: "w", + icon: 'Import', + }, + ]; + }, + }, + }, + }; + const guestConnection = await register(registrationConfig); + } + init().catch(console.error) + }, []); + return IFrame for integration with Host... +} +``` + +```js +// RailContent.js + +import { attach } from "@adobe/uix-guest"; + +// ... + +export default () => { + const { railId } = useParams(); + if (!railId) { + console.error('Rail id parameter is missed'); + return; + } + + // If you need to interact with an AEM instance + const connection = await attach({ id: extensionId }); + + return ( + + Content generate by the extension Rail#{railId} + + ); +}; + +``` + +![Extended Properties Rail](properties-rail-ext.png) + +## API Reference + +### Extension registration API + +The interface available for use from the extension side can be described using the following TypeScript types (although TypeScript usage is not mandatory): + +```js +type RailExtensionApi = { + rightPanel: { + addRails(): ExtensionRail[]; + }; +}; + +type ExtensionRail = { + id: string; + header: string; + url: string; + icon: string; + hotkey?: `${ModifierType}${KeyType}`; +}; +``` + +| Field | Type | Required | Description | +|-----------|-----------------------------------------------------------------------------|----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------| +| id | `string` | ✔️ | **Must be unique** across all extensions. Consider adding a vendor prefix to this field | +| header | `string` | ✔️ | The tooltip that appears when hovering over the tool icon and the header of the panel | +| url | `string` | ✔️ | The URL of the page to be loaded into the iframe, serving as the content source for the panel. | +| icon | `string` | ✔️ | Name of a [React-Spectrum workflow icon](https://react-spectrum.adobe.com/react-spectrum/workflow-icons.html#available-icons) | +| hotkey | `string` | | The hotkey field allows you to define keyboard shortcuts or hotkeys. Ensure uniqueness and avoid conflicts with system-wide functions when defining hotkeys. | + +#### hotkey + +You can combine modifiers like `shift`, `ctrl`, `alt`, or `cmd` with alphanumeric keys (A-Z, 0-9) or special keys such as `minus`, `equal`, or arrow keys. Ensure uniqueness and avoid conflicts with system-wide functions when defining hotkeys. + +ModifierType could be combinations of: `shift+`, `ctrl+`, `alt+`, `cmd+`, `shift+ctrl+`, `shift+alt+`, `shift+cmd+`, `ctrl+alt+`, `ctrl+cmd+`, `alt+cmd+`, `shift+ctrl+alt+`, `shift+ctrl+cmd+`, `shift+alt+cmd+`, `ctrl+alt+cmd+`, `shift+ctrl+alt+cmd+`, ``. + +KeyType could be: Alphanumeric keys `(A-Z, 0-9)`, `minus`, `equal`, `bracketleft`, `bracketright`, `backslash`, `semicolon`, `quote`, `comma`, `period`, `slash`, `backquote`, `enter`, `escape`, `backspace`, `tab`, `insert`, `home`, `end`, `pageup`, `pagedown`, `delete`, `arrowup`, `arrowdown`, `arrowleft`, `arrowright`. + +Examples: `ctrl+k`, `ctrl+shift+s`, `shift+arrowup`, `c.` + +## Limitation + +Each click on the icon corresponding to the panel will result in the re-rendering of that panel. The panel content is not cached and will be re-rendered diff --git a/src/pages/services/aem-universal-editor/api/properties-rails/properties-rail-ext.png b/src/pages/services/aem-universal-editor/api/properties-rails/properties-rail-ext.png new file mode 100644 index 00000000..74d29bdb Binary files /dev/null and b/src/pages/services/aem-universal-editor/api/properties-rails/properties-rail-ext.png differ diff --git a/src/pages/services/aem-universal-editor/api/properties-rails/properties-rail-initial.png b/src/pages/services/aem-universal-editor/api/properties-rails/properties-rail-initial.png new file mode 100644 index 00000000..2727a237 Binary files /dev/null and b/src/pages/services/aem-universal-editor/api/properties-rails/properties-rail-initial.png differ diff --git a/src/pages/services/aem-universal-editor/index.md b/src/pages/services/aem-universal-editor/index.md new file mode 100644 index 00000000..c60d2dd3 --- /dev/null +++ b/src/pages/services/aem-universal-editor/index.md @@ -0,0 +1,19 @@ +--- +title: Universal Editor Extensibility +description: Universal Editor Extensibility +contributors: + - https://github.com/AdobeDocs/uix +--- + +# Universal Editor + +The [Universal Editor](https://experienceleague.adobe.com/en/docs/experience-manager-cloud-service/content/implementing/developing/universal-editor/introduction) is a versatile visual editor that is part of Adobe Experience Manager Sites. It enables authors to do what-you-see-is-what-you-get (WYSIWYG) editing of any headless or headful experience. Understand how it can help content authors deliver exceptional experiences and how it offers unparalleled freedom for developers. + +In this section, you will find the available [extension points](api) and examples of how to utilize them. + +As well as answer on the following topics: + +- [Accessing data from the universal editor and integrating it into your extension](api/data). +- [Leveraging universal editor events within your application](api/events). + +![universal-editor](universal-editor.png) \ No newline at end of file diff --git a/src/pages/services/aem-universal-editor/universal-editor.png b/src/pages/services/aem-universal-editor/universal-editor.png new file mode 100644 index 00000000..cc6af730 Binary files /dev/null and b/src/pages/services/aem-universal-editor/universal-editor.png differ