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
This commit is contained in:
@@ -13,12 +13,14 @@ import {
|
||||
createFiddle, loadFiddle, updateFiddle, listTags,
|
||||
listVersions, getVersion, revertVersion,
|
||||
listCollections, createCollection, addToCollection,
|
||||
publishFiddle, unpublishFiddle, getPublishStatus,
|
||||
importGist,
|
||||
} from './api.js';
|
||||
import { getPref, setPref } from './preferences.js';
|
||||
import { initEmmet } from './emmet.js';
|
||||
import { initKeybindings } from './keybindings.js';
|
||||
import { initResizer, clearInlineSizes } from './resizer.js';
|
||||
import { exportHtml } from './export.js';
|
||||
import { exportHtml, generateStandaloneHtml } from './export.js';
|
||||
import { showQrModal } from './qr.js';
|
||||
import { initDevtools } from './devtools.js';
|
||||
import { initNetwork, clearNetwork } from './network-panel.js';
|
||||
@@ -31,6 +33,7 @@ import { registerCustomThemes, THEMES } from './editor-themes.js';
|
||||
import { GALLERY_TEMPLATES } from './templates.js';
|
||||
import { configureTypeDefaults, registerSnippetProviders } from './autocomplete.js';
|
||||
import { initNpmSearch } from './npm-search.js';
|
||||
import { openSlideManager, addCurrentSlide, startPresentation, stopPresentation } from './presentation.js';
|
||||
|
||||
let currentId = null;
|
||||
let debounceTimer = null;
|
||||
@@ -68,6 +71,10 @@ const STARTER_TEMPLATES = {
|
||||
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; }`,
|
||||
},
|
||||
'python': {
|
||||
js: `print("Hello from Python!")\n\nimport math\nprint(f"Pi is {math.pi:.4f}")\n\n# Use the sys module\nimport sys\nprint(f"Python {sys.version}")\n\n# List comprehension\nsquares = [x**2 for x in range(10)]\nprint(f"Squares: {squares}")`,
|
||||
html: '',
|
||||
},
|
||||
'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; }`,
|
||||
@@ -494,6 +501,63 @@ async function init() {
|
||||
}
|
||||
});
|
||||
|
||||
// Publish button
|
||||
$('#btn-publish').addEventListener('click', async () => {
|
||||
if (!currentId) {
|
||||
showToast('Save the fiddle first before publishing');
|
||||
return;
|
||||
}
|
||||
const mode = getCurrentMode();
|
||||
const { html, css, js } = getEditorValues();
|
||||
const cssType = getCssType();
|
||||
const title = $('#title-input').value || 'Untitled';
|
||||
try {
|
||||
const compiledCss = await compileCss(css, cssType);
|
||||
const result = await compileJs(js, mode);
|
||||
const standaloneHtml = generateStandaloneHtml({
|
||||
title, html, css: compiledCss, js: result.js, mode,
|
||||
extraCss: result.extraCss,
|
||||
tailwind: getTailwindChecked(),
|
||||
isModule: result.isModule || false,
|
||||
renderedHtml: result.renderedHtml || null,
|
||||
previewTheme: getPref('previewTheme'),
|
||||
resources: currentResources,
|
||||
});
|
||||
const pubResult = await publishFiddle(currentId, standaloneHtml);
|
||||
const fullUrl = `${location.origin}${pubResult.url}`;
|
||||
try { await navigator.clipboard.writeText(fullUrl); } catch (_) {}
|
||||
showToast(`Published! ${fullUrl} (copied)`);
|
||||
} catch (e) {
|
||||
showToast(`Publish failed: ${e.message}`);
|
||||
}
|
||||
});
|
||||
|
||||
// Import Gist button
|
||||
$('#btn-import-gist').addEventListener('click', async () => {
|
||||
const gistUrl = prompt('Paste a GitHub Gist URL:');
|
||||
if (!gistUrl || !gistUrl.trim()) return;
|
||||
try {
|
||||
const data = await importGist(gistUrl.trim());
|
||||
// Switch mode
|
||||
if (data.mode) {
|
||||
$('#framework-mode').value = data.mode;
|
||||
handleModeChange(data.mode);
|
||||
}
|
||||
// Set CSS type
|
||||
if (data.css_type && data.css_type !== 'css') {
|
||||
setCssType(data.css_type);
|
||||
}
|
||||
// Set editor values
|
||||
setEditorValues({ html: data.html || '', css: data.css || '', js: data.js || '' });
|
||||
// Set title
|
||||
if (data.title) $('#title-input').value = data.title;
|
||||
run();
|
||||
showToast('Gist imported successfully');
|
||||
} catch (e) {
|
||||
showToast(`Import failed: ${e.message}`);
|
||||
}
|
||||
});
|
||||
|
||||
$('#btn-qr').addEventListener('click', () => {
|
||||
const url = currentId ? `${location.origin}/f/${currentId}` : location.href;
|
||||
showQrModal(url);
|
||||
@@ -531,6 +595,21 @@ async function init() {
|
||||
openCollectionModal(); // refresh list
|
||||
});
|
||||
|
||||
// Presentation mode
|
||||
const presModal = $('#presentation-modal');
|
||||
$('#btn-presentation').addEventListener('click', () => openSlideManager(currentId));
|
||||
$('#presentation-modal-close').addEventListener('click', () => presModal.classList.add('hidden'));
|
||||
presModal.addEventListener('click', (e) => { if (e.target === presModal) presModal.classList.add('hidden'); });
|
||||
$('#btn-add-slide').addEventListener('click', () => addCurrentSlide(currentId));
|
||||
$('#btn-start-presentation').addEventListener('click', () => startPresentation(currentId));
|
||||
$('#pres-exit').addEventListener('click', () => stopPresentation());
|
||||
$('#pres-prev').addEventListener('click', () => {
|
||||
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowLeft' }));
|
||||
});
|
||||
$('#pres-next').addEventListener('click', () => {
|
||||
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight' }));
|
||||
});
|
||||
|
||||
// npm search in resources modal
|
||||
initNpmSearch((pkg) => {
|
||||
currentResources.push({ type: pkg.type, url: pkg.url });
|
||||
|
||||
Reference in New Issue
Block a user