import { initEditors, switchMode, getEditorValues, setEditorValues, setOnChange, getCurrentMode, getCssType, setCssType, MODE_TABS, MODE_TO_JS_TYPE, JS_TYPE_TO_MODE, } from './editors.js'; import { renderPreview } from './preview.js'; import { initConsole, clearConsole } from './console-panel.js'; import { compileCss } from './preprocessors.js'; import { compileJs } from './js-preprocessors.js'; import { createFiddle, loadFiddle, updateFiddle } from './api.js'; let currentId = null; let debounceTimer = null; const $ = (sel) => document.querySelector(sel); const STARTER_TEMPLATES = { 'html-css-js': { html: '', css: '', js: '' }, 'typescript': { html: '
', css: '', js: `interface Greeting {\n name: string;\n message: string;\n}\n\nconst greet = (g: Greeting): string => \`\${g.message}, \${g.name}!\`;\n\nconst result = greet({ name: "World", message: "Hello" });\ndocument.getElementById("app")!.innerHTML = \`

\${result}

\`;\nconsole.log(result);`, }, 'react': { js: `const App = () => {\n const [count, setCount] = React.useState(0);\n return (\n
\n

Hello React

\n \n
\n );\n};\n\nReactDOM.createRoot(document.getElementById('root')).render();`, css: '', html: '', }, 'react-ts': { js: `const App: React.FC = () => {\n const [count, setCount] = React.useState(0);\n return (\n
\n

Hello React + TypeScript

\n \n
\n );\n};\n\nReactDOM.createRoot(document.getElementById('root')!).render();`, css: '', html: '', }, 'vue': { js: `\n\n`, css: '', }, 'svelte': { js: `\n\n

Hello Svelte

\n\n\n`, css: '', }, }; async function run() { const mode = getCurrentMode(); const { html, css, js } = getEditorValues(); const cssType = getCssType(); try { const compiledCss = await compileCss(css, cssType); const result = await compileJs(js, mode); clearConsole(); // Show warnings from compilation (e.g., Svelte) if (result.warnings && result.warnings.length) { result.warnings.forEach((w) => { window.postMessage({ type: 'console', method: 'warn', args: [w] }, '*'); }); } renderPreview(html, compiledCss, result.js, mode, result.extraCss || ''); } catch (e) { clearConsole(); renderPreview(html, '', '', mode); window.postMessage({ type: 'console', method: 'error', args: [`Compile error: ${e.message}`] }, '*'); } } function scheduleRun() { clearTimeout(debounceTimer); debounceTimer = setTimeout(run, 500); } function showToast(msg) { const toast = $('#share-toast'); toast.textContent = msg; toast.classList.remove('hidden'); setTimeout(() => toast.classList.add('hidden'), 3000); } async function save() { const { html, css, js } = getEditorValues(); const title = $('#title-input').value || 'Untitled'; const css_type = getCssType(); const js_type = MODE_TO_JS_TYPE[getCurrentMode()] || 'javascript'; try { if (currentId) { await updateFiddle(currentId, { title, html, css, css_type, js, js_type }); showToast(`Saved! Share: ${location.origin}/f/${currentId}`); } else { const result = await createFiddle({ title, html, css, css_type, js, js_type }); currentId = result.id; history.pushState(null, '', `/f/${currentId}`); showToast(`Saved! Share: ${location.origin}/f/${currentId}`); } } catch (e) { showToast(`Save failed: ${e.message}`); } } async function fork() { const { html, css, js } = getEditorValues(); const title = ($('#title-input').value || 'Untitled') + ' (fork)'; const css_type = getCssType(); const js_type = MODE_TO_JS_TYPE[getCurrentMode()] || 'javascript'; try { const result = await createFiddle({ title, html, css, css_type, js, js_type }); currentId = result.id; $('#title-input').value = title; history.pushState(null, '', `/f/${currentId}`); showToast(`Forked! New URL: ${location.origin}/f/${currentId}`); } catch (e) { showToast(`Fork failed: ${e.message}`); } } async function loadFromUrl() { const match = location.pathname.match(/^\/f\/([a-zA-Z0-9_-]+)$/); if (!match) return; try { const fiddle = await loadFiddle(match[1]); currentId = fiddle.id; $('#title-input').value = fiddle.title; // Restore mode from js_type const mode = JS_TYPE_TO_MODE[fiddle.js_type] || 'html-css-js'; $('#framework-mode').value = mode; switchMode(mode); // Restore CSS type setCssType(fiddle.css_type || 'css'); setEditorValues(fiddle); setTimeout(run, 100); } catch (e) { showToast(`Failed to load fiddle: ${e.message}`); } } function handleModeChange(newMode) { const oldMode = getCurrentMode(); if (newMode === oldMode) return; switchMode(newMode); // Insert starter template if editors are empty const { html, css, js } = getEditorValues(); if (!html && !css && !js) { const template = STARTER_TEMPLATES[newMode]; if (template) { setEditorValues(template); } } scheduleRun(); } function init() { initEditors('html-css-js'); setOnChange(scheduleRun); initConsole(); // Mode selector $('#framework-mode').addEventListener('change', (e) => { handleModeChange(e.target.value); }); // Toolbar buttons $('#btn-run').addEventListener('click', run); $('#btn-save').addEventListener('click', save); $('#btn-fork').addEventListener('click', fork); // Keyboard shortcuts document.addEventListener('keydown', (e) => { if ((e.ctrlKey || e.metaKey) && e.key === 's') { e.preventDefault(); save(); } if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { e.preventDefault(); run(); } }); // Load fiddle from URL if present loadFromUrl(); // Handle browser back/forward window.addEventListener('popstate', () => { currentId = null; loadFromUrl(); }); } init();