[]; // Initialize Monaco Editor require.config({ paths: { vs: '/js/vendor/vs' } }); require(['vs/editor/editor.main'], function () { initializeMonacoEditor(); }); function initializeMonacoEditor() { const container = document.getElementById('monacoEditorContainer'); if (!container) return; // Create Monaco editor monacoEditor = monaco.editor.create(container, { value: '', language: 'html', theme: 'vs-dark', automaticLayout: true, fontSize: 14, lineNumbers: 'on', roundedSelection: true, scrollBeyondLastLine: false, readOnly: false, minimap: { enabled: true }, wordWrap: 'on', formatOnPaste: true, formatOnType: true, }); // Add event listeners monacoEditor.onDidChangeModelContent(() => { if (activeFile) { const fileData = openFiles.get(activeFile); if (fileData) { fileData.modified = true; fileData.content = monacoEditor.getValue(); updateTabLabel(activeFile, true); updateEditorStatus('Modified'); } } }); monacoEditor.onDidChangeCursorPosition((e) => { const position = `Ln ${e.position.lineNumber}, Col ${e.position.column}`; document.getElementById('editorPosition').textContent = position; }); // Define custom theme based on app theme monaco.editor.defineTheme('gb-dark', { base: 'vs-dark', inherit: true, rules: [], colors: { 'editor.background': '#1e1e1e', 'editor.foreground': '#d4d4d4', 'editor.lineHighlightBackground': '#2d2d2d', } }); monaco.editor.setTheme('gb-dark'); console.log('Monaco Editor initialized successfully'); updateEditorStatus('Ready'); } // Language detection based on file extension function detectLanguage(filePath) { const ext = filePath.split('.').pop().toLowerCase(); const languageMap = { 'html': 'html', 'htm': 'html', 'css': 'css', 'js': 'javascript', 'json': 'json', 'ts': 'typescript', 'bas': 'basic', 'py': 'python', 'rs': 'rust', 'md': 'markdown', 'xml': 'xml', 'yaml': 'yaml', 'yml': 'yaml', 'sql': 'sql', }; return languageMap[ext] || 'plaintext'; } // Open file in editor function openFile(filePath, content = '') { if (!monacoEditor) { console.error('Monaco Editor not initialized'); return; } // If file not already open, add it if (!openFiles.has(filePath)) { const language = detectLanguage(filePath); openFiles.set(filePath, { content: content, language: language, modified: false }); addTab(filePath, language); } // Switch to file activeFile = filePath; const fileData = openFiles.get(filePath); // Update editor monacoEditor.setValue(fileData.content); monaco.editor.setModelLanguage(monacoEditor.getModel(), fileData.language); // Update UI document.getElementById('editorLanguage').textContent = fileData.language.toUpperCase(); highlightActiveTab(filePath); updateEditorStatus('Ready'); } // Add tab to tab bar function addTab(filePath, language) { const tabBar = document.getElementById('editorTabBar'); const fileName = filePath.split('/').pop(); const tab = document.createElement('div'); tab.className = 'editor-tab'; tab.id = `tab-${filePath.replace(/\//g, '-')}`; tab.setAttribute('data-filepath', filePath); tab.style.cssText = ` display: flex; align-items: center; gap: 8px; padding: 8px 16px; background: var(--bg); border-right: 1px solid var(--border); cursor: pointer; font-size: 13px; color: var(--text); min-width: 120px; max-width: 200px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; `; const icon = getFileIcon(language); tab.innerHTML = ` ${icon} ${fileName} `; tab.addEventListener('click', () => openFile(filePath)); tabBar.appendChild(tab); } // Update tab label to show modified state function updateTabLabel(filePath, modified) { const tab = document.getElementById(`tab-${filePath.replace(/\//g, '-')}`); if (tab) { const tabName = tab.querySelector('.tab-name'); const fileName = filePath.split('/').pop(); tabName.textContent = modified ? `● ${fileName}` : fileName; } } // Highlight active tab function highlightActiveTab(filePath) { const tabs = document.querySelectorAll('.editor-tab'); tabs.forEach(tab => { if (tab.getAttribute('data-filepath') === filePath) { tab.style.background = 'var(--surface)'; tab.style.borderBottom = '2px solid var(--accent)'; } else { tab.style.background = 'var(--bg)'; tab.style.borderBottom = 'none'; } }); } // Close tab function closeTab(filePath) { const fileData = openFiles.get(filePath); if (fileData && fileData.modified) { if (!confirm(`Save changes to ${filePath}?`)) { return; } saveFile(filePath); } // Remove tab const tab = document.getElementById(`tab-${filePath.replace(/\//g, '-')}`); if (tab) tab.remove(); // Remove from open files openFiles.delete(filePath); // If this was the active file, switch to another if (activeFile === filePath) { const remaining = Array.from(openFiles.keys()); if (remaining.length > 0) { openFile(remaining[0]); } else { activeFile = null; monacoEditor.setValue(''); updateEditorStatus('No file open'); } } } // Get file icon based on language function getFileIcon(language) { const icons = { 'html': '🌐', 'css': '🎨', 'javascript': '⚡', 'json': '📋', 'typescript': '💠', 'basic': '📊', 'python': '🐍', 'rust': '🦀', 'markdown': '📝', 'xml': '📄', 'yaml': '⚙️', 'sql': '🗃️', }; return icons[language] || '📄'; } // Save file function editorSave() { if (!activeFile) { alert('No file open'); return; } saveFile(activeFile); } function saveFile(filePath) { const fileData = openFiles.get(filePath); if (!fileData) return; // Here you would typically save to backend console.log('Saving file:', filePath, fileData.content); // Simulate save fetch('/api/editor/file/' + encodeURIComponent(filePath), { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ content: fileData.content, language: fileData.language }) }) .then(response => response.json()) .then(data => { if (data.success) { fileData.modified = false; updateTabLabel(filePath, false); updateEditorStatus('Saved'); setTimeout(() => updateEditorStatus('Ready'), 2000); } else { alert('Failed to save file: ' + (data.error || 'Unknown error')); } }) .catch(err => { console.error('Save error:', err); // For now, just mark as saved locally fileData.modified = false; updateTabLabel(filePath, false); updateEditorStatus('Saved (local)'); }); } // Publish file function editorPublish() { if (!activeFile) { alert('No file open'); return; } // Save first editorSave(); // Show deployment modal if (typeof showDeploymentModal === 'function') { showDeploymentModal(); } else { alert('Publish feature coming soon!'); } } // Close editor function closeEditor() { const container = document.getElementById('vibeEditorContainer'); if (container) { container.style.display = 'none'; } } // Show editor function showEditor() { const container = document.getElementById('vibeEditorContainer'); if (container) { container.style.display = 'flex'; } } // Update editor status function updateEditorStatus(status) { const statusEl = document.getElementById('editorStatus'); if (statusEl) { statusEl.textContent = status; } } // Create new file function createNewFile() { const fileName = prompt('Enter file name:', 'untitled.html'); if (fileName) { openFile(fileName, ''); } } // Load file tree function loadFileTree(files) { fileTree = files; renderFileTree(); } // Render file tree function renderFileTree() { const container = document.getElementById('fileTreeContent'); if (!container) return; container.innerHTML = ''; fileTree.forEach(file => { const item = document.createElement('div'); item.className = 'file-tree-item'; item.style.cssText = ` padding: 8px 16px; cursor: pointer; font-size: 13px; color: var(--text); border-left: 3px solid transparent; display: flex; align-items: center; gap: 8px; `; const icon = getFileIcon(detectLanguage(file.path)); item.innerHTML = `${icon}${file.name}`; item.addEventListener('click', () => { // Highlight selected document.querySelectorAll('.file-tree-item').forEach(i => { i.style.borderLeftColor = 'transparent'; i.style.background = 'transparent'; }); item.style.borderLeftColor = 'var(--accent)'; item.style.background = 'rgba(132, 214, 105, 0.06)'; // Load file loadFile(file.path); }); item.addEventListener('mouseenter', () => { if (item.style.borderLeftColor === 'transparent') { item.style.background = 'var(--surface-hover)'; } }); item.addEventListener('mouseleave', () => { if (item.style.borderLeftColor === 'transparent') { item.style.background = 'transparent'; } }); container.appendChild(item); }); } // Load file from server function loadFile(filePath) { fetch('/api/editor/file/' + encodeURIComponent(filePath)) .then(response => response.json()) .then(data => { if (data.success) { openFile(filePath, data.content); } else { console.error('Failed to load file:', data.error); } }) .catch(err => { console.error('Load error:', err); // Open with empty content openFile(filePath, ''); }); } // Keyboard shortcuts document.addEventListener('keydown', (e) => { if (e.ctrlKey || e.metaKey) { switch (e.key) { case 's': e.preventDefault(); editorSave(); break; case 'p': e.preventDefault(); // Quick open const file = prompt('Open file:'); if (file) openFile(file); break; } } }); // Initialize with sample files loadFileTree([ { name: 'index.html', path: 'index.html' }, { name: 'styles.css', path: 'styles.css' }, { name: 'app.js', path: 'app.js' }, ]); .9; transform: translateY(-1px); } .editor-tab:hover { background: var(--surface-hover) !important; } .file-tree-item:hover { background: var(--surface-hover); } /* Scrollbar styling */ .file-tree-content::-webkit-scrollbar, .editor-tab-bar::-webkit-scrollbar { width: 8px; height: 8px; } .file-tree-content::-webkit-scrollbar-track, .editor-tab-bar::-webkit-scrollbar-track { background: var(--bg); } .file-tree-content::-webkit-scrollbar-thumb, .editor-tab-bar::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; } .file-tree-content::-webkit-scrollbar-thumb:hover, .editor-tab-bar::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }