UNPKG

@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
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 || {}), }); };