Add editor experience features: auto-run, layouts, resizable panels, Emmet, vim/emacs

- Auto-run toggle with localStorage persistence (toolbar checkbox)
- Layout selector: default, top/bottom, editor-only, preview-only
- Resizable panels via drag dividers with double-click reset
- Emmet abbreviation expansion for HTML, CSS, and JSX
- Vim and Emacs keybinding modes with lazy-loaded CDN libraries
- Shared preferences module for localStorage management
- Editor hooks for tab switch and mode change callbacks
This commit is contained in:
root
2026-02-26 11:19:14 -06:00
parent 463b563423
commit 7f51af17a3
8 changed files with 407 additions and 7 deletions

View File

@@ -1,6 +1,7 @@
import {
initEditors, switchMode, getEditorValues, setEditorValues,
setOnChange, getCurrentMode, getCssType, setCssType,
relayoutEditors,
MODE_TABS, MODE_TO_JS_TYPE, JS_TYPE_TO_MODE,
} from './editors.js';
import { renderPreview } from './preview.js';
@@ -8,6 +9,10 @@ import { initConsole, clearConsole } from './console-panel.js';
import { compileCss } from './preprocessors.js';
import { compileJs } from './js-preprocessors.js';
import { createFiddle, loadFiddle, updateFiddle } from './api.js';
import { getPref, setPref } from './preferences.js';
import { initEmmet } from './emmet.js';
import { initKeybindings } from './keybindings.js';
import { initResizer, clearInlineSizes } from './resizer.js';
let currentId = null;
let debounceTimer = null;
@@ -67,6 +72,7 @@ async function run() {
}
function scheduleRun() {
if (!getPref('autoRun')) return;
clearTimeout(debounceTimer);
debounceTimer = setTimeout(run, 500);
}
@@ -155,10 +161,43 @@ function handleModeChange(newMode) {
scheduleRun();
}
function init() {
function applyLayout(layout) {
const grid = $('.grid');
// Remove all layout classes
grid.classList.remove('layout-top-bottom', 'layout-editor-only', 'layout-preview-only');
// Clear resizer inline styles when switching layouts
clearInlineSizes();
if (layout === 'top-bottom') grid.classList.add('layout-top-bottom');
else if (layout === 'editor-only') grid.classList.add('layout-editor-only');
else if (layout === 'preview-only') grid.classList.add('layout-preview-only');
setPref('layout', layout);
// Give DOM time to reflow then relayout editors
requestAnimationFrame(() => relayoutEditors());
}
async function init() {
// Load Emmet before editors so completion providers are registered
await initEmmet();
initEditors('html-css-js');
setOnChange(scheduleRun);
initConsole();
initResizer();
initKeybindings();
// Auto-run checkbox
const autoRunCb = $('#auto-run-checkbox');
autoRunCb.checked = getPref('autoRun');
autoRunCb.addEventListener('change', (e) => setPref('autoRun', e.target.checked));
// Layout selector
const layoutSel = $('#layout-mode');
const savedLayout = getPref('layout') || 'default';
layoutSel.value = savedLayout;
if (savedLayout !== 'default') applyLayout(savedLayout);
layoutSel.addEventListener('change', (e) => applyLayout(e.target.value));
// Mode selector
$('#framework-mode').addEventListener('change', (e) => {