npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@kontent-ai/rich-text-resolver

v1.2.2

Published

Kontent.ai rich text element resolver and PortableText transformer for JavaScript and TypeScript

Downloads

6,274

Readme

resolverlogo

Kontent.ai rich text resolver

Last modified Issues Contributors MIT License codecov Stack Overflow Discord

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.

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.

💡 The intermediate JSON structure can be manipulated before rendering into Portable text or used altogether independently. See JSON transformer docs for further information.

Portable text resolution

Portable text supports majority of popular languages and frameworks.

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:

    const input = `<figure data-asset-id="guid" data-image-id="guid"><img src="https://asseturl.xyz" data-asset-id="guid" data-image-id="guid" alt=""></figure>`;

    // 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.

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

const richTextValue = "<rich text html>";
const linkedItems = ["<array of linked items>"]; // e.g. from SDK
const parsedTree = browserParse(richTextValue);
const portableText = transformToPortableText(parsedTree);

const portableTextComponents: PortableTextOptions = {
  components: {
    types: {
      image: ({ value }: PortableTextTypeComponentOptions<PortableTextImage>) => {
        // helper method for resolving images
        return resolveImage(value, toHTMLImageDefault); 
      },
      component: ({ value }: PortableTextTypeComponentOptions<PortableTextComponent>) => {
        const linkedItem = linkedItems.find(
          (item) => item.system.codename === value.component._ref
        );
        switch (linkedItem?.system.type) {
          case "component_type_codename": {
            return `<p>resolved value of text_element: ${linkedItem?.elements.text_element.value}</p>`;
          }
          default: {
            return `Resolver for type ${linkedItem?.system.type} not implemented.`;
          }
        }
      },
      table: ({ value }: PortableTextTypeComponentOptions<PortableTextTable> => {
        // helper method for resolving tables
        const tableHtml = resolveTable(value, toHTML);
        return tableHtml;
      },
    },
    marks: {
      internalLink: ({ children, value }: PortableTextMarkComponentOptions<PortableTextInternalLink>) => {
        return `<a href="https://website.com/${value.reference._ref}">${children}</a>`;
      },
      link: ({ children, value }: PortableTextMarkComponentOptions<PortableTextExternalLink>) => {
        return `<a href=${value?.href!} data-new-window=${value["data-new-window"]}>${children}</a>`;
      },
    },
  },
};

const resolvedHtml = toHTML(portableText, portableTextComponents);

React resolution

React, using @portabletext/react package.

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

// assumes richTextElement from SDK

const portableTextComponents: Partial<PortableTextReactComponents> = {
  types: {
    component: ({ value }: PortableTextTypeComponentProps<PortableTextComponent>) => {
      const item = richTextElement.linkedItems.find(item => item.system.codename === value.component._ref);
      return <div>{item?.elements.text_element.value}</div>;
    },
    table: ({ value }: PortableTextTypeComponentProps<PortableTextTable>) => {
      const tableString = resolveTable(value, toPlainText);
      return <>{tableString}</>;
    }
  },
  marks: {
    link: ({ value, children }: PortableTextMarkComponentProps<PortableTextExternalLink>) => {
      return (
        <a href={value?.href} rel={value?.rel} title={value?.title} data-new-window={value?.['data-new-window']}>
          {children}
        </a>
      )
    },
    internalLink: ({ value, children }: PortableTextMarkComponentProps<PortableTextInternalLink>) => {
      const item = richTextElement.linkedItems.find(item => item.system.id === value?.reference._ref);
      return (
        <a href={"https://website.xyz/" + item?.system.codename}>
          {children}
        </a>
      )
    }
  }
}

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 (
    <PortableText value={portableText} components={portableTextComponents} />
  );
};

Gatsby.js

For Gatsby.js, it is necessary to ignore the RichText browser module by customizing webpack configuration in order to utilize the package.

// 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

<script setup>
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.
};
</script>

<template>
  <PortableText :value="props.value" :components="components" />
</template>

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.

  const richTextContent =
    `<p>Here is an <a data-item-id="12345"><strong>internal link</strong></a> in some text.</p>`;

  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.