So, earlier, in Make Your Mark(Down) – Part 1 I tried to introduce you to an editable Markdown control with preview, the next thing we decided we wanted was charts/graphs (courtesy of Mermaid) and some reasonable syntax highlighting which we achieved using highlight.js.
So, we added the relevant packages using Yarn (as is the way in our organisation)
yarn add mermaid highlight.js
Then we needed a whole new way for us to handle rendering – this is where the renderer for marked came in handy. Basically, we created a custom renderer for code and changed some of the rules in order to ensure that we ended up with the expected behaviour.
First off, we needed to create a new renderer, and tell it how to output for the Mermaid controls and charts:
private async createRenderer() {
// Create the renderer instance
const renderer = new marked.Renderer();
renderer.code = (code, language) => {
// We use the definitions given to us from Mermaid's control
if (code.match(/^(graph|sequenceDiagram)/)) {
// Create a mermaid div
return '<div class="mermaid">' + code + '</div>';
}
}
// return this renderer as constructed above
return renderer;
}
Now, whenever we render the markdown (using the marked function from part 1), we also pass in this renderer
const renderer = await createRenderer();
const result = await marked(input, {renderer, async: true});
However, this doesn’t seem to do anything about the code formatting – so for that we had to modify the code as follows
private async createRenderer() {
// Create the renderer instance
const renderer = new marked.Renderer();
renderer.code = (code, language) => {
try {
// We use the definitions given to us from Mermaid's control
if (code.match(/^(graph|sequenceDiagram)/)) {
// Create a Mermaid div
return '<div class="mermaid">' + code + '</div>';
}
// Check if HighlightJS has a definition for the language
if (hljs.getLanguage(language)) {
// If it does, render it as defined
return '<pre><code>' + hljs.highlight(code, { language }).value + '</code></pre>';
}
// If not, attempt automatic detection and highlight accordingly
return '<pre><code>' + hljs.highlightAuto(code).value + '</code></pre>';
} catch (e) {
// And if something goes wrong, don't bother highlighting
return '<pre><code>' + code + '</code></pre>';
}
}
// Return the renderer as constructed above
return renderer;
}
And that’s sort of it – the full snippet is as below
import { Renderer, marked } from "marked";
import mermaid from "mermaid";
import hljs from 'highlight.js';
import 'highlight.js/styles/github.css';
class MarkdownRenderer {
private renderer: Renderer;
static create(input: HTMLInputElement, target: HTMLElement) {
return new MarkdownRenderer(input, target);
}
private async createRenderer() {
const renderer = new marked.Renderer();
renderer.code = (code, language) => {
try {
if (code.match(/^(graph|sequenceDiagram)/)) {
return '<div class="mermaid">' + code + '</div>';
}
if (hljs.getLanguage(language)) {
return '<pre><code>' + hljs.highlight(code, { language }).value + '</code></pre>';
}
return '<pre><code>' + hljs.highlightAuto(code).value + '</code></pre>';
} catch (e) {
return '<pre><code>' + code + '</code></pre>';
}
}
return renderer;
}
private constructor(element: HTMLInputElement, private target: HTMLElement) {
this.createRenderer()
.then((renderer) => {
this.renderer = renderer;
}).then(() => {
element.addEventListener('keyup', async (event) => {
await this.renderMarkdown((event.target as HTMLInputElement).value);
await mermaid.run();
});
});
}
private async getMarkdown(markdown: string) {
return await marked(markdown, { renderer: this.renderer, async: true })
}
private async renderMarkdown(markdown: string) {
this.target.innerHTML = await this.getMarkdown(markdown);
}
}
As always, this code is subject to our normal terms and if you want more info, just let me know!

