devportal2 / static /js /editor.js
Akay Borana
Fix input: remove CSS hacks that broke xterm.js native mobile input
dc2321b
Raw
History Blame Contribute Delete
10.2 kB
// 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'); }
}