Spaces:
Sleeping
Sleeping
| // static/js/editor.js v8 | |
| var editor; | |
| function initEditor() { | |
| var el = document.getElementById('ace-editor'); | |
| if (!el) return; | |
| // Already initialized — just resize and focus | |
| if (editor) { | |
| try { editor.resize(); editor.focus(); } catch(e) {} | |
| return; | |
| } | |
| try { | |
| editor = ace.edit('ace-editor'); | |
| } catch(e) { | |
| console.error('Ace editor init failed:', e); | |
| return; | |
| } | |
| editor.setTheme('ace/theme/tomorrow_night_eighties'); | |
| editor.session.setMode('ace/mode/python'); | |
| editor.setOptions({ | |
| fontSize: '14px', | |
| fontFamily: "'JetBrains Mono', monospace", | |
| showPrintMargin: false, | |
| enableBasicAutocompletion: true, | |
| }); | |
| var fnameInput = document.getElementById('current-filename'); | |
| if (fnameInput) fnameInput.addEventListener('input', checkRunBtn); | |
| // Force focus on click anywhere in editor area | |
| var editorContainer = document.getElementById('editor-main-container'); | |
| if (editorContainer) { | |
| editorContainer.addEventListener('click', function() { | |
| if (editor) editor.focus(); | |
| }); | |
| } | |
| // Resize + focus when editor view becomes active | |
| var obs = new MutationObserver(function() { | |
| var ev = document.getElementById('editor-view'); | |
| if (ev && ev.classList.contains('active')) { | |
| setTimeout(function() { | |
| if (editor) { try { editor.resize(); editor.focus(); } catch(e) {} } | |
| }, 150); | |
| } | |
| }); | |
| var ev = document.getElementById('editor-view'); | |
| if (ev) obs.observe(ev, { attributes: true, attributeFilter: ['class'] }); | |
| // Initial focus | |
| setTimeout(function() { | |
| if (editor) { try { editor.focus(); } catch(e) {} } | |
| }, 200); | |
| loadFiles(); | |
| } | |
| function fileIcon(f) { | |
| var m = { '.py': 'fa-brands fa-python', '.js': 'fa-brands fa-js', '.html': 'fa-brands fa-html5', '.css': 'fa-brands fa-css3-alt', '.json': 'fa-solid fa-code', '.md': 'fa-solid fa-file-lines' }; | |
| var c = { '.py': '#4B8BBE', '.js': '#F7DF1E', '.html': '#E34F26', '.css': '#1572B6', '.json': '#8BC34A', '.md': 'var(--text-2)' }; | |
| var ext = f.split('.').pop() ? '.' + f.split('.').pop() : ''; | |
| return '<i class="' + (m[ext] || 'fa-solid fa-file') + '" style="color:' + (c[ext] || 'var(--text-3)') + '"></i>'; | |
| } | |
| async function loadFiles() { | |
| if (!currentToken) return; | |
| try { | |
| var res = await fetch('/api/files', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({token: currentToken}) }); | |
| var data = await res.json(); | |
| var list = document.getElementById('file-list'); | |
| if (!list) return; | |
| list.innerHTML = ''; | |
| if (data.files && data.files.length) { | |
| data.files.forEach(function(f) { | |
| var div = document.createElement('div'); | |
| div.className = 'file-item'; | |
| div.innerHTML = fileIcon(f) + '<span class="file-item-name">' + f + '</span><button class="file-item-del" title="Delete" onclick="deleteFile(event,\'' + f + '\')"><i class="fa-solid fa-trash"></i></button>'; | |
| div.onclick = function() { openFile(f); }; | |
| list.appendChild(div); | |
| }); | |
| } else { | |
| list.innerHTML = '<div style="color:var(--text-3);font-size:12px;text-align:center;padding:var(--sp-5) 0">No files. Tap + to create.</div>'; | |
| } | |
| } catch (e) { console.error(e); } | |
| } | |
| function newFile() { | |
| document.getElementById('current-filename').value = 'untitled.html'; | |
| checkRunBtn(); | |
| if (editor) { editor.setValue('<!DOCTYPE html>\n<html>\n<head>\n <title>New</title>\n</head>\n<body>\n <h1>Hello</h1>\n</body>\n</html>', -1); editor.session.setMode('ace/mode/html'); editor.focus(); } | |
| } | |
| async function openFile(name) { | |
| document.getElementById('current-filename').value = name; | |
| checkRunBtn(); | |
| document.querySelectorAll('.file-item').forEach(function(el) { el.classList.toggle('active', el.textContent.trim() === name); }); | |
| try { | |
| var res = await fetch('/api/file/read', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({token: currentToken, filename: name}) }); | |
| var data = await res.json(); | |
| if (!data.error && editor) { | |
| editor.setValue(data.content, -1); | |
| var modes = { '.py': 'python', '.js': 'javascript', '.html': 'html', '.css': 'css', '.json': 'json' }; | |
| var ext = '.' + name.split('.').pop(); | |
| editor.session.setMode('ace/mode/' + (modes[ext] || 'text')); | |
| editor.focus(); | |
| } | |
| } catch (e) { showToast('Error reading file', 'error'); } | |
| } | |
| async function saveFile() { | |
| var name = document.getElementById('current-filename').value; | |
| var content = editor ? editor.getValue() : ''; | |
| if (!name) { showToast('Enter a filename', 'warning'); return; } | |
| var btn = document.querySelector('.ed-btn.primary'); | |
| if (btn) btn.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> Saving...'; | |
| try { | |
| var res = await fetch('/api/file/save', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({token: currentToken, filename: name, content: content}) }); | |
| var data = await res.json(); | |
| if (data.success) { showToast('Saved ' + name, 'success'); loadFiles(); if (document.getElementById('preview-container').classList.contains('visible')) runCode(); } | |
| else showToast('Error: ' + data.error, 'error'); | |
| } catch (e) { showToast('Network error', 'error'); } | |
| if (btn) btn.innerHTML = '<i class="fa-solid fa-floppy-disk"></i> Save'; | |
| } | |
| async function renameFile() { | |
| var newName = prompt('New filename:'); | |
| if (!newName) return; | |
| var old = document.getElementById('current-filename').value; | |
| try { | |
| var res = await fetch('/api/file/rename', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({token: currentToken, filename: old, new_name: newName}) }); | |
| var data = await res.json(); | |
| if (data.success) { document.getElementById('current-filename').value = newName; checkRunBtn(); loadFiles(); showToast('Renamed', 'success'); } | |
| else showToast(data.error || 'Failed', 'error'); | |
| } catch (e) { showToast('Error', 'error'); } | |
| } | |
| async function aiEdit() { | |
| var p = prompt('What should AI do?'); | |
| if (!p || !editor) return; | |
| var orig = editor.getValue(); | |
| editor.setValue('AI processing...', -1); | |
| try { | |
| var res = await fetch('/api/ai_edit', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({prompt: p, content: orig}) }); | |
| var data = await res.json(); | |
| if (data.code && !data.code.includes('NETWORK_ERROR')) { editor.setValue(data.code, -1); showToast('AI edit applied', 'success'); } | |
| else { editor.setValue(orig, -1); showToast('AI edit failed', 'error'); } | |
| } catch (e) { editor.setValue(orig, -1); showToast('Network error', 'error'); } | |
| } | |
| function checkRunBtn() { | |
| var f = document.getElementById('current-filename').value; | |
| var btn = document.getElementById('run-btn'); | |
| var show = f.endsWith('.html') || f.endsWith('.js') || f.endsWith('.css'); | |
| btn.style.display = show ? 'inline-flex' : 'none'; | |
| if (!show) closePreview(); | |
| } | |
| async function runCode() { | |
| var name = document.getElementById('current-filename').value; | |
| await saveFile(); | |
| var pc = document.getElementById('preview-container'); | |
| var iframe = document.getElementById('live-preview-frame'); | |
| pc.classList.add('visible'); | |
| if (name.endsWith('.html')) { iframe.src = '/preview/' + currentToken + '/' + name; } | |
| else { | |
| var content = editor.getValue(); | |
| var html = name.endsWith('.js') ? '<html><body><script>' + content + '<\/script></body></html>' : '<html><head><style>' + content + '</style></head><body><h1>CSS Preview</h1></body></html>'; | |
| iframe.removeAttribute('src'); | |
| var doc = iframe.contentWindow.document; doc.open(); doc.write(html); doc.close(); | |
| } | |
| } | |
| function closePreview() { document.getElementById('preview-container').classList.remove('visible'); } | |
| function toggleFullscreen() { | |
| var pc = document.getElementById('preview-container'); | |
| var icon = document.getElementById('fs-icon'); | |
| pc.classList.toggle('fullscreen'); | |
| icon.className = pc.classList.contains('fullscreen') ? 'fa-solid fa-compress' : 'fa-solid fa-expand'; | |
| } | |
| async function deleteFile(e, name) { | |
| e.stopPropagation(); | |
| if (!confirm('Delete ' + name + '?')) return; | |
| try { | |
| var res = await fetch('/api/file/delete', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({token: currentToken, filename: name}) }); | |
| var data = await res.json(); | |
| if (data.success) { showToast('Deleted ' + name, 'success'); if (document.getElementById('current-filename').value === name) { document.getElementById('current-filename').value = ''; editor.setValue('', -1); checkRunBtn(); } loadFiles(); } | |
| else showToast('Failed: ' + data.error, 'error'); | |
| } catch (e) { showToast('Network error', 'error'); } | |
| } | |
| async function createFolder() { | |
| var name = prompt('Folder name:'); | |
| if (!name) return; | |
| try { | |
| var res = await fetch('/api/folder/create', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({token: currentToken, filename: name}) }); | |
| var data = await res.json(); | |
| if (data.success) { showToast('Created ' + name, 'success'); loadFiles(); } else showToast('Failed', 'error'); | |
| } catch (e) { showToast('Error', 'error'); } | |
| } | |
| async function deleteFolder() { | |
| var name = prompt('Folder to delete:'); | |
| if (!name || !confirm('Delete "' + name + '" and all contents?')) return; | |
| try { | |
| var res = await fetch('/api/folder/delete', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({token: currentToken, filename: name}) }); | |
| var data = await res.json(); | |
| if (data.success) { showToast('Deleted ' + name, 'success'); loadFiles(); } else showToast('Failed', 'error'); | |
| } catch (e) { showToast('Error', 'error'); } | |
| } | |