Connor McCutcheon
/ SkyCode
main.js
js
// 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 => ({
    "'": '&#39;',
    '"': '&quot;',
    '\\': '&#92;',
    '<': '&lt;',
    '>': '&gt;',
    '&': '&amp;'
  }[c]));
}
function escapeHtml(s) {
  if (s == null) return '';
  return String(s).replace(/[<>&"']/g, c => ({
    '<': '&lt;',
    '>': '&gt;',
    '&': '&amp;',
    '"': '&quot;',
    "'": '&#39;'
  }[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();
});
No comments yet.