// Main editor initialization
// This file sets up global state and initializes the Monaco editor
// Security helpers - escape strings for use in HTML attributes
function escapeAttr(s) {
if (s == null) return '';
return String(s).replace(/['"\\<>&]/g, c => ({
"'": ''',
'"': '"',
'\\': '\',
'<': '<',
'>': '>',
'&': '&'
}[c]));
}
function escapeHtml(s) {
if (s == null) return '';
return String(s).replace(/[<>&"']/g, c => ({
'<': '<',
'>': '>',
'&': '&',
'"': '"',
"'": '''
}[c]));
}
// Global state
let editor = null;
let files = [];
let currentFile = null;
let contextMenuFile = null;
// Multi-tab state: { path: { model, viewState, isDirty, originalContent } }
const tabState = new Map();
// Status display
function setStatus(msg, ok = true) {
const el = document.getElementById('status');
el.textContent = msg;
el.className = ok ? 'text-xs text-success' : 'text-xs text-error';
setTimeout(() => { el.textContent = ''; }, 3000);
}
// Settings
let userSettings = {
theme: 'dark',
font_size: 14,
tab_size: 4,
word_wrap: true,
minimap: false,
persist_workspace: true
};
async function loadSettings() {
try {
const res = await fetch('/api/settings');
if (res.ok) {
userSettings = await res.json();
applySettings();
}
} catch (err) {
console.error('Failed to load settings:', err);
}
}
function getMonacoTheme(daisyTheme) {
const lightThemes = ['light', 'cupcake', 'bumblebee', 'emerald', 'corporate', 'retro', 'cyberpunk', 'valentine', 'garden', 'lofi', 'pastel', 'fantasy', 'wireframe', 'cmyk', 'autumn', 'acid', 'lemonade', 'winter'];
return lightThemes.includes(daisyTheme) ? 'vs-light' : 'vs-dark';
}
function applySettings() {
document.documentElement.setAttribute('data-theme', userSettings.theme);
if (!editor) return;
const monacoTheme = getMonacoTheme(userSettings.theme);
editor.updateOptions({
fontSize: userSettings.font_size,
tabSize: userSettings.tab_size,
wordWrap: userSettings.word_wrap ? 'on' : 'off',
minimap: { enabled: userSettings.minimap }
});
monaco.editor.setTheme(monacoTheme);
}
function showSettingsModal() {
document.getElementById('setting-theme').value = userSettings.theme;
document.getElementById('setting-font-size').value = userSettings.font_size;
document.getElementById('setting-tab-size').value = userSettings.tab_size;
document.getElementById('setting-word-wrap').checked = userSettings.word_wrap;
document.getElementById('setting-minimap').checked = userSettings.minimap;
document.getElementById('setting-persist-workspace').checked = userSettings.persist_workspace !== false;
updateFontSizeLabel();
document.getElementById('settings-modal').showModal();
}
function updateFontSizeLabel() {
const value = document.getElementById('setting-font-size').value;
document.getElementById('font-size-value').textContent = value + 'px';
}
async function saveSettings() {
const newSettings = {
theme: document.getElementById('setting-theme').value,
font_size: parseInt(document.getElementById('setting-font-size').value),
tab_size: parseInt(document.getElementById('setting-tab-size').value),
word_wrap: document.getElementById('setting-word-wrap').checked,
minimap: document.getElementById('setting-minimap').checked,
persist_workspace: document.getElementById('setting-persist-workspace').checked
};
try {
const res = await fetch('/api/settings', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newSettings)
});
if (!res.ok) throw new Error('Failed to save');
userSettings = newSettings;
applySettings();
setStatus('Settings saved');
document.getElementById('settings-modal').close();
} catch (err) {
setStatus('Failed to save settings', false);
}
}
// Workspace sync function
async function syncWorkspace() {
setStatus('Syncing workspace...');
try {
const res = await fetch('/api/workspace/sync', { method: 'POST' });
if (!res.ok) {
const err = await res.json();
throw new Error(err.error || 'Sync failed');
}
setStatus('Workspace synced successfully');
await loadProjects();
await loadFiles();
if (currentFile) {
const fileRes = await fetch('/api/files/open?path=' + encodeURIComponent(currentFile));
if (fileRes.ok) {
const data = await fileRes.json();
if (editor.getValue() !== data.content) {
const pos = editor.getPosition();
editor.setValue(data.content);
if (pos) editor.setPosition(pos);
}
}
}
} catch (err) {
setStatus('Sync failed: ' + err.message, false);
}
}
// Initialize Monaco Editor
require.config({ paths: { 'vs': 'https://cdn.jsdelivr.net/npm/monaco-editor@0.52.0/min/vs' }});
require(['vs/editor/editor.main'], function() {
editor = monaco.editor.create(document.getElementById('editor-container'), {
value: '// Welcome to SkyCode!\n// Create a new file or open an existing one to start coding.',
language: 'plaintext',
theme: 'vs-dark',
automaticLayout: true,
minimap: { enabled: false },
fontSize: 14,
lineNumbers: 'on',
scrollBeyondLastLine: false,
wordWrap: 'on'
});
// Load settings, projects, and files
loadSettings();
loadProjects();
loadFiles();
// Keyboard shortcuts
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, saveCurrentFile);
// Initialize terminal resizer
setupTerminalResizer();
// Load existing terminals
loadExistingTerminals();
});