{"id":13314687,"url":"https://github.com/kontent-ai/smart-link","last_synced_at":"2026-01-08T15:18:51.531Z","repository":{"id":40468296,"uuid":"265587848","full_name":"kontent-ai/smart-link","owner":"kontent-ai","description":"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).","archived":false,"fork":false,"pushed_at":"2025-04-04T14:52:27.000Z","size":35256,"stargazers_count":9,"open_issues_count":24,"forks_count":4,"subscribers_count":15,"default_branch":"master","last_synced_at":"2025-04-07T13:11:15.678Z","etag":null,"topics":["kontent-ai","kontent-ai-tool","kontent-sdk","web-spotlight"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kontent-ai.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-05-20T14:16:22.000Z","updated_at":"2025-03-19T11:30:25.000Z","dependencies_parsed_at":"2024-04-17T00:32:15.741Z","dependency_job_id":"cd61a560-3309-4eb4-adc2-59752a6a118b","html_url":"https://github.com/kontent-ai/smart-link","commit_stats":{"total_commits":184,"total_committers":11,"mean_commits":"16.727272727272727","dds":0.375,"last_synced_commit":"9c304a772b1f0b278e1eee5a648644c79dc2005c"},"previous_names":["kentico/kontent-smart-link"],"tags_count":31,"template":false,"template_full_name":"Kentico/repo-template","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kontent-ai%2Fsmart-link","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kontent-ai%2Fsmart-link/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kontent-ai%2Fsmart-link/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kontent-ai%2Fsmart-link/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kontent-ai","download_url":"https://codeload.github.com/kontent-ai/smart-link/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252156908,"owners_count":21703371,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["kontent-ai","kontent-ai-tool","kontent-sdk","web-spotlight"],"created_at":"2024-07-29T18:11:53.442Z","updated_at":"2026-01-08T15:18:51.525Z","avatar_url":"https://github.com/kontent-ai.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Kontent.ai Smart Link SDK\n\n![licence](https://img.shields.io/github/license/kontent-ai/smart-link)\n![npm](https://img.shields.io/npm/v/@kontent-ai/smart-link)\n![downloads](https://img.shields.io/npm/dt/@kontent-ai/smart-link)\n![jsdelivr](https://img.shields.io/jsdelivr/npm/hm/@kontent-ai/smart-link)\n\n###### [Contributing](https://github.com/kontent-ai/.github/blob/main/CONTRIBUTING.md) | [Troubleshooting](https://github.com/kontent-ai/smart-link/blob/master/TROUBLESHOOTING.md) | [Breaking changes](https://github.com/kontent-ai/smart-link/blob/master/BREAKING.md) | [Release guide](https://github.com/kontent-ai/smart-link/wiki/How-to-release-a-new-version-of-Smart-Link-SDK)\n\nKontent.ai Smart Link SDK simplifies and enhances the process of editing and managing web content in [Live Preview](https://kontent.ai/features/live-preview/) by embedding \"smart links\" into your web pages. These smart links, defined by specific [HTML data attributes](https://www.w3schools.com/tags/att_data-.asp) you set,\ncreate 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.\n\n\u003e [!WARNING] \n\u003e **Important note:** Kontent.ai Smart Link SDK is **a browser-only SDK**. Make sure to always initialize the Smart Link SDK in a browser context.\n\n## Features\n\n- ✏️ **Edit Smart Links:** Quickly navigate from your website's preview to the corresponding content in Kontent.ai.\n- ➕ **Add Smart Links:** Simplify the addition of modular content directly from your preview.\n- 🔄️ **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.\n- 👀 **Live Preview:** Experience real-time content changes even before they're saved.\n\n## Table of Contents\n\n* [Installation](#installation)\n* [Quickstart](#quickstart)\n* [Data attributes](#data-attributes)\n    * [Available data attributes](#available-data-attributes)\n    * [Data attributes hierarchy](#data-attributes-hierarchy)\n    * [Smart Links](#smart-links)\n* [SDK initialization](#sdk-initialization)\n    * [Configuration](#configuration)\n    * [Preview autorefresh in Live Preview](#preview-autorefresh-in-web-spotlight)\n    * [Live preview in Live Preview](#live-preview-in-web-spotlight)\n    * [Combining autorefresh and live preview](#combining-autorefresh-and-live-preview)\n    * [Customization](#customization)\n* [Known issues](#known-issues)\n    * [Nested iframes](#nested-iframes)\n    * [SameSite cookie in Next.js app](#samesite-cookie-in-nextjs-app)\n* [Examples](#examples)\n    * [HTML \u0026 UMD \u0026 CDN](#html--umd--cdn)\n    * [React](#react)\n    * [Triggering SSG rebuilds with custom refresh logic](#triggering-ssg-rebuilds-with-custom-refresh-logic)\n* [Tests](#tests)\n    * [Unit tests](#unit-tests)\n    * [Visual regression tests](#visual-regression-tests)\n    * [Updating Visual Regression Tests](#updating-visual-regression-tests)\n\n## Installation\n\nYou can install this library using `npm` or using global CDNs such as `jsdelivr`.\n\n### npm\n\n```\nnpm i @kontent-ai/smart-link --save\n```\n\n### jsdelivr\n\nWhen 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.\n\n- `dist/bundles/kontent-smart-link.min.js`\n\n##### kontent-smart-link.min.js\n![Gzip browser bundle](https://img.badgesize.io/https://app.unpkg.com/@kontent-ai/smart-link@4.0.3/files/dist/bundles/kontent-smart-link.min.js?compression=gzip)\n\n```html\n\u003cscript type='text/javascript'\n        src='https://cdn.jsdelivr.net/npm/@kontent-ai/smart-link@latest/dist/bundles/kontent-smart-link.min.js'\u003e\u003c/script\u003e\n```\n\n\u003e [!NOTE] \n\u003e To prevent potential issues from arising due to breaking changes, it is recommended to replace `@latest` with a specific version number.\n\n## Quickstart\n\nTo integrate the Kontent.ai Smart Link SDK into your web project and enable smart link injection, follow these steps:\n\n1. **Include SDK:** Add the SDK to your project.\n2. **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](#data-attributes).\n    ```html\n    \u003cmain data-kontent-environment-id='00000000-0000-0000-0000-000000000000' data-kontent-language-codename='default'\u003e\n      \u003cdiv data-kontent-item-id='00000000-0000-0000-0000-000000000000'\u003e\n        \u003cdiv data-kontent-element-codename='title'\u003eTitle\u003c/div\u003e\n      \u003c/div\u003e\n    \u003c/main\u003e\n   ```\n3. **Initialization:** Initialize the SDK in your code to create the smart link to Kontent.ai. You can read more about\n   SDK initialization [here](#sdk-initialization).\n    ```ts\n   const instance = KontentSmartLink.initialize();\n   ```\n\nFor more complex examples, check the [Examples](#examples) section.\n\n## Data attributes\n\nThe Kontent.ai Smart Link SDK relies on manually specified data attributes in your HTML markup. 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 environment ID and element codenames.\n\n\u003e [!NOTE] \n\u003e **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.**\n\n### Available data attributes\n\n\u003cdetails\u003e\n\u003csummary\u003eView complete data attributes reference\u003c/summary\u003e\n\n| Attribute                                 |                                                                                                     Value                                                                                                      | Description                                                                                                                                                 |\n|-------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|-------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| `data-kontent-environment-id`                 |                                                                                                      guid                                                                                                      | Kontent.ai environment ID.                                                                                                                                  |\n| `data-kontent-language-codename`          |                                                                                                     string                                                                                                     | Kontent.ai language codename.                                                                                                                               |\n| `data-kontent-item-id`                    |                                                                                                      guid                                                                                                      | Content item ID.                                                                                                                                            |\n| `data-kontent-component-id`               |                                                                                                      guid                                                                                                      | [Content component](https://kontent.ai/learn/tutorials/write-and-collaborate/structure-your-content/structure-your-content#a-create-single-use-content) ID. |\n| `data-kontent-element-codename`           |                                                                                                     string                                                                                                     | Content type element codename.                                                                                                                              |\n| `data-kontent-add-button`                 |                                                                                                       -                                                                                                        | Specifies that node should have add-button rendered near it.                                                                                                |\n| `data-kontent-add-button-insert-position` |                                                                              `start` \u0026#124; `before` \u0026#124; `after` \u0026#124; `end`                                                                               | Specifies the insert position of an item/content component added using add button.                                                                          |\n| `data-kontent-add-button-render-position` | `bottom-start` \u0026#124; `bottom` \u0026#124; `bottom-end` \u0026#124; `left-start` \u0026#124; `left` \u0026#124; `left-end` \u0026#124; `top-start` \u0026#124; `top` \u0026#124; `top-end` \u0026#124; `right-start` \u0026#124; `right` \u0026#124; `right-end` | Specifies visual location of add button.                                                                                                                    |\n| `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.      |\n\n\u003c/details\u003e\n\n### Data attributes hierarchy\n\nThe SDK processes data attributes in a hierarchical structure. While the SDK internally parses these attributes from the most nested (bottom) to the least nested (top), we recommend implementing them in your HTML from top to bottom. This means starting with the highest-level container elements and working your way down to the most specific elements. Here's how:\n\n- **Environment-Level Attributes:** Begin by assigning the `data-kontent-environment-id` attribute to a high-level element, such as the `\u003cbody\u003e` tag. Similarly, assign the `data-kontent-language-codename` alongside the environment id ID to establish the language context for all contained elements.\n\n\u003e [!NOTE] \n\u003e The environment ID and language codename values can be configured during SDK initialization. See the [Configuration section](#configuration) for details on setting these values programmatically.\n\n- **Content-Specific Attributes:** After setting environment attributes, mark your content items and their elements:\n  - Use `data-kontent-item-id` on elements that represent entire content items\n  - Use `data-kontent-element-codename` on elements that represent specific fields within those items \n  - Use `data-kontent-component-id` on elements that represent content components within rich text elements\n\n  This mapping allows the SDK to connect your HTML elements directly to their corresponding content in Kontent.ai.\n\n- **Nested Content:** For rich text or linked items containing additional content:\n  - For linked items: Mark nested content with `data-kontent-item-id` to represent each nested item\n  - For rich text: Mark content components with `data-kontent-component-id` to represent reusable components within the rich text\n  - Continue this pattern for any deeper nesting levels\n\n\u003e [!NOTE]\n\u003e The Smart Link SDK provides helper functions to simplify the creation of data attributes. For more information, see the [data attribute helper functions](./src/utils/dataAttributes/helpers.ts).\n\n\n### Smart Links\n\nSmart links create clickable overlays on your content that connect directly to Kontent.ai's editing interface. Think of them as \"edit buttons\" or \"add buttons\" that appear when you hover over content elements.\n\n| Smart Link Type | Purpose | Works Outside Live Preview? | Key Attributes |\n|----------------|---------|---------------------------|---------------|\n| **Element Edit** | Edit specific content elements | ✅ Yes | `data-kontent-element-codename` |\n| **Item Edit** | Edit content items | ✅ Yes | `data-kontent-item-id` |\n| **Component Edit** | Edit content components | ❌ Live Preview only | `data-kontent-component-id` |\n| **Add Content** | Add new content/components | ❌ Live Preview only | `data-kontent-add-button` |\n\n**What happens when clicked:**\n- **Outside Live Preview:** Redirects to Kontent.ai item editor\n- **Inside Live Preview:** Opens in-context editor for that specific field\n\n---\n\n#### Smart Links Guide\n\n1. **Set up your page context**\nEvery smart link needs to know which project and language it's working with:\n\n```html\n\u003c!-- Set these once at a high level (like \u003cbody\u003e or main container) --\u003e\n\u003cmain data-kontent-environment-id='1d50a0f7-9033-48f3-a96e-7771c73f9683' \n      data-kontent-language-codename='default'\u003e\n  \u003c!-- Your content goes here --\u003e\n\u003c/main\u003e\n```\n\n2. **Make a text field editable**\nThe most common use case - clicking text to edit it:\n\n```html\n\u003cmain data-kontent-environment-id='1d50a0f7-9033-48f3-a96e-7771c73f9683' \n      data-kontent-language-codename='default'\u003e\n  \u003c!-- Edit Button on content item with specified ID --\u003e\n  \u003carticle data-kontent-item-id='af858748-f48a-4169-9b35-b10c9d3984ef'\u003e\n    \u003c!-- Edit Button on \"title\" element --\u003e\n    \u003ch1 data-kontent-element-codename='title'\u003eHow to Use Smart Links\u003c/h1\u003e\n    \u003c!-- Edit Button on \"content\" element --\u003e\n    \u003cdiv data-kontent-element-codename='content'\u003e\n      Smart links make editing content incredibly easy...\n    \u003c/div\u003e\n  \u003c/article\u003e\n\u003c/main\u003e\n```\n\n#### Content Components\n\nContent components are reusable pieces within rich text. Each needs its own ID:\n\n```html\n\u003cmain data-kontent-environment-id='1d50a0f7-9033-48f3-a96e-7771c73f9683' \n      data-kontent-language-codename='default'\u003e\n      \n  \u003carticle data-kontent-item-id='af858748-f48a-4169-9b35-b10c9d3984ef'\u003e\n    \n    \u003c!-- Edit button on rich text element --\u003e\n    \u003cdiv data-kontent-element-codename='article_content'\u003e\n      \n      \u003c!-- Edit button on component with that ID --\u003e\n      \u003cblockquote data-kontent-component-id='51a90561-9084-4d32-9e34-80da7c88c202'\u003e\n        \u003c!-- Edit button on \"quote_text\" element inside component --\u003e\n        \u003cp data-kontent-element-codename='quote_text'\u003e\n          \"Smart links revolutionized our content workflow.\"\n        \u003c/p\u003e\n      \u003c/blockquote\u003e\n      \n      \u003c!-- Another content component --\u003e\n      \u003cfigure data-kontent-component-id='23e657d2-e4ce-4878-a77d-365db46c956d'\u003e\n        \u003cimg data-kontent-element-codename='image' src=\"...\" alt=\"...\"\u003e\n      \u003c/figure\u003e\n    \u003c/div\u003e\n  \u003c/article\u003e\n\u003c/main\u003e\n```\n\n---\n\n#### Adding New Modular Content\n\nAdd buttons let editors insert new linked items/components directly in the preview:\n\n**Adding to the end of a list:**\n```html\n\u003c!-- Navigation menu that editors can extend --\u003e\n\u003cnav data-kontent-item-id='6ea11626-336d-47e5-9f35-2d44fa1ad6d6'\u003e\n  \u003cul data-kontent-element-codename='navigation_items'\n      data-kontent-add-button\n      data-kontent-add-button-render-position='bottom'\n      data-kontent-add-button-insert-position='end'\u003e\n    \n    \u003cli data-kontent-component-id='036acd8f-5e6d-4023-b0f8-a4b8e0b573b1'\u003e\n      \u003ca data-kontent-element-codename='link_text'\u003eHome\u003c/a\u003e\n    \u003c/li\u003e\n    \u003cli data-kontent-component-id='f539f1bc-9dc4-4df5-8876-dbb1de5ae6eb'\u003e\n      \u003ca data-kontent-element-codename='link_text'\u003eAbout\u003c/a\u003e\n    \u003c/li\u003e\n    \n    \u003c!-- Add button appears here --\u003e\n  \u003c/ul\u003e\n\u003c/nav\u003e\n```\n\n**Adding before/after specific linked items:**\n```html\n\u003c!-- Article sections where editors can insert new linked item sections --\u003e\n\u003carticle data-kontent-item-id='af858748-f48a-4169-9b35-b10c9d3984ef'\u003e\n  \u003cdiv data-kontent-element-codename='article_sections'\u003e\n    \n    \u003c!-- Existing linked item section with \"add before\" button --\u003e\n    \u003csection data-kontent-item-id='12345678-1234-1234-1234-123456789012'\n             data-kontent-add-button\n             data-kontent-add-button-render-position='top-start'\n             data-kontent-add-button-insert-position='before'\u003e\n      \u003ch2 data-kontent-element-codename='section_title'\u003eIntroduction\u003c/h2\u003e\n      \u003cp data-kontent-element-codename='section_content'\u003e...\u003c/p\u003e\n    \u003c/section\u003e\n    \n    \u003c!-- Another linked item section with \"add after\" button --\u003e\n    \u003csection data-kontent-item-id='87654321-4321-4321-4321-210987654321'\n             data-kontent-add-button\n             data-kontent-add-button-render-position='bottom-end'\n             data-kontent-add-button-insert-position='after'\u003e\n      \u003ch2 data-kontent-element-codename='section_title'\u003eConclusion\u003c/h2\u003e\n      \u003cp data-kontent-element-codename='section_content'\u003e...\u003c/p\u003e\n    \u003c/section\u003e\n  \u003c/div\u003e\n\u003c/article\u003e\n```\n\n\u003e [!NOTE]\n\u003e For a demonstration of Smart Link buttons, check out the [samples/smartlink.html](./samples/smartlink.html) file in this repository which showcases different button types and positions.\n\n## SDK initialization\n\nTo 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:\n\n- `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.\n    ```ts\n    const instance = KontentSmartLink.initialize({ queryParam: \"preview\" });\n    ```\n- `initializeOnLoad`: Delays SDK initialization until the entire page has loaded. This approach is particularly useful for including the SDK in the `\u003chead\u003e` of your webpage, ensuring that all page elements are fully loaded before\n  initialization begins.\n    ```ts\n    KontentSmartLink.initializeOnLoad().then(instance =\u003e {\n      // SDK is fully initialized and ready to use\n    });\n    ```\n\nBoth methods return an SDK instance, with `initializeOnLoad` returning a promise that resolves to an instance. It is important to manage this instance appropriately:\n\n- **Accessing the SDK instance:** Store the returned instance if you need to access SDK methods after initialization.\n\n- **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.\n    ```ts\n    useEffect(() =\u003e {\n      const instance = KontentSmartLink.initialize();\n      return () =\u003e instance.destroy(); \n    })\n    ```\n\nThe Kontent.ai Smart Link SDK adapts its behavior based on where it's running to provide the optimal content management experience:\n\n#### Outside Live Preview\n\nWhen used outside of Live Preview, 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 features that could be used outside of Live Preview are limited.\n\n#### Inside Live Preview\n\nIf the SDK detects that it is run inside an iframe, it attempts to connect to Live Preview through iframe messages early during initialization. Upon successful communication with Live Preview, the SDK disables query parameter\nreliance and activates additional functionalities designed for in-context editing and preview.\n\n\n### Configuration\n\nCustomize how the SDK operate on your preview website with optional configuration arguments passed during initialization. Configuration can be adjusted post-initialization using the `setConfiguration` method.\n\n\u003cdetails\u003e\n\u003csummary\u003eView all configuration options\u003c/summary\u003e\n\n| Attribute             |                           Default                           | Description                                                                                                                                                                                                                                                                                                                                                    |\n|-----------------------|:-----------------------------------------------------------:|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| debug                 |                            false                            | Set to `true` to enable detailed logging, aiding in development and troubleshooting.                                                                                                                                                                                         \n| defaultDataAttributes | ```{ environmentId: undefined, languageCodename: undefined }``` | Define default values for essential data attributes to streamline setup.                                                                                                                                                                                                                                                                                       |\n| queryParam            |                        `ksl-enabled`                        | Name of the query parameter that must be present in the URL to turn the smart link injection on. Only the presence of this query parameter is checked. Query parameter is only used outside Live Preview. |\n\n\u003c/details\u003e\n\n\n### Preview autorefresh in Live Preview\n\nMaintaining an up-to-date preview is essential when editing content in Live Preview 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.\n\n#### Prerequisites for autorefresh\n\nTo enable autorefresh in your preview web app, ensure:\n\n1. **SDK Version:** Your application is using version 2.2.0 or higher of the Smart Link SDK.\n2. **API Header:** The `X-KC-Wait-For-Loading-New-Content` header is set to `true` for requests to the Delivery Preview API.\n\n#### Implementing a custom refresh handler\n\nThere 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.\n\nThis custom handler overrides the default refresh behavior, allowing for tailored refresh logic based on your specific requirements. Implement it as follows:\n\n```ts\nimport KontentSmartLink, { IRefreshMessageData } from '@kontent-ai/smart-link';\n\nconst sdk = KontentSmartLink.initialize();\n\nsdk.on(KontentSmartLinkEvent.Refresh, (data, metadata, originalRefresh) =\u003e {\n  // Implement your custom refresh logic here\n});\n```\n\nYou can then unregister the custom handler using the `.off` method.\n\nFor more complex example, check the [Examples](#examples) section.\n\n\n### Live reload in Live Preview\n\nAs of version 3.2.0, the Kontent.ai Smart Link SDK introduces support for live reload within Live Preview. 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.\n\n\u003e [!NOTE]\n\u003e The live reload 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.\n\n#### Implementing live reload in your application\n\nTo set up live reload, listen for update events from the SDK. These events are triggered after content is edited in Kontent.ai, providing you with the updated data. \nIn a typical SPA, you would fetch the data from the Delivery API and store them in memory. When the SDK triggers an update event, you would then update the stored items in memory to display the latest content. \nTo easily apply the updates on you items, SDKs provide you wtih helper functions:\n  - `applyUpdateOnItem` - A function that applies the update data directly to a content item, modifying its elements, content components, and existing linked items according to the changes made in the editor.\n\n  - `applyUpdateOnItemAndLoadLinkedItems` - A function that applies updates to the content item and attempts to load newly added linked items using the provided callback function. The callback function should handle loading the items from the Delivery API, including any polling logic needed since newly added items may not be immediately available.\n\n```ts\nimport KontentSmartLink, { KontentSmartLinkEvent, applyUpdateOnItem, applyUpdateOnItemAndLoadLinkedItems } from '@kontent-ai/smart-link';\n\nconst setItems = (items: ...) =\u003e {} // setItems logic\n\n// Initialize the SDK\nconst sdk = KontentSmartLink.initialize({ ... });\n\nconst fetchItemsFromDeliveryApi = (itemCodenames: ReadonlyArray\u003cstring\u003e) =\u003e client.items().inFilter(system.codename, items).toAllPromise().then(res =\u003e res.data.items);\n\n// Listen for updates and apply them to your application\nsdk.on(KontentSmartLinkEvent.Update, (data: IUpdateMessageData) =\u003e {\n  // Use this data to update your application state or UI as needed e.g.:\n  setItems((items) =\u003e items.map(item =\u003e applyUpdateOnItem(item, data)));\n  // or\n  Promise.all(items.map(item =\u003e applyUpdateOnItemAndLoadLinkedItems(item, data, fetchItemsFromDeliveryApi)))\n    .then(setItems);\n});\n```\n\nFor more complex example, check the [Examples](#examples) section.\n\n### Combining autorefresh and live reload\n\nWhile autorefresh ensures that content updates are accurately reflected post-save, live reload offers the advantage of immediate visual feedback before changes are saved. To maximize content management efficiency, we recommend using live reload for instant editing feedback and relying on autorefresh to confirm that all changes are correctly saved\nand 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.\n\n#### Customization\n\nThe following [custom CSS properties](https://developer.mozilla.org/en-US/docs/Web/CSS/--*) can be used to customize the\nvisuals of the SDK output.\n\n\u003cdetails\u003e\n\u003csummary\u003eView CSS customization options\u003c/summary\u003e\n\n| Custom property | Default | Description |\n|-----------------|---------|-------------|\n| --ksl-color-background-default | `rgba(255, 255, 255, 1)` | Default background color used in toolbar and popover. |\n| --ksl-color-background-default-disabled | `rgba(223, 223, 223, 1)` | Disabled background color for buttons inside toolbar and popover. |\n| --ksl-color-background-default-hover | `rgba(21, 21, 21, 0.1)` | Hover background color for buttons inside toolbar and popover. |\n| --ksl-color-background-default-selected | `rgba(255, 240, 239, 1)` | Selected background color for buttons inside toolbar and popover. |\n| --ksl-color-background-secondary | `rgba(20, 22, 25, 1)` | Secondary background color used in tooltips. |\n| --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. |\n| --ksl-color-primary-hover | `rgba(149, 48, 0, 1)` | Primary color used as a hover background color in add buttons. |\n| --ksl-color-primary-transparent | `rgba(219, 60, 0, 0.5)` | Primary color with transparency used as a default border color in highlights. |\n| --ksl-color-text-default | `rgba(255, 255, 255, 1)` | Text color used on a default background (buttons inside toolbar and popover). |\n| --ksl-color-text-default-disabled | `rgba(140, 140, 140, 1)` | Disabled text color used on a default background. |\n| --ksl-color-text-secondary | `rgba(21, 21, 21, 1)` | Text color used inside tooltips and add buttons. |\n| --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. |\n| --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. |\n| --ksl-z-index | `9000` | Base value of z-index used for calculation of individual values for each ksl-element type |\n\n\u003c/details\u003e\n\nThese styles can be applied globally using the :root selector or scoped to specific elements for more precise theming.\n\n```css\n:root {\n    --ksl-color-background-default: rgba(4, 102, 200, 1);\n    --ksl-color-background-default-disabled: rgba(2, 62, 125, 1);\n    --ksl-color-background-default-hover: rgba(0, 40, 85, 0.1);\n    --ksl-color-background-secondary: rgba(2, 62, 125, 1);\n    --ksl-color-background-default-selected: rgba(3, 83, 164, .1);\n    --ksl-color-primary: rgba(4, 102, 200, 1);\n    --ksl-color-primary-transparent: rgba(4, 102, 200, 0.5);\n    --ksl-color-primary-hover: rgba(2, 62, 125, 1);\n    --ksl-color-text-default: rgba(255, 255, 255, 1);\n    --ksl-color-text-default-disabled: rgba(51, 65, 92, 1);\n    --ksl-color-text-secondary: rgba(255, 255, 255, 1);\n    --ksl-shadow-default: 0 8px 32px rgba(0, 24, 69, 0.24), 0 0 8px rgba(0, 0, 0, 0.03);\n    --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);\n    --ksl-z-index: 9000;\n}\n```\n\n## Known issues\n\n### Nested iframes\n\nIn 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 Live Preview iframe directly, affecting functionality. To address this, follow the guidance provided\nin [this issue](https://github.com/kontent-ai/smart-link/issues/16).\n\n### SameSite cookie in Next.js app\n\nNext.js developers may encounter an issue with `SameSite` cookies not being set correctly. This can be solved by\nutilizing the code snippet below in\nyour [API route handling preview](https://nextjs.org/docs/advanced-features/preview-mode) file to replace `SameSite=Lax`\nto `SameSite=None; Secure;` right after `res.setPreviewData` call.\n\n```js\nconst setCookieSameSite = (res, value) =\u003e {\n  const cookies = res.getHeader(\"Set-Cookie\");\n  const updatedCookies = cookies?.map((cookie) =\u003e\n    cookie.replace(\n      \"SameSite=Lax\",\n      `SameSite=${value}; Secure;`\n    )\n  )\n  res.setHeader(\n    \"Set-Cookie\",\n    updatedCookies\n  );\n};\n\nexport default function handler(req, res) {\n  // ...\n  res.setPreviewData({})\n\n  // THIS NEEDED TO BE ADDED\n  setCookieSameSite(res, \"None\");\n  // ...\n}\n```\n\n## Examples\n\n### HTML \u0026 UMD \u0026 CDN\n\n\u003cdetails\u003e\n\u003csummary\u003eView HTML/CDN implementation example\u003c/summary\u003e\n\nThis 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.\n\n```html\n\u003chtml\u003e\n  \u003chead\u003e\n    \u003ctitle\u003eKontent.ai Smart Link - HTML example\u003c/title\u003e\n    \u003c!-- Include the SDK from a CDN --\u003e\n    \u003cscript type='text/javascript'\n            src='https://cdn.jsdelivr.net/npm/@kontent-ai/smart-link@latest/dist/bundles/kontent-smart-link.min.js'\u003e\u003c/script\u003e\n    \u003cscript type='text/javascript'\u003e\n      // Initialize the SDK upon page load\n      kontentSmartLink.KontentSmartLink.initializeOnLoad({ queryParam: 'preview' }).then((sdk) =\u003e {\n        // NOTE: this is just an example of what your live preview implementation may look like\n        sdk.on(\"update\", (data) =\u003e {\n          data.elements.forEach((i) =\u003e {\n            const codename = i.element.codename;\n            const domElement = document.querySelector(`[data-kontent-element-codename=${codename}]`);\n            \n            if (domElement) {\n              domElement.innerHTML = i.data.value;\n            }\n          });\n        });\n      });\n    \u003c/script\u003e\n  \u003c/head\u003e\n  \u003cbody data-kontent-enviroment-id='1d50a0f7-9033-48f3-a96e-7771c73f9683' data-kontent-language-codename='en-US'\u003e\n    \u003c!-- Example content with data attributes for smart link injection --\u003e\n    \u003cnav class='navigation' data-kontent-item-id='6ea11626-336d-47e5-9f35-2d44fa1ad6d6'\u003e\n      \u003cimg class='navigation__logo' data-kontent-element-codename='logo' /\u003e\n      \u003cul\n        class='navigation__list'\n        data-kontent-element-codename='navigation'\n        data-kontent-add-button\n        data-kontent-render-position='left'\n        data-kontent-insert-position='start'\n      \u003e\n        \u003cli class='navigation__list-item' data-kontent-component-id='036acd8f-5e6d-4023-b0f8-a4b8e0b573b1'\u003e\n          \u003cspan data-kontent-element-codename='title'\u003eHome\u003c/span\u003e\n        \u003c/li\u003e\n        \u003cli class='navigation__list-item' data-kontent-component-id='f539f1bc-9dc4-4df5-8876-dbb1de5ae6eb'\u003e\n          \u003cspan data-kontent-element-codename='title'\u003eAbout us\u003c/span\u003e\n        \u003c/li\u003e\n      \u003c/ul\u003e\n    \u003c/nav\u003e\n    \u003cdiv\n      class='page'\n      data-kontent-item-id='af858748-f48a-4169-9b35-b10c9d3984ef'\n      data-kontent-element-codename='page_content'\n    \u003e\n      \u003cdiv\n        class='section'\n        data-kontent-component-id='51a90561-9084-4d32-9e34-80da7c88c202'\n        data-kontent-add-button\n        data-kontent-add-button-render-position='bottom'\n        data-kontent-add-button-insert-position='after'\n      \u003e\n        \u003cimg class='home__banner' data-kontent-element-codename='image' /\u003e\n        \u003ch1 data-kontent-element-codename='title'\u003eHome page\u003c/h1\u003e\n      \u003c/div\u003e\n      \u003cdiv\n        class='section'\n        data-kontent-component-id='23e657d2-e4ce-4878-a77d-365db46c956d'\n        data-kontent-add-button\n        data-kontent-add-button-render-position='bottom'\n        data-kontent-add-button-insert-position='after'\n      \u003e\n        \u003cp data-kontent-element-codename='text'\u003e...\u003c/p\u003e\n      \u003c/div\u003e\n    \u003c/div\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\n\u003c/details\u003e\n\n### React\n\n\u003cdetails\u003e\n\u003csummary\u003eView React implementation examples\u003c/summary\u003e\n\nIn 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.\n\n#### Creating the SmartLink context\n\n```tsx\n// src/contexts/SmartLink.tsx\nimport React, { type PropsWithChildren, useContext, useState, useMemo, useEffect, createContext } from 'react';\nimport KontentSmartLink, { KontentSmartLinkEvent } from '@kontent-ai/smart-link';\nimport KontentSmartLink, {\n  KontentSmartLinkEvent,\n  type IRefreshMessageData,\n  type IRefreshMessageMetadata,\n  type IUpdateMessageData,\n} from '@kontent-ai/smart-link';\n\ntype SmartLinkContextValue = {\n  readonly smartLink?: KontentSmartLink | null;\n}\n\nconst defaultContextValue: SmartLinkContextValue = {\n  smartLink: undefined,\n};\n\nconst SmartLinkContext = createContext\u003cSmartLinkContextValue\u003e(defaultContextValue);\n\nexport const SmartLinkProvider: React.FC\u003cPropsWithChildren\u003e = ({ children }) =\u003e {\n  const [smartLink, setSmartLink] = useState\u003cKontentSmartLink | null\u003e(null);\n\n  useEffect(() =\u003e {\n    const instance = KontentSmartLink.initialize({\n      queryParam: 'preview',\n      defaultDataAttributes: {\n        environmentId: 'your-envrionment-id',\n        languageCodename: 'your-language-codename',\n      },\n    });\n\n    setSmartLink(instance);\n\n    // Cleanup on component unmount\n    return () =\u003e instance.destroy();\n  }, []);\n\n  const value = useMemo(() =\u003e ({ smartLink }), [smartLink]);\n\n  return (\n    \u003cSmartLinkContext.Provider value={value}\u003e\n      {children}\n    \u003c/SmartLinkContext.Provider\u003e\n  );\n};\n\n// Custom hook for easy access to the SmartLink instance\nexport const useSmartLink = (): KontentSmartLink | null =\u003e {\n  const { smartLink } = useContext(SmartLinkContext);\n\n  if (typeof smartLink === 'undefined') {\n    throw new Error('You need to place SmartLinkProvider to one of the parent components to use useSmartLink.');\n  }\n\n  return smartLink;\n};\n\n// Custom hook for easy setup of a custom refresh handler\nexport const useCustomRefresh = (callback: (data: IRefreshMessageData, metadata: IRefreshMessageMetadata, originalRefresh: () =\u003e void) =\u003e void): void =\u003e {\n  const smartLink = useSmartLink();\n\n  useEffect(() =\u003e {\n    if (smartLink) {\n      smartLink.on(KontentSmartLinkEvent.Refresh, callback);\n\n      return () =\u003e smartLink.off(KontentSmartLinkEvent.Refresh, callback);\n    }\n\n    return;\n  }, [smartLink, callback]);\n};\n\n// Custom hook for easy access of live preview\nexport const useLivePreview = (callback: (data: IUpdateMessageData) =\u003e void): void =\u003e {\n  const smartLink = useSmartLink();\n\n  useEffect(() =\u003e {\n    if (smartLink) {\n      smartLink.on(KontentSmartLinkEvent.Update, callback);\n\n      return () =\u003e smartLink.off(KontentSmartLinkEvent.Update, callback);\n    }\n\n    return;\n  }, [smartLink, callback]);\n};\n```\n\nUsing the SmartLink provider in your app:\n\n```tsx\n// src/App.tsx\nimport React from 'react';\nimport { SmartLinkProvider } from './contexts/SmartLink';\n\nconst App: React.FC = () =\u003e {\n  return (\n    \u003cSmartLinkProvider\u003e\n      {/* Your app components go here, now with smart link support */}\n    \u003c/SmartLinkProvider\u003e\n  );\n};\n```\n\n#### Optimizing content updates in React with custom refresh logic\n\nUsing 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.\n\nThe following example showcases an efficient approach to handling such content updates within a React application.\n\n```tsx\nimport React, { useState, useCallback } from 'react';\nimport { useCustomRefresh } from '../context/SmartLink'; // Ensure correct import path\nimport type { IRefreshMessageData, IRefreshMessageMetadata } from '@kontent-ai/smart-link';\n\nconst YourComponent: React.FC = () =\u003e {\n  const [data, setData] = useState(null);\n  const fetchData = useCallback((environmentId, languageCodename, itemCodename) =\u003e {\n    // Your data fetching logic here using a delivery-sdk or request to Delivery API endpoint\n  }, []);\n\n  // Define the custom refresh logic.\n  const onRefresh = useCallback((data: IRefreshMessageData, metadata: IRefreshMessageMetadata, originalRefresh: () =\u003e void) =\u003e {\n    // Check if the refresh was triggered manually and perform a full refresh if so.\n    if (metadata.manualRefresh) {\n      originalRefresh();\n    } else {\n      // For automatic refreshes, refetch data for the updated item only.\n      const { environmentId, languageCodename, updatedItemCodename } = data;\n      fetchData(environmentId, languageCodename, updatedItemCodename);\n    }\n  }, [fetchData]);\n\n  // Use the custom refresh hook with the defined logic.\n  useCustomRefresh(onRefresh);\n\n  return \u003cdiv\u003e{data}\u003c/div\u003e;\n};\n```\nf\n#### Live Preview in Your Application\n\nUsing the `useLivePreview` hook we defined in the previous example, you can enhance your application with real-time\ncontent updates during the editing process. This hook listens for live update messages from the Kontent.ai Smart Link\nSDK and applies those updates directly to the content item being displayed.\n\nThis example demonstrates setting up a `useLivePreview` hook within a React component to dynamically update content\nitems as changes are made in the in-context editor.\n\n```tsx\nimport { useState, useCallback, useEffect } from 'react';\nimport { useLivePreview } from '../contexts/SmartLinkContext'; // Adjust the import path as needed\nimport { type IUpdateMessageData, applyUpdateOnItem, applyUpdateOnItemAndLoadLinkedItems } from \"@kontent-ai/smart-link\"\nimport type { IContentItem } from '@kontent-ai/delivery-sdk/lib/models/item-models';\n\nconst useContentItem = (codename: string) =\u003e {\n  const [item, setItem] = useState\u003cIContentItem | null\u003e(null);\n  // Assume useDeliveryClient is a custom hook to obtain a configured delivery client instance\n  const deliveryClient = useDeliveryClient();\n\n  const handleLiveUpdate = useCallback((data: IUpdateMessageData) =\u003e {\n    if (item) {\n      setItem(applyUpdateOnItem(item, data));\n      // or use applyUpdateOnItemAndLoadLinkedItems to load added linked items\n      applyUpdateOnItemAndLoadLinkedItems(item, data, codenamesToFetch =\u003e deliveryClient.items(codenamesToFetch).toAllPromise())\n        .then(setItem);\n    }\n  }, [codename, item]);\n\n  useEffect(() =\u003e {\n    // Fetch the content item initially and upon codename changes\n    deliveryClient.item\u003cIContentItem\u003e(codename)\n      .toPromise()\n      .then(res =\u003e setItem(res.item));\n  }, [codename, deliveryClient]);\n\n  useLivePreview(handleLiveUpdate);\n\n  return item;\n};\n\n// Example component using the useContentItem hook\nexport const ContentItemComponent = ({ codename }) =\u003e {\n  const item = useContentItem(codename);\n\n  // Render logic for the content item\n  return (\n    \u003cdiv\u003e\n      {/* Render your content item here */}\n      \u003ch2\u003e{item?.elements.name}\u003c/h2\u003e\n      {/* More render logic */}\n    \u003c/div\u003e\n  );\n};\n```\n\n\u003c/details\u003e\n\n### Triggering SSG rebuilds with custom refresh logic\n\n\u003cdetails\u003e\n\u003csummary\u003eView SSG rebuild implementation example\u003c/summary\u003e\n\nFor websites built with static site generators, applying content updates typically requires triggering\na rebuild of the site. The following example shows how you can initiate a rebuild with\nthe custom refresh handler, ensuring that your site reflects the latest content changes.\n\nThis example uses Gatsby deployed to Netlify, but the solution for other SSG\nframeworks should be similar.\n\nThe following Netlify serverless function, `deploy-status`, checks the status of the latest deployment, enabling\nyour application to wait for a rebuild to complete before refreshing the content.\n\n```js\n// ./.netlify/functions/deploy-status.js\nimport fetch from 'node-fetch';\n\n// Environment variables for Netlify site ID and access token\nconst siteId = process.env.NETLIFY_SITE_ID;\nconst token = process.env.NETLIFY_TOKEN;\n\n// Handler to check the status of the last deployment\nconst handler = async event =\u003e {\n  try {\n    const endpoint = `https://api.netlify.com/api/v1/sites/${siteId}/deploys`;\n    const result = await fetch(endpoint, { headers: { Authorization: `Bearer ${token}` } });\n    const data = await result.json();\n\n    // Assuming the first entry is the latest deployment\n    const deploy = { state: data[0].state };\n\n    return { statusCode: 200, body: JSON.stringify(deploy) };\n  } catch (error) {\n    return { statusCode: 500, body: error.toString() };\n  }\n};\n\nmodule.exports = { handler };\n```\n\n\nWe can then trigger the rebuild process inside our custom refresh handler and wait for the deployment process to finish.\nAfter that the page can be refreshed using the `originalRefresh` callback.\n\n```tsx\nimport React, { useCallback, useEffect } from 'react';\nimport { useCustomRefresh } from '../context/SmartLinkContext'; // Adjust the import path as needed\n\nconst triggerRebuildOnNetlifyAndWaitForDeploy = useCallback(() =\u003e {\n  // Trigger the Netlify build hook\n  fetch('https://api.netlify.com/build_hooks/YOUR_BUILD_HOOK_ID?trigger_title=autorefresh', { method: 'POST' })\n    .then(async () =\u003e {\n      // Check the deployment status repeatedly until it's 'ready'\n      const checkDeployStatus = async () =\u003e {\n        const response = await fetch('/.netlify/functions/deploy-status');\n        const { state } = await response.json();\n        if (state !== 'ready') {\n          setTimeout(checkDeployStatus, 3000); // Check again after 3 seconds\n        }\n      };\n\n      await checkDeployStatus();\n    });\n}, []);\n\nconst PageWithAutoRefresh = () =\u003e {\n  useCustomRefresh((data, metadata, originalRefresh) =\u003e {\n    if (!metadata.manualRefresh) {\n      triggerRebuildOnNetlifyAndWaitForDeploy().then(originalRefresh);\n    } else {\n      originalRefresh();\n    }\n  });\n\n  // Page content goes here\n  return \u003cdiv\u003ePage Content\u003c/div\u003e;\n};\n\nexport default PageWithAutoRefresh;\n```\n\n\u003c/details\u003e\n\n## Tests\n\n### Unit tests\n\nSince this SDK highly depends on browser APIs, the unit tests are run by [Vitest Browser](https://vitest.dev/guide/browser/) inside Chrome\nbrowser. To run all tests in a watch mode you can use the `npm run test:unit` command. To run all tests only once you\ncan use the `npm run test:unit:ci` command. All unit tests are located in the `test-browser` folder.\n\n### Visual regression tests\n\nVisual regression testing is implemented using [Playwright Components](https://playwright.dev/docs/test-components). Pre-built components are available into which Smart-Link loads. After the component is mounted, Playwright performs a screenshot test of the component. \n\nTo run Visual Regression tests use `npm run test:visual` command. Or you can use\nthe `npm run test:visual:ci` to start tests in a UI mode.\n\n\u003e [!NOTE]\n\u003e Visual regression tests use the built version of SDK, so before running them make sure you rebuild the SDK after the last changes you made.\n\n### Updating Visual Regression Tests\n\nRendering differences across operating systems can lead to variations in screenshots. In our GitHub Action workflow for visual tests, reference screenshots are generated using the `ubuntu-latest` environment. To ensure consistency, a dedicated workflow produces an artifact with the updated screenshots.\n\nFollow these steps if you need to update the screenshots:\n\n1. Navigate to the **Actions** tab in your GitHub repository.\n2. Locate and select the **Update Snapshots** workflow.\n3. Manually trigger the workflow on your branch.\n4. Once the workflow completes, download the generated artifact containing the updated screenshots.\n5. Copy the updated screenshots into the repository.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkontent-ai%2Fsmart-link","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkontent-ai%2Fsmart-link","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkontent-ai%2Fsmart-link/lists"}