Add QoL features: preview theme, external resources, shortcuts, mobile layout
- Dark/light preview theme toggle with localStorage persistence and dark CSS injection in preview, export, and embed - External CSS/JS resources modal with per-fiddle persistence in options column, injected as link/script tags - Keyboard shortcuts cheat sheet modal (? button or ? key) - Mobile-responsive CSS with breakpoints at 768px and 480px for both editor and browse pages
This commit is contained in:
@@ -193,3 +193,16 @@ html, body {
|
|||||||
color: var(--text-dim);
|
color: var(--text-dim);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Mobile responsive */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.browse-header { padding: 10px 16px; }
|
||||||
|
.browse-toolbar { padding: 10px 16px; }
|
||||||
|
.tags-bar { padding: 0 16px 10px; }
|
||||||
|
.fiddle-grid { grid-template-columns: 1fr; padding: 0 16px 16px; gap: 12px; }
|
||||||
|
.pagination { padding: 0 16px 16px; }
|
||||||
|
}
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.fiddle-grid { grid-template-columns: 1fr; }
|
||||||
|
#search-input { min-width: 0; }
|
||||||
|
}
|
||||||
|
|||||||
@@ -210,8 +210,8 @@ body.resizing iframe { pointer-events: none; }
|
|||||||
/* Dividers */
|
/* Dividers */
|
||||||
.divider { background: var(--border); transition: background 0.15s; z-index: 2; }
|
.divider { background: var(--border); transition: background 0.15s; z-index: 2; }
|
||||||
|
|
||||||
/* Layout/keybinding selects — match framework select */
|
/* Layout/keybinding/preview-theme selects — match framework select */
|
||||||
#layout-mode, #keybinding-mode {
|
#layout-mode, #keybinding-mode, #preview-theme {
|
||||||
background: var(--bg); color: var(--text); border: 1px solid var(--border);
|
background: var(--bg); color: var(--text); border: 1px solid var(--border);
|
||||||
padding: 4px 6px; border-radius: 4px; font-size: 12px; cursor: pointer;
|
padding: 4px 6px; border-radius: 4px; font-size: 12px; cursor: pointer;
|
||||||
}
|
}
|
||||||
@@ -284,3 +284,62 @@ body.resizing iframe { pointer-events: none; }
|
|||||||
font-size: 13px; z-index: 999; transition: opacity 0.3s;
|
font-size: 13px; z-index: 999; transition: opacity 0.3s;
|
||||||
}
|
}
|
||||||
.toast.hidden { opacity: 0; pointer-events: none; }
|
.toast.hidden { opacity: 0; pointer-events: none; }
|
||||||
|
|
||||||
|
/* Resources modal */
|
||||||
|
.resource-inputs { display: flex; flex-direction: column; gap: 8px; margin-bottom: 12px; }
|
||||||
|
.resource-row { display: flex; gap: 6px; align-items: center; }
|
||||||
|
.resource-row input {
|
||||||
|
flex: 1; background: var(--bg); border: 1px solid var(--border); color: var(--text);
|
||||||
|
padding: 5px 8px; border-radius: 4px; font-size: 12px;
|
||||||
|
}
|
||||||
|
.resource-row input:focus { border-color: var(--accent); outline: none; }
|
||||||
|
.resource-list { display: flex; flex-direction: column; gap: 4px; max-height: 200px; overflow-y: auto; }
|
||||||
|
.resource-item {
|
||||||
|
display: flex; align-items: center; justify-content: space-between; gap: 6px;
|
||||||
|
background: var(--bg); padding: 4px 8px; border-radius: 4px; font-size: 11px;
|
||||||
|
}
|
||||||
|
.resource-item .resource-type {
|
||||||
|
font-size: 9px; font-weight: 700; text-transform: uppercase;
|
||||||
|
padding: 1px 4px; border-radius: 2px; flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.resource-item .resource-type.css { background: #264f78; color: #9cdcfe; }
|
||||||
|
.resource-item .resource-type.js { background: #4d3b00; color: #dcdcaa; }
|
||||||
|
.resource-item .resource-url { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text-dim); }
|
||||||
|
.resource-item .resource-remove { cursor: pointer; color: var(--text-dim); background: none; border: none; padding: 0 2px; font-size: 14px; }
|
||||||
|
.resource-item .resource-remove:hover { color: #f44747; }
|
||||||
|
|
||||||
|
/* Shortcuts table */
|
||||||
|
.shortcuts-table { width: 100%; border-collapse: collapse; text-align: left; }
|
||||||
|
.shortcuts-table td { padding: 6px 10px; font-size: 13px; border-bottom: 1px solid var(--border); }
|
||||||
|
.shortcuts-table td:first-child { white-space: nowrap; color: var(--text); }
|
||||||
|
.shortcuts-table td:last-child { color: var(--text-dim); }
|
||||||
|
.shortcuts-table kbd {
|
||||||
|
background: var(--bg); border: 1px solid var(--border); padding: 1px 6px;
|
||||||
|
border-radius: 3px; font-family: inherit; font-size: 12px;
|
||||||
|
}
|
||||||
|
.shortcuts-divider td { font-size: 11px; font-weight: 600; color: var(--text-dim); text-transform: uppercase; padding-top: 12px; border-bottom: none; }
|
||||||
|
|
||||||
|
/* Preview dark theme — set iframe bg to match */
|
||||||
|
#preview-frame.preview-dark { background: #1e1e1e; }
|
||||||
|
|
||||||
|
/* Mobile responsive */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.toolbar { flex-wrap: wrap; height: auto; min-height: var(--toolbar-h); padding: 6px 8px; }
|
||||||
|
.toolbar-left, .toolbar-right { flex-wrap: wrap; }
|
||||||
|
.grid {
|
||||||
|
grid-template-columns: 1fr !important;
|
||||||
|
grid-template-rows: 1fr 1fr 120px !important;
|
||||||
|
}
|
||||||
|
.panel-editor { grid-column: 1 !important; grid-row: 1 !important; }
|
||||||
|
.panel-preview { grid-column: 1 !important; grid-row: 2 !important; }
|
||||||
|
.panel-console { grid-column: 1 !important; grid-row: 3 !important; }
|
||||||
|
.divider-col, .divider-row { display: none !important; }
|
||||||
|
/* Override all layout variants too */
|
||||||
|
.layout-top-bottom .panel-editor,
|
||||||
|
.layout-top-bottom .panel-preview,
|
||||||
|
.layout-top-bottom .panel-console { grid-column: 1 !important; }
|
||||||
|
}
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
#title-input, .tags-input-wrap { display: none; }
|
||||||
|
.modal-content { margin: 8px; min-width: unset !important; width: calc(100% - 16px); }
|
||||||
|
}
|
||||||
|
|||||||
@@ -39,6 +39,12 @@
|
|||||||
<datalist id="tags-datalist"></datalist>
|
<datalist id="tags-datalist"></datalist>
|
||||||
<div id="tags-display" class="tags-display"></div>
|
<div id="tags-display" class="tags-display"></div>
|
||||||
</div>
|
</div>
|
||||||
|
<select id="preview-theme" title="Preview background theme">
|
||||||
|
<option value="light">Light</option>
|
||||||
|
<option value="dark">Dark</option>
|
||||||
|
</select>
|
||||||
|
<button id="btn-resources" class="btn-secondary" title="External CSS/JS resources">Resources</button>
|
||||||
|
<button id="btn-shortcuts" class="btn-secondary" title="Keyboard shortcuts (?)" aria-label="Keyboard shortcuts">?</button>
|
||||||
<label class="tailwind-toggle" title="Enable Tailwind CSS">
|
<label class="tailwind-toggle" title="Enable Tailwind CSS">
|
||||||
<input type="checkbox" id="tailwind-checkbox">
|
<input type="checkbox" id="tailwind-checkbox">
|
||||||
Tailwind
|
Tailwind
|
||||||
@@ -77,6 +83,45 @@
|
|||||||
</div>
|
</div>
|
||||||
</main>
|
</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>
|
||||||
|
<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 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 id="qr-modal" class="modal-overlay hidden">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { showQrModal } from './qr.js';
|
|||||||
let currentId = null;
|
let currentId = null;
|
||||||
let debounceTimer = null;
|
let debounceTimer = null;
|
||||||
let currentTags = [];
|
let currentTags = [];
|
||||||
|
let currentResources = [];
|
||||||
|
|
||||||
const $ = (sel) => document.querySelector(sel);
|
const $ = (sel) => document.querySelector(sel);
|
||||||
|
|
||||||
@@ -84,12 +85,14 @@ async function run() {
|
|||||||
tailwind: getTailwindChecked(),
|
tailwind: getTailwindChecked(),
|
||||||
isModule: result.isModule || false,
|
isModule: result.isModule || false,
|
||||||
renderedHtml: result.renderedHtml || null,
|
renderedHtml: result.renderedHtml || null,
|
||||||
|
previewTheme: getPref('previewTheme'),
|
||||||
|
resources: currentResources,
|
||||||
};
|
};
|
||||||
|
|
||||||
renderPreview(html, compiledCss, result.js, mode, result.extraCss || '', options);
|
renderPreview(html, compiledCss, result.js, mode, result.extraCss || '', options);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
clearConsole();
|
clearConsole();
|
||||||
renderPreview(html, '', '', mode, '', { tailwind: getTailwindChecked() });
|
renderPreview(html, '', '', mode, '', { tailwind: getTailwindChecked(), previewTheme: getPref('previewTheme'), resources: currentResources });
|
||||||
window.postMessage({ type: 'console', method: 'error', args: [`Compile error: ${e.message}`] }, '*');
|
window.postMessage({ type: 'console', method: 'error', args: [`Compile error: ${e.message}`] }, '*');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -114,7 +117,7 @@ async function save() {
|
|||||||
const js_type = MODE_TO_JS_TYPE[getCurrentMode()] || 'javascript';
|
const js_type = MODE_TO_JS_TYPE[getCurrentMode()] || 'javascript';
|
||||||
const listed = $('#listed-checkbox').checked ? 1 : 0;
|
const listed = $('#listed-checkbox').checked ? 1 : 0;
|
||||||
const tags = currentTags.slice();
|
const tags = currentTags.slice();
|
||||||
const options = JSON.stringify({ tailwind: getTailwindChecked() });
|
const options = JSON.stringify({ tailwind: getTailwindChecked(), resources: currentResources });
|
||||||
try {
|
try {
|
||||||
if (currentId) {
|
if (currentId) {
|
||||||
await updateFiddle(currentId, { title, html, css, css_type, js, js_type, listed, options, tags });
|
await updateFiddle(currentId, { title, html, css, css_type, js, js_type, listed, options, tags });
|
||||||
@@ -137,7 +140,7 @@ async function fork() {
|
|||||||
const js_type = MODE_TO_JS_TYPE[getCurrentMode()] || 'javascript';
|
const js_type = MODE_TO_JS_TYPE[getCurrentMode()] || 'javascript';
|
||||||
const listed = $('#listed-checkbox').checked ? 1 : 0;
|
const listed = $('#listed-checkbox').checked ? 1 : 0;
|
||||||
const tags = currentTags.slice();
|
const tags = currentTags.slice();
|
||||||
const options = JSON.stringify({ tailwind: getTailwindChecked() });
|
const options = JSON.stringify({ tailwind: getTailwindChecked(), resources: currentResources });
|
||||||
try {
|
try {
|
||||||
const result = await createFiddle({ title, html, css, css_type, js, js_type, listed, options, tags });
|
const result = await createFiddle({ title, html, css, css_type, js, js_type, listed, options, tags });
|
||||||
currentId = result.id;
|
currentId = result.id;
|
||||||
@@ -170,10 +173,12 @@ async function loadFromUrl() {
|
|||||||
currentTags = (fiddle.tags || []).map(t => t.name);
|
currentTags = (fiddle.tags || []).map(t => t.name);
|
||||||
renderTags();
|
renderTags();
|
||||||
|
|
||||||
// Restore options (tailwind checkbox)
|
// Restore options (tailwind checkbox, resources)
|
||||||
const opts = JSON.parse(fiddle.options || '{}');
|
const opts = JSON.parse(fiddle.options || '{}');
|
||||||
const twCb = $('#tailwind-checkbox');
|
const twCb = $('#tailwind-checkbox');
|
||||||
if (twCb) twCb.checked = !!opts.tailwind;
|
if (twCb) twCb.checked = !!opts.tailwind;
|
||||||
|
currentResources = opts.resources || [];
|
||||||
|
renderResourceList();
|
||||||
|
|
||||||
setEditorValues(fiddle);
|
setEditorValues(fiddle);
|
||||||
setTimeout(run, 100);
|
setTimeout(run, 100);
|
||||||
@@ -264,6 +269,18 @@ async function init() {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
run();
|
run();
|
||||||
}
|
}
|
||||||
|
// Escape closes any open modal
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
document.querySelectorAll('.modal-overlay:not(.hidden)').forEach(m => m.classList.add('hidden'));
|
||||||
|
}
|
||||||
|
// ? key opens shortcuts (only when not typing in an input/editor)
|
||||||
|
if (e.key === '?' && !e.ctrlKey && !e.metaKey) {
|
||||||
|
const tag = document.activeElement?.tagName;
|
||||||
|
if (tag !== 'INPUT' && tag !== 'TEXTAREA' && !document.activeElement?.closest('.editor-area')) {
|
||||||
|
e.preventDefault();
|
||||||
|
$('#shortcuts-modal').classList.remove('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Tags input
|
// Tags input
|
||||||
@@ -298,6 +315,8 @@ async function init() {
|
|||||||
tailwind: getTailwindChecked(),
|
tailwind: getTailwindChecked(),
|
||||||
isModule: result.isModule || false,
|
isModule: result.isModule || false,
|
||||||
renderedHtml: result.renderedHtml || null,
|
renderedHtml: result.renderedHtml || null,
|
||||||
|
previewTheme: getPref('previewTheme'),
|
||||||
|
resources: currentResources,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showToast(`Export failed: ${e.message}`);
|
showToast(`Export failed: ${e.message}`);
|
||||||
@@ -309,6 +328,40 @@ async function init() {
|
|||||||
showQrModal(url);
|
showQrModal(url);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Preview theme selector
|
||||||
|
const themeSel = $('#preview-theme');
|
||||||
|
const savedTheme = getPref('previewTheme');
|
||||||
|
themeSel.value = savedTheme;
|
||||||
|
themeSel.addEventListener('change', (e) => {
|
||||||
|
setPref('previewTheme', e.target.value);
|
||||||
|
scheduleRun();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Resources modal
|
||||||
|
const resModal = $('#resources-modal');
|
||||||
|
$('#btn-resources').addEventListener('click', () => resModal.classList.remove('hidden'));
|
||||||
|
$('#resources-modal-close').addEventListener('click', () => resModal.classList.add('hidden'));
|
||||||
|
resModal.addEventListener('click', (e) => { if (e.target === resModal) resModal.classList.add('hidden'); });
|
||||||
|
|
||||||
|
$('#btn-add-css').addEventListener('click', () => {
|
||||||
|
const input = $('#resource-css-input');
|
||||||
|
const url = input.value.trim();
|
||||||
|
if (url) { currentResources.push({ type: 'css', url }); input.value = ''; renderResourceList(); scheduleRun(); }
|
||||||
|
});
|
||||||
|
$('#btn-add-js').addEventListener('click', () => {
|
||||||
|
const input = $('#resource-js-input');
|
||||||
|
const url = input.value.trim();
|
||||||
|
if (url) { currentResources.push({ type: 'js', url }); input.value = ''; renderResourceList(); scheduleRun(); }
|
||||||
|
});
|
||||||
|
$('#resource-css-input').addEventListener('keydown', (e) => { if (e.key === 'Enter') $('#btn-add-css').click(); });
|
||||||
|
$('#resource-js-input').addEventListener('keydown', (e) => { if (e.key === 'Enter') $('#btn-add-js').click(); });
|
||||||
|
|
||||||
|
// Shortcuts modal
|
||||||
|
const scModal = $('#shortcuts-modal');
|
||||||
|
$('#btn-shortcuts').addEventListener('click', () => scModal.classList.remove('hidden'));
|
||||||
|
$('#shortcuts-modal-close').addEventListener('click', () => scModal.classList.add('hidden'));
|
||||||
|
scModal.addEventListener('click', (e) => { if (e.target === scModal) scModal.classList.add('hidden'); });
|
||||||
|
|
||||||
// Load fiddle from URL if present
|
// Load fiddle from URL if present
|
||||||
loadFromUrl();
|
loadFromUrl();
|
||||||
|
|
||||||
@@ -334,6 +387,22 @@ function renderTags() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderResourceList() {
|
||||||
|
const container = $('#resource-list');
|
||||||
|
container.innerHTML = '';
|
||||||
|
currentResources.forEach((r, i) => {
|
||||||
|
const item = document.createElement('div');
|
||||||
|
item.className = 'resource-item';
|
||||||
|
item.innerHTML = `<span class="resource-type ${r.type}">${r.type}</span><span class="resource-url" title="${r.url}">${r.url}</span><button class="resource-remove">×</button>`;
|
||||||
|
item.querySelector('.resource-remove').addEventListener('click', () => {
|
||||||
|
currentResources.splice(i, 1);
|
||||||
|
renderResourceList();
|
||||||
|
scheduleRun();
|
||||||
|
});
|
||||||
|
container.appendChild(item);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function loadTagSuggestions() {
|
async function loadTagSuggestions() {
|
||||||
try {
|
try {
|
||||||
const { tags } = await listTags();
|
const { tags } = await listTags();
|
||||||
|
|||||||
@@ -79,11 +79,25 @@ async function init() {
|
|||||||
? `<script src="https://cdn.tailwindcss.com"><\/script>\n`
|
? `<script src="https://cdn.tailwindcss.com"><\/script>\n`
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
|
// Dark preview theme — from fiddle options or URL param
|
||||||
|
const previewTheme = params.get('theme') || opts.previewTheme || 'light';
|
||||||
|
const darkCss = previewTheme === 'dark'
|
||||||
|
? `<style>body { background: #1e1e1e; color: #ccc; }</style>\n`
|
||||||
|
: '';
|
||||||
|
|
||||||
|
// External resources
|
||||||
|
let resourceTags = '';
|
||||||
|
const resources = opts.resources || [];
|
||||||
|
for (const r of resources) {
|
||||||
|
if (r.type === 'css') resourceTags += `<link rel="stylesheet" href="${r.url}">\n`;
|
||||||
|
else if (r.type === 'js') resourceTags += `<script src="${r.url}"><\/script>\n`;
|
||||||
|
}
|
||||||
|
|
||||||
const doc = `<!DOCTYPE html>
|
const doc = `<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
${tailwindScript}<style>${allCss}</style>
|
${darkCss}${resourceTags}${tailwindScript}<style>${allCss}</style>
|
||||||
${importMapTag}
|
${importMapTag}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { extractBareImports, buildImportMapTag } from './import-map.js';
|
|||||||
/**
|
/**
|
||||||
* Export a fiddle as a standalone HTML file and trigger download.
|
* Export a fiddle as a standalone HTML file and trigger download.
|
||||||
*/
|
*/
|
||||||
export function exportHtml({ title, html, css, js, mode, extraCss = '', isModule = false, tailwind = false, renderedHtml = null }) {
|
export function exportHtml({ title, html, css, js, mode, extraCss = '', isModule = false, tailwind = false, renderedHtml = null, previewTheme = 'light', resources = [] }) {
|
||||||
const runtime = getFrameworkRuntime(mode);
|
const runtime = getFrameworkRuntime(mode);
|
||||||
const allCss = extraCss ? `${css}\n${extraCss}` : css;
|
const allCss = extraCss ? `${css}\n${extraCss}` : css;
|
||||||
|
|
||||||
@@ -47,13 +47,25 @@ export function exportHtml({ title, html, css, js, mode, extraCss = '', isModule
|
|||||||
? `<script src="https://cdn.tailwindcss.com"><\/script>\n`
|
? `<script src="https://cdn.tailwindcss.com"><\/script>\n`
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
|
const darkCss = previewTheme === 'dark'
|
||||||
|
? `<style>body { background: #1e1e1e; color: #ccc; }</style>\n`
|
||||||
|
: '';
|
||||||
|
|
||||||
|
let resourceTags = '';
|
||||||
|
if (resources && resources.length) {
|
||||||
|
for (const r of resources) {
|
||||||
|
if (r.type === 'css') resourceTags += `<link rel="stylesheet" href="${r.url}">\n`;
|
||||||
|
else if (r.type === 'js') resourceTags += `<script src="${r.url}"><\/script>\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const doc = `<!DOCTYPE html>
|
const doc = `<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>${escHtml(title)}</title>
|
<title>${escHtml(title)}</title>
|
||||||
${tailwindScript}<style>
|
${darkCss}${resourceTags}${tailwindScript}<style>
|
||||||
${allCss}
|
${allCss}
|
||||||
</style>
|
</style>
|
||||||
${importMapTag}
|
${importMapTag}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ const DEFAULTS = {
|
|||||||
layout: 'default',
|
layout: 'default',
|
||||||
keybindings: 'default',
|
keybindings: 'default',
|
||||||
panelSizes: null,
|
panelSizes: null,
|
||||||
|
previewTheme: 'light',
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getPref(key) {
|
export function getPref(key) {
|
||||||
|
|||||||
@@ -107,12 +107,26 @@ export function renderPreview(html, css, js, mode = 'html-css-js', extraCss = ''
|
|||||||
? `<script src="https://cdn.tailwindcss.com"><\/script>\n`
|
? `<script src="https://cdn.tailwindcss.com"><\/script>\n`
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
|
// Dark preview theme
|
||||||
|
const darkCss = options.previewTheme === 'dark'
|
||||||
|
? `<style>body { background: #1e1e1e; color: #ccc; }</style>\n`
|
||||||
|
: '';
|
||||||
|
|
||||||
|
// External resources
|
||||||
|
let resourceTags = '';
|
||||||
|
if (options.resources && options.resources.length) {
|
||||||
|
for (const r of options.resources) {
|
||||||
|
if (r.type === 'css') resourceTags += `<link rel="stylesheet" href="${r.url}">\n`;
|
||||||
|
else if (r.type === 'js') resourceTags += `<script src="${r.url}"><\/script>\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const doc = `<!DOCTYPE html>
|
const doc = `<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
${consoleInterceptor}
|
${consoleInterceptor}
|
||||||
${tailwindScript}<style>${allCss}</style>
|
${darkCss}${resourceTags}${tailwindScript}<style>${allCss}</style>
|
||||||
${importMapTag}
|
${importMapTag}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -120,5 +134,7 @@ ${bodyContent}
|
|||||||
${loaderScript}
|
${loaderScript}
|
||||||
</body>
|
</body>
|
||||||
</html>`;
|
</html>`;
|
||||||
|
// Update iframe bg class
|
||||||
|
frame.classList.toggle('preview-dark', options.previewTheme === 'dark');
|
||||||
frame.srcdoc = doc;
|
frame.srcdoc = doc;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user