Working with Contentful has been buttery smooth, right up until rendering code snippets in rich text fields. The official blog has provided generous guidance on how to render rich text. I used their code as a base and combined with content types, I managed to figure out a way to render code snippets neatly.
1. On the admin dashboard, create a new content type called code block with three fields
Rich text field: for the code block itself
Description: short text field to provide a entry name to track on the admin dashboard
Language: a short text field to indicate which language linting should be applied
2. Add rendering options to codebase
In the component where rich text and code snippets will be rendered, add the below rendering options.
const renderOptions = {
renderNode: {
[INLINES.EMBEDDED_ENTRY]: (node, children) => {
// target the contentType of the EMBEDDED_ENTRY to display as you need
if (node.data.target.sys.contentType.sys.id === "blogPost") {
return (
<a href={`/blog/${node.data.target.fields.slug}`}>
{node.data.target.fields.title}
</a>
);
}
},
[BLOCKS.EMBEDDED_ENTRY]: (node, children) => {
// target the contentType of the EMBEDDED_ENTRY to display as you need
if (node.data.target.sys.contentType.sys.id === "codeBlock") {
return (
<SyntaxHighlighter language={ node.data.target.fields.language } style={docco}>{ node.data.target.fields.code }</SyntaxHighlighter>
);
}
if (node.data.target.sys.contentType.sys.id === "videoEmbed") {
return (
<iframe
src={node.data.target.fields.embedUrl}
height="100%"
width="100%"
frameBorder="0"
scrolling="no"
title={node.data.target.fields.title}
allowFullScreen={true}
/>
);
}
},
[BLOCKS.EMBEDDED_ASSET]: (node, children) => {
return (
<img
src={`https://${node.data.target.fields.file.url}`}
height={node.data.target.fields.file.details.image.height}
width={node.data.target.fields.file.details.image.width}
alt={node.data.target.fields.description}
/>
);
},
[BLOCKS.PARAGRAPH]: (node, children) => {
if (node.content.length === 1 && node.content[0].marks.find((x) => x.type === "code")) {
return <pre>{children}</pre>
}
return <p>{ children }</p>
}
},
renderMark: {
[MARKS.CODE]: (text) => {
return (
<SyntaxHighlighter language="javascript" style={docco}>{ text}</SyntaxHighlighter>
)
}
}
};
In the location where the text will actually be rendered, add the below
{ documentToReactComponents(mainText,renderOptions)}
3. Create a new code block and insert into the rich text
Use either inline entry or entry option.
I was inspired by Christian Coda's approach to rendering code snippets with using content type. Christian Coda's `renderMarks` approach means all inline code snippets will be rendered as javascript linting.
However, by formatting code blocks as an embeddable entry (`BLOCKS.EMBEDDED_ENTRY`), I can display code block according to language type. There are pros and cons to both approaches. Christian's approach means all linting will be applied as though it was javascript. The codeblocks approach is much more suitable for me in terms of ease of styling. The onliy perceivable downside is code blocks will be an additional piece of content to be managed on the admin dashboard -- not an impassable inconvenience from my point of view.
One downside which I have yet to resolve is text wrapping so watch this space!