@k9n/scully-plugin-mermaid
Version:
this plugin for Scully provides a postRenderer to generate a SVGs from mermaid source code sections
105 lines (92 loc) • 3.23 kB
text/typescript
import {
getPluginConfig,
HandledRoute,
log,
logWarn,
yellow,
} from '@scullyio/scully';
import { JSDOM } from 'jsdom';
import MermaidAPI from 'mermaid/mermaidAPI';
import { renderMermaid } from 'mermaid-render';
import { MermaidPluginName } from './constants';
import { ElementWrapper, MermaidPluginConfig } from './interfaces';
const defaultSelector = '.language-mermaid';
const defaultWrapper: ElementWrapper = {
tagName: 'div',
classNames: ['mermaid-svg'],
};
export const mermaidPlugin = async (html: string, routeData: HandledRoute) => {
const pluginConfig = getPluginConfig<MermaidPluginConfig>(MermaidPluginName);
const selector = pluginConfig.selector || defaultSelector;
let useWrapper = true;
let wrapper: ElementWrapper = defaultWrapper;
if (pluginConfig.wrapper === false) {
useWrapper = false;
}
if (
typeof pluginConfig.wrapper === 'object' &&
pluginConfig.wrapper.tagName
) {
wrapper = pluginConfig.wrapper;
}
let mermaidMatches = 0;
const route = routeData.route;
try {
const dom = new JSDOM(html);
const { window } = dom;
const nodeList = window.document.querySelectorAll(selector);
const elements = [].slice.call(nodeList);
// count the mermaid matches for logging
mermaidMatches = elements.length;
for (let i = 0; i < elements.length; i++) {
const svgCode = await getSvg(
elements[i].textContent,
pluginConfig.config,
);
if (svgCode) {
let mermaidTargetEl: Element;
if (!useWrapper) {
const templateEl = window.document.createElement('template');
templateEl.innerHTML = svgCode.trim();
mermaidTargetEl = templateEl.content.firstElementChild;
} else {
mermaidTargetEl = window.document.createElement(
wrapper.tagName.toLowerCase(),
);
if (wrapper.classNames.length) {
mermaidTargetEl.className = wrapper.classNames.join(' ');
}
mermaidTargetEl.innerHTML = svgCode;
}
const tagName = nodeList.item(i).tagName;
if (tagName === 'PRE') {
nodeList.item(i).replaceWith(mermaidTargetEl); // replace the whole `pre`-Element
} else if (tagName === 'CODE') {
nodeList.item(i).parentElement.tagName === 'PRE'
? nodeList.item(i).parentElement.replaceWith(mermaidTargetEl) // replace the whole parent `pre`-Element
: nodeList.item(i).replaceWith(mermaidTargetEl); // replace the `code`-Element
} else {
logWarn(
`Selector '${yellow(
selector,
)}' matches neither a 'pre' nor a 'code' element. Can't render / insert graphic.`,
);
}
}
}
log(`Rendered ${mermaidMatches} Mermaid SVGs`);
return Promise.resolve(dom.serialize());
} catch (e) {
logWarn(`error in mermaidPlugin, didn't handle route '${yellow(route)}'`);
}
// in case of failure return unchanged HTML to keep flow going
return Promise.resolve(html);
};
export const getSvg = (
data: string,
config?: MermaidAPI.Config,
): Promise<string> => {
return renderMermaid(data, {
initParams: Promise.resolve(config || {}),
});
};