Files
fiddle/public/js/embed.js
root 77f64d2862 Add Tailwind CSS toggle, Markdown/WASM modes, and npm import resolution
- Tailwind CSS: toolbar checkbox injects Play CDN into preview, persisted
  per-fiddle via new options JSON column
- Markdown mode: uses marked.js CDN, renders markdown to HTML preview with
  CSS tab for custom styling
- WASM mode: starter template with inline WebAssembly add function, supports
  top-level await via module detection
- npm imports: auto-detect bare import specifiers in module code and inject
  importmap pointing to esm.sh CDN
- Module auto-detection for html-css-js mode (import/export statements)
- DB migration adds options column, server passes through all API endpoints
- All features work across preview, export, and embed
2026-02-26 15:15:53 -06:00

106 lines
3.0 KiB
JavaScript

import { loadFiddle } from './api.js';
import { getFrameworkRuntime } from './js-preprocessors.js';
import { extractBareImports, buildImportMapTag } from './import-map.js';
const MODE_MAP = {
javascript: 'html-css-js',
typescript: 'typescript',
react: 'react',
'react-ts': 'react-ts',
vue: 'vue',
svelte: 'svelte',
markdown: 'markdown',
wasm: 'wasm',
};
async function init() {
// Extract fiddle ID from URL: /embed/:id
const match = location.pathname.match(/^\/embed\/([a-zA-Z0-9_-]+)$/);
if (!match) {
document.body.innerHTML = '<div class="embed-error">Invalid embed URL</div>';
return;
}
// Theme
const params = new URLSearchParams(location.search);
if (params.get('theme') === 'dark') document.body.classList.add('dark');
try {
const fiddle = await loadFiddle(match[1]);
const mode = MODE_MAP[fiddle.js_type] || 'html-css-js';
const runtime = getFrameworkRuntime(mode);
const opts = JSON.parse(fiddle.options || '{}');
// For embed, we compile client-side using the same preprocessors
const { compileCss } = await import('./preprocessors.js');
const { compileJs } = await import('./js-preprocessors.js');
const compiledCss = await compileCss(fiddle.css, fiddle.css_type || 'css');
const result = await compileJs(fiddle.js, mode);
const allCss = result.extraCss ? `${compiledCss}\n${result.extraCss}` : compiledCss;
const finalHtml = result.renderedHtml || fiddle.html;
const finalJs = result.renderedHtml ? '' : result.js;
let bodyContent;
if (mode === 'vue' || mode === 'svelte') {
bodyContent = finalHtml ? `${finalHtml}\n${runtime.bodyHtml}` : runtime.bodyHtml;
} else if (runtime.bodyHtml) {
bodyContent = `${finalHtml}\n${runtime.bodyHtml}`;
} else {
bodyContent = finalHtml;
}
const isModule = result.isModule || mode === 'svelte';
// Build importmap for module scripts with bare imports
let importMapTag = '';
if (isModule && finalJs) {
const bareImports = extractBareImports(finalJs);
if (bareImports.length) {
importMapTag = buildImportMapTag(bareImports);
}
}
let scripts = '';
if (finalJs) {
if (isModule) {
scripts = `<script type="module">\n${escapeScriptClose(finalJs)}\n<\/script>`;
} else {
for (const url of runtime.scripts) {
scripts += `<script src="${url}"><\/script>\n`;
}
scripts += `<script>\n${escapeScriptClose(finalJs)}\n<\/script>`;
}
}
const tailwindScript = opts.tailwind
? `<script src="https://cdn.tailwindcss.com"><\/script>\n`
: '';
const doc = `<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
${tailwindScript}<style>${allCss}</style>
${importMapTag}
</head>
<body>
${bodyContent}
${scripts}
</body>
</html>`;
document.getElementById('preview-frame').srcdoc = doc;
} catch (e) {
document.body.innerHTML = `<div class="embed-error">Failed to load fiddle: ${e.message}</div>`;
}
}
function escapeScriptClose(code) {
return code.replace(/<\/script/gi, '<\\/script');
}
init();