let count = 1; function escapeHtml(str) { return String(str).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); } /** * Turns code fields with the :preview suffix into interactive code previews. */ module.exports = function (doc, options) { options = { within: 'body', // the element containing the code fields to convert ...options }; const within = doc.querySelector(options.within); if (!within) { return doc; } within.querySelectorAll('[class*=":preview"]').forEach(code => { const pre = code.closest('pre'); if (!pre) { return; } const adjacentPre = pre.nextElementSibling?.tagName.toLowerCase() === 'pre' ? pre.nextElementSibling : null; const reactCode = adjacentPre?.querySelector('code[class$="react"]'); const sourceGroupId = `code-preview-source-group-${count}`; const isExpanded = code.getAttribute('class').includes(':expanded'); const noCodePen = code.getAttribute('class').includes(':no-codepen'); count++; const htmlButton = ` `; const reactButton = ` `; const codePenButton = ` `; const codePreview = `
${code.textContent}
${escapeHtml(code.textContent)}
${ reactCode ? `
${escapeHtml(reactCode.textContent)}
` : '' }
${reactCode ? ` ${htmlButton} ${reactButton} ` : ''} ${noCodePen ? '' : codePenButton}
`; pre.insertAdjacentHTML('afterend', codePreview); pre.remove(); if (adjacentPre) { adjacentPre.remove(); } }); // Wrap code preview scripts in anonymous functions so they don't run in the global scope doc.querySelectorAll('.code-preview__preview script').forEach(script => { if (script.type === 'module') { // Modules are already scoped script.textContent = script.innerHTML; } else { // Wrap non-modules in an anonymous function so they don't run in the global scope script.textContent = `(() => { ${script.innerHTML} })();`; } }); return doc; };