- 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
377 lines
19 KiB
HTML
377 lines
19 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Fiddle</title>
|
|
<link rel="stylesheet" href="/css/style.css">
|
|
</head>
|
|
<body>
|
|
<header class="toolbar">
|
|
<div class="toolbar-left">
|
|
<a href="/" class="logo" title="Browse fiddles">Fiddle</a>
|
|
<select id="framework-mode">
|
|
<option value="html-css-js">HTML / CSS / JS</option>
|
|
<option value="typescript">TypeScript</option>
|
|
<option value="react">React (JSX)</option>
|
|
<option value="react-ts">React + TS</option>
|
|
<option value="vue">Vue</option>
|
|
<option value="svelte">Svelte</option>
|
|
<option value="markdown">Markdown</option>
|
|
<option value="wasm">WASM</option>
|
|
<option value="python">Python</option>
|
|
</select>
|
|
<input type="text" id="title-input" placeholder="Untitled" spellcheck="false">
|
|
<div class="tags-input-wrap">
|
|
<input type="text" id="tags-input" placeholder="Tags..." list="tags-datalist" spellcheck="false">
|
|
<datalist id="tags-datalist"></datalist>
|
|
<div id="tags-display" class="tags-display"></div>
|
|
</div>
|
|
</div>
|
|
<div class="toolbar-right">
|
|
<div class="toolbar-toggles">
|
|
<label class="toolbar-toggle" title="Enable Tailwind CSS">
|
|
<input type="checkbox" id="tailwind-checkbox">
|
|
<span>TW</span>
|
|
</label>
|
|
<label class="toolbar-toggle" title="Auto-run on change">
|
|
<input type="checkbox" id="auto-run-checkbox" checked>
|
|
<span>Auto</span>
|
|
</label>
|
|
<label class="toolbar-toggle" title="Auto-format on save">
|
|
<input type="checkbox" id="format-save-checkbox">
|
|
<span>Fmt</span>
|
|
</label>
|
|
<label class="toolbar-toggle" title="Show in browse page">
|
|
<input type="checkbox" id="listed-checkbox" checked>
|
|
<span>Listed</span>
|
|
</label>
|
|
</div>
|
|
<div class="toolbar-divider"></div>
|
|
<button id="btn-run" title="Run (Ctrl+Enter)">Run</button>
|
|
<button id="btn-save" title="Save (Ctrl+S)">Save</button>
|
|
<button id="btn-fork" class="btn-secondary" title="Fork">Fork</button>
|
|
<div class="toolbar-divider"></div>
|
|
<button id="btn-templates" class="btn-icon" title="Starter templates">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>
|
|
</button>
|
|
<button id="btn-import-gist" class="btn-icon" title="Import from GitHub Gist">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"/></svg>
|
|
</button>
|
|
<button id="btn-resources" class="btn-icon" title="External resources">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>
|
|
</button>
|
|
<button id="btn-history" class="btn-icon" title="Version history">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
|
|
</button>
|
|
<button id="btn-embed" class="btn-icon" title="Embed code">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>
|
|
</button>
|
|
<button id="btn-publish" class="btn-icon" title="Publish to clean URL">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z"/><path d="M12 15l-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z"/><path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0"/><path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5"/></svg>
|
|
</button>
|
|
<button id="btn-export" class="btn-icon" title="Export HTML">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
|
|
</button>
|
|
<button id="btn-collection" class="btn-icon" title="Collections">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>
|
|
</button>
|
|
<button id="btn-presentation" class="btn-icon" title="Presentation mode">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21l4-4 4 4"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
|
|
</button>
|
|
<button id="btn-qr" class="btn-icon" title="QR code">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="2" width="8" height="8" rx="1"/><rect x="14" y="2" width="8" height="8" rx="1"/><rect x="2" y="14" width="8" height="8" rx="1"/><rect x="14" y="14" width="4" height="4"/><line x1="22" y1="14" x2="22" y2="22"/><line x1="14" y1="22" x2="22" y2="22"/></svg>
|
|
</button>
|
|
<div class="toolbar-divider"></div>
|
|
<div class="settings-popover-wrap">
|
|
<button id="btn-settings" class="btn-icon" title="Settings">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
|
|
</button>
|
|
<div id="settings-popover" class="settings-popover hidden">
|
|
<div class="settings-group">
|
|
<label class="settings-label">Layout</label>
|
|
<select id="layout-mode">
|
|
<option value="default">Default</option>
|
|
<option value="top-bottom">Top / Bottom</option>
|
|
<option value="editor-only">Editor Only</option>
|
|
<option value="preview-only">Preview Only</option>
|
|
</select>
|
|
</div>
|
|
<div class="settings-group">
|
|
<label class="settings-label">Theme</label>
|
|
<select id="editor-theme">
|
|
<option value="vs-dark">VS Dark</option>
|
|
<option value="vs">VS Light</option>
|
|
<option value="hc-black">High Contrast</option>
|
|
<option value="monokai">Monokai</option>
|
|
<option value="dracula">Dracula</option>
|
|
<option value="github-dark">GitHub Dark</option>
|
|
</select>
|
|
</div>
|
|
<div class="settings-group">
|
|
<label class="settings-label">Font</label>
|
|
<select id="editor-font" title="Editor font">
|
|
<option value="default">Default Font</option>
|
|
<option value="Fira Code">Fira Code</option>
|
|
<option value="JetBrains Mono">JetBrains Mono</option>
|
|
<option value="Source Code Pro">Source Code Pro</option>
|
|
<option value="IBM Plex Mono">IBM Plex Mono</option>
|
|
<option value="Cascadia Code">Cascadia Code</option>
|
|
</select>
|
|
</div>
|
|
<div class="settings-group">
|
|
<label class="settings-label">Keybindings</label>
|
|
<select id="keybinding-mode">
|
|
<option value="default">Default</option>
|
|
<option value="vim">Vim</option>
|
|
<option value="emacs">Emacs</option>
|
|
</select>
|
|
</div>
|
|
<div class="settings-group">
|
|
<label class="settings-label">Preview</label>
|
|
<select id="preview-theme" title="Preview background">
|
|
<option value="light">Light</option>
|
|
<option value="dark">Dark</option>
|
|
</select>
|
|
</div>
|
|
<div class="settings-group">
|
|
<label class="settings-label">Shortcuts</label>
|
|
<button id="btn-shortcuts" class="btn-small">View all</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<main class="grid">
|
|
<div class="panel panel-editor">
|
|
<div class="tab-bar" id="tab-bar"></div>
|
|
<div id="editor-area" class="editor-area"></div>
|
|
<div id="vim-status-bar" class="vim-status-bar"></div>
|
|
</div>
|
|
<div class="divider divider-col" id="divider-col"></div>
|
|
<div class="panel panel-preview">
|
|
<div class="panel-label">
|
|
<span>Preview</span>
|
|
<span class="device-toggles">
|
|
<button class="device-btn active" data-device="desktop" title="Desktop (100%)">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
|
|
</button>
|
|
<button class="device-btn" data-device="tablet" title="Tablet (768px)">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="4" y="2" width="16" height="20" rx="2"/><line x1="12" y1="18" x2="12" y2="18" stroke-linecap="round"/></svg>
|
|
</button>
|
|
<button class="device-btn" data-device="mobile" title="Mobile (375px)">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="5" y="2" width="14" height="20" rx="2"/><line x1="12" y1="18" x2="12" y2="18" stroke-linecap="round"/></svg>
|
|
</button>
|
|
</span>
|
|
</div>
|
|
<div class="preview-viewport" id="preview-viewport">
|
|
<iframe id="preview-frame" sandbox="allow-scripts allow-same-origin"></iframe>
|
|
</div>
|
|
</div>
|
|
<div class="divider divider-row" id="divider-row"></div>
|
|
<div class="panel panel-console">
|
|
<div class="devtools-tabs">
|
|
<button class="devtools-tab active" data-tab="console">Console</button>
|
|
<button class="devtools-tab" data-tab="network">Network</button>
|
|
<button class="devtools-tab" data-tab="elements">Elements</button>
|
|
<button class="devtools-tab" data-tab="performance">Performance</button>
|
|
<span class="devtools-spacer"></span>
|
|
<button id="btn-clear-devtools" class="btn-small">Clear</button>
|
|
</div>
|
|
<div class="devtools-panels">
|
|
<div class="devtools-panel active" id="panel-console">
|
|
<div id="console-output"></div>
|
|
</div>
|
|
<div class="devtools-panel" id="panel-network">
|
|
<div id="network-output"></div>
|
|
</div>
|
|
<div class="devtools-panel" id="panel-elements">
|
|
<div id="elements-output"></div>
|
|
</div>
|
|
<div class="devtools-panel" id="panel-performance">
|
|
<div id="performance-output"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
|
|
<div id="resources-modal" class="modal-overlay hidden">
|
|
<div class="modal-content" style="min-width:360px">
|
|
<div class="modal-header">
|
|
<span>External Resources</span>
|
|
<button id="resources-modal-close" class="btn-small">×</button>
|
|
</div>
|
|
<div class="resource-inputs">
|
|
<div class="resource-row">
|
|
<input type="text" id="resource-css-input" placeholder="CSS URL (e.g. https://fonts.googleapis.com/...)">
|
|
<button id="btn-add-css" class="btn-small">+ CSS</button>
|
|
</div>
|
|
<div class="resource-row">
|
|
<input type="text" id="resource-js-input" placeholder="JS URL (e.g. https://cdn.jsdelivr.net/...)">
|
|
<button id="btn-add-js" class="btn-small">+ JS</button>
|
|
</div>
|
|
<div class="resource-row npm-search-row">
|
|
<input type="text" id="npm-search-input" placeholder="Search npm packages..." autocomplete="off">
|
|
<div id="npm-search-results" class="npm-search-results hidden"></div>
|
|
</div>
|
|
</div>
|
|
<div id="resource-list" class="resource-list"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="shortcuts-modal" class="modal-overlay hidden">
|
|
<div class="modal-content" style="min-width:380px">
|
|
<div class="modal-header">
|
|
<span>Keyboard Shortcuts</span>
|
|
<button id="shortcuts-modal-close" class="btn-small">×</button>
|
|
</div>
|
|
<table class="shortcuts-table">
|
|
<tbody>
|
|
<tr><td><kbd>Ctrl/Cmd</kbd> + <kbd>Enter</kbd></td><td>Run code</td></tr>
|
|
<tr><td><kbd>Ctrl/Cmd</kbd> + <kbd>S</kbd></td><td>Save fiddle</td></tr>
|
|
<tr><td><kbd>?</kbd></td><td>Show shortcuts</td></tr>
|
|
<tr><td><kbd>Shift</kbd> + <kbd>Alt</kbd> + <kbd>F</kbd></td><td>Format code (Prettier)</td></tr>
|
|
<tr><td><kbd>Ctrl/Cmd</kbd> + <kbd>D</kbd></td><td>Toggle diff view</td></tr>
|
|
<tr class="shortcuts-divider"><td colspan="2">Keybinding Modes</td></tr>
|
|
<tr><td><kbd>Vim</kbd></td><td>Full vim keybindings (select in toolbar)</td></tr>
|
|
<tr><td><kbd>Emacs</kbd></td><td>Full emacs keybindings (select in toolbar)</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="qr-modal" class="modal-overlay hidden">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<span>Share via QR Code</span>
|
|
<button id="qr-modal-close" class="btn-small">×</button>
|
|
</div>
|
|
<div id="qr-canvas"></div>
|
|
<div id="qr-url" class="qr-url"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="templates-modal" class="modal-overlay hidden">
|
|
<div class="modal-content templates-modal-content">
|
|
<div class="modal-header">
|
|
<span>Starter Templates</span>
|
|
<button id="templates-modal-close" class="btn-small">×</button>
|
|
</div>
|
|
<div class="templates-grid" id="templates-grid"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="history-modal" class="modal-overlay hidden">
|
|
<div class="modal-content history-modal-content">
|
|
<div class="modal-header">
|
|
<span>Version History</span>
|
|
<button id="history-modal-close" class="btn-small">×</button>
|
|
</div>
|
|
<div id="history-list" class="history-list"></div>
|
|
<div id="history-preview" class="history-preview hidden">
|
|
<div class="history-preview-header">
|
|
<span id="history-preview-label"></span>
|
|
<button id="history-restore-btn">Restore This Version</button>
|
|
</div>
|
|
<div id="history-diff" class="history-diff"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="embed-modal" class="modal-overlay hidden">
|
|
<div class="modal-content embed-modal-content">
|
|
<div class="modal-header">
|
|
<span>Embed Code</span>
|
|
<button id="embed-modal-close" class="btn-small">×</button>
|
|
</div>
|
|
<div class="embed-options">
|
|
<label>Theme <select id="embed-theme"><option value="light">Light</option><option value="dark">Dark</option></select></label>
|
|
<label>Show tabs <select id="embed-tabs"><option value="1">Yes</option><option value="0">No</option></select></label>
|
|
<label>Auto-run <select id="embed-autorun"><option value="1">Yes</option><option value="0">No</option></select></label>
|
|
<label>Width <input type="text" id="embed-width" value="100%" style="width:70px"></label>
|
|
<label>Height <input type="text" id="embed-height" value="400" style="width:70px"></label>
|
|
</div>
|
|
<div class="embed-code-wrap">
|
|
<pre id="embed-code" class="embed-code"></pre>
|
|
<button id="embed-copy" class="btn-small">Copy</button>
|
|
</div>
|
|
<div class="embed-live-preview">
|
|
<iframe id="embed-preview-frame" style="border:1px solid var(--border);border-radius:4px;width:100%;height:200px"></iframe>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="collection-modal" class="modal-overlay hidden">
|
|
<div class="modal-content" style="min-width:340px">
|
|
<div class="modal-header">
|
|
<span>Add to Collection</span>
|
|
<button id="collection-modal-close" class="btn-small">×</button>
|
|
</div>
|
|
<div class="collection-new-row">
|
|
<input type="text" id="new-collection-name" placeholder="New collection name...">
|
|
<button id="btn-create-collection" class="btn-small">Create</button>
|
|
</div>
|
|
<div id="collection-list" class="collection-list"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="presentation-modal" class="modal-overlay hidden">
|
|
<div class="modal-content" style="min-width:380px">
|
|
<div class="modal-header">
|
|
<span>Presentation Mode</span>
|
|
<button id="presentation-modal-close" class="btn-small">×</button>
|
|
</div>
|
|
<div style="padding:8px 16px;display:flex;gap:8px;align-items:center">
|
|
<button id="btn-add-slide" class="btn-small">+ Add Current State as Slide</button>
|
|
<span id="slide-count" style="color:var(--text-dim);font-size:12px"></span>
|
|
</div>
|
|
<div id="slide-list" class="slide-list"></div>
|
|
<div style="padding:8px 16px">
|
|
<button id="btn-start-presentation" style="width:100%">Start Presentation</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="presentation-overlay" class="presentation-overlay hidden">
|
|
<div class="pres-header">
|
|
<span id="pres-counter" class="pres-counter"></span>
|
|
<button id="pres-exit" class="pres-exit-btn">Exit (Esc)</button>
|
|
</div>
|
|
<div class="pres-content">
|
|
<iframe id="pres-preview-frame" class="pres-iframe" sandbox="allow-scripts allow-same-origin"></iframe>
|
|
</div>
|
|
<div class="pres-footer">
|
|
<button id="pres-prev" class="pres-nav-btn">← Prev</button>
|
|
<div id="pres-notes" class="pres-notes"></div>
|
|
<button id="pres-next" class="pres-nav-btn">Next →</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="share-toast" class="toast hidden"></div>
|
|
|
|
<script>
|
|
// Monaco AMD loader
|
|
const MONACO_CDN = 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.52.2';
|
|
const script = document.createElement('script');
|
|
script.src = `${MONACO_CDN}/min/vs/loader.min.js`;
|
|
script.onload = () => {
|
|
require.config({ paths: { vs: `${MONACO_CDN}/min/vs` } });
|
|
// Cross-origin worker proxy
|
|
window.MonacoEnvironment = {
|
|
getWorkerUrl: function (_workerId, label) {
|
|
return `data:text/javascript;charset=utf-8,${encodeURIComponent(`
|
|
self.MonacoEnvironment = { baseUrl: '${MONACO_CDN}/min/' };
|
|
importScripts('${MONACO_CDN}/min/vs/base/worker/workerMain.js');
|
|
`)}`;
|
|
}
|
|
};
|
|
require(['vs/editor/editor.main'], () => {
|
|
import('/js/app.js');
|
|
});
|
|
};
|
|
document.head.appendChild(script);
|
|
</script>
|
|
</body>
|
|
</html>
|