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

@@ -1,13 +1,38 @@
import { getPref } from './preferences.js';
const editorOpts = {
minimap: { enabled: false },
automaticLayout: true,
fontSize: 13,
lineNumbers: 'on',
scrollBeyondLastLine: false,
theme: 'vs-dark',
theme: getPref('editorTheme') || 'vs-dark',
tabSize: 2,
renderWhitespace: 'none',
padding: { top: 6 },
quickSuggestions: { other: true, comments: false, strings: true },
suggestOnTriggerCharacters: true,
acceptSuggestionOnEnter: 'on',
parameterHints: { enabled: true },
wordBasedSuggestions: 'currentDocument',
suggest: {
snippetsPreventQuickSuggestions: false,
showSnippets: true,
showWords: true,
showKeywords: true,
showMethods: true,
showFunctions: true,
showVariables: true,
showClasses: true,
showInterfaces: true,
showProperties: true,
showEvents: true,
showConstants: true,
},
autoClosingBrackets: 'always',
autoClosingQuotes: 'always',
autoSurround: 'languageDefined',
bracketPairColorization: { enabled: true },
};
export const MODE_TABS = {
@@ -72,8 +97,10 @@ let currentMode = 'html-css-js';
let activeTab = null;
let cssType = 'css';
let onChangeCallback = null;
let onTabSwitchCallback = null;
let onModeChangeCallback = null;
let tabSwitchCallbacks = [];
let modeChangeCallbacks = [];
let onFormatCallback = null;
let onDiffCallback = null;
const tabBar = () => document.getElementById('tab-bar');
const editorArea = () => document.getElementById('editor-area');
@@ -83,11 +110,19 @@ export function setOnChange(cb) {
}
export function setOnTabSwitch(cb) {
onTabSwitchCallback = cb;
tabSwitchCallbacks.push(cb);
}
export function setOnModeChange(cb) {
onModeChangeCallback = cb;
modeChangeCallbacks.push(cb);
}
export function setOnFormat(cb) {
onFormatCallback = cb;
}
export function setOnDiff(cb) {
onDiffCallback = cb;
}
export function getActiveEditor() {
@@ -124,20 +159,23 @@ export function setCssType(type) {
function configureJsxSupport(mode) {
if (mode === 'react' || mode === 'react-ts') {
// Enable JSX in JavaScript defaults
monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
target: monaco.languages.typescript.ScriptTarget.ESNext,
jsx: monaco.languages.typescript.JsxEmit.React,
jsxFactory: 'React.createElement',
allowNonTsExtensions: true,
allowJs: true,
lib: ['esnext', 'dom', 'dom.iterable'],
});
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
target: monaco.languages.typescript.ScriptTarget.ESNext,
jsx: monaco.languages.typescript.JsxEmit.React,
jsxFactory: 'React.createElement',
allowNonTsExtensions: true,
lib: ['esnext', 'dom', 'dom.iterable'],
});
// Add React type definitions for IntelliSense
import('./autocomplete.js').then(m => m.addReactTypes());
}
}
@@ -196,6 +234,29 @@ function renderTabBar(tabs) {
btn.addEventListener('click', () => switchTab(tab.id));
bar.appendChild(btn);
});
// Spacer + Format + Diff buttons
const spacer = document.createElement('span');
spacer.className = 'tab-bar-spacer';
bar.appendChild(spacer);
const fmtBtn = document.createElement('button');
fmtBtn.className = 'tab-bar-btn format-btn';
fmtBtn.textContent = 'Format';
fmtBtn.title = 'Format code (Shift+Alt+F)';
fmtBtn.addEventListener('click', () => {
if (onFormatCallback) onFormatCallback();
});
bar.appendChild(fmtBtn);
const diffBtn = document.createElement('button');
diffBtn.className = 'tab-bar-btn diff-btn';
diffBtn.textContent = 'Diff';
diffBtn.title = 'Toggle diff view (Ctrl+D)';
diffBtn.addEventListener('click', () => {
if (onDiffCallback) onDiffCallback();
});
bar.appendChild(diffBtn);
}
export function initEditors(mode = 'html-css-js') {
@@ -248,7 +309,7 @@ export function switchMode(mode) {
switchTab(tabs[0].id);
if (onModeChangeCallback) onModeChangeCallback(mode);
modeChangeCallbacks.forEach(cb => cb(mode));
}
export function switchTab(tabId) {
@@ -274,7 +335,7 @@ export function switchTab(tabId) {
editors[tabId].focus();
}
if (onTabSwitchCallback) onTabSwitchCallback(tabId, editors[tabId]);
tabSwitchCallbacks.forEach(cb => cb(tabId, editors[tabId]));
}
export function getEditorValues() {
@@ -294,3 +355,8 @@ export function setEditorValues({ html = '', css = '', js = '' }) {
export function getActiveTab() {
return activeTab;
}
export function setEditorTheme(themeId) {
editorOpts.theme = themeId;
monaco.editor.setTheme(themeId);
}