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
This commit is contained in:
root
2026-02-26 15:15:53 -06:00
parent e41c3e7dc4
commit 77f64d2862
13 changed files with 273 additions and 41 deletions

View File

@@ -1,42 +1,62 @@
import { getFrameworkRuntime } from './js-preprocessors.js';
import { extractBareImports, buildImportMapTag } from './import-map.js';
/**
* Export a fiddle as a standalone HTML file and trigger download.
*/
export function exportHtml({ title, html, css, js, mode, extraCss = '', isModule = false }) {
export function exportHtml({ title, html, css, js, mode, extraCss = '', isModule = false, tailwind = false, renderedHtml = null }) {
const runtime = getFrameworkRuntime(mode);
const allCss = extraCss ? `${css}\n${extraCss}` : css;
const finalHtml = renderedHtml || html;
const finalJs = renderedHtml ? '' : js;
let bodyContent;
if (mode === 'vue' || mode === 'svelte') {
bodyContent = html ? `${html}\n${runtime.bodyHtml}` : runtime.bodyHtml;
bodyContent = finalHtml ? `${finalHtml}\n${runtime.bodyHtml}` : runtime.bodyHtml;
} else if (runtime.bodyHtml) {
bodyContent = `${html}\n${runtime.bodyHtml}`;
bodyContent = `${finalHtml}\n${runtime.bodyHtml}`;
} else {
bodyContent = html;
bodyContent = finalHtml;
}
const effectiveIsModule = isModule || mode === 'svelte';
// Build importmap for module scripts with bare imports
let importMapTag = '';
if (effectiveIsModule && finalJs) {
const bareImports = extractBareImports(finalJs);
if (bareImports.length) {
importMapTag = buildImportMapTag(bareImports);
}
}
let scripts = '';
if (js) {
if (isModule) {
scripts = `<script type="module">\n${js}\n<\/script>`;
if (finalJs) {
if (effectiveIsModule) {
scripts = `<script type="module">\n${finalJs}\n<\/script>`;
} else {
for (const url of runtime.scripts) {
scripts += `<script src="${url}"><\/script>\n`;
}
scripts += `<script>\n${js}\n<\/script>`;
scripts += `<script>\n${finalJs}\n<\/script>`;
}
}
const tailwindScript = tailwind
? `<script src="https://cdn.tailwindcss.com"><\/script>\n`
: '';
const doc = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${escHtml(title)}</title>
<style>
${tailwindScript}<style>
${allCss}
</style>
${importMapTag}
</head>
<body>
${bodyContent}