Giter Site home page Giter Site logo

kontent-ai / rich-text-resolver-js Goto Github PK

View Code? Open in Web Editor NEW
8.0 3.0 3.0 1004 KB

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

Home Page: https://www.npmjs.com/package/@kontent-ai/rich-text-resolver

License: MIT License

JavaScript 1.09% TypeScript 98.91%
kontent-ai portable-text rich-text headless-cms javascript portabletext typescript

rich-text-resolver-js's Introduction

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

const portableTextComponent: IPortableTextComponent = {
_type: "component",
_key: "guid",
component: {
_ref: "linkedItemOrComponentCodename",
_type: "reference",
}
};

Image

const portableTextImage: IPortableTextImage = {
_type: "image",
_key: "guid",
asset: {
_type: "reference",
_ref: "bc6f3ce5-935d-4446-82d4-ce77436dd412",
url: "https://assets-us-01.kc-usercontent.com:443/.../image.jpg"
}
};

๐Ÿ’ก 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

const portableTextItemLink: IPortableTextInternalLink = {
_type: "internalLink",
_key: "guid",
reference: {
_ref: "0184a8ac-9781-4292-9e30-1fb56f648a6c",
_type: "reference",
}
};

Table

const portableTextTable: IPortableTextTable = {
_type: "table",
_key: "guid",
numColumns: 1,
rows: [
{
_type: "row",
_key: "",
cells: [
{
_type: "cell",
_key: "guid",
childBlocksCount: 1,
content: [
{
_type: "block",
_key: "guid",
markDefs: [],
style: "normal",
children: [
{
_type: "span",
_key: "guid",
marks: [],
text: "cell text content",
}
]
}

๐Ÿ’ก 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>

rich-text-resolver-js's People

Contributors

dependabot[bot] avatar ivankiral avatar jirilojda avatar kontent-ai-bot avatar pokornyd avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

rich-text-resolver-js's Issues

Add default resolutions for image

Motivation

Images are usually resolved as-is, but the current implementation doesn't provide any default resolution for Kontent.ai assets in rich text, forcing you to specify a custom image resolver.

Proposed solution

Add helper methods for default conversion of image blocks to a corresponding output entity.

Additional context

Ideally, there should be a helper method for each of the supported frameworks/languages (React, Vue, HTML...)

Cannot find module '@kontent-ai/rich-text-resolver' or its corresponding type declarations in v1.0.0

Brief bug description

After update from @kontent-ai/rich-text-resolver 0.0.5 to 1.0.0 an error occured:
Cannot find module '@kontent-ai/rich-text-resolver' or its corresponding type declarations

Repro steps

  1. Update @kontent-ai/rich-text-resolver to 1.0.0
  2. Try to import anything from the library

Expected behavior

Cannot use the library in version 1.0.0 as it doesn't export any functionality.

Test environment

  • node v20.11.0
  • typescript 5.3.3

Screenshots

image

Crypto package for browser is missing

In browser, there is no crypto package, it is necessary to include it.

Full error when using version 0.1.5:

Module not found: Error: Can't resolve 'crypto' in 'C:\projects\sample-app-react\node_modules\@pokornyd\kontent-ai-rich-text-parser\dist\esnext\transformers\portable_text_transformer'
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.

If you want to include a polyfill, you need to:
        - add a fallback 'resolve.fallback: { "crypto": require.resolve("crypto-browserify") }'
        - install 'crypto-browserify'
If you don't want to include a polyfill, you can use an empty module like this:
        resolve.fallback: { "crypto": false }

ERROR in ./node_modules/@pokornyd/kontent-ai-rich-text-parser/dist/esnext/transformers/portable_text_transformer/PortableTextTransformer.js 2:0-28
Module not found: Error: Can't resolve 'crypto' in 'C:\projects\sample-app-react\node_modules\@pokornyd\kontent-ai-rich-text-parser\dist\esnext\transformers\portable_text_transformer'

BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.

If you want to include a polyfill, you need to:
        - add a fallback 'resolve.fallback: { "crypto": require.resolve("crypto-browserify") }'
        - install 'crypto-browserify'
If you don't want to include a polyfill, you can use an empty module like this:
        resolve.fallback: { "crypto": false }

Try to include crypto only in browserParser

Consider adding this as an experimental feature to product

Motivation

Having a standardized JSON output side by side the default HTML representation can simplify the resolution process and provide a welcome, structured alternative.

Proposed solution

For starters, deploying the package as a lambda function and making it available via a public endpoint. Subsequently decide, whether adding the JSON representation next to the HTML in API response is a viable approach.

External link duplicating in portable text creating invalid HTML

Brief bug description

When I have a paragraph with two links, transformToPortableText adds both link marks to the second one. This makes the second link come out as nested <a> tags (which causes Svelte to yell at me ๐Ÿ˜ž).

Repro steps

const testString = `<p>Text <a href="https://example.com">inner text 1</a> text between <a href="https://example.org">inner text 2</a>.</p>`
const parsedTest =  nodeParse(testString);
const portableTextTest = transformToPortableText(parsedTest);
console.log(parsedTest, portableTextTest)

The result is fine after parsing:

[
  {
    "tagName": "p",
    "attributes": {},
    "children": [
      { "content": "Text ", "type": "text" },
      {
        "tagName": "a",
        "attributes": { "href": "https://example.com" },
        "children": [{ "content": "inner text 1", "type": "text" }],
        "type": "tag"
      },
      { "content": " text between ", "type": "text" },
      {
        "tagName": "a",
        "attributes": { "href": "https://example.org" },
        "children": [{ "content": "inner text 2", "type": "text" }],
        "type": "tag"
      },
      { "content": ".", "type": "text" }
    ],
    "type": "tag"
  }
]

But after transformToPortableText, the result comes out as:

[
  { "_type": "span", "_key": "iSczJn1Pg8pTUjvX", "marks": [], "text": "Text " },
  {
    "_type": "span",
    "_key": "SWxS7iGMHitsRZom",
    "marks": ["iYc6g7G26JypEGko"],
    "text": "inner text 1"
  },
  { "_type": "span", "_key": "la7pr9mo2C8vgWZB", "marks": [], "text": " text between " },
  {
    "_type": "span",
    "_key": "Ec7Mp0px0yauHd8X",
    "marks": ["iYc6g7G26JypEGko", "NalzCVntF2xWEzzZ"],
    "text": "inner text 2"
  },
  { "_type": "span", "_key": "5byILNTDvtJohbog", "marks": [], "text": "." }
]

That's two marks on the second link.

For reference, the markDefs:

[
  {
    "_key": "iYc6g7G26JypEGko",
    "_type": "link",
    "href": "https://example.com"
  },
  {
    "_key": "NalzCVntF2xWEzzZ",
    "_type": "link",
    "href": "https://example.org"
  }
]

Expected behavior

The second link should have only one mark ("NalzCVntF2xWEzzZ" in the example).

Test environment

  • Platform: Node 20
  • Version "@kontent-ai/rich-text-resolver": "^1.0.1"

Rich text transformer fails with complex list

Brief bug description

If you try to parser the table with list, it fails with error:

TypeError: Cannot read properties of undefined (reading 'push')

      142 |         if (item._type === 'cell') {
      143 |             const tableRow = mergedItems.pop() as IPortableTextTableRow;
    > 144 |             tableRow.cells.push(item);
          |                            ^
      145 |             mergedItems.push(tableRow);
      146 |         } else {
      147 |             mergedItems.push(item);

      at src/transformers/portable-text-transformer/portable-text-transformer.ts:144:28
          at Array.reduce (<anonymous>)
      at mergeRowsAndCells (src/transformers/portable-text-transformer/portable-text-transformer.ts:141:38)
      at src/utils/transformer-utils.ts:158:84
      at src/utils/transformer-utils.ts:158:67
      at src/utils/transformer-utils.ts:158:67
      at src/utils/transformer-utils.ts:158:67
      at transformToPortableText (src/transformers/portable-text-transformer/portable-text-transformer.ts:57:12)
      at Object.<anonymous> (tests/transfomers/portable-text-transformer/portable-text-transformer.spec.ts:144:43)

Repro steps

  1. Go to '...'
  2. Click on '....'
  3. Scroll down to '....'
  4. See error

Expected behavior

Do not fail

Test environment

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

Additional context

YOu can start with https://github.com/kontent-ai/rich-text-resolver-js/tree/tables-with-list-issue branch, that already contains a failing test - just run npm run test -- -u (to generate a snapshot if succeed) and you get an error in portable text transformer โ€บ Transform table with everything in it

Cleanup export structure

When importing the modules, the intelligence suggest multiple places to import the package from:

image
image
image
image

Extend data component type

Motivation

Extend the components base type to be able to use type/intelligence for resolution logic.

Main example: Table component resolution

Proposed solution

An ideal solution for the above problems.

Additional context

Current resolution needs to adjust value, rows, and cells` property retyped to any

    table: ({ value }: any) => (
      <Table>
        <tbody>
          {value.rows.map((row: any) => (
            <tr key={row._key}>
              {row.cells.map((cell: any) => (
                <td key={cell._key}>
                  <PortableText
                    value={cell.content}
                    components={getPortableTextComponents(element)}
                  />
                </td>
              ))}
            </tr>
          ))}
        </tbody>
      </Table>
    ),

See RichTextElement.tsx resolution in storybook repository for Kontent.ai.

Link overflow (React)

When I use a showcase from the README on Sample app react, the internalLink resolution overflow to content to sibling mark.

image

Data for rich text: https://deliver.kontent.ai/975bf280-fd91-488c-994c-2f04416e5ee3/items/coffee_beverages_explained

Implementation: https://github.com/kontent-ai/sample-app-react/blob/59be982471515c8a4ca97fa0ccfc97333cf94486/src/Components/RichText.tsx#L99-L106

@pokornyd -> Also I have updated the definition for portableTextComponents to be defined as const portableTextComponents: Partial<PortableTextReactComponents> from the package ``

import React from 'react';
import {
 ElementModels,
 Elements,
} from '@kontent-ai/delivery-sdk';
import { PortableText, PortableTextReactComponents, toPlainText } from '@portabletext/react';
import { browserParse, resolveTable, transform } from '@pokornyd/kontent-ai-rich-text-parser';

// ...
const portableTextComponents: Partial<PortableTextReactComponents> = {
// ...

Multiple spans result in duplicates in the parsed PortableText object

In situations, where a block (usually a paragraph) contains multiple text spans (like a piece of regular text, next to a bold or otherwise marked text), the resulting PortableText contains duplicate blocks, depending on how many nested spans there were.

Example input: <p>text<strong>bold</strong></p>

Parse output:

Array [
  Object {
    "_key": "guid",
    "_type": "block",
    "children": Array [
      Object {
        "_key": "guid",
        "_type": "span",
        "marks": Array [],
        "text": "text",
      },
      Object {
        "_key": "guid",
        "_type": "span",
        "marks": Array [
          "strong",
        ],
        "text": "bold",
      },
    ],
    "markDefs": Array [],
    "style": "normal",
  },
  Object {
    "_key": "guid",
    "_type": "block",
    "children": Array [
      Object {
        "_key": "guid",
        "_type": "span",
        "marks": Array [],
        "text": "text",
      },
      Object {
        "_key": "guid",
        "_type": "span",
        "marks": Array [
          "strong",
        ],
        "text": "bold",
      },
    ],
    "markDefs": Array [],
    "style": "normal",
  },
]

Likely caused by incorrectly returning blocks in recursion steps that should only mutate the currently processed block, instead of returning a new one.

Error: Cannot read properties of undefined (reading 'RichTextNodeParser') when used in an Astro project

Hey, I'm trying to test this in an Astro project but the package doesn't seem to be compatible.

Using the normal import below throws an error.

import { RichTextNodeParser } from '@pokornyd/kontent-ai-rich-text-parser';
const parser = new RichTextNodeParser();
const parsed = parser.parse("<h1>Test</h1>");
Cannot read properties of undefined (reading 'RichTextNodeParser')

However, importing from the dist folder works.

import { RichTextNodeParser } from '@pokornyd/kontent-ai-rich-text-parser/dist/src/parser/node/index';
const parser = new RichTextNodeParser();
const parsed = parser.parse("<h1>Test</h1>");

StackBlitz example of the issue here: https://stackblitz.com/edit/withastro-astro-xgknjr?file=src/pages/index.astro

Add support for new ES array methods

Some of the new ES array methods are not available in the project, despite targeting esnext and using typescript version 5.0.x. notably, findLast is not available, which consequently required implementing a custom method for that matter.

Figure out project configuration to be able to refactor the code to use the newest array methods.

Rename generic transform method from portable text transformer

transform method from PortableText transformer is too generic.

When using it look like that:

import { browserParse, transform } from '@pokornyd/kontent-ai-rich-text-parser';

  const parsedTree = browserParse(props.element.value);
  const portableText = transform(parsedTree);

Readme React sample

Brief bug description

React sample in the readme didn't work for me

Repro steps

  1. Install
  2. Copy paste React sample
  3. Gives multiple errors, mainly on naming

Expected behavior

This is the working React sample:

import { PortableText, PortableTextReactComponents } from "@portabletext/react";
import {
  browserParse,
  transformToPortableText,
} from '@kontent-ai/rich-text-resolver';
import { useMemo } from "react";

const createPortableTextComponents = (linkedItems) => {
  const portableTextComponents: Partial<PortableTextReactComponents> = {
    types: {
      component: (block) => {
        const item = linkedItems.find(
          (item) => item.system.codename === block.value.component._ref
        );
        return <div>{item?.elements.text_element.value}</div>;
      },
      table: ({ value }) => {
        const table = (
          <table>
            {value.rows.map((row) => (
              <tr>
                {row.cells.map((cell) => {
                  return (
                    <td>
                      <PortableText
                        value={cell.content}
                        components={portableTextComponents}
                      />
                    </td>
                  );
                })}
              </tr>
            ))}
          </table>
        );
        return table;
      },
      image: ({ value }) => {
        // It is possible to use images from the rich text element response same as for linked items
        // const image = images.find(image => image.image_id === value.asset._ref)
        return <img src={value.asset.url}></img>;
      },
    },
    marks: {
      link: ({ value, children }) => {
        const target = (value?.href || "").startsWith("http")
          ? "_blank"
          : undefined;
        return (
          <a
            href={value?.href}
            target={target}
            rel={value?.rel}
            title={value?.title}
            data-new-window={value["data-new-window"]}
          >
            {children}
          </a>
        );
      },
      internalLink: ({ value, children }) => {
        // It is possible to use links from the rich text element response same as for linked items
        // const item = links.find(link => link.link_id === value.reference._ref);
        return (
          <a href={"https://somerandomwebsite.xyz/" + value.reference._ref}>
            {children}
          </a>
        );
      },
    },
  };

  const MyComponent = ({ props }) => {
    // https://github.com/portabletext/react-portabletext#customizing-components
    const parsedTree = browserParse(props.element.value);
    const portableText = transformToPortableText(parsedTree);

    return (
      <PortableText value={portableText} components={portableTextComponents} />
    );
  }
};

Test environment

n/a

Additional context

n/a

Screenshots

n/a

Transforming complex RTE HTML fails

Brief bug description

I've setup a RTE with a lot of overlapping styles, lists and a table with images and lists. I took the HTML from deliver and attempted to resolve it into portable text, but the resolver crashed. Parsing into json tree seems to work fine.

node_modules\@kontent-ai\rich-text-resolver\dist\cjs\src\transformers\portable-text-transformer\portable-text-transf
ormer.js:195
    const numCols = tableRow.children.length;
                                      ^

TypeError: Cannot read properties of undefined (reading 'length')
    at transformTable (...\portable-text-experiment\node_modules\@kontent-ai\rich-text-resolver\dist\cjs\src\transformers\portable-text-transform
er\portable-text-transformer.js:195:39)
    at transformElement (...\portable-text-experiment\node_modules\@kontent-ai\rich-text-resolver\dist\cjs\src\transformers\portable-text-transfo
rmer\portable-text-transformer.js:147:12)
    at transformNode (...\portable-text-experiment\node_modules\@kontent-ai\rich-text-resolver\dist\cjs\src\transformers\portable-text-transforme
r\portable-text-transformer.js:142:16)
    at Array.<anonymous> (...\portable-text-experiment\node_modules\@kontent-ai\rich-text-resolver\dist\cjs\src\transformers\portable-text-transf
ormer\portable-text-transformer.js:131:33)
    at Array.flatMap (<anonymous>)
    at flatten (...\portable-text-experiment\node_modules\@kontent-ai\rich-text-resolver\dist\cjs\src\transformers\portable-text-transformer\port
able-text-transformer.js:110:18)
    at Object.transformToPortableText (...\portable-text-experiment\node_modules\@kontent-ai\rich-text-resolver\dist\cjs\src\transformers\portabl
e-text-transformer\portable-text-transformer.js:14:23)
    at Object.<anonymous> (...\portable-text-experiment\index.js:13:31)
    at Module._compile (node:internal/modules/cjs/loader:1267:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1321:10)

Even after removing the table from the HTML, I got a different error

node_modules\@kontent-ai\rich-text-resolver\dist\cjs\src\transformers\portable-text-transformer\portable-text-transf
ormer.js:70
            previousBlock.children.push(item);
                                   ^

TypeError: Cannot read properties of undefined (reading 'push')
    at (...\portable-text-experiment\node_modules\@kontent-ai\rich-text-resolver\dist\cjs\src\transformers\portable-text-transformer\portable-text
-transformer.js:70:36
    at Array.reduce (<anonymous>)
    at mergeBlocksAndSpans (...\portable-text-experiment\node_modules\@kontent-ai\rich-text-resolver\dist\cjs\src\transformers\portable-text-tran
sformer\portable-text-transformer.js:67:38)
    at ...\portable-text-experiment\node_modules\@kontent-ai\rich-text-resolver\dist\cjs\src\utils\transformer-utils.js:122:129
    at ...\portable-text-experiment\node_modules\@kontent-ai\rich-text-resolver\dist\cjs\src\utils\transformer-utils.js:122:112
    at Object.transformToPortableText (...\portable-text-experiment\node_modules\@kontent-ai\rich-text-resolver\dist\cjs\src\transformers\portabl
e-text-transformer\portable-text-transformer.js:15:12)
    at Object.<anonymous> (...\portable-text-experiment\index.js:13:31)
    at Module._compile (node:internal/modules/cjs/loader:1267:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1321:10)
    at Module.load (node:internal/modules/cjs/loader:1125:32)

Repro steps

Try to transform following HTML into portable text in node.

"<p>Text</p>\n<p><strong>Bold text</strong></p>\n<p><strong>Bold text </strong><em><strong>with itallic</strong></em><strong> in it</strong></p>\n<p><strong>Overlapping bold </strong><em><strong>over</strong></em><em> itallic text</em></p>\n<ol>\n <li>Odered list</li>\n <li>Ord<strong>ered </strong><strong><sub>li</sub></strong><a href=\"http://www.example.com\"><em><strong><sub>s</sub></strong></em><em>t with s</em>tyles and li</a>nk\n <ol>\n <li>Nested ordered list</li>\n <li>Nested ordered list\n <ol>\n <li>More nested ordered list</li>\n <li><br></li>\n </ol>\n </li>\n </ol>\n </li>\n</ol>\n<h1><br></h1>\n<figure data-asset-id=\"8c35b61a-8fcb-4089-a576-5a5e7a158bf2\" data-image-id=\"8c35b61a-8fcb-4089-a576-5a5e7a158bf2\"><img src=\"https://example.com/image.png\" data-asset-id=\"8c35b61a-8fcb-4089-a576-5a5e7a158bf2\" data-image-id=\"8c35b61a-8fcb-4089-a576-5a5e7a158bf2\" alt=\"\"></figure>\n<h1>Heading</h1>\n<h4>Heading little</h4>\n<table><tbody>\n <tr><td>1</td><td>2 - w<strong>ith bold te</strong>xt</td><td>3 - w<a href=\"http://www.example.com\">ith link ins</a>ide</td></tr>\n <tr><td>4 <em>- w</em><a data-item-id=\"6538fde0-e6e5-425c-8642-278e637b2dc1\" href=\"\"><em>ith lin</em>k to cont</a>ent</td><td><p>5 - with image in <em>table</em></p>\n<figure data-asset-id=\"8c35b61a-8fcb-4089-a576-5a5e7a158bf2\" data-image-id=\"8c35b61a-8fcb-4089-a576-5a5e7a158bf2\"><img src=\"https://example.com/image.png\" data-asset-id=\"8c35b61a-8fcb-4089-a576-5a5e7a158bf2\" data-image-id=\"8c35b61a-8fcb-4089-a576-5a5e7a158bf2\" alt=\"\"></figure>\n<p><em>and style over the i</em>mage</p>\n</td><td><p>6 - with list&nbsp;</p>\n<ul>\n <li>List in table</li>\n <li>Another list item in table\n <ul>\n <li>Nested in table\n <ul>\n <li>More nested in table&nbsp;\n <ol>\n <li>Ordered inside unorederd</li>\n <li>More unordered</li>\n </ol>\n </li>\n </ul>\n </li>\n </ul>\n <ol>\n <li>Returning byck</li>\n </ol>\n </li>\n</ul>\n<ol>\n <li><br></li>\n</ol>\n</td></tr>\n <tr><td>7</td><td>8</td><td>9</td></tr>\n</tbody></table>"

Expected behavior

Transform into portable text without crash

Test environment

  • node 20.0
  • Version 0.0.4

Define practices for enriching component blocks

Motivation

Currently, it is problematic to inject external data for blocks/marks. The only viable solution right now is using closure.

It might be interesting to have a way of extending the context of the blocks - esp components.

Proposed solution

At least document the approaches and state benefits and possible issues.

Ideally, add some extension point (do not implement is right into the parser) to include the data.

It might work something like:

const parsedTree = nodeParse(elements.body_copy.value);
const portableTextValue = transformToPortableText(parsedTree)
.map(extendLinkedItems<X>(async (IportableItem) => X));

TO define

  • Generic extension type
  • Think about sync vs. async calls
  • Default enrichment format

Additional context

Discussion about astro showcase extending the object:
https://discord.com/channels/821885171984891914/1131384958611632169

Implementation showcase of current workaround
https://github.com/Simply007/beatiful-team-activity-astro/blob/portable-component-data-context/src/pages/blog/%5Bslug%5D.astro#L64

Transform tree to portable text in a single pass

Current portable text transformation logic is implemented as a composed function, working on a previously flattened structure of portable text blocks. To improve performance and readability, this should be refactored to a single pass e.g. in the following manner:

const transform = (elements) => elements.flatMap(el => {
  switch(el._type) {
    case 'table':
      return transformTable(el, emptyState);
    case 'p':
      return transformBlock(el, emptyState);
    default:
      throw new Error('Invalid top-level element');
  }
})
const transformTable = (el, state) => {
  const rows = el.children.reduce(transformRow, state);
  const table = createTable(..., rows);
  return { elements: [table], state: state };
};
const transformLink = (el, state) => {
  const mark = createMark(...);
  const markDef = createLink(...);
  return { elements: [], state: { ...state, marks: [...state.marks, mark], markDefs: [...state.markDefs, markdDef]}};
};
const transformSpan = (el, state) => {
  const span = createSpan(..., state.marks);
  return { elements: [span], state: { ...state, marks: [] } };
};
const transformBlock = (el, state) => {
  const { elements: spans, state: newState } = el.children.reduce(transform, state);
  const block = createBlock(..., spans, newState.markdDefs);
  return { elements: [block], state: { ...newState, spans: [], markDefs: [] }};
};

Provide options to manipulate the Portable Text output

Motivation

Similarly to JSON transformation, tools to manipulate the portable text output can further extend its capabilities. For example wrapping an existing block into another block, converting components/linked items to a typed custom block etc.

Proposed solution

Add CRUD operations for manipulating portable text JSON, possibly using the existing internal methods for creating blocks.

Extend example with more descriptive TS type

Motivation

If is possible to use Partial for resolution logic and specify what properties the should have provide.

Proposed solution

Add this to the readme i.e. for image component and table

const getPortableTextComponents = (element: Elements.RichTextElement): Partial<TPortableTextReactComponents<'image' | 'component' | 'table'>> => ({

Additional context

Usage could be seen in Kontent.ai's storybook RT resolution.

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.