Petal

Make your rich text portable

Customizing rich text content with JavaScript just got easier thanks to our newly released Rich Text Resolver.


Daniel PokornyPublished on Jan 29, 2024

Rich text content plays a key role in the domain of content creation, allowing editors to craft structured and formatted content through a simple WYSIWYG interface. Although the conventional HTML representation within Kontent.ai is robust, there is potential for enhancement, particularly in terms of customization beyond the capabilities of the rich text editor, as well as in the resolution of linked items and components. Let’s explore how our new Rich Text Resolver can improve your experience in this regard!

JSON, for starters

Kontent.ai rich text resolver provides an option to parse rich text HTML into a simple JSON tree structure. Combined with the helper methods included in the package, this sets up a baseline for the customization of your rich text content. More importantly, though, the JSON tree serves as an intermediary for transformation into a feature-packed, comprehensive format…

Enter Portable Text

A well-defined standard, Portable Text provides an easily extensible alternative to HTML-rich text representation, complemented with a range of external packages for resolution in popular frameworks. Full definition and usage examples can be found in corresponding repositories. Let’s instead explore specific scenarios where Portable Text can come in handy.

SDK agnostic resolution

Rich text resolution has so far been tied to one of our supported SDKs, in this case, TypeScript/JavaScript. As a result, if you were not using an SDK, either because you opted for our native GraphQL endpoint instead of REST API or have your own custom layer for querying the latter, you had to implement the component/linked item resolution from scratch.

With the resolver, this is no longer the case, as you can pull the rich text value from a GraphQL or REST API response and pass it to a suitable parse method for your environment (browserParse or nodeParse). The returned ParseResult can be subsequently converted to Portable Text with the transformToPortableText function and resolved using a suitable package depending on your language and framework of choice, such as React or Vue, with no SDK involved. Pretty neat, wouldn’t you say?

Resolving components and linked items

Modular content, in the form of either components or linked items, allows you to extend default rich text with more complex structures. Both of these entities are unified under a common ‘component’ custom block in their Portable Text representation, which can be resolved as per your requirements, for example, with @portabletext/to-html package:

// example component resolution using @portabletext/to-html package and TypeScript
// assumes `element` comes from TypeScript SDK

const getPortableTextComponents = (
  element: Elements.RichTextElement
): PortableTextOptions => ({
  components: {
    types: {
      component: ({
        value,
      }: PortableTextTypeComponentOptions<PortableTextComponent>) => {
        const linkedItem = element.linkedItems.find(
          (item) => item.system.codename === value.component._ref
        );
        switch (linkedItem?.system.type) {
          case "warning": {
            return `<p><strong>Warning</strong></p>\n<p>${linkedItem.elements.warning_text.value}</p>`;
          }
          default: {
            return `Resolver for type ${linkedItem?.system.type} not implemented.`;
          }
        }
      },
      // logic for resolving other types of blocks
    },
  },
});

console.log(toHTML(portableTextValue, { components: getPortableTextComponents() }));

Adding anchor links

On top of components and linked items, the object representation of Portable Text allows customizing default HTML tags as well. One situation this can be leveraged in is creating anchor links. By overriding a header tag (h1-h6), you can add an ID attribute to it and wrap its inner content into an <a> tag, thus creating a clickable anchor out of any heading used in the rich text. See example implementation using @portabletext/react package:

// example anchor creation using @portabletext/react package with TypeScript

const createAnchorString = (block: PortableTextBlock): string => {
  // logic to sanitize header content into a URL-friendly string
};

const portableTextComponents: Partial<PortableTextReactComponents> = {
  block: {
    h1: ({ value, children }: PortableTextComponentProps<PortableTextBlock>) => (
      <h1 id={createAnchorString(value)}>
        <a href={`#${createAnchorString(value)}`}>{children}</a>
      </h1>
    ),
  },
};

Image resolution

Kontent.ai assets in rich text are represented by a custom image block in Portable Text. For their resolution, you can use a transformImage helper method, accepting a callback function that should return a valid HTML/JSX, be it a bare-bones img tag or a more complex and stylized structure. Default callbacks are provided for HTML/React and Vue.

// example image resolution using @portabletext/to-html package with TypeScript

const portableTextComponents: PortableTextOptions = {
  components: {
    types: {
      image: ({
        value,
      }: PortableTextTypeComponentOptions<PortableTextImage>) => {
        return resolveImage(value, toHTMLImageDefault);
      },
    },
  },
};

console.log(toHTML(portableTextValue, { components: portableTextComponents }));

Table resolution

Similarly to assets, tables in rich text are represented by a custom Portable Text table block, a hierarchical structure with nested rows, columns, and cells that contain the actual content blocks. The package provides a helper function transformTable, which accepts a callback to modify the content of the table’s cells. For rendering them as-is, default callbacks are again provided for your convenience.

// example table resolution using @portabletext/to-html package with TypeScript

const portableTextComponents: PortableTextOptions = {
  components: {
    types: {
      table: ({
        value,
      }: PortableTextTypeComponentOptions<PortableTextTable>) => {
        return resolveTable(value, toHTML);
      },
    },
  },
};

console.log(toHTML(portableTextValue, { components: portableTextComponents }));

Wrap up

Our new Rich Text Resolver provides a standalone alternative for component/linked items resolution and extends beyond SDK capabilities by allowing customization of default rich text entities. Combined with existing resolution packages for a majority of popular frameworks, it is a handy addition to an existing list of content manipulation tools at your disposal.

You can explore the full range of the tool's capabilities in the GitHub repository, where you can find further usage examples and complete documentation.

Written by

Daniel Pokorny

As a Developer Advocate, I merge technical expertise with customer insights acquired during my tenure in the Support team to develop innovative open-source tools, streamlining the tasks of developers working with Kontent.ai.

More articles from Daniel

Feeling like your brand’s content is getting lost in the noise?

Listen to our new podcast for practical tips, tricks, and strategies to make your content shine. From AI’s magic touch to content management mastery and customer experience secrets, we’ll cover it all.

Listen now
Kontent Waves