Add browse dashboard, tags, visibility control, export, QR sharing, and embed mode
- Browse dashboard at / with search, framework filter, tag pills, and pagination - Tags system with autocomplete datalist and per-fiddle tag management - Listed/unlisted toggle for visibility control (unlisted still accessible via direct URL) - Export standalone HTML with inlined CSS/JS and framework CDN tags - QR code modal for sharing fiddle URLs - Embed mode at /embed/:id for minimal preview-only rendering - Extract shared loadScript() utility from 4 files into utils.js - Database schema: listed column, tags and fiddle_tags tables with index
This commit is contained in:
82
public/js/embed.js
Normal file
82
public/js/embed.js
Normal file
@@ -0,0 +1,82 @@
|
||||
import { loadFiddle } from './api.js';
|
||||
import { getFrameworkRuntime } from './js-preprocessors.js';
|
||||
|
||||
const MODE_MAP = {
|
||||
javascript: 'html-css-js',
|
||||
typescript: 'typescript',
|
||||
react: 'react',
|
||||
'react-ts': 'react-ts',
|
||||
vue: 'vue',
|
||||
svelte: 'svelte',
|
||||
};
|
||||
|
||||
async function init() {
|
||||
// Extract fiddle ID from URL: /embed/:id
|
||||
const match = location.pathname.match(/^\/embed\/([a-zA-Z0-9_-]+)$/);
|
||||
if (!match) {
|
||||
document.body.innerHTML = '<div class="embed-error">Invalid embed URL</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Theme
|
||||
const params = new URLSearchParams(location.search);
|
||||
if (params.get('theme') === 'dark') document.body.classList.add('dark');
|
||||
|
||||
try {
|
||||
const fiddle = await loadFiddle(match[1]);
|
||||
const mode = MODE_MAP[fiddle.js_type] || 'html-css-js';
|
||||
const runtime = getFrameworkRuntime(mode);
|
||||
|
||||
// For embed, we compile client-side using the same preprocessors
|
||||
const { compileCss } = await import('./preprocessors.js');
|
||||
const { compileJs } = await import('./js-preprocessors.js');
|
||||
|
||||
const compiledCss = await compileCss(fiddle.css, fiddle.css_type || 'css');
|
||||
const result = await compileJs(fiddle.js, mode);
|
||||
|
||||
const allCss = result.extraCss ? `${compiledCss}\n${result.extraCss}` : compiledCss;
|
||||
|
||||
let bodyContent;
|
||||
if (mode === 'vue' || mode === 'svelte') {
|
||||
bodyContent = fiddle.html ? `${fiddle.html}\n${runtime.bodyHtml}` : runtime.bodyHtml;
|
||||
} else if (runtime.bodyHtml) {
|
||||
bodyContent = `${fiddle.html}\n${runtime.bodyHtml}`;
|
||||
} else {
|
||||
bodyContent = fiddle.html;
|
||||
}
|
||||
|
||||
let scripts = '';
|
||||
if (result.js) {
|
||||
if (result.isModule) {
|
||||
scripts = `<script type="module">\n${escapeScriptClose(result.js)}\n<\/script>`;
|
||||
} else {
|
||||
for (const url of runtime.scripts) {
|
||||
scripts += `<script src="${url}"><\/script>\n`;
|
||||
}
|
||||
scripts += `<script>\n${escapeScriptClose(result.js)}\n<\/script>`;
|
||||
}
|
||||
}
|
||||
|
||||
const doc = `<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style>${allCss}</style>
|
||||
</head>
|
||||
<body>
|
||||
${bodyContent}
|
||||
${scripts}
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
document.getElementById('preview-frame').srcdoc = doc;
|
||||
} catch (e) {
|
||||
document.body.innerHTML = `<div class="embed-error">Failed to load fiddle: ${e.message}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
function escapeScriptClose(code) {
|
||||
return code.replace(/<\/script/gi, '<\\/script');
|
||||
}
|
||||
|
||||
init();
|
||||
Reference in New Issue
Block a user