Giter Club home page Giter Club logo

kontent-ai / smart-link Goto Github PK

View Code? Open in Web Editor NEW
9.0 16.0 4.0 31.24 MB

Kontent.ai Smart Link SDK can be used to automatically inject smart links to Kontent.ai according to manually specified HTML data attributes on your website. It also lets you connect your website with Web Spotlight (for faster editing and preview of your content).

License: MIT License

JavaScript 1.93% TypeScript 97.79% HTML 0.27%
web-spotlight kontent-ai kontent-sdk kontent-ai-tool

smart-link's Introduction

Kontent.ai Smart Link SDK

licence npm downloads jsdelivr

Kontent.ai Smart Link SDK simplifies and enhances the process of editing and managing web content in Web Spotlight by embedding "smart links" into your web pages. These smart links, defined by specific HTML data attributes you set, create a direct bridge to the Kontent.ai CMS. This allows content creators and editors to quickly navigate from the preview website to the corresponding content in the Kontent.ai platform for editing or previewing.

⚠️ Important note: Kontent.ai Smart Link SDK is a browser-only SDK, which means that the Node.js environment is not currently supported. Make sure to always initialize the Smart Link SDK in a browser context.

Table of Contents

Features

  • ✏️ Edit Smart Links: Quickly navigate from your website's preview to the corresponding content in Kontent.ai, making content editing seamless and efficient.
  • Add Smart Links: Simplify the addition of modular content directly from your preview, enhancing content management without leaving the web context.
  • 🔄️ Automatic Reloads/Rebuilds: Set up the automatic webpages reloads when your content is ready in the Delivery Preview API, ensuring your preview always reflects the latest saved changes.
  • 👀 Live Preview: Experience real-time content changes even before they're saved, enabling a dynamic editing process that boosts productivity.

Installation

You can install this library using npm or using global CDNs such as jsdelivr.

npm

npm i @kontent-ai/smart-link

jsdelivr

When you include the UMD bundle of this library in the script tag of your HTML page, an SDK becomes available under the KontentSmartLink global variable. Both the JS bundle and its minified version can be found in the dist folder.

  • dist/kontent-smart-link.umd.min.js
  • dist/kontent-smart-link.umd.js

kontent-smart-link.umd.js

Gzip browser bundle

<script type='text/javascript'
        src='https://cdn.jsdelivr.net/npm/@kontent-ai/smart-link@latest/dist/kontent-smart-link.umd.js'></script>
kontent-smart-link.umd.min.js

Gzip browser bundle

<script type='text/javascript'
        src='https://cdn.jsdelivr.net/npm/@kontent-ai/smart-link@latest/dist/kontent-smart-link.umd.min.js'></script>

To prevent potential issues from arising due to breaking changes, it is recommended to replace @latest with a specific version number.

Usage

Quickstart

To integrate the Kontent.ai Smart Link SDK into your web project and enable smart link injection, follow these steps:

  1. Include SDK: Add the SDK to your project. You can do this by installing it from npm or embedding the UMD bundle in the script tag of your HTML page.
  2. Specify HTML data attributes: Define the HTML data attributes on your webpage elements where you want the smart links to appear. Detailed guidance on setting these attributes can be found here.
    <main data-kontent-project-id='00000000-0000-0000-0000-000000000000' data-kontent-language-codename='default'>
      <div data-kontent-item-id='00000000-0000-0000-0000-000000000000'>
        <div data-kontent-element-codename='title'>Title</div>
      </div>
    </main>
  3. Initialization: Initialize the SDK in your code to create the smart link to Kontent.ai. You can read more about SDK initialization here.
    const instance = KontentSmartLink.initialize({
      defaultDataAttributes: {
        projectId: '00000000-0000-0000-0000-000000000000',
        languageCodename: 'default', 
      },
    });
  4. Set up the custom autorefresh behavior (optional): The automatic page refresh feature after content is available on Delivery Preview API is enabled out of the box. However, in some situations you may need to define some custom behavior. You can read more about it here.
  5. Set up the live preview (optional): Make sure your website know how to react to the live preview messages from Kontent.ai. You can read more about live preview here.

For more complex examples, check the Examples section.

Data attributes

The Kontent.ai Smart Link SDK relies heavily on manually specified data attributes in your HTML markup to function properly. These attributes are essential for the SDK to identify where and how to integrate smart links into your content, they also enable the SDK to access necessary information, such as the Kontent.ai project ID and element codenames. It's important to note that the SDK does not automatically insert these data attributes into your HTML. You are responsible for adding them manually.

Available data attributes

Attribute Value Description
data-kontent-project-id guid Kontent.ai environment ID.
data-kontent-language-codename string Kontent.ai language codename.
data-kontent-item-id guid Content item ID.
data-kontent-component-id guid Content component ID.
data-kontent-element-codename string Content type element codename.
data-kontent-add-button - Specifies that node should have add-button rendered near it.
data-kontent-add-button-insert-position start | before | after | end Specifies the insert position of an item/content component added using add button.
data-kontent-add-button-render-position bottom-start | bottom | bottom-end | left-start | left | left-end | top-start | top | top-end | right-start | right | right-end Specifies visual location of add button.
data-kontent-disable-features highlight Specifies that the selected node should not have highlight (which includes edit smart links). Useful when there are too many smart links on your page.

Data attributes hierarchy

Setting up data attributes in a hierarchical structure is a strategic approach to simplify your integration with the Kontent.ai Smart Link SDK. This method eliminates the need to repeat attributes across multiple elements, making your code cleaner and more maintainable.

Hierarchical setup explained:
  • Project-Level Attributes: Begin by assigning the data-kontent-project-id attribute to a high-level element, such as the <body> tag. That approach ensures that all elements within the body inherit this project identifier, linking them to your specific Kontent.ai project. Similarly, if your content is published in a single language, assign the data-kontent-language-codename alongside the project ID to establish the language context for all contained elements.
  • Content-Specific Attributes: Next, identify elements representing individual content items or components within your webpage. Assign each a data-kontent-item-id, making these elements recognizable to the SDK as distinct pieces of content. For elements corresponding to specific fields or components within these elements, use the data-kontent-element-codename to map each directly to its counterpart in Kontent.ai.
  • Nested Content: In scenarios involving rich text or linked items that contain additional content elements, continue this hierarchical assignment. Place relevant item or component IDs on their respective container elements.
  • Content Components: Unlike standard content items, content components are designed to be used within a specific rich text element and do not appear as standalone items in you Content Inventory. To effectively integrate these content components with the Kontent.ai Smart Link SDK, it's essential to mark them with the data-kontent-component-id attribute within your HTML, ensuring it's correctly linked to its parent content item in the CMS.

Smart links

The Kontent.ai Smart Link SDK supports four distinct types of smart links, each designed to enhance your content editing workflow. These smart links are powered by the data attributes from the previous section and primarily function within the Web Spotlight preview iframe.

Edit element smart link

This smart link enables direct editing of a content item's specific element within the preview. When clicked:

  • Inside Web Spotlight: Opens the In-Context editor, focusing on the selected element.
  • Outside Web Spotlight: Redirects to the corresponding item in the Kontent.ai editor.

Data Attributes: data-kontent-project-id, data-kontent-language-codename, data-kontent-item-id, data-kontent-component-id? (optional), and data-kontent-element-codename.

<div data-kontent-project-id='00000000-0000-0000-0000-000000000000' data-kontent-language-codename='default'>
  <section data-kontent-item-id='00000000-0000-0000-0000-000000000000'>
    <div data-kontent-element-codename='codename'>Content</div>
  </section>
</div>

Edit content component smart link

This smart link enables direct editing of content components within the preview. It triggers the In-Context editor and brings the component into view.

Availability: Exclusive to the Web Spotlight environment.

Data attributes: data-kontent-project-id, data-kontent-language-codename, data-kontent-item-id, and data-kontent-component-id.

<div data-kontent-project-id='00000000-0000-0000-0000-000000000000' data-kontent-language-codename='default'>
  <section data-kontent-item-id='00000000-0000-0000-0000-000000000000'>
    <div data-kontent-component-id='00000000-0000-0000-0000-000000000000'>
      <div>Article</div>
      <div>Content</div>
    </div>
  </section>
</div>

Edit content item smart link

This smart link enables the editing of entire content items through a preview click, opening the In-Context editor.

Availability: Exclusive to the Web Spotlight environment.

Data attributes: data-kontent-project-id, data-kontent-language-codename, and data-kontent-item-id.

<div data-kontent-project-id='00000000-0000-0000-0000-000000000000' data-kontent-language-codename='default'>
  <section data-kontent-item-id='00000000-0000-0000-0000-000000000000'>
    <div>Article</div>
    <div>Content</div>
  </section>
</div>

Add content smart link

This smart link enables the addition of new modular content directly within your page's preview, supporting both rich-text and linked item elements.

Availability: Exclusive to the Web Spotlight environment.

Fixed add content smart link

Fixed add content smart link positions new content at predetermined points of the target rich-text or linked items (start or end).

Data attributes: data-kontent-project-id, data-kontent-language-codename, data-kontent-item-id, data-kontent-component-id? (optional), data-kontent-element-codename (codename of the rich-text or linked items element), data-kontent-add-button, data-kontent-add-button-render-position?, data-kontent-add-button-insert-position=start|end.

<div data-kontent-project-id='00000000-0000-0000-0000-000000000000' data-kontent-language-codename='default'>
  <section data-kontent-item-id='00000000-0000-0000-0000-000000000000'>
    <div
      data-kontent-element-codename='rich-text-element-codename'
      data-kontent-add-button
      data-kontent-render-position='bottom'
      data-kontent-insert-position='end'
    >
      ...
    </div>
  </section>
</div>
Relative add content smart link

Relative add content smart link allows new content to be placed relative to existing elements (before or after). For example, you can insert a new content component before or after the existing content component in rich-text element. To turn add content smart link into a relative one, you need to set data-kontent-insert-position to before or after and provide target id on the same node using data-kontent-item-id or data-kontent-component-id attribute.

Data attributes: data-kontent-project-id, data-kontent-language-codename, data-kontent-item-id, data-kontent-component-id? (optional), data-kontent-element-codename (codename of the rich-text or linked items element), data-kontent-item-id|data-kontent-component-id (target item), data-kontent-add-button, data-kontent-add-button-render-position?, data-kontent-add-button-insert-position=before|after.

<div data-kontent-project-id='00000000-0000-0000-0000-000000000000' data-kontent-language-codename='default'>
  <section data-kontent-item-id='00000000-0000-0000-0000-000000000000'>
    <div data-kontent-element-codename='rich-text-element-codename'>
      <div
        data-kontent-component-id='00000000-0000-0000-0000-000000000000'
        data-kontent-add-button
        data-kontent-render-position='start'
        data-kontent-insert-position='before'
      >...
      </div>
    </div>
  </section>
</div>

SDK initialization

To activate the Kontent.ai Smart Link SDK on your website, you need to initialize it after setting up all required data attributes. The SDK offers two methods for initialization:

  • initialize: Instantly initializes the SDK, making it ready to use. This method is ideal when your webpage is fully loaded or when SDK initialization occurs after the document's DOMContentLoaded event.
    const instance = KontentSmartLink.initialize({ queryParam: "preview" });
  • initializeOnLoad: Delays SDK initialization until the entire page has loaded. This approach is particularly useful for including the SDK in the <head> of your webpage, ensuring that all page elements are fully loaded before initialization begins.
    KontentSmartLink.initializeOnLoad().then(instance => {
      // SDK is fully initialized and ready to use
    });

Both methods return an SDK instance, with initializeOnLoad returning a promise that resolves to an instance. It is important to manage this instance appropriately:

  • Accessing the SDK instance: Store the returned instance if you need to access SDK methods after initialization.
  • Resource management: The SDK leverages event listeners, timeouts, and observers to function properly. To prevent memory leaks or unintended behavior always invoke the .destroy() method on the SDK instance before re-initializing the SDK. This is crucial in single-page applications or dynamic webpages where content might be loaded multiple times without a full page refresh.
    useEffect(() => {
      const instance = KontentSmartLink.initialize();
      return () => instance.destroy(); 
    })

Configuration

Customize how the SDK operated on your preview website with optional configuration arguments passed during initialization. Configuration can be adjusted post-initialization using the setConfiguration method.

Attribute Default Description
debug false Set to true to enable detailed logging, aiding in development and troubleshooting. Note: This may impact performance.
defaultDataAttributes { projectId: undefined, languageCodename: undefined } Define default values for essential data attributes to streamline setup.
queryParam ksl-enabled Name of the query parameter that must be present in the URL to turn the smart link injection on. It is not necessary for query parameter to have a truthy value (just the presence of this query parameter is checked). If set to falsy value ('', null), the smart link injection will always be enabled. Query parameter is only used outside Web Spotlight.

Customization

The following custom CSS properties can be used to customize the visuals of the SDK output.

Custom property Default Description
--ksl-color-background-default rgba(255, 255, 255, 1) Default background color used in toolbar and popover.
--ksl-color-background-default-disabled rgba(223, 223, 223, 1) Disabled background color for buttons inside toolbar and popover.
--ksl-color-background-default-hover rgba(21, 21, 21, 0.1) Hover background color for buttons inside toolbar and popover.
--ksl-color-background-default-selected rgba(255, 240, 239, 1) Selected background color for buttons inside toolbar and popover.
--ksl-color-background-secondary rgba(20, 22, 25, 1) Secondary background color used in tooltips.
--ksl-color-primary rgba(219, 60, 0, 1) Primary color used as a hover border color in highlights and as a background color in add buttons.
--ksl-color-primary-hover rgba(149, 48, 0, 1) Primary color used as a hover background color in add buttons.
--ksl-color-primary-transparent rgba(219, 60, 0, 0.5) Primary color with transparency used as a default border color in highlights.
--ksl-color-text-default rgba(255, 255, 255, 1) Text color used on a default background (buttons inside toolbar and popover).
--ksl-color-text-default-disabled rgba(140, 140, 140, 1) Disabled text color used on a default background.
--ksl-color-text-secondary rgba(21, 21, 21, 1) Text color used inside tooltips and add buttons.
--ksl-shadow-default 0 8px 32px rgba(16, 33, 60, 0.24), 0 0 8px rgba(0, 0, 0, 0.03) Default shadow for toolbar.
--ksl-shadow-primary 0 8px 10px rgba(219, 60, 0, 0.2), 0 6px 20px rgba(219, 60, 0, 0.12), 0 8px 14px rgba(219, 60, 0, 0.14) Shadow for add buttons.
--ksl-z-index 9000 Base value of z-index used for calculation of individual values for each ksl-element type

These styles can be applied globally using the :root selector or scoped to specific elements for more precise theming.

:root {
    --ksl-color-background-default: rgba(4, 102, 200, 1);
    --ksl-color-background-default-disabled: rgba(2, 62, 125, 1);
    --ksl-color-background-default-hover: rgba(0, 40, 85, 0.1);
    --ksl-color-background-secondary: rgba(2, 62, 125, 1);
    --ksl-color-background-default-selected: rgba(3, 83, 164, .1);
    --ksl-color-primary: rgba(4, 102, 200, 1);
    --ksl-color-primary-transparent: rgba(4, 102, 200, 0.5);
    --ksl-color-primary-hover: rgba(2, 62, 125, 1);
    --ksl-color-text-default: rgba(255, 255, 255, 1);
    --ksl-color-text-default-disabled: rgba(51, 65, 92, 1);
    --ksl-color-text-secondary: rgba(255, 255, 255, 1);
    --ksl-shadow-default: 0 8px 32px rgba(0, 24, 69, 0.24), 0 0 8px rgba(0, 0, 0, 0.03);
    --ksl-shadow-primary: 0 8px 10px rgba(4, 102, 200, 0.2), 0 6px 20px rgba(4, 102, 200, 0.12), 0 8px 14px rgba(4, 102, 200, 0.14);
    --ksl-z-index: 9000;
}

Preview autorefresh in Web Spotlight

Maintaining an up-to-date preview is essential when editing content in Web Spotlight using the in-context editor. The Kontent.ai Smart Link SDK introduces a preview autorefresh feature from version 2.2.0 onwards, ensuring your preview automatically updates after your changes are saved without manual refreshes.

Prerequisites for autorefresh

To enable autorefresh in your preview web app, ensure:

  1. SDK Version: Your application is using the latest version of the Smart Link SDK.
  2. API Header: The X-KC-Wait-For-Loading-New-Content header is set to true for requests to the Delivery Preview API.

With these conditions met, Web Spotlight automatically refreshes the preview once your changes are ready on Delivery Preview API, streamlining the editing process.

Advanced: Implementing a custom refresh handler

There are scenarios where a full page refresh may not be ideal, such as when using a static site generator or when aiming to update only a portion of the page. To accommodate diverse needs, the Smart Link SDK offers the capability to define a custom refresh handler.

This custom handler overrides the default refresh behavior, allowing for tailored refresh logic based on your specific requirements. Implement it as follows:

import { IRefreshMessageData } from './IFrameCommunicatorTypes';

const sdk = KontentSmartLink.initialize();

sdk.on(KontentSmartLinkEvent.Refresh, (data, metadata, originalRefresh) => {
  // Implement your custom refresh logic here
});

You can then unregister the custom handler using the .off method.

For more complex example, check the Examples section.

Parameters for the custom refresh handler
Argument Type Description
Data { projectId: string, languageCodename: string, updatedItemCodename: string } | undefined Provides details on the updated item that caused autorefresh. It is only available when refresh is triggered automatically.
Metadata { manualRefresh: boolean } Manual refresh is set to true when the refresh was user-initiated.
Original refresh () => void The SDK's default refresh function.

Live preview in Web Spotlight

As of version 3.2.0, the Kontent.ai Smart Link SDK introduces support for live preview within Web Spotlight. This feature enhances the content editing experience by providing real-time updates within your preview environment through iframe communication immediately after edits are made in the in-context editor.

Note: The live preview requires manual integration to function. Your preview website will not automatically update with changes; it is your responsibility to implement how these updates are processed and displayed in your application.

Implementing live preview in your application

To set up live preview, listen for update events from the SDK. These events are triggered after content is edited in Kontent.ai, providing you with the updated data:

import KontentSmartLink, { KontentSmartLinkEvent } from '@kontent-ai/smart-link';

// Initialize the SDK
const sdk = KontentSmartLink.initialize({ ... });

// Listen for updates and apply them to your application
sdk.on(KontentSmartLink.Update, (data: IUpdateMessageData) => {
  // Use this data to update your application state or UI as needed
});

For more complex example, check the Examples section.

Data contract

The Update event delivers data of type IUpdateMessageData, containing detailed information about the updated content elements:

interface IUpdateMessageData {
  projectId: string;
  variant: {
    id: string;
    codename: string;
  };
  item: {
    id: string;
    codename: string;
  };
  elements: ReadonlyArray<{
    element: {
      id: string;
      codename: string;
    };
    type: ElementType,
    data: Omit<Element, 'type' | 'name'>
  }>;
}

You can find ElementType and Element definition in @kontent-ai/delivery-sdk repository.

Modular content in live preview

Live preview updates for content items that include linked items only provide the codenames of these linked items. To fully update your application with changes to these linked items, you may need to fetch their full details from the Delivery Preview API after receiving the live update message. This ensures that all parts of your content are up-to-date.

Content components within rich text elements, however, are directly included in the live update messages. This means changes to these components are immediately reflected in the live preview, without needing additional fetches.

Combining autorefresh and live preview

While autorefresh ensures that content updates are accurately reflected post-save, live preview offers the advantage of immediate visual feedback before changes are saved. To maximize content management efficiency, we recommend using live preview for instant editing feedback and relying on autorefresh to confirm that all changes are correctly saved and displayed. This combination provides a seamless editing experience, allowing content editors to preview changes in real-time and ensuring that the final content displayed is up-to-date with the Delivery Preview API.

Outside Web Spotlight

When used outside of Web Spotlight, the SDK leverages URL query parameters to manage the activation of smart links. By default, it looks for the ksl-enabled parameter in the webpage URL. However, this parameter can be customized using the queryParam option during SDK initialization.

The SDK only checks for the presence of this query parameter, disregarding its value. As such, any of the following configurations are considered valid:

  • ?ksl-enabled=true
  • ?ksl-enabled=false
  • ?ksl-enabled

The features that could be used outside of Web Spotlight are limited. For detailed information about the types of smart links that are supported in this context, please refer to the Smart Links section.

Inside Web Spotlight

If the SDK detects that it is run inside an iframe, it attempts to connect to Web Spotlight through iframe messages early during initialization. Upon successful communication with Web Spotlight, the SDK disables query parameter reliance and activates additional functionalities designed for in-context editing and preview.

Iframe communication

The SDK and Web Spotlight exchange a series of messages for various interactions, from initialization to content editing and refresh requests. All message types are listed below.

Message Data Origin Description
kontent-smart-link:initialized { projectId: string | null, languageCodename: string | null, enabled: boolean } SDK This message is sent by the SDK when it is initialized.
kontent-smart-link:initialized:response - Host This message is sent by the host as a response to initialized message.
kontent-smart-link:status { enabled: boolean } Host This message is used to toggle the SDK features.
kontent-smart-link:element:clicked { projectId: string, languageCodename: string, itemId: string, contentComponentId?: string, elementCodename: string } SDK This message is sent by the SDK when element with data-kontent-element-codename attribute is clicked.
kontent-smart-link:content-component:clicked { projectId: string, languageCodename: string, itemId: string, contentComponentId: string } SDK This message is sent by the SDK when element with data-kontent-component-id attribute is clicked.
kontent-smart-link:content-item:clicked { projectId: string, languageCodename: string, itemId: string } SDK This message is sent by the SDK when element with data-kontent-item-id attribute is clicked.
kontent-smart-link:add:initial { projectId: string, languageCodename: string, itemId: string, contentComponentId?: string, elementCodename: string, insertPosition: { targetId?: string, placement: 'start' | 'end' | 'before' | 'after', } } SDK This message is sent by the SDK when add button is clicked.
kontent-smart-link:add:initial:response { elementType: 'LinkedItems' | 'RichText' | 'Unknown', isParentPublished: boolean, permissions: Map<string,string> } Host This message is sent by the host as a response to initial add button click.
kontent-smart-link:add:action { projectId: string, languageCodename: string, itemId: string, contentComponentId?: string, elementCodename: string, action: string, insertPosition: { targetId?: string, placement: 'start' | 'end' | 'before' | 'after', } } SDK This message is sent by the SDK when add button action is clicked.
kontent-smart-link:preview:refresh { projectId: string, languageCodename: string, updatedItemCodename: string } | undefined Host This message is sent when preview has to be refreshed.
kontent-smart-link:preview:current-url - Host This message is sent by host as a request to get URL of current iframe.
kontent-smart-link:preview:current-url:response { previewUrl: string } SDK This message is sent by SDK as a response on the kontent-smart-link:preview:current-url message.
kontent-smart-link:preview:update Data contract Host This message is sent by host when an element value has been changed in the in-context editor.

Examples

HTML & UMD & CDN

This example demonstrates how to quickly integrate the Kontent.ai Smart Link SDK into a webpage using a CDN. It's ideal for static sites or projects without a build process, allowing you to enhance your preview with smart link capabilities using straightforward HTML and JavaScript.

<html>
  <head>
    <title>Kontent.ai Smart Link - HTML example</title>
    <!-- Include the SDK from a CDN -->
    <script type='text/javascript'
            src='https://cdn.jsdelivr.net/npm/@kontent-ai/[email protected]/dist/kontent-smart-link.umd.min.js'></script>
    <script type='text/javascript'>
      // Initialize the SDK upon page load
      KontentSmartLink.initializeOnLoad({ queryParam: 'preview' }).then((sdk) => {
        // NOTE: this is just an example of what your live preview implementation may look like
        sdk.on("update", (data) => {
          data.elements.forEach((i) => {
            const codename = i.element.codename;
            const domElement = document.querySelector(`[data-kontent-element-codename=${codename}]`);
            
            if (domElement) {
              domElement.innerHTML = i.data.value;
            }
          });
        });
      });
    </script>
  </head>
  <body data-kontent-project-id='1d50a0f7-9033-48f3-a96e-7771c73f9683' data-kontent-language-codename='en-US'>
    <!-- Example content with data attributes for smart link injection -->
    <nav class='navigation' data-kontent-item-id='6ea11626-336d-47e5-9f35-2d44fa1ad6d6'>
      <img class='navigation__logo' data-kontent-element-codename='logo' />
      <ul
        class='navigation__list'
        data-kontent-element-codename='navigation'
        data-kontent-add-button
        data-kontent-render-position='left'
        data-kontent-insert-position='start'
      >
        <li class='navigation__list-item' data-kontent-component-id='036acd8f-5e6d-4023-b0f8-a4b8e0b573b1'>
          <span data-kontent-element-codename='title'>Home</span>
        </li>
        <li class='navigation__list-item' data-kontent-component-id='f539f1bc-9dc4-4df5-8876-dbb1de5ae6eb'>
          <span data-kontent-element-codename='title'>About us</span>
        </li>
      </ul>
    </nav>
    <div
      class='page'
      data-kontent-item-id='af858748-f48a-4169-9b35-b10c9d3984ef'
      data-kontent-element-codename='page_content'
    >
      <div
        class='section'
        data-kontent-component-id='51a90561-9084-4d32-9e34-80da7c88c202'
        data-kontent-add-button
        data-kontent-add-button-render-position='bottom'
        data-kontent-add-button-insert-position='after'
      >
        <img class='home__banner' data-kontent-element-codename='image' />
        <h1 data-kontent-element-codename='title'>Home page</h1>
      </div>
      <div
        class='section'
        data-kontent-component-id='23e657d2-e4ce-4878-a77d-365db46c956d'
        data-kontent-add-button
        data-kontent-add-button-render-position='bottom'
        data-kontent-add-button-insert-position='after'
      >
        <p data-kontent-element-codename='text'>...</p>
      </div>
    </div>
  </body>
</html>

Note: Make sure to replace @3.2.0 with the latest SDK version for improved features and fixes.

ES6

For projects using modern JavaScript frameworks or build systems, this example illustrates how to import and initialize the Kontent.ai Smart Link SDK within an ES6 module. This approach is well-suited for applications built with tools like Webpack, Rollup, or when working within SPA frameworks like React, Vue, or Angular.

import KontentSmartLink, { KontentSmartLinkEvent } from "@kontent-ai/smart-link";

// This is just an example of SDK initialization inside ES6 module.
// HTML markup should still contain all necessary data-attributes.
const kontentSmartLink = KontentSmartLink.initialize({
  debug: true,
  defaultDataAttributes: {
    projectId: "1d50a0f7-9033-48f3-a96e-7771c73f9683",
    languageCodename: "default",
  },
  queryParam: "ksl-preview"
});

kontentSmartLink.on(KontentSmartLinkEvent.Update, (data) => {
  // Update content of the page using data
});

Tip: Adjust defaultDataAttributes according to your Kontent.ai project's ID and language codename.

React

In a React application, we recommend utilizing React's Context API to create a centralized store for the SDK instance, ensuring easy access and management of smart links within your component tree. This advanced example demonstrates setting up a SmartLinkContext to provide a Kontent.ai Smart Link SDK instance throughout your React application.

Creating the SmartLink context

// src/contexts/SmartLink.tsx
import React, { PropsWithChildren, useContext, useState, useMemo, useEffect } from 'react';
import KontentSmartLink, { KontentSmartLinkEvent } from '@kontent-ai/smart-link';
import {
  IRefreshMessageData,
  IRefreshMessageMetadata,
  IUpdateMessageData,
} from '@kontent-ai/smart-link/types/lib/IFrameCommunicatorTypes';

interface SmartLinkContextValue {
  readonly smartLink?: KontentSmartLink | null;
}

const defaultContextValue: SmartLinkContextValue = {
  smartLink: undefined,
};

const SmartLinkContext = createContext<SmartLinkContextValue>(defaultContextValue);

export const SmartLinkProvider: React.FC<PropsWithChildren> = ({ children }) => {
  const [smartLink, setSmartLink] = useState<KontentSmartLink>(null);

  useEffect(() => {
    const instance = KontentSmartLink.initialize({
      queryParam: 'preview',
      defaultDataAttributes: {
        projectId: 'your-project-id', // Replace 'your-project-id' with your actual project ID
        languageCodename: 'your-language-codename', // Replace 'your-language-codename' with your actual language codename
      },
    });

    setSmartLink(instance);

    // Cleanup on component unmount
    return () => instance.destroy();
  }, []);

  const value = useMemo(() => ({ smartLink }), [smartLink]);

  return (
    <SmartLinkContext.Provider>
      {children}
    </SmartLinkContext.Provider>
  );
};

// Custom hook for easy access to the SmartLink instance
export const useSmartLink = (): KontentSmartLink | null => {
  const { smartLink } = useContext(SmartLinkContext);

  if (typeof smartLink === 'undefined') {
    throw new Error('You need to place SmartLinkProvider to one of the parent components to use useSmartLink.');
  }

  return smartLink;
};

// Custom hook for easy setup of a custom refresh handler
export const useCustomRefresh = (callback: (data: IRefreshMessageData, metadata: IRefreshMessageMetadata, originalRefresh: () => void) => void): void => {
  const smartLink = useSmartLink();

  useEffect(() => {
    if (smartLink) {
      smartLink.on(KontentSmartLinkEvent.Refresh, callback);

      return smartLink.off(KontentSmartLinkEvent.Refresh, callback);
    }

    return;
  }, [smartLink, callback]);
};

// Custom hook for easy access of live preview
export const useLivePreview = (callback: (data: IUpdateMessageData) => void): void => {
  const smartLink = useSmartLink();

  useEffect(() => {
    if (smartLink) {
      smartLink.on(KontentSmartLinkEvent.Update, callback);

      return smartLink.off(KontentSmartLinkEvent.Update, callback);
    }

    return;
  }, [smartLink, callback]);
};

Using the SmartLink provider in your app:

// src/App.tsx
import React from 'react';
import { SmartLinkProvider } from './contexts/SmartLink';

const App: React.FC = () => {
  return (
    <SmartLinkProvider>
      {/* Your app components go here, now with smart link support */}
    </SmartLinkProvider>
  );
};

Optimizing content updates in React with custom refresh logic

Using the custom refresh handler and the useCustomRefresh hook we defined in the previous example. It is possible to only update a small portion of the UI without reloading the entire page when your changes are available on Delivery Preview API.

The following example showcases an efficient approach to handling such content updates within a React application.

import React, { useState, useCallback, useContext } from 'react';
import { useCustomRefresh } from '../context/SmartLink'; // Ensure correct import path
import { IRefreshMessageData, IRefreshMessageMetadata } from '@kontent-ai/smart-link/types/lib/IFrameCommunicatorTypes';

const YourComponent: React.FC = () => {
  const [data, setData] = useState(null);
  const fetchData = useCallback((projectId, languageCodename, itemCodename) => {
    // Your data fetching logic here using a delivery-sdk or request to Delivery API endpoint
  }, []);

  // Define the custom refresh logic.
  const onRefresh = useCallback((data: IRefreshMessageData, metadata: IRefreshMessageMetadata, originalRefresh: () => void) => {
    // Check if the refresh was triggered manually and perform a full refresh if so.
    if (metadata.manualRefresh) {
      originalRefresh();
    } else {
      // For automatic refreshes, refetch data for the updated item only.
      const { projectId, languageCodename, updatedItemCodename } = data;
      fetchData(projectId, languageCodename, updatedItemCodename);
    }
  }, [fetchData]);

  // Use the custom refresh hook with the defined logic.
  useCustomRefresh(onRefresh);

  return <div>{data}</div>;
};

Triggering SSG rebuilds with custom refresh logic

For websites built with static site generators, applying content updates typically requires triggering a rebuild of the site. The following example shows how you can initiate a rebuild with the custom refresh handler, ensuring that your site reflects the latest content changes.

This example uses Gatsby deployed to Netlify, but the solution for other SSG frameworks should be similar.

The following Netlify serverless function, deploy-status, checks the status of the latest deployment, enabling your application to wait for a rebuild to complete before refreshing the content.

// ./.netlify/functions/deploy-status.js
import fetch from 'node-fetch';

// Environment variables for Netlify site ID and access token
const siteId = process.env.NETLIFY_SITE_ID;
const token = process.env.NETLIFY_TOKEN;

// Handler to check the status of the last deployment
const handler = async event => {
  try {
    const endpoint = `https://api.netlify.com/api/v1/sites/${siteId}/deploys`;
    const result = await fetch(endpoint, { headers: { Authorization: `Bearer ${token}` } });
    const data = await result.json();

    // Assuming the first entry is the latest deployment
    const deploy = { state: data[0].state };

    return { statusCode: 200, body: JSON.stringify(deploy) };
  } catch (error) {
    return { statusCode: 500, body: error.toString() };
  }
};

module.exports = { handler };

We can then trigger the rebuild process inside our custom refresh handler and wait for the deployment process to finish. After that the page can be refreshed using the originalRefresh callback.

import React, { useCallback, useEffect } from 'react';
import { useCustomRefresh } from '../context/SmartLinkContext'; // Adjust the import path as needed

const triggerRebuildOnNetlifyAndWaitForDeploy = useCallback(() => {
  // Trigger the Netlify build hook
  fetch('https://api.netlify.com/build_hooks/YOUR_BUILD_HOOK_ID?trigger_title=autorefresh', { method: 'POST' })
    .then(async () => {
      // Check the deployment status repeatedly until it's 'ready'
      const checkDeployStatus = async () => {
        const response = await fetch('/.netlify/functions/deploy-status');
        const { state } = await response.json();
        if (state !== 'ready') {
          setTimeout(checkDeployStatus, 3000); // Check again after 3 seconds
        }
      };

      await checkDeployStatus();
    });
}, []);

const PageWithAutoRefresh = () => {
  useCustomRefresh((data, metadata, originalRefresh) => {
    if (!metadata.manualRefresh) {
      triggerRebuildOnNetlifyAndWaitForDeploy().then(originalRefresh);
    } else {
      originalRefresh();
    }
  });

  // Page content goes here
  return <div>Page Content</div>;
};

export default PageWithAutoRefresh;

Live Preview in Your Application

Using the useLivePreview hook we defined in the previous example, you can enhance your application with real-time content updates during the editing process. This hook listens for live update messages from the Kontent.ai Smart Link SDK and applies those updates directly to the content item being displayed.

This example demonstrates setting up a useLivePreview hook within a React component to dynamically update content items as changes are made in the in-context editor.

import React, { useState, useCallback, useEffect } from 'react';
import { useLivePreview } from '../contexts/SmartLinkContext'; // Adjust the import path as needed
import { IContentItem } from '@kontent-ai/delivery-sdk/lib/models/item-models';
import { IUpdateMessageData } from '@kontent-ai/smart-link/types/lib/IFrameCommunicatorTypes';

// Function to update content item elements with live preview data
const updateContentItemElements = (item: IContentItem, data: IUpdateMessageData) => {
  return data.elements.reduce((acc, el) => {
    const { element, ...rest } = el;
    acc[element.codename] = { ...acc[element.codename], ...rest.data };
    return acc;
  }, item?.elements ?? {});
};

const useContentItem = (codename: string) => {
  const [item, setItem] = useState<IContentItem | null>(null);
  // Assume useDeliveryClient is a custom hook to obtain a configured delivery client instance
  const deliveryClient = useDeliveryClient();

  const handleLiveUpdate = useCallback((data: IUpdateMessageData) => {
    if (item && data.item.codename === codename) {
      const updatedElements = updateContentItemElements(item, data);
      setItem({ ...item, elements: updatedElements });
    }
  }, [codename, item]);

  useEffect(() => {
    const fetchItem = async () => {
      // Fetch the content item initially and upon codename changes
      const response = await deliveryClient.item<IContentItem>(codename).toPromise();
      setItem(response.item);
    };
    fetchItem();
  }, [codename, deliveryClient]);

  useLivePreview(handleLiveUpdate);

  return item;
};

// Example component using the useContentItem hook
export const ContentItemComponent = ({ codename }) => {
  const item = useContentItem(codename);

  // Render logic for the content item
  return (
    <div>
      {/* Render your content item here */}
      <h2>{item?.name}</h2>
      {/* More render logic */}
    </div>
  );
};

Known issues

Nested iframes

In scenarios where your content is displayed within nested iframes (e.g. to simulate different device resolutions, or to handle redirects to the right preview website based on the item), the SDK's initialization messages may not reach the top-level Web Spotlight iframe directly, affecting functionality. To address this, follow the guidance provided in this issue.

SameSite cookie in Next.js app

Next.js developers may encounter an issue with SameSite cookies not being set correctly. This can be solved by utilizing the code snippet below in your API route handling preview file to replace SameSite=Lax to SameSite=None; Secure; right after res.setPreviewData call.

const setCookieSameSite = (res, value) => {
  const cookies = res.getHeader("Set-Cookie");
  const updatedCookies = cookies?.map((cookie) =>
    cookie.replace(
      "SameSite=Lax",
      `SameSite=${value}; Secure;`
    )
  )
  res.setHeader(
    "Set-Cookie",
    updatedCookies
  );
};

export default function handler(req, res) {
  // ...
  res.setPreviewData({})

  // THIS NEEDED TO BE ADDED
  setCookieSameSite(res, "None");
  // ...
}

Tests

Unit tests

Since this SDK highly depends on browser APIs, the unit tests are run by Karma test runner (+ Jasmine) inside Chrome browser. To run all tests in a watch mode you can use the npm run test:unit command. To run all tests only once you can use the npm run test:unit:ci command. All unit tests are located in the test-browser folder.

Visual regression tests

Visual regression testing is implemented using Storybook and Loki. Each story in Storybook represents a test case, which is then used by Loki to generate screenshots. In order to run visual regression tests you need to start Storybook using the npm run storybook command and then start loki testing using the npm run test:visual command. Or you can use the npm run test:visual:ci command to automatically start the Storybook server in a CI mode and run visual tests.

Visual regression tests use the built version of SDK, so before running them make sure you rebuild the SDK after the last change you made. You can this using the npm run build command or using the npm run dev command to start build in a watch mode.

Please note that the reference screenshots for visual regression tests are created on the ubuntu-latest environment, which is utilized in our GitHub Action workflow for visual tests. It means tests could (and probably will) fail on Windows. In case the visual regression tests fail during a pull request, and you need to update reference screenshots, you can locate the new screenshots in the failed GitHub Action run. Navigate to the Artifacts section (.loki/current) of the failed run to find the updated screenshots.

Breaking changes

All breaking changes can be found in a separate markdown file.

Feedback & Contribution

Feedback & Contributions are welcomed. Feel free to take/start an issue & submit PR.

smart-link's People

Contributors

dependabot[bot] avatar hejtmii avatar ivankiral avatar janbarton avatar kontent-ai-bot avatar pavelvyskocil avatar peterskoda avatar petrsvihlik avatar simply007 avatar snyk-bot avatar vladbulyukhin avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

smart-link's Issues

Add button toolbar is not fully visible on the edge of the screen

Brief bug description

When the add button is on the edge of the preview page inside iFrame, the tooltip and toolbar are not shown correctly.

Expected behavior

The add button and its tooltip should be fully visible even on the edge of the screen.

Screenshots

image-2021-06-10-11-51-35-717

Add a Troubleshooting section to docs and cover the X-Frame-Options

Motivation

Depending on the server's settings, it might be necessary to adjust the X-Frame-Options header in order to make Web Spotlight work flawlessly.

Proposed solution

Add a Troubleshooting section to the README. This new section should explain the problem, outline the possible solutions, and refer to appropriate detailed resources.

Additional context

This was pointed out to me by @Simply007 at kontent-ai/sample-app-net@384efb0#r44481635
This can serve as an example for .NET-based solutions.

Make smart link injection possible without a query parameter

Motivation

For our website, we have a preview stage separated from the production site. On that preview stage, we want to have smart links always injected. However, as the SDK requires a query parameter to enable smart link injection, editors must add that parameter by themselves which is annoying. Another possible solution is to inject that query parameter programmatically which is annoying too.

Proposed solution

Allow the queryParam to accept an empty string which would result in the smart link injection with no query parameter. Any other reasonable type of configuration that would result in the smart link injection with no query parameter is acceptable too.

Developer toolbar

Motivation

In some situations, while developing data attributes, it is difficult to verify that edit buttons will show properly on the webpage: e.g. if I want to use kontent-smart-link-enabled query param in an application which does not keep query params between links/redirects, I would need to do changes in routing just for this sake.

Proposed solution

It would be nice to allow developers enable/disable edit buttons on some specific page without refresh or altering query params. E.g. some developer toolbar.

CSS properties are overriden - causing spotlight overlay to not display

Brief bug description

When using the latest version of Kontent Smart Link (v2.1.0), the :host CSS properties will be overidden by any higher-order CSS selector. For example, Tailwind CSS's Preflight sets all border width's to 0. This causes the :host border-width to be ignored, and the border width to be set to 0. This causes the spotlight overlay to be hidden.

image
image

This can be remediated by flagging the appropriate properties as !important:

image
image

Repro steps

  1. Create a page using a higher-order CSS selector (such as those used by Tailwind CSS's Preflight)
  2. Observe - no spotlight overlay will show
  3. Add the !important flag
  4. Observe - overlay will show

Expected behavior

The overlay should display.

image

Test environment

  • Platform/OS: Windows 10
  • Browser: Firefox
  • Version: 89.0.2 (64-bit)

Additional context

Add any other context about the problem here.

Screenshots

Add links to screenshots, if possible.

Add option to hide features

Motivation

In some scenarios, it would be handy to have an option to hide some of the features which are not desirable in a given context. Take these examples

  • I want to show a relative add-button on an element. To do that I need to add an itemId attribute on that element. The itemId attribute will also render the edit-button, which I don’t want here. => I can’t hide the edit-button while keeping the add-button, without resorting to some wrapper and css workaround, which is cumbersome.
  • by default, the edit button functionality extends to the whole area of the element it's enabled for. this can be potentially undesirable in situations where the overlaid content includes some interactive/clickable buttons, in which case smartlink needs to be disabled.

Proposed solution

Provide more configuration options, which would allow disabling unwanted features either per element basis (e.g. using an optional boolean attribute) or globally

Additional context

Add any other context, screenshots, or reference links about the feature request here.

Border not rendered if the element is big

Brief bug description

I have tried to render the web spotlight and if I use it on pretty long article text, the border and the pencil is not rendered. The click-through is working still.

I don't see the <kontent-smart-link-element> tag in <kontent-smart-link-overlay> tag in html.

image

Repro steps

  1. Clone https://github.com/Kentico/gatsby-starter-kontent-lumen/tree/webspotlight
  2. npm i # should copy .env.template and name it .env
  3. npm run develop
  4. Enter http://localhost:8000/articles/humane-typography-in-the-digital-age/?preview-mode=true

Expected behavior

I expect to have a pencil and border no matter what 😄

Additional context

If I un-zoom the page the border is rendered.
image

Add developer mode to see all buttons outside WSL

Motivation

Some of the smartlink features are only available in WSL environment. While understandable from customer's point of view, it would make development easier if there was an option to display all buttons in a regular preview.

Proposed solution

Add a toggle in the SDK initialization or as a query parameter, which would display all buttons, regardless of the environment the site is rendered in.

data-kontent-add-button only functions if content item id and element codename are on the same HTML tag

Brief bug description

What went wrong?
After adding a data-kontent-add-button & data-kontent-element-codename attributes to a div, the + button appeared next to a related items field in the preview, but clicking it greyed it out and gave a JS exception in the console:
image

The console suggests theres no language, project, content item or element codename set, however all are set in parent html elements.

Repro steps

  1. Create a content item with a related items field.
  2. Set the codename to "related_pages"
  3. Create a content item, and add some pages to the related pages field from 2.
  4. Note the content item id
  5. Setup a page with this sample html:
<body data-kontent-language-codename="en-GB" data-kontent-project-id="YOUR PROJECT ID">

    <div class="page-container" data-kontent-item-id="<The content item id from 4. that has the related_pages field>">

          <div class="related-pages" data-kontent-element-codename="related_pages" data-kontent-add-button>
               <h1>Example related page list item 1</h1>
               <h1>Example related page list item 2</h1>
           </div>

      </div>

</body>
  1. View this page on the web spotlight preview area (might require setting up the content item as a subpage of homepage)
  2. Click the + button next to the example h1s
  3. See the error in the console.

Note: This doesnt occur as soon as you add data-kontent-item-id="<The content item id from 4. that has the related_pages field>" to the .related-pages html element.

Expected behavior

Like the other data-kontent tags, Id expect the kontent-item-id to be inherited based on the hierarchy for use on the kontent-element's button, as per the readme:

'Although it is possible to put all previously specified data attributes on the same DOM node, you don't have to do it. We recommend you set data attributes hierarchically so that you don't have to duplicate the same attributes.'

Test environment

  • Platform/OS: .NET Core 5, Windows 11
  • Browser Chromium
  • Version 99

SDK Does not support multiple delivery URLs

Brief bug description

What went wrong?
When developing an application with multiple URLs endpoints for the web, we need the ability to use SmartLink on different domains. In my scenario, I'm using an Azure Function to determine which URL should be used to preview content based primarily on the collection that the content belongs to. This performs a 302 redirect to the correct URL.

The issue then arises that Smark Link forces the origin and the preview URL to match. If they do not, an error is raised and the SmartLink SDK will not render. This is not compatible with the omnichannel narrative and ties Kontent to only being capable of supporting a single web application.

Repro steps

  1. Set a preview URL for a content type to link to an Azure Function (or another API endpoint) that will redirect to another domain containing the application.
  2. Configure preview mode and SmartLink in the target web application
  3. View page in web spotlight
  4. View error in console - SmartLink does not render.

Expected behavior

SmartLink should open regardless of the different origin.

Test environment

  • Platform/OS: [e.g. .NET Core 2.1, iOS]
  • Browser [e.g. chrome, safari]
  • Version [e.g. 22]

Additional context

Other use cases here/scenarios are being able to provide a customer support portal, consumer-facing and B2B web application from the same project in Kontent.

If the concern/reason for this restriction is related to security, I'd rather make a 'secret' available in my applications that can be validated that the current implementation or the ability to add preview URL aliases. Simply preventing it (and also not documenting that limitation as far as I can see) is making working with Web Spotlight less of a delight than it could/should be :)

Screenshots

Add links to screenshots, if possible.
image

My preview URL for the above image looks like this: https://gr-schedule.azurewebsites.net/api/gr-preview-url?secret=1234&cn={Codename}&col={Collection}&l={Lang}&url={URLSlug}

Vue.js smart links are not highlighted on the first load

Brief bug description

When the SDK is initialized inside of the main Vue component (App.vue) using lifecycle methods (mounted, destroyed), smart links are not highlighted (there is no blue border around Kontent elements), but clicking on the element still redirects you to Kontent.

Expected behavior

Smart links should be highlighted on the first load of Vue application as they are highlighted in React applications.

Additional context

I investigated it a little and found out that the problem is probably here in the relevantMutations computation condition. We used && in this condition between addedNodes and removedNodes, because we mostly tested this SDK using React application and React always tries to make as many changes to DOM in one mutation as possible, that is why there are always both removedNodes and addedNodes. But as I found out today Vue separates additions and removals into separate mutations and this condition is never met. Replacing it with something like this should fix the problem:

const relevantMutations = mutations.filter(
  (mutation) =>
    mutation.target instanceof Element &&
    mutation.type === 'childList' &&
    ![HighlighterElementTag, HighlighterContainerTag].includes(mutation.target.tagName) &&
    (Array.from(mutation.addedNodes).some((node) => (node as HTMLElement).tagName !== 
    HighlighterElementTag) ||
    Array.from(mutation.removedNodes).some((node) => (node as HTMLElement).tagName !== 
    HighlighterElementTag))
);

Add helper methods for creating attributes

Motivation

Manually adding element attributes can be rather repetitive, especially for add buttons, where multiple attributes need to be specified. This could be mitigated to some extent by including helper methods in the SDK.

Proposed solution

Add a set of helper methods which will create the required attributes for each available smartlink button based on user-provided values. See example implementation in our sample app source code.

SDK does not work outside of browser

Brief bug description

Using this SDK is problematic under node.js (e.g. when using Angular universal = SSR) because SDK directly invokes browser specific API - MutationObserver (https://github.com/Kentico/kontent-smart-link/blob/e173a36628ccfb851a0f54b93a965957b3d3e309/src/lib/NodeSmartLinkProvider.ts#L44)

In such cases you get ReferenceError: MutationObserver is not defined exception.

This SDK should check whether the API is available and use Polyfills (if available for our use case) or use different API alltogether specific to Node.js. When using API specific do Node.js, be sure to "exclude" this code publicly exported API that might be used in browser where this API is not available as it might prevent you from building the library with Typescript/Webpack etc.. Careful testing is needed for this to work both in node.js & browsers.

If none of this is possible, we should add a disclaimer that node.js is not supported, but I don't think that should be our goal as this should be quite common use case.

Use environment ID instead of project ID

Motivation

In the product, we're using environment ID as the name of the identifier, but the Smart Link SDK still uses the data-kontent-project-id attribute, which is confusing to users.

Proposed solution

Update the SDK to use data-kontent-environment-id instead because that's what you can get in the product UI.

Missing data-kontent-language-codename attribute logs project ID error to console.

Brief bug description

While using SmartLink SDK in Web Spotlight, when data-kontent-language-codename is missing, the error:
Logger.js:15 [KSL][Error]: Project ID is required to handle element click.

is logged to the browser console, even if the kontent-data-project-id attribute is set correctly in the markup.

Repro steps

  1. Open up a WSL project with SL SDK
  2. Remove the data-kontent-language-codename attribute using Chrome DevTools
  3. Click a SL SDK edit button
  4. See browser console error in DevTools

Expected behavior

The console should only report that there is an issue with the language codename element, not the project ID.

Test environment

  • Platform/OS: Windows 10, gatsby-starter-kontent-lumen sample site, gatsby-source-kontent 7.2.0, SL SDK v2.1.0
  • Browser Chrome
  • Version 96.0.4664.110

Screenshots

image

image

Links are not highlighted

Brief bug description

In some cases, links are not highlighted (no dashed border and edit icon). However, they are clickable and lead to the correct place in KK admin.
Could be duplicate of #21

Expected behavior

Smart links should be highlighted.

Additional context

I implement this in the KK Docs and I experience the issue on article pages like https://docs.kontent.ai/tutorials/set-up-kontent/set-up-your-project/projects (Smart links are not on the live site. However if you try to implement Smart links on that page, you will experience the issue.) This is a classic Express.js site with full server-side rendering. Smart links JS and CSS files are loaded using CDN.

I noticed that on pages that have the issue, the "kontent-smart-link-overlay" element gets rendered twice. See the screenshot linked below. Maybe this could be related to the issue.

Screenshots

https://prnt.sc/xlg4j8

Website with SDK cannot be used inside of an iframe (other than WS)

Motivation

Right now if the website connected to the SDK is run inside of an iframe (other than WS), the redirect feature and Web Spotlight features (enable/disable editable elements, in-context editor, etc.) won't work. This happens because the SDK assumes that the iframe, it is being run inside of, is a Web Spotlight preview iframe and tries sending messages to this iframe which are left unprocessed. Currently, there is no way to override this behavior.

Proposed solution

First of all, we need to decide whether we want to somehow fix this issue or not. There are several possible solutions that can be used to solve it:

  • We can add a boolean flag to the SDK configuration object that will allow users to disable the iframe communication. This way when someone wants to wrap a website connected to the SDK with an iframe, they can disable the iframe communication feature and smart links will always open Kontent in a new tab instead of sending iframe messages. The main disadvantage of this method is that without the iframe communication smart links will open Kontent in a new tab even in Web Spotlight because Web Spotlight requires iframe communication to work properly.
  • We can add some public helpers to SDK that will help users with this problem (more information about this can be found in the Workaround section of this issue).

Workaround

If you plan on using the website (connected to the SDK) inside of an iframe, you can use this workaround to make everything work properly with Web Spotlight.

The first thing you need to do is listen to all message events on your host page window object.

window.addEventListener('message', handleMessage);

Then whenever you receive a new message, you need to check whether it is a message related to SDK. If it is related to the SDK, you need to check if you are currently inside an iframe and if you aren't, you just generate a smart link (using buildKontentLink helper from @kentico/kontent-smart-link) and open it in a new tab. If you are inside an iframe, you need to check the direction of the received message (upwards from inner iframe/downwards from parent window) by comparing the source of this event to the iframe content window. Finally, you need to resend the whole event to the parent window or inner iframe according to the event direction.

import { buildKontentLink } from '@kentico/kontent-smart-link';

function handleMessage(event) {
  const eventTypeRegex = /^kontent-smart-link:/;
  
  // check if this message is not related to the SDK
  if (!event.data || !event.data.type || !eventTypeRegex.test(event.data.type)) {
    return;
  }

  const isInsideIFrame = window.self !== window.parent;
  const message = event.data;

  if (isInsideIFrame) {
    // The iframe variable has a reference to the iframe element.
    // You don't have to use document.querySelector here, for example,
    // in React you can use `useRef` hook to save the reference and then 
    // use it here, in Vue you can use `$refs`, etc.
    const iframe = document.querySelector('#your-iframe-id');
    const isMessageGoingUpwards = event.source === iframe.contentWindow;

    if (isMessageGoingUpwards) {
      // resend this message to the parent window
      window.parent.postMessage(message, '*');
    } else {
      // resend this message to the iframe
      iframe.contentWindow.postMessage(message, '*');
    }
  } else if (message.type === 'kontent-smart-link:element:clicked') {
    const link = buildKontentLink(event.data.data);
    window.open(link, '_blank');
  }
}

Additional context

Add any other context, screenshots, or reference links about the feature request here.

Allow edit item button outside Web Spotlight

Motivation

Edit item is effectively a superset of Edit element, the latter being available both in WSL and standard preview. As such, it would make sense to allow Edit item to be rendered in preview as well, as a similar functionality can be achieved by using the first element of an item to render Edit element button, which seems like an unnecessary workaround.

Proposed solution

Make Edit item button available outside web spotlight context.

Make controls' z-index configurable

Motivation

It would be great to have z-index CSS property of overlay (border with pencil).

I have my menu with z-index:10002 (inherited from template) , so I would appreciate being able to customize that.

image

Proposed solution

Make ti a part of the configuration

Additional context

Add any other context, screenshots, or reference links about the feature request here.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.