Auto generate anchor links in Contentful & documentToHtmlString
The `documentToHtmlString` ingests a JSON representation of the content and allows you to traverse that tree and either use the default conversion, or intercept a node and provide your own HTML formatter.
So let's quickly see a default usage in action:
First install the required packages:
npm install slugify @contentful/rich-text-types @contentful/rich-text-html-renderer
Then let's have a look at the code:
import { BLOCKS } from'@contentful/rich-text-types';
import { documentToHtmlString } from'@contentful/rich-text-html-renderer';
export const useDocumentToHtmlString = (document) => {
const options = {}
return documentToHtmlString({ ...document }, options);
}
This isn't really doing anything yet. The function takes in a `document` which is the node representation of a document structure. The structure is made up of `BLOCK`s (which we'll see in a moment). A document could look something like this:
{
nodeType: 'document',
data: {},
content: [
{ nodeType: 'paragraph', data: {}, content: [Array] },
{ nodeType: 'paragraph', data: {}, content: [Array] },
// ...and so on
]
}
On the `content` property, you can find what is meant by a BLOCK: it has a nodeType, data and more content following a similar pattern.
The standard behaviour of the `documentToHtmlString` would be to simply convert the known nodeTypes to it's HTML counterpart. In the example, the nodeType `paragraph` would be converted to it's content being wrapped by `<p> ... </p>` tags.
Providing `options` to the documentToHtmlString give you an entry point to define custom return patterns. Typically this is done to wrap embedded assets such as images in custom markup. We can also apply this for headings.
Let's first define the renderer for a heading type:
const renderAsAnchor = (node, hLevel) => {
const value = documentToHtmlString(node);
const slug = slugify(value).toLowerCase();
return `<a name="${slug}"></a><h${hLevel}>${value}</h${hLevel}>`
}
The function takes in a node and the type of heading (1 through 6 for instance). It renders the node value by reusing the documentToHtmlString and based on the value, we will render a `slug`, used to write in the anchor name, to make it predictable. Last step is returning the anchor, with the value of the node wrapped in an `<h*>` tag, depending on the provided level.
We can add the function to the desired blocks using the preset block types from Contentful:
export const useDocumentToHtmlString = (document) => {
const renderAsAnchor = (node, level) => {
const value = documentToHtmlString(node);
const slug = slugify(value).toLowerCase();
return `<a name="${slug}"></a><h${level}>${value}</h${level}>`
}
const options = {
renderNode: {
[BLOCKS.HEADING_1]: (node: any) => renderAsAnchor(node, 1),
[BLOCKS.HEADING_2]: (node: any) => renderAsAnchor(node, 2),
[BLOCKS.HEADING_3]: (node: any) => renderAsAnchor(node, 3),
[BLOCKS.HEADING_4]: (node: any) => renderAsAnchor(node, 4),
[BLOCKS.HEADING_5]: (node: any) => renderAsAnchor(node, 5),
}
}
return documentToHtmlString({ ...document }, options);
}
The renderNode object allows you to define the custom renders you want to use for specific types of blocks.
Easy as that!