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:
@@ -47,8 +47,22 @@ const STARTER_TEMPLATES = {
|
||||
js: `<script>\n let count = 0;\n</script>\n\n<h1>Hello Svelte</h1>\n<button on:click={() => count++}>Count: {count}</button>\n\n<style>\n h1 { color: #ff3e00; }\n button { padding: 8px 16px; cursor: pointer; }\n</style>`,
|
||||
css: '',
|
||||
},
|
||||
'markdown': {
|
||||
js: `# Hello Markdown\n\nThis is a **Markdown** fiddle. Write your content here and see it rendered in the preview.\n\n## Features\n\n- Headers, **bold**, *italic*\n- Lists (ordered and unordered)\n- Code blocks with syntax highlighting\n- Links, images, and more\n\n### Code Example\n\n\`\`\`javascript\nconst greeting = "Hello, World!";\nconsole.log(greeting);\n\`\`\`\n\n> Blockquotes work too!\n\n| Column 1 | Column 2 |\n|----------|----------|\n| Cell A | Cell B |`,
|
||||
css: `body {\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;\n max-width: 720px;\n margin: 0 auto;\n padding: 24px;\n line-height: 1.6;\n color: #1a1a1a;\n}\n\nh1, h2, h3 { margin-top: 1.5em; margin-bottom: 0.5em; }\nh1 { border-bottom: 2px solid #eee; padding-bottom: 0.3em; }\n\ncode {\n background: #f4f4f4;\n padding: 2px 6px;\n border-radius: 3px;\n font-size: 0.9em;\n}\n\npre {\n background: #f4f4f4;\n padding: 16px;\n border-radius: 6px;\n overflow-x: auto;\n}\n\npre code { background: none; padding: 0; }\n\nblockquote {\n border-left: 4px solid #ddd;\n margin: 1em 0;\n padding: 0.5em 1em;\n color: #555;\n}\n\ntable {\n border-collapse: collapse;\n width: 100%;\n margin: 1em 0;\n}\n\nth, td {\n border: 1px solid #ddd;\n padding: 8px 12px;\n text-align: left;\n}\n\nth { background: #f4f4f4; font-weight: 600; }`,
|
||||
},
|
||||
'wasm': {
|
||||
html: '<h1>WebAssembly Demo</h1>\n<div id="output"></div>',
|
||||
css: `body {\n font-family: monospace;\n padding: 24px;\n background: #1a1a2e;\n color: #0f0;\n}\n\nh1 { color: #00d4ff; margin-bottom: 16px; }\n#output { white-space: pre; font-size: 14px; line-height: 1.8; }`,
|
||||
js: `// Inline WebAssembly "add" module (no external URL needed)\n// WAT source: (module (func (export "add") (param i32 i32) (result i32) local.get 0 local.get 1 i32.add))\nconst wasmBytes = new Uint8Array([\n 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,\n 0x01, 0x07, 0x01, 0x60, 0x02, 0x7f, 0x7f, 0x01, 0x7f,\n 0x03, 0x02, 0x01, 0x00,\n 0x07, 0x07, 0x01, 0x03, 0x61, 0x64, 0x64, 0x00, 0x00,\n 0x0a, 0x09, 0x01, 0x07, 0x00, 0x20, 0x00, 0x20, 0x01, 0x6a, 0x0b\n]);\n\nconst out = document.getElementById('output');\n\ntry {\n const { instance } = await WebAssembly.instantiate(wasmBytes);\n const add = instance.exports.add;\n\n out.textContent = [\n \`WASM loaded successfully!\`,\n \`\`,\n \`add(2, 3) = \${add(2, 3)}\`,\n \`add(100, 200) = \${add(100, 200)}\`,\n \`add(-5, 10) = \${add(-5, 10)}\`,\n ].join('\\n');\n} catch (e) {\n out.textContent = 'Error: ' + e.message;\n}`,
|
||||
},
|
||||
};
|
||||
|
||||
function getTailwindChecked() {
|
||||
const cb = $('#tailwind-checkbox');
|
||||
return cb ? cb.checked : false;
|
||||
}
|
||||
|
||||
async function run() {
|
||||
const mode = getCurrentMode();
|
||||
const { html, css, js } = getEditorValues();
|
||||
@@ -66,10 +80,16 @@ async function run() {
|
||||
});
|
||||
}
|
||||
|
||||
renderPreview(html, compiledCss, result.js, mode, result.extraCss || '');
|
||||
const options = {
|
||||
tailwind: getTailwindChecked(),
|
||||
isModule: result.isModule || false,
|
||||
renderedHtml: result.renderedHtml || null,
|
||||
};
|
||||
|
||||
renderPreview(html, compiledCss, result.js, mode, result.extraCss || '', options);
|
||||
} catch (e) {
|
||||
clearConsole();
|
||||
renderPreview(html, '', '', mode);
|
||||
renderPreview(html, '', '', mode, '', { tailwind: getTailwindChecked() });
|
||||
window.postMessage({ type: 'console', method: 'error', args: [`Compile error: ${e.message}`] }, '*');
|
||||
}
|
||||
}
|
||||
@@ -94,12 +114,13 @@ async function save() {
|
||||
const js_type = MODE_TO_JS_TYPE[getCurrentMode()] || 'javascript';
|
||||
const listed = $('#listed-checkbox').checked ? 1 : 0;
|
||||
const tags = currentTags.slice();
|
||||
const options = JSON.stringify({ tailwind: getTailwindChecked() });
|
||||
try {
|
||||
if (currentId) {
|
||||
await updateFiddle(currentId, { title, html, css, css_type, js, js_type, listed, tags });
|
||||
await updateFiddle(currentId, { title, html, css, css_type, js, js_type, listed, options, tags });
|
||||
showToast(`Saved! Share: ${location.origin}/f/${currentId}`);
|
||||
} else {
|
||||
const result = await createFiddle({ title, html, css, css_type, js, js_type, listed, tags });
|
||||
const result = await createFiddle({ title, html, css, css_type, js, js_type, listed, options, tags });
|
||||
currentId = result.id;
|
||||
history.pushState(null, '', `/f/${currentId}`);
|
||||
showToast(`Saved! Share: ${location.origin}/f/${currentId}`);
|
||||
@@ -116,8 +137,9 @@ async function fork() {
|
||||
const js_type = MODE_TO_JS_TYPE[getCurrentMode()] || 'javascript';
|
||||
const listed = $('#listed-checkbox').checked ? 1 : 0;
|
||||
const tags = currentTags.slice();
|
||||
const options = JSON.stringify({ tailwind: getTailwindChecked() });
|
||||
try {
|
||||
const result = await createFiddle({ title, html, css, css_type, js, js_type, listed, tags });
|
||||
const result = await createFiddle({ title, html, css, css_type, js, js_type, listed, options, tags });
|
||||
currentId = result.id;
|
||||
$('#title-input').value = title;
|
||||
history.pushState(null, '', `/f/${currentId}`);
|
||||
@@ -148,6 +170,11 @@ async function loadFromUrl() {
|
||||
currentTags = (fiddle.tags || []).map(t => t.name);
|
||||
renderTags();
|
||||
|
||||
// Restore options (tailwind checkbox)
|
||||
const opts = JSON.parse(fiddle.options || '{}');
|
||||
const twCb = $('#tailwind-checkbox');
|
||||
if (twCb) twCb.checked = !!opts.tailwind;
|
||||
|
||||
setEditorValues(fiddle);
|
||||
setTimeout(run, 100);
|
||||
} catch (e) {
|
||||
@@ -204,6 +231,12 @@ async function init() {
|
||||
autoRunCb.checked = getPref('autoRun');
|
||||
autoRunCb.addEventListener('change', (e) => setPref('autoRun', e.target.checked));
|
||||
|
||||
// Tailwind checkbox
|
||||
const twCb = $('#tailwind-checkbox');
|
||||
if (twCb) {
|
||||
twCb.addEventListener('change', () => scheduleRun());
|
||||
}
|
||||
|
||||
// Layout selector
|
||||
const layoutSel = $('#layout-mode');
|
||||
const savedLayout = getPref('layout') || 'default';
|
||||
@@ -259,7 +292,13 @@ async function init() {
|
||||
try {
|
||||
const compiledCss = await compileCss(css, cssType);
|
||||
const result = await compileJs(js, mode);
|
||||
exportHtml({ title, html, css: compiledCss, js: result.js, mode, extraCss: result.extraCss, isModule: result.isModule });
|
||||
exportHtml({
|
||||
title, html, css: compiledCss, js: result.js, mode,
|
||||
extraCss: result.extraCss,
|
||||
tailwind: getTailwindChecked(),
|
||||
isModule: result.isModule || false,
|
||||
renderedHtml: result.renderedHtml || null,
|
||||
});
|
||||
} catch (e) {
|
||||
showToast(`Export failed: ${e.message}`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user