+----------------------+
| Metadata             |
+----------+-----------+
| Template | guides    |
+----------+-----------+
| Category | resources |
+----------+-----------+

+-------------------+
| Section Metadata  |
+---------+---------+
| style   | content |
+---------+---------+

# Extending the Sidekick

The goal of this document is to explain how developers can interact with the sidekick, and how it can be customized at a project level.

## Events

The sidekick emits the following events:

+-------------------------------------------------------------------------------------------------------+
| Table                                                                                                 |
+-------------------------------------------------------------------------------------------------------+
| +:----------------:+:------------------:+:---------------------------:+:---------------------------:+ |
| | **Event Name**   | **Target Element** | **Payload**                 | **Description**             | |
| +------------------+--------------------+-----------------------------+-----------------------------+ |
| | `sidekick-ready` | `document`         | \-                          | The sidekick element has    | |
| |                  |                    |                             | been added to the DOM and   | |
| |                  |                    |                             | is ready for use.           | |
| +------------------+--------------------+-----------------------------+-----------------------------+ |
| | `shown`          | `aem-sidekick`     | \-                          | The extension has been      | |
| |                  |                    |                             | toggled on.                 | |
| +------------------+--------------------+-----------------------------+-----------------------------+ |
| | `hidden`         | `aem-sidekick`     | \-                          | The extension has been      | |
| |                  |                    |                             | toggled off, or the close   | |
| |                  |                    |                             | button has been clicked.    | |
| +------------------+--------------------+-----------------------------+-----------------------------+ |
| | `previewed`      | `aem-sidekick`     | (string) The path(s) of the | The Preview button has been | |
| |                  |                    | previewed resource          | clicked.                    | |
| +------------------+--------------------+-----------------------------+-----------------------------+ |
| | `updated`        | `aem-sidekick`     | (string) The path of the    | The Reload button has been  | |
| |                  |                    | updated resource            | clicked.                    | |
| +------------------+--------------------+-----------------------------+-----------------------------+ |
| | `published`      | `aem-sidekick`     | (string) The path of the    | The Publish button has been | |
| |                  |                    | published resource          | clicked.                    | |
| +------------------+--------------------+-----------------------------+-----------------------------+ |
| | `deleted`        | `aem-sidekick`     | (string) The path of the    | The Delete button has been  | |
| |                  |                    | deleted resource            | clicked.                    | |
| +------------------+--------------------+-----------------------------+-----------------------------+ |
| | `unpublished`    | `aem-sidekick`     | (string) The path of the    | The Unpublish button has    | |
| |                  |                    | unpublished resource        | been clicked.               | |
| +------------------+--------------------+-----------------------------+-----------------------------+ |
| | `env-switched`   | `aem-sidekick`     | (object) An object with the | The user has switched the   | |
| |                  |                    | following properties:       | environment.                | |
| |                  |                    |                             |                             | |
| |                  |                    | - (string) sourceUrl: The   |                             | |
| |                  |                    |   source URL                |                             | |
| |                  |                    | - (string) targetUrl: The   |                             | |
| |                  |                    |   target URL                |                             | |
| +------------------+--------------------+-----------------------------+-----------------------------+ |
| | `plugin-used`    | `aem-sidekick`     | (string) The plugin ID      | A plugin button has been    | |
| |                  |                    |                             | clicked                     | |
| +------------------+--------------------+-----------------------------+-----------------------------+ |
| | `logged-in`      | `aem-sidekick`     | (object) The profile object | The user has signed in.     | |
| +------------------+--------------------+-----------------------------+-----------------------------+ |
| | `logged-out`     | `aem-sidekick`     | (object) The profile object | The user has signed out.    | |
| +------------------+--------------------+-----------------------------+-----------------------------+ |
| | `status-fetched` | `aem-sidekick`     | (object) The status object  | The status for a resource   | |
| |                  |                    |                             | has been fetched from the   | |
| |                  |                    |                             | Admin API                   | |
| +------------------+--------------------+-----------------------------+-----------------------------+ |
| | `custom:<name>`  | `aem-sidekick`     | (object) An object with the | A custom event-based plugin | |
| |                  |                    | following properties:       | button has been clicked.    | |
| |                  |                    |                             |                             | |
| |                  |                    | - (object) config: The      |                             | |
| |                  |                    |   sidekick configuration    |                             | |
| |                  |                    | - (object) location: The    |                             | |
| |                  |                    |   location object           |                             | |
| |                  |                    | - (object) status: The      |                             | |
| |                  |                    |   status object             |                             | |
| +------------------+--------------------+-----------------------------+-----------------------------+ |
+-------------------------------------------------------------------------------------------------------+

An event’s payload can be found in the `event.detail` property.

### Listening for Events

In your project code (e.g. in `/scripts/scripts.js`), you can react to sidekick events as follows (replace `foo` with the name of the event you want to listen for):

```
const doFoo = ({ detail: payload }) => {
  console.log('something happened', payload);
  // your custom code goes here
};

const sk = document.querySelector('aem-sidekick');
if (sk) {
  // sidekick already loaded
  sk.addEventListener('foo', doFoo);
} else {
  // wait for sidekick to be loaded
  document.addEventListener('sidekick-ready', () => {
    // sidekick now loaded
    document.querySelector('aem-sidekick')
      .addEventListener('foo', doFoo);
  }, { once: true });
}
```

## Customizing the Sidekick

You can customize the sidekick for your project by adding a `sidekick` object to your site configuration. See here for an example of how to [use the configuration service](https://main--helix-website--adobe.aem.page/docs/config-service-setup#update-sidekick-configuration).

```
{
  "project": "My project",
  "plugins": []
}
```

For all available configuration options, see the [config schema](https://tools.aem.live/sidekick/config.schema.json). Here are some basics to get you started:

### Host Settings

**Note:** The following host properties are *optional* in the sidekick configuration and should only be used to explicitly override the default values. In general, host names should be defined in the [CDN settings of your site configuration](https://main--helix-website--adobe.aem.page/docs/config-service-setup#update-production-cdn) instead.

- `host` (string) The host name of your production website. Overrides `cdn.prod.host`, no default. Sidekick will redirect to the live environment on publish if no production host name is defined.
- `previewHost` (string) The host name of the preview environment. Overrides `cdn.preview.host`, defaults to `*.aem.page` if undefined.
- `liveHost` (string) The host name of the live environment. Overrides `cdn.live.host`, defaults to `*.aem.live` if undefined. Sidekick will skip the live environment on publish in case a production host name is defined.
- `reviewHost` (string) The host name of the review environment. Overrides `cdn.review.host`, defaults to `*.aem.reviews` if undefined.
- `trustedHosts` (string\[]) Additional host names trusted to access a [protected site](https://main--helix-website--adobe.aem.page/docs/authentication-setup-site) using sidekick authentication (use with caution!)

### Custom Plugins

Plugins allow you to add custom functionality to the sidekick, enhancing your users’ experience.

#### Common Plugin Properties

The following properties are applicable to all plugin types:

- `id` (string) is mandatory and must be unique within a sidekick configuration.
- `title` (string) will be shown on the plugin button.
- `titleI18n` (object\<string, string>) defines optionally translated titles.\
  Supported languages: en, de,  es,  fr,  it,  ja,  ko,  pt\_BR,  zh\_CN, and zh\_TW.
- `pinned` (boolean) determines if the plugin is pinned to the toolbar (default) or folded into the … menu.
- `environments` (string\[]) specifies where the plugin should appear:
  - any (default) - any environment
  - dev - the local development URL (e.g. `http://localhost:3000/`)
  - edit - an editor view (e.g. Word, Excel, Google Docs)
  - admin - a folder view (e.g. SharePoint, Google Drive)
  - preview - a preview URL (e.g. `https://main--site--org.aem.page/`)
  - live - a live URL (e.g. `https://main--site--org.aem.live/`)
  - prod - a preview URL (e.g. `https://www.example.com/`)
- `excludePaths` (string\[]) defines patterns to exclude the plugin based on the path in the current tab’s URL.
- `includePaths` (string\[]) defines patterns to include the plugin based on the path in the current tab’s URL.

#### URL-based Plugins

You can specify a `url`  that will be opened in a new tab when the plugin button is clicked:

```
{
  "plugins": [
    {
      "id": "foo",
      "title": "Foo",
      "url": "/tools/sidekick/foo.html"
    }
  ]
}
```

The following properties are specific to URL-based plugins:

- `passConfig` (boolean) pass the project configuration via query parameters
- `passReferrer` (boolean) pass the originating URL via query parameters

#### Event-based Plugins

Alternatively, you can specify the name of an `event` to be fired when the plugin button is clicked. This allows the execution of custom JavaScript code in the context of your page by listening for the event on the sidekick element. Custom events will have a `custom:` prefix. For your convenience, the custom event dispatched contains a copy of the current sidekick state.

**Note:** Event-based plugins can only be used in the following environments: Development, Preview, Live and Production. Executing custom code is not possible in Edit or Admin.

```
{
  "plugins": [
    {
      "id": "foo",
      "title": "Foo",
      "event": "foo"
    }
  ]
}
```

In your project code (e.g. in `/scripts/scripts.js`), you can react to the event as follows:

```
const doFoo = ({ detail: payload }) => {
  console.log('a custom event happened', payload);
  // your custom code goes here
};

const sk = document.querySelector('aem-sidekick');
if (sk) {
  // sidekick already loaded
    sk.addEventListener('custom:foo', doFoo);
} else {
  // wait for sidekick to be loaded
  document.addEventListener('sidekick-ready', () => {
    // sidekick now loaded
    document.querySelector('aem-sidekick')
      .addEventListener('custom:foo', doFoo);
  }, { once: true });
}
```

### Special Plugin Types

#### Palette Plugins

Palettes are variants of URL-based plugins which load the configured URL inside a floating palette instead of opening a new tab.

- `isPalette` (boolean) opens the target of a URL-based plugin in a palette instead of a new tab.
- `paletteRect` (string) optionally defines the size and position of the palette in the format of a [DOMRect](https://developer.mozilla.org/en-US/docs/Web/API/DOMRect).

The following example creates a standard palette placed at the bottom left of the window:

```
{
  "plugins": [
    {
      "id": "foo",
      "title": "Foo",
      "url": "/tools/sidekick/foo-palette.html",
      "isPalette": true
    }
  ]
}
```

If you wish to change the size and positioning of your palette, use `paletteRect`:

```
{
  "plugins": [
    {
      "id": "foo",
      "title": "Foo",
      "url": "/tools/sidekick/foo-palette.html",
      "isPalette": true,
      "paletteRect": "top:150px;left:7%;height:675px;width:85vw;"
    }
  ]
}
```

##### Manipulating a palette from within

Using Chrome's messaging API, you can tell the sidekick to close your palette, for example when the user clicks a button inside of it. The `id` property of the message object is the ID of your palette plugin:

```
chrome.runtime.sendMessage('igkmdomcgoebiipaifhmpfjhbjccggml', {
  id: 'foo',
  action: 'closePalette',
});
```

You can also resize and reposition your palette dynamically. The `id` property of the message object is the ID of your palette plugin. The `rect` object can contain CSS properties and values for `top`, `right`, `bottom`, `left`, `width` and `height`.

```
chrome.runtime.sendMessage('igkmdomcgoebiipaifhmpfjhbjccggml', {
  id: 'foo',
  action: 'resizePalette',
  rect: { "top": "100px", "left": "20px", "width": "50vw", "height": "500px" }
});
```

#### Popover Plugins

Popovers are variants of URL-based plugins which load the configured URL inside a popover instead of opening a new tab. Popovers are centered above the plugin's button.

- `isPopover` (boolean) opens the target of a URL-based plugin in a popover instead of a new tab.
- `popoverRect` (string) optionally defines the width and height of the popover in the format of a [DOMRect](https://developer.mozilla.org/en-US/docs/Web/API/DOMRect).

```
{
  "plugins": [
    {
      "id": "foo",
      "title": "Foo",
      "url": "/tools/sidekick/foo-popover.html",
      "isPopover": true
    }
  ]
}

```

If you wish to change the width or height of your palette, use `popoverRect`:

```
{
  "plugins": [
    {
      "id": "foo",
      "title": "Foo",
      "url": "/tools/sidekick/foo-popover.html",
      "isPopover": true,
      "popoverRect": "width:300px;height:200px;"
    }
  ]
}
```

A `theme` query parameter is appended to the URL to style the iframe in line with the current sidekick theme. If no background color is set on the content’s body, it will inherit the popover’s translucent background.

##### Manipulating a popover from within

Using Chrome's messaging API, you can tell the sidekick to close your popover, for example when the user clicks a button inside of it. The `id` property of the message object is the ID of your popover plugin:

```
chrome.runtime.sendMessage('igkmdomcgoebiipaifhmpfjhbjccggml', {
  action: 'closePopover',
  id: 'foo',
});
```

You can also resize your popover dynamically. The `id` property of the message object is the ID of your popover plugin. The `rect` object can contain CSS properties and values for `width` and `height`.

```
chrome.runtime.sendMessage('igkmdomcgoebiipaifhmpfjhbjccggml', {
  id: 'foo',
  action: 'resizePopover',
  rect: { "width": "50vw", "height": "500px" }
});
```

#### Container Plugins

Containers allow you grouping plugins together and help save space in the toolbar. Clicking a container plugin simply toggles its dropdown, it can’t have its own URL or event action.

- `isContainer` (boolean) renders a plugin as a dropdown instead of a button.
- `containerId` (string) adds a plugin to a container plugin with the specified ID.

The following example creates a container named “Tools” and places a plugin “Foo” in it:

```
{
  "plugins": [
    {
      "id": "tools",
      "title": "Tools",
      "isContainer": true
    },
    {
      "id": "foo",
      "containerId": "tools",
      "title": "Foo",
      "event": "foo"
    }
  ]
}
```

#### Badge Plugins

Badges allow you to add labels to the sidekick under certain conditions. They will be rendered on the right hand side of the toolbar. Badges have a merley decorative purpose and can’t be clicked.

- `isBadge` (boolean) renders a plugin as a badge instead of a button.
- `badgeVariant` (string) optionally determines the badge’s color scheme (gray, red, orange, yellow, chartreuse, celery, green, seafoam, cyan, blue, indigo, purple, fuchsia, or magenta)

The following example adds a “Stage” badge to the sidekick in the preview environment:

```
{
  "plugins": [
    {
      "id": "stage",
      "title": "Stage",
      "isBadge": true,
      "environments" ["preview"]
    }
  ]
}
```

### Customizing Default Plugins

By specifying an `id` of a default plugin (such as `preview`, `update` or `publish`), you can modify the conditions under which they will be rendered. The following [plugin properties](#common-plugin-properties) will be considered: `environments`, `excludePaths`, and `includePaths`.

In the following example, the publish plugin would only appear in the preview environment, and only if the path of the current resource does not contain a `drafts` segment:

```
{
  "plugins": [
    {
      "id": "publish",
      "excludePaths": ["**/drafts/**"],
      "environments": ["preview"]
    }
  ]
}
```

#### Publish Confirmations

The publish plugin can be configured to show a confirmation dialog prior to executing the action, prompting the user to verify the action and giving them the ability to cancel it. This can be used as an extra precaution, for example when dealing with critical content that could have business implications if published erroneously:

```
{
  "plugins": [
    {
      "id": "publish",
      "confirm": true
    }
  ]
}
```

**Note:** Publish confirmations can only be enabled for your entire site.

### Custom Edit URLs

If your project does not use SharePoint or Google Drive as content source, you can tell the sidekick how to link to your custom editing environment when the user clicks *Edit*.

The following two config options are available:

- `editUrlLabel` (string) set the label visible to the user
- `editUrlPattern` (string) defines an URL pattern for the custom editing environment. Supports placeholders like `{{contentSourceUrl}}` or `{{pathname}}`.

```
{
  "editUrlLabel": "Your Editor",
  "editUrlPattern": "{{contentSourceUrl}}{{pathname}}?cmd=open"
}
```

### Special Views

You can specify a special view for the sidekick to redirect to when the current tab’s URL matches a certain pattern. This can help you provide a seamless user experience across different media types, and also enables the execution of custom code (event-based plugins). The original resource URL will be available in a `url` query parameter.

The properties `path` and `viewer` are mandatory. Optionally, you can specify a `title` that will be shown at the top, and you can provide localized titles in a `titleI18n` object:

```
{
  "specialViews": [
    {
      "title": "Custom JSON Viewer",
      "path" : "**.json",
      "viewer": "/tools/sidekick/custom-json-viewer/index.html"
    }
  ]
}
```

\
At the path specified by `viewer`, add an HTML file to your GitHub repository, for example:

```
<html>
<head>
  <title>Custom JSON Viewer</title>
  <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="./custom-json-viewer.css">
  <!-- add your custom code -->
  <script src="./custom-json-viewer.js"></script>
</head>
  <body>
  </body>
</html>
```

Add an optional CSS file in the same directory, and a JS file with your custom logic, for example:

```
try {
  // get the resource URL from the url query parameter
  const url = new URL(window.location.href).searchParams.get('url');
  if (url) {
    const res = await fetch(url);
    if (res.ok) {
      const text = await res.text();
      const data = JSON.parse(text);
      // do something with the data, e.g.
      document.body.innerHTML = `
        <pre>
          ${JSON.stringify(data, null, 2)}
        </pre>
       `;
    } else {
      throw new Error(`failed to load ${url}: ${res.status}`);
    }
  }
} catch (e) {
  console.error('error rendering custom json view', e);
}
```

## Development Workflow

The following workflows are designed for detached sidekick development to prevent unintentional disruptions for the authors on your production site:

### Using a Site Copy

If your site’s configuration is stored in the [Configuration Service](https://main--helix-website--adobe.aem.page/docs/config-service-setup), you can use temporary site copies for sidekick development:

1. Create a [copy of your site](https://main--helix-website--adobe.aem.page/docs/repoless) in the Configuration Service. For example, if the name of your original site is `site1`, you could create a `site1-dev`, reusing the same code and content.
2. Open the preview URL for `site1-dev` in your browser: `https://main--site1-dev--org.aem.page`.
3. Make your desired changes to the [sidekick object](https://www.aem.live/docs/config-service-setup#update-sidekick-configuration) in the `site1-dev` configuration.
4. Refresh the browser tab after each change to test your changes.
5. When done, copy the [sidekick object](https://www.aem.live/docs/config-service-setup#update-sidekick-configuration) from `site1-dev` to the `site1` configuration to roll your changes out to all authors.

**Note:** When using the sidekick in an editor environment (Google Drive or Microsoft Sharepoint), it will load the config from the original site by default. If you want the sidekick to let you choose which configuration to load, first [add the new site](https://main--helix-website--adobe.aem.page/docs/sidekick#adding-projects) to your sidekick from the preview or live URL. Now the sidekick will display a picker with all matching sites.

### Using a Repository Branch

If your site’s configuration is not stored in the [Configuration Service](https://main--helix-website--adobe.aem.page/docs/config-service-setup), you can use a branch in GitHub for sidekick development:

1. On your site’s GitHub repository, create a branch from `main`. For this example, we’ll use  `dev` as the branch name.
2. Open the preview URL for the `dev` branch in your browser: `https://dev--site1--org.aem.page`.
3. Open or create the following file in your repository: `/tools/sidekick/config.json`.
4. Make your desired changes to the sidekick configuration file and push changes to the `dev` branch.
5. Refresh the browser tab after each change to test your changes.
6. When done, create a pull request and merge the changes to the `main` branch of your repository.

**Caution:** Never commit directly to the `main` branch in your original repository. Always create a branch and ask for a review of your changes via pull request before merging into `main`.

**Note:** When using the sidekick in an editor environment (Google Drive or Microsoft Sharepoint), it will load the config from the original site by default. If you want the sidekick to let you choose which configuration to load, first [add the new site](https://main--helix-website--adobe.aem.page/docs/sidekick#adding-projects) to your sidekick from the preview or live URL. Now the sidekick will display a picker with all matching sites.
