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:
@@ -19,6 +19,7 @@ import { showQrModal } from './qr.js';
|
||||
let currentId = null;
|
||||
let debounceTimer = null;
|
||||
let currentTags = [];
|
||||
let currentResources = [];
|
||||
|
||||
const $ = (sel) => document.querySelector(sel);
|
||||
|
||||
@@ -84,12 +85,14 @@ async function run() {
|
||||
tailwind: getTailwindChecked(),
|
||||
isModule: result.isModule || false,
|
||||
renderedHtml: result.renderedHtml || null,
|
||||
previewTheme: getPref('previewTheme'),
|
||||
resources: currentResources,
|
||||
};
|
||||
|
||||
renderPreview(html, compiledCss, result.js, mode, result.extraCss || '', options);
|
||||
} catch (e) {
|
||||
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}`] }, '*');
|
||||
}
|
||||
}
|
||||
@@ -114,7 +117,7 @@ async function save() {
|
||||
const js_type = MODE_TO_JS_TYPE[getCurrentMode()] || 'javascript';
|
||||
const listed = $('#listed-checkbox').checked ? 1 : 0;
|
||||
const tags = currentTags.slice();
|
||||
const options = JSON.stringify({ tailwind: getTailwindChecked() });
|
||||
const options = JSON.stringify({ tailwind: getTailwindChecked(), resources: currentResources });
|
||||
try {
|
||||
if (currentId) {
|
||||
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 listed = $('#listed-checkbox').checked ? 1 : 0;
|
||||
const tags = currentTags.slice();
|
||||
const options = JSON.stringify({ tailwind: getTailwindChecked() });
|
||||
const options = JSON.stringify({ tailwind: getTailwindChecked(), resources: currentResources });
|
||||
try {
|
||||
const result = await createFiddle({ title, html, css, css_type, js, js_type, listed, options, tags });
|
||||
currentId = result.id;
|
||||
@@ -170,10 +173,12 @@ async function loadFromUrl() {
|
||||
currentTags = (fiddle.tags || []).map(t => t.name);
|
||||
renderTags();
|
||||
|
||||
// Restore options (tailwind checkbox)
|
||||
// Restore options (tailwind checkbox, resources)
|
||||
const opts = JSON.parse(fiddle.options || '{}');
|
||||
const twCb = $('#tailwind-checkbox');
|
||||
if (twCb) twCb.checked = !!opts.tailwind;
|
||||
currentResources = opts.resources || [];
|
||||
renderResourceList();
|
||||
|
||||
setEditorValues(fiddle);
|
||||
setTimeout(run, 100);
|
||||
@@ -264,6 +269,18 @@ async function init() {
|
||||
e.preventDefault();
|
||||
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
|
||||
@@ -298,6 +315,8 @@ async function init() {
|
||||
tailwind: getTailwindChecked(),
|
||||
isModule: result.isModule || false,
|
||||
renderedHtml: result.renderedHtml || null,
|
||||
previewTheme: getPref('previewTheme'),
|
||||
resources: currentResources,
|
||||
});
|
||||
} catch (e) {
|
||||
showToast(`Export failed: ${e.message}`);
|
||||
@@ -309,6 +328,40 @@ async function init() {
|
||||
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
|
||||
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() {
|
||||
try {
|
||||
const { tags } = await listTags();
|
||||
|
||||
Reference in New Issue
Block a user