Add responsive preview, editor themes, template gallery, devtools, and autocomplete

- Device breakpoint toggles (mobile 375px / tablet 768px / desktop 100%)
- Editor theme selector with 6 themes (VS Dark/Light, High Contrast, Monokai, Dracula, GitHub Dark)
- Starter template gallery with 8 pre-built templates (Todo, API Fetch, CSS Animation, etc.)
- Code autocomplete with DOM/React type definitions and snippet completions
- Devtools panels: console, network, elements, performance
- Code formatter (Prettier), diff view, and linter integration
This commit is contained in:
root
2026-02-27 01:22:01 -06:00
parent b18c9c1dc8
commit 6ca8519250
19 changed files with 1755 additions and 29 deletions

View File

@@ -28,6 +28,70 @@ const consoleInterceptor = `
window.onerror = function(msg, url, line, col) {
parent.postMessage({ type: 'console', method: 'error', args: ['Error: ' + msg + ' (line ' + line + ')'] }, '*');
};
// --- Network: PerformanceObserver for resource loads ---
try {
var netObserver = new PerformanceObserver(function(list) {
var entries = list.getEntries().map(function(e) {
return { name: e.name, initiatorType: e.initiatorType, duration: e.duration, transferSize: e.transferSize || 0, startTime: e.startTime };
});
if (entries.length) parent.postMessage({ type: 'devtools', tab: 'network', entries: entries }, '*');
});
netObserver.observe({ type: 'resource', buffered: true });
} catch(e) {}
// --- Elements: serialize DOM tree on DOMContentLoaded ---
function serializeNode(node, depth) {
if (depth > 15) return null;
if (node.nodeType === 3) {
var t = node.textContent;
if (!t.trim()) return null;
return { type: 'text', text: t };
}
if (node.nodeType !== 1) return null;
var tag = node.tagName.toLowerCase();
if (tag === 'script' || tag === 'style') {
return { type: 'element', tag: tag, attrs: getAttrs(node), children: [] };
}
var children = [];
for (var i = 0; i < node.childNodes.length; i++) {
var c = serializeNode(node.childNodes[i], depth + 1);
if (c) children.push(c);
}
return { type: 'element', tag: tag, attrs: getAttrs(node), children: children };
}
function getAttrs(el) {
var arr = [];
for (var i = 0; i < el.attributes.length; i++) {
arr.push({ name: el.attributes[i].name, value: el.attributes[i].value });
}
return arr;
}
function sendElements() {
var tree = serializeNode(document.documentElement, 0);
parent.postMessage({ type: 'devtools', tab: 'elements', tree: tree }, '*');
}
document.addEventListener('DOMContentLoaded', function() { setTimeout(sendElements, 50); });
window.addEventListener('message', function(e) {
if (e.data && e.data.type === 'devtools-request' && e.data.tab === 'elements') sendElements();
});
// --- Performance: timing metrics ---
window.__fiddle_scriptStart = performance.now();
window.addEventListener('load', function() {
var scriptEnd = window.__fiddle_scriptEnd || performance.now();
var metrics = {
scriptDuration: scriptEnd - window.__fiddle_scriptStart,
domNodes: document.getElementsByTagName('*').length,
resourceCount: performance.getEntriesByType('resource').length
};
var nav = performance.getEntriesByType('navigation');
if (nav && nav.length) {
metrics.domContentLoaded = nav[0].domContentLoadedEventEnd;
metrics.loadEvent = nav[0].loadEventEnd || performance.now();
}
parent.postMessage({ type: 'devtools', tab: 'performance', metrics: metrics }, '*');
});
})();
<\/script>
`;
@@ -45,15 +109,16 @@ function escapeScriptClose(code) {
* followed by an inline <script> for user code.
*/
function buildLoaderScript(runtimeUrls, userJs, isModule) {
const endMarker = 'window.__fiddle_scriptEnd = performance.now();';
if (isModule) {
return `\n<script type="module">\n${escapeScriptClose(userJs)}\n<\/script>`;
return `\n<script type="module">\n${escapeScriptClose(userJs)}\n${endMarker}\n<\/script>`;
}
let parts = '';
for (const url of runtimeUrls) {
parts += `<script src="${url}"><\/script>\n`;
}
parts += `<script>\n${escapeScriptClose(userJs)}\n<\/script>`;
parts += `<script>\n${escapeScriptClose(userJs)}\n${endMarker}\n<\/script>`;
return parts;
}
@@ -102,9 +167,9 @@ export function renderPreview(html, css, js, mode = 'html-css-js', extraCss = ''
? buildLoaderScript(runtime.scripts, finalJs, isModule)
: '';
// Tailwind CDN injection
// Tailwind CDN injection (suppress production warning)
const tailwindScript = options.tailwind
? `<script src="https://cdn.tailwindcss.com"><\/script>\n`
? `<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`
: '';
// Dark preview theme