Files
fiddle/public/js/export.js
root ae8dbafb20 Add Python REPL, instant deploy, Gist import, presentation mode, and CSS visual tools
- Python mode via Pyodide WASM runtime with stdout/stderr console integration
- Publish fiddles to clean /p/:slug URLs as standalone HTML pages
- Import code from GitHub Gist URLs with auto-detection of language/mode
- Presentation mode with slide management, fullscreen viewer, and keyboard nav
- Enable Monaco color decorators for inline CSS color pickers
- Extract reusable generateStandaloneHtml from export module
2026-02-27 15:50:55 -06:00

96 lines
3.0 KiB
JavaScript

import { getFrameworkRuntime } from './js-preprocessors.js';
import { extractBareImports, buildImportMapTag } from './import-map.js';
/**
* Generate standalone HTML string from fiddle data.
*/
export function generateStandaloneHtml({ title, html, css, js, mode, extraCss = '', isModule = false, tailwind = false, renderedHtml = null, previewTheme = 'light', resources = [] }) {
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 = finalHtml ? `${finalHtml}\n${runtime.bodyHtml}` : runtime.bodyHtml;
} else if (runtime.bodyHtml) {
bodyContent = `${finalHtml}\n${runtime.bodyHtml}`;
} else {
bodyContent = finalHtml;
}
const effectiveIsModule = isModule || mode === 'svelte';
let importMapTag = '';
if (effectiveIsModule && finalJs) {
const bareImports = extractBareImports(finalJs);
if (bareImports.length) {
importMapTag = buildImportMapTag(bareImports);
}
}
let scripts = '';
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${finalJs}\n<\/script>`;
}
}
const tailwindScript = tailwind
? `<script>var _tw=console.warn;console.warn=function(){if(typeof arguments[0]==='string'&&arguments[0].indexOf('cdn.tailwindcss.com')!==-1)return;_tw.apply(console,arguments)}<\/script>\n<script src="https://cdn.tailwindcss.com"><\/script>\n<script>console.warn=_tw<\/script>\n`
: '';
const darkCss = previewTheme === 'dark'
? `<style>body { background: #1e1e1e; color: #ccc; }</style>\n`
: '';
let resourceTags = '';
if (resources && resources.length) {
for (const r of resources) {
if (r.type === 'css') resourceTags += `<link rel="stylesheet" href="${r.url}">\n`;
else if (r.type === 'js') resourceTags += `<script src="${r.url}"><\/script>\n`;
}
}
return `<!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>
${darkCss}${resourceTags}${tailwindScript}<style>
${allCss}
</style>
${importMapTag}
</head>
<body>
${bodyContent}
${scripts}
</body>
</html>`;
}
/**
* Export a fiddle as a standalone HTML file and trigger download.
*/
export function exportHtml(opts) {
const doc = generateStandaloneHtml(opts);
const blob = new Blob([doc], { type: 'text/html' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${(opts.title || 'fiddle').replace(/[^a-zA-Z0-9_-]/g, '_')}.html`;
a.click();
URL.revokeObjectURL(url);
}
function escHtml(str) {
return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}