All files / src/plugins mermaid.ts

100% Statements 13/13
100% Branches 3/3
100% Functions 4/4
100% Lines 12/12

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112      1x     2x                                                                                                                                                                   1x         2x 1x 1x 1x 1x 1x 1x 1x     1x                
import cheerio from 'cheerio';
import { PluginContext, FrontMatter } from './Plugin.js';
 
const DEFAULT_CDN_ADDRESS = 'https://unpkg.com/mermaid@10/dist/mermaid.esm.min.mjs';
 
function genScript(address: string) {
  return `<script type="module">
    window.mermaidPromise = null;
    
    const loadMermaid = () => {
      if (window.mermaidPromise === null) {
        window.mermaidPromise = import('${address || DEFAULT_CDN_ADDRESS}')
          .then(({ default: mermaid }) => {
            mermaid.initialize({
              startOnLoad: false,
            });
            console.log('Mermaid loaded successfully');
            return mermaid;
          })
          .catch((error) => {
            console.error('Mermaid failed to load:', error);
            window.mermaidPromise = false;
            return null;
          });
      }
      return window.mermaidPromise;
    };
 
    const renderMermaidDiagrams = (elements) => {
      if (!elements || elements.length === 0) {
        return Promise.resolve();
      }
 
      return loadMermaid().then(mermaid => {
        if (!mermaid) return;
        return mermaid.run({ nodes: Array.from(elements) })
          .catch(err => console.error('Error rendering mermaid diagrams:', err));
      });
    };
 
    const setupMermaidObserver = () => {
      const observer = new MutationObserver((mutations) => {
        let newMermaidElements = [];
        
        mutations.forEach(mutation => {
          if (mutation.addedNodes.length) {
            mutation.addedNodes.forEach(node => {
              if (node.nodeType === 1 && node.classList && 
                  node.classList.contains('mermaid')) {
                newMermaidElements.push(node);
              }
              
              if (node.nodeType === 1 && node.querySelectorAll) {
                const mermaidInNode = node.querySelectorAll('.mermaid');
                if (mermaidInNode.length) {
                  newMermaidElements = [...newMermaidElements, ...mermaidInNode];
                }
              }
            });
          }
        });
        
        if (newMermaidElements.length > 0) {
          renderMermaidDiagrams(newMermaidElements);
        }
      });
      
      observer.observe(document.body, {
        childList: true,
        subtree: true
      });
      
      return observer;
    };
 
    // Initialize on DOM content loaded
    document.addEventListener('DOMContentLoaded', () => {
      const existingDiagrams = document.querySelectorAll('.mermaid');
      if (existingDiagrams.length > 0) {
        renderMermaidDiagrams(existingDiagrams);
      }
      
      setupMermaidObserver();
    });
 
  </script>`;
}
 
const tagConfig = {
  mermaid: {
    isSpecial: true,
  },
};
const getScripts = (pluginContext: PluginContext) => [genScript(pluginContext.address)];
const postRender = (pluginContext: PluginContext, frontmatter: FrontMatter, content: string) => {
  const $ = cheerio.load(content);
  const mermaidTags = $('mermaid');
  if (mermaidTags.length > 0) {
    mermaidTags.each((index: number, node: cheerio.Element) => {
      const $node = $(node);
      $node.replaceWith(`<pre class="mermaid">${$node.html()}</pre>`);
    });
  }
  return $.html();
};
 
export {
  tagConfig,
  getScripts,
  postRender,
};