Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/kontent-ai/rich-text-resolver-js

Tool for transforming rich text content from HTML into a JSON structure and Portable Text format.
https://github.com/kontent-ai/rich-text-resolver-js

headless-cms javascript kontent-ai portable-text portabletext rich-text typescript

Last synced: 8 days ago
JSON representation

Tool for transforming rich text content from HTML into a JSON structure and Portable Text format.

Awesome Lists containing this project

README

        

![resolverlogo](https://github.com/kontent-ai/rich-text-resolver-js/assets/52500882/5cd40306-1b36-4d57-8731-f7718e61ebea)
# Kontent.ai rich text resolver

![Last modified][last-commit]
[![Issues][issues-shield]][issues-url]
[![Contributors][contributors-shield]][contributors-url]
[![MIT License][license-shield]][license-url]
[![codecov][codecov-shield]][codecov-url]
[![Stack Overflow][stack-shield]](https://stackoverflow.com/tags/kontent-ai)
[![Discord][discord-shield]](https://discord.gg/SKCxwPtevJ)

This package provides you with tools to transform rich text element value from Kontent.ai into a JSON tree and optionally to [portable text standard](https://github.com/portabletext/portabletext).

## Installation

Install the package via npm

`npm i @kontent-ai/rich-text-resolver`

---

## Usage

Module provides two functions to parse rich text HTML into a simplified JSON tree: `browserParse` for client-side resolution and `nodeParse` for server-side use with Node.js. Their use is identical, the only difference is the underlying parsing logic.

Parsed output can then be passed to a `transformToPortableText` function, which converts the JSON tree into portable text blocks.

Full specification of portable text format can be found in [the corresponding repository](https://github.com/portabletext/portabletext).

> 💡 The intermediate JSON structure can be manipulated before rendering into Portable text or used altogether independently. See [JSON transformer](docs/index.md) docs for further information.

### Portable text resolution

Portable text supports majority of popular languages and frameworks.

- React: [react-portabletext](https://github.com/portabletext/react-portabletext)
- HTML: [to-html](https://github.com/portabletext/to-html)
- Svelte: [svelte-portabletext](https://github.com/portabletext/svelte-portabletext)
- Vue: [vue-portabletext](https://github.com/portabletext/vue-portabletext)

Resolution is described in each corresponding repository. You can also find example resolution below.

### Custom portable text blocks

Besides default blocks for common elements, Portable text supports custom blocks, which can represent other entities. Each custom block should extend `ArbitraryTypedObject` to ensure `_key` and `_type` properties are present. Key should be a unique identifier (e.g. guid), while type should indicate what the block represents. Value of `_type` property is used for subsequent override and resolution purposes. **This package comes with built-in custom block definitions for representing Kontent.ai-specific objects:**

#### Component/linked item

https://github.com/kontent-ai/rich-text-resolver-js/blob/6fe68490a32bb304d141cff741fb7e57001550eb/showcase/showcase.ts#L3-L11

#### Image

https://github.com/kontent-ai/rich-text-resolver-js/blob/6fe68490a32bb304d141cff741fb7e57001550eb/showcase/showcase.ts#L13-L22

> 💡 For image resolution, you may use `resolveImage` helper function. You can provide it either with a custom resolution method or use provided default implementations for HTML and Vue, `toHTMLImageDefault` and `toVueImageDefault` respectively.

#### Item link

https://github.com/kontent-ai/rich-text-resolver-js/blob/6fe68490a32bb304d141cff741fb7e57001550eb/showcase/showcase.ts#L24-L31

#### Table

https://github.com/kontent-ai/rich-text-resolver-js/blob/6fe68490a32bb304d141cff741fb7e57001550eb/showcase/showcase.ts#L33-L59

> 💡 For table resolution, you may use `resolveTable` helper function. You can provide it either with a custom resolution method or use default implementation from a resolution package of your choice (such as `toHTML` or `toPlainText`)


## Examples

### Modifying portable text nodes

Package exports a `traversePortableText` method, which accepts a `PortableTextObject` and a callback function. The method recursively traverses all subnodes and optionally modifies them with the provided callback:

```ts
const input = ``;

// Adds height parameter to asset reference and changes _type.
const processBlocks = (block: PortableTextObject) => {
if (block._type === "image") {
const modifiedReference = {
...block.asset,
height: 300
}

return {
...block,
asset: modifiedReference,
_type: "modifiedImage"
}
}

// logic for modifying other object types...
}

const portableText = transformToPortableText(input);
const modifiedPortableText = portableText.map(block => traversePortableText(block, processBlocks));
```

### Plain HTML resolution

HTML resolution using `@portabletext/to-html` package.

```ts
import { escapeHTML, PortableTextOptions, toHTML } from "@portabletext/to-html";
import {
browserParse,
transformToPortableText,
resolveTable,
resolveImage,
toHTMLImageDefault,
} from "@kontent-ai/rich-text-resolver";

const richTextValue = "";
const linkedItems = [""]; // e.g. from SDK
const parsedTree = browserParse(richTextValue);
const portableText = transformToPortableText(parsedTree);

const portableTextComponents: PortableTextOptions = {
components: {
types: {
image: ({ value }: PortableTextTypeComponentOptions) => {
// helper method for resolving images
return resolveImage(value, toHTMLImageDefault);
},
component: ({ value }: PortableTextTypeComponentOptions) => {
const linkedItem = linkedItems.find(
(item) => item.system.codename === value.component._ref
);
switch (linkedItem?.system.type) {
case "component_type_codename": {
return `

resolved value of text_element: ${linkedItem?.elements.text_element.value}

`;
}
default: {
return `Resolver for type ${linkedItem?.system.type} not implemented.`;
}
}
},
table: ({ value }: PortableTextTypeComponentOptions => {
// helper method for resolving tables
const tableHtml = resolveTable(value, toHTML);
return tableHtml;
},
},
marks: {
internalLink: ({ children, value }: PortableTextMarkComponentOptions) => {
return `${children}`;
},
link: ({ children, value }: PortableTextMarkComponentOptions) => {
return `${children}`;
},
},
},
};

const resolvedHtml = toHTML(portableText, portableTextComponents);
```

### React resolution

React, using `@portabletext/react` package.

```tsx
import { PortableText, PortableTextReactComponents } from "@portabletext/react";

// assumes richTextElement from SDK

const portableTextComponents: Partial = {
types: {
component: ({ value }: PortableTextTypeComponentProps) => {
const item = richTextElement.linkedItems.find(item => item.system.codename === value.component._ref);
return

{item?.elements.text_element.value}
;
},
table: ({ value }: PortableTextTypeComponentProps) => {
const tableString = resolveTable(value, toPlainText);
return <>{tableString}>;
}
},
marks: {
link: ({ value, children }: PortableTextMarkComponentProps) => {
return (

{children}

)
},
internalLink: ({ value, children }: PortableTextMarkComponentProps) => {
const item = richTextElement.linkedItems.find(item => item.system.id === value?.reference._ref);
return (

{children}

)
}
}
}

const MyComponent = ({ props }) => {
// https://github.com/portabletext/react-portabletext#customizing-components

const parsedTree = browserParse(props.element.value); // or nodeParse for SSR
const portableText = transformToPortableText(parsedTree);

return (

);
};
```

#### Gatsby.js

For [Gatsby.js](https://www.gatsbyjs.com), it is necessary to [ignore the RichText browser module by customizing webpack configuration](https://www.gatsbyjs.com/docs/debugging-html-builds/#fixing-third-party-modules) in order to utilize the package.

```js
// gatsby-node.js

// https://www.gatsbyjs.com/docs/debugging-html-builds/#fixing-third-party-modules
exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {
if (stage === "build-html" || stage === "develop-html") {
actions.setWebpackConfig({
module: {
rules: [
{
test: /rich-text-browser-parser/,
use: loaders.null(),
},
],
},
});
}
};
```

### Vue resolution
Using `@portabletext/vue` package

```ts

import {
PortableText,
PortableTextComponentProps,
PortableTextComponents,
toPlainText,
} from "@portabletext/vue";
import {
resolveTableVue as resolveTable,
resolveImageVue as resolveImage,
toVueImageDefault,
} from "@kontent-ai/rich-text-resolver";

const components: PortableTextComponents = {
types: {
image: ({ value }: PortableTextComponentProps<PortableTextImage>) =>
resolveImage(value, h, toVueImageDefault),
table: ({ value }: PortableTextComponentProps<PortableTextTable>) =>
resolveTable(value, h, toPlainText),
},
// marks etc.
};

```

### MAPI transformation

`toManagementApiFormat` is a custom transformation method built upon `toHTML` package, allowing you to restore portable text previously created from management API rich text back into MAPI supported format.

```ts
const richTextContent =
`

Here is an internal link in some text.

`;

const tree = nodeParse(richTextContent);
const portableText = transformToPortableText(tree);

// your logic to modify the portable text

const validManagementApiFormat = toManagementApiFormat(portableText);
```

> [!WARNING]
> MAPI transformation logic expects Portable Text that had been previously created from management API rich text and performs only minimal validation.
>
> Transformation from other formats (such as delivery API) is not supported unless the blocks are manually adjusted to be MAPI compatible prior to running the method.

[last-commit]: https://img.shields.io/github/last-commit/kontent-ai/rich-text-resolver-js?style=for-the-badge
[contributors-shield]: https://img.shields.io/github/contributors/kontent-ai/rich-text-resolver-js?style=for-the-badge
[contributors-url]: https://github.com/kontent-ai/rich-text-resolver-js/graphs/contributors
[issues-shield]: https://img.shields.io/github/issues/kontent-ai/rich-text-resolver-js.svg?style=for-the-badge
[issues-url]: https://github.com/kontent-ai/rich-text-resolver-js/issues
[license-shield]: https://img.shields.io/github/license/kontent-ai/rich-text-resolver-js?label=license&style=for-the-badge
[license-url]: https://github.com/kontent-ai/rich-text-resolver-js/blob/main/LICENSE
[stack-shield]: https://img.shields.io/badge/Stack%20Overflow-ASK%20NOW-FE7A16.svg?logo=stackoverflow&logoColor=white&style=for-the-badge
[discord-shield]: https://img.shields.io/discord/821885171984891914?label=Discord&logo=Discord&logoColor=white&style=for-the-badge
[codecov-shield]: https://img.shields.io/codecov/c/github/kontent-ai/rich-text-resolver-js/main.svg?style=for-the-badge
[codecov-url]: https://app.codecov.io/github/kontent-ai/rich-text-resolver-js