2026-01-06 22:57:00 -03:00
<!-- Editor - General Bots (Code & Text Editor) -->
< style >
/* Editor uses global theme variables from base.css */
2025-12-03 18:42:22 -03:00
.editor-container {
display: flex;
flex-direction: column;
2026-01-06 22:57:00 -03:00
height: calc(100vh - 64px);
background: var(--bg, #0f172a);
color: var(--text, #f8fafc);
2025-12-03 18:42:22 -03:00
}
.editor-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 20px;
2026-01-06 22:57:00 -03:00
background: var(--surface, #1e293b);
border-bottom: 1px solid var(--border, #334155);
2025-12-03 18:42:22 -03:00
}
.editor-title {
display: flex;
align-items: center;
gap: 12px;
}
.editor-title-icon {
font-size: 24px;
}
.editor-title-text {
font-size: 16px;
font-weight: 600;
}
.editor-path {
font-size: 12px;
2026-01-06 22:57:00 -03:00
color: var(--text-secondary, #94a3b8);
2025-12-03 18:42:22 -03:00
}
.editor-toolbar {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 20px;
2026-01-06 22:57:00 -03:00
background: var(--surface, #1e293b);
border-bottom: 1px solid var(--border, #334155);
2025-12-03 18:42:22 -03:00
}
.toolbar-group {
display: flex;
align-items: center;
gap: 4px;
padding-right: 12px;
2026-01-06 22:57:00 -03:00
border-right: 1px solid var(--border, #334155);
2025-12-03 18:42:22 -03:00
}
.toolbar-group:last-child {
border-right: none;
}
.btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 14px;
border: none;
border-radius: 6px;
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
2026-01-06 22:57:00 -03:00
background: var(--surface-hover, #334155);
color: var(--text, #f8fafc);
2025-12-03 18:42:22 -03:00
}
.btn:hover {
2026-01-06 22:57:00 -03:00
background: var(--border-light, #475569);
2025-12-03 18:42:22 -03:00
}
.btn-primary {
2026-01-06 22:57:00 -03:00
background: var(--primary, #3b82f6);
2025-12-03 18:42:22 -03:00
color: white;
}
.btn-primary:hover {
2026-01-06 22:57:00 -03:00
background: var(--primary-hover, #2563eb);
2025-12-03 18:42:22 -03:00
}
2025-12-28 11:50:52 -03:00
.btn-magic {
background: linear-gradient(135deg, #8b5cf6, #3b82f6);
color: white;
border: none;
}
.btn-magic:hover {
background: linear-gradient(135deg, #3b82f6, #8b5cf6);
transform: scale(1.02);
}
.magic-panel {
position: fixed;
right: 20px;
bottom: 60px;
width: 400px;
max-height: 500px;
2026-01-06 22:57:00 -03:00
background: var(--surface, #1e293b);
border: 1px solid var(--border, #334155);
2025-12-28 11:50:52 -03:00
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0,0,0,0.4);
z-index: 1000;
display: none;
flex-direction: column;
overflow: hidden;
}
.magic-panel.visible {
display: flex;
}
.magic-header {
padding: 16px;
2026-01-06 22:57:00 -03:00
border-bottom: 1px solid var(--border, #334155);
2025-12-28 11:50:52 -03:00
display: flex;
align-items: center;
justify-content: space-between;
background: linear-gradient(135deg, rgba(139,92,246,0.1), rgba(59,130,246,0.1));
}
.magic-header h3 {
font-size: 14px;
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
}
.magic-close {
background: none;
border: none;
2026-01-06 22:57:00 -03:00
color: var(--text-secondary, #94a3b8);
2025-12-28 11:50:52 -03:00
cursor: pointer;
font-size: 18px;
}
.magic-content {
flex: 1;
overflow-y: auto;
padding: 16px;
}
.magic-loading {
text-align: center;
padding: 40px;
2026-01-06 22:57:00 -03:00
color: var(--text-secondary, #94a3b8);
2025-12-28 11:50:52 -03:00
}
.magic-result {
2026-01-06 22:57:00 -03:00
background: var(--surface-hover, #334155);
2025-12-28 11:50:52 -03:00
border-radius: 8px;
padding: 12px;
margin-bottom: 12px;
}
.magic-result pre {
font-family: monospace;
font-size: 12px;
white-space: pre-wrap;
overflow-x: auto;
}
.magic-apply-btn {
background: var(--success);
color: white;
border: none;
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
margin-top: 12px;
}
2025-12-03 18:42:22 -03:00
.btn-small {
padding: 6px 10px;
font-size: 12px;
}
.editor-content {
flex: 1;
display: flex;
overflow: hidden;
}
.editor-wrapper {
display: flex;
flex: 1;
overflow: hidden;
}
.line-numbers {
width: 50px;
2026-01-06 22:57:00 -03:00
background: var(--surface, #1e293b);
border-right: 1px solid var(--border, #334155);
2025-12-03 18:42:22 -03:00
padding: 16px 8px;
overflow: hidden;
text-align: right;
user-select: none;
font-family: "Consolas", monospace;
font-size: 13px;
line-height: 1.6;
2026-01-06 22:57:00 -03:00
color: var(--text-secondary, #94a3b8);
2025-12-03 18:42:22 -03:00
}
.text-editor {
flex: 1;
2026-01-06 22:57:00 -03:00
background: var(--bg, #0f172a);
color: var(--text, #f8fafc);
2025-12-03 18:42:22 -03:00
border: none;
padding: 16px;
font-family: "Consolas", monospace;
font-size: 13px;
line-height: 1.6;
resize: none;
outline: none;
white-space: pre;
overflow: auto;
tab-size: 4;
}
.csv-editor {
flex: 1;
overflow: auto;
padding: 16px;
}
.csv-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
.csv-table th,
.csv-table td {
2026-01-06 22:57:00 -03:00
border: 1px solid var(--border, #334155);
2025-12-03 18:42:22 -03:00
padding: 0;
min-width: 120px;
}
.csv-table th {
2026-01-06 22:57:00 -03:00
background: var(--surface-hover, #334155);
2025-12-03 18:42:22 -03:00
font-weight: 600;
}
.csv-table .row-num {
width: 40px;
min-width: 40px;
2026-01-06 22:57:00 -03:00
background: var(--surface, #1e293b);
color: var(--text-secondary, #94a3b8);
2025-12-03 18:42:22 -03:00
text-align: center;
padding: 8px 4px;
font-size: 12px;
}
.csv-input {
width: 100%;
background: transparent;
border: none;
2026-01-06 22:57:00 -03:00
color: var(--text, #f8fafc);
2025-12-03 18:42:22 -03:00
padding: 8px 12px;
font-size: 13px;
outline: none;
}
.csv-input:focus {
2026-01-06 22:57:00 -03:00
background: var(--surface, #1e293b);
2025-12-03 18:42:22 -03:00
}
.status-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 6px 16px;
2026-01-06 22:57:00 -03:00
background: var(--surface, #1e293b);
border-top: 1px solid var(--border, #334155);
2025-12-03 18:42:22 -03:00
font-size: 12px;
2026-01-06 22:57:00 -03:00
color: var(--text-secondary, #94a3b8);
2025-12-03 18:42:22 -03:00
}
.status-left,
.status-right {
display: flex;
align-items: center;
gap: 16px;
}
.dirty-indicator {
width: 8px;
height: 8px;
background: var(--warning);
border-radius: 50%;
margin-left: 8px;
}
.htmx-indicator {
display: none;
}
.htmx-request .htmx-indicator {
display: inline-block;
}
.spinner {
width: 14px;
height: 14px;
border: 2px solid transparent;
border-top-color: currentColor;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.notification {
position: fixed;
bottom: 60px;
right: 20px;
padding: 12px 20px;
2026-01-06 22:57:00 -03:00
background: var(--surface-hover, #334155);
2025-12-03 18:42:22 -03:00
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
2026-01-06 22:57:00 -03:00
border-left: 4px solid var(--primary, #3b82f6);
2025-12-03 18:42:22 -03:00
opacity: 0;
transform: translateX(100%);
transition: all 0.3s ease;
}
.notification.show {
opacity: 1;
transform: translateX(0);
}
.notification.success {
border-left-color: var(--success);
}
.notification.error {
border-left-color: var(--error);
}
2026-01-06 22:57:00 -03:00
< / style >
< div class = "editor-container" >
2025-12-03 18:42:22 -03:00
<!-- Header -->
< div class = "editor-header" >
< div class = "editor-title" >
< span class = "editor-title-icon" > 📝< / span >
< div >
< span
class="editor-title-text"
id="editor-filename"
2026-01-10 10:54:05 -03:00
hx-get="/api/editor/filename"
2025-12-03 18:42:22 -03:00
hx-trigger="load"
hx-swap="innerHTML">< / div >
Untitled
< / span >
< div
class="editor-path"
id="editor-filepath"
2026-01-10 10:54:05 -03:00
hx-get="/api/editor/filepath"
2025-12-03 18:42:22 -03:00
hx-trigger="load"
hx-swap="innerHTML">
< / div >
< / div >
< span
class="dirty-indicator"
id="dirty-indicator"
style="display: none;"
title="Unsaved changes">
< / span >
< / div >
< div >
< a href = "#drive"
class="btn btn-small"
hx-get="/api/drive/list"
hx-target="#main-content"
hx-push-url="true">
✕ Close
< / a >
< / div >
< / div >
<!-- Toolbar -->
< div class = "editor-toolbar" >
< div class = "toolbar-group" >
< button
class="btn btn-primary btn-small"
2026-01-10 10:54:05 -03:00
hx-post="/api/editor/save"
2025-12-03 18:42:22 -03:00
hx-include="#text-editor"
hx-indicator="#save-spinner"
hx-swap="none"
hx-on::after-request="showSaveNotification(event)">
< span class = "htmx-indicator spinner" id = "save-spinner" > < / span >
💾 Save
< / button >
< button
class="btn btn-small"
2026-01-10 10:54:05 -03:00
hx-get="/api/editor/save-as"
2025-12-03 18:42:22 -03:00
hx-target="#save-dialog"
hx-swap="innerHTML">
Save As
< / button >
< / div >
< div class = "toolbar-group" >
< button
class="btn btn-small"
2026-01-10 10:54:05 -03:00
hx-post="/api/editor/undo"
2025-12-03 18:42:22 -03:00
hx-target="#editor-content"
hx-swap="innerHTML">
↩️ Undo
< / button >
< button
class="btn btn-small"
2026-01-10 10:54:05 -03:00
hx-post="/api/editor/redo"
2025-12-03 18:42:22 -03:00
hx-target="#editor-content"
hx-swap="innerHTML">
↪️ Redo
< / button >
< / div >
< div class = "toolbar-group" id = "text-tools" >
< button
class="btn btn-small"
2026-01-10 10:54:05 -03:00
hx-post="/api/editor/format"
2025-12-03 18:42:22 -03:00
hx-include="#text-editor"
hx-target="#text-editor"
hx-swap="innerHTML">
{ } Format
< / button >
2025-12-28 11:50:52 -03:00
< button
class="btn btn-small btn-magic"
onclick="showMagicPanel()"
title="AI Improvements (Ctrl+M)">
✨ Magic
< / button >
2025-12-03 18:42:22 -03:00
< / div >
< div class = "toolbar-group" id = "csv-tools" style = "display: none;" >
< button
class="btn btn-small"
2026-01-10 10:54:05 -03:00
hx-post="/api/editor/csv/add-row"
2025-12-03 18:42:22 -03:00
hx-target="#csv-table-body"
hx-swap="beforeend">
➕ Row
< / button >
< button
class="btn btn-small"
2026-01-10 10:54:05 -03:00
hx-post="/api/editor/csv/add-column"
2025-12-03 18:42:22 -03:00
hx-target="#csv-editor"
hx-swap="innerHTML">
➕ Column
< / button >
< / div >
< / div >
2025-12-28 11:50:52 -03:00
<!-- Magic AI Panel -->
< div class = "magic-panel" id = "magic-panel" >
< div class = "magic-header" >
< h3 > ✨ AI Improvements< / h3 >
< button class = "magic-close" onclick = "hideMagicPanel()" > × < / button >
< / div >
< div class = "magic-content" id = "magic-content" >
< div class = "magic-loading" > Analyzing code...< / div >
< / div >
< / div >
2025-12-03 18:42:22 -03:00
<!-- Editor Content - loaded via HTMX based on file type -->
< div class = "editor-content" id = "editor-content" >
<!-- Text Editor (default) -->
< div class = "editor-wrapper" id = "text-editor-wrapper" >
< div
class="line-numbers"
id="line-numbers"
2026-01-10 10:54:05 -03:00
hx-get="/api/editor/line-numbers"
2025-12-03 18:42:22 -03:00
hx-trigger="keyup from:#text-editor delay:100ms"
hx-swap="innerHTML">
1
< / div >
< textarea
class="text-editor"
id="text-editor"
name="content"
spellcheck="false"
2026-01-10 10:54:05 -03:00
hx-post="/api/editor/autosave"
2025-12-03 18:42:22 -03:00
hx-trigger="keyup changed delay:5s"
hx-swap="none"
hx-indicator="#autosave-indicator"
placeholder="Start typing or open a file...">< / textarea >
< / div >
<!-- CSV Editor (shown for .csv files) -->
< div class = "csv-editor" id = "csv-editor" style = "display: none;" >
< table class = "csv-table" >
< thead id = "csv-table-head" >
< tr >
< th class = "row-num" > #< / th >
< th >
< input
type="text"
class="csv-input"
name="header_0"
value="Column 1"
2026-01-10 10:54:05 -03:00
hx-post="/api/editor/csv/update-header"
2025-12-03 18:42:22 -03:00
hx-trigger="change"
hx-swap="none">
< / th >
< / tr >
< / thead >
< tbody
id="csv-table-body"
2026-01-10 10:54:05 -03:00
hx-get="/api/editor/csv/rows"
2025-12-03 18:42:22 -03:00
hx-trigger="load"
hx-swap="innerHTML">
< / tbody >
< / table >
< / div >
< / div >
<!-- Status Bar -->
< div class = "status-bar" >
< div class = "status-left" >
< span
id="file-type"
2026-01-10 10:54:05 -03:00
hx-get="/api/editor/filetype"
2025-12-03 18:42:22 -03:00
hx-trigger="load"
hx-swap="innerHTML">
📄 Plain Text
< / span >
< span > UTF-8< / span >
< span
id="autosave-indicator"
class="htmx-indicator"
style="font-size: 11px;">
Saving...
< / span >
< / div >
< div class = "status-right" >
< span
id="cursor-position"
2026-01-10 10:54:05 -03:00
hx-get="/api/editor/position"
2025-12-03 18:42:22 -03:00
hx-trigger="click from:#text-editor, keyup from:#text-editor"
hx-swap="innerHTML">
Ln 1, Col 1
< / span >
< / div >
< / div >
< / div >
<!-- Save Dialog (loaded via HTMX) -->
< div id = "save-dialog" > < / div >
<!-- Notification -->
< div class = "notification" id = "notification" > < / div >
< script >
// Minimal JS for notification display (could be replaced with htmx extension)
function showSaveNotification(event) {
const notification = document.getElementById('notification');
if (event.detail.successful) {
notification.textContent = '✓ File saved';
notification.className = 'notification success show';
document.getElementById('dirty-indicator').style.display = 'none';
} else {
notification.textContent = '✗ Save failed';
notification.className = 'notification error show';
}
setTimeout(() => notification.classList.remove('show'), 3000);
}
// Mark as dirty on edit
document.getElementById('text-editor')?.addEventListener('input', function() {
document.getElementById('dirty-indicator').style.display = 'inline-block';
});
// Keyboard shortcuts using htmx triggers
document.addEventListener('keydown', function(e) {
if ((e.ctrlKey || e.metaKey) & & e.key === 's') {
e.preventDefault();
2026-01-10 10:54:05 -03:00
htmx.trigger(document.querySelector('[hx-post="/api/editor/save"]'), 'click');
2025-12-03 18:42:22 -03:00
}
});
2025-12-28 11:50:52 -03:00
function showMagicPanel() {
document.getElementById('magic-panel').classList.add('visible');
runMagicAnalysis();
}
function hideMagicPanel() {
document.getElementById('magic-panel').classList.remove('visible');
}
async function runMagicAnalysis() {
const content = document.getElementById('magic-content');
const code = document.getElementById('text-editor').value;
if (!code.trim()) {
2026-01-06 22:57:00 -03:00
content.innerHTML = '< p style = "color:var(--text-secondary, #94a3b8);text-align:center;padding:40px;" > No code to analyze. Start typing or open a file.< / p > ';
2025-12-28 11:50:52 -03:00
return;
}
content.innerHTML = '< div class = "magic-loading" > ✨ Analyzing your code...< / div > ';
try {
2026-01-10 10:54:05 -03:00
const response = await fetch('/api/editor/magic', {
2025-12-28 11:50:52 -03:00
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code: code })
});
if (response.ok) {
const result = await response.json();
renderMagicResult(result);
} else {
content.innerHTML = '< p style = "color:var(--error);padding:20px;" > Failed to analyze. Try again.< / p > ';
}
} catch (e) {
content.innerHTML = '< p style = "color:var(--error);padding:20px;" > Error connecting to AI service.< / p > ';
}
}
function renderMagicResult(result) {
const content = document.getElementById('magic-content');
if (result.improved_code) {
content.innerHTML = `
< div class = "magic-result" >
< p > < strong > Suggested improvements:< / strong > < / p >
2026-01-06 22:57:00 -03:00
< p style = "color:var(--text-secondary, #94a3b8);margin:8px 0;" > ${result.explanation || 'Improved code structure and patterns.'}< / p >
2025-12-28 11:50:52 -03:00
< pre > ${escapeHtml(result.improved_code)}< / pre >
< button class = "magic-apply-btn" onclick = "applyMagicCode()" > Apply Changes< / button >
< / div >
`;
window.magicImprovedCode = result.improved_code;
} else if (result.suggestions) {
content.innerHTML = result.suggestions.map(s => `
< div class = "magic-result" >
< p > < strong > ${s.title}< / strong > < / p >
2026-01-06 22:57:00 -03:00
< p style = "color:var(--text-secondary, #94a3b8);" > ${s.description}< / p >
2025-12-28 11:50:52 -03:00
< / div >
`).join('');
} else {
content.innerHTML = '< p style = "padding:20px;" > Your code looks good! No suggestions at this time.< / p > ';
}
}
function applyMagicCode() {
if (window.magicImprovedCode) {
document.getElementById('text-editor').value = window.magicImprovedCode;
hideMagicPanel();
}
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
document.addEventListener('keydown', (e) => {
if (e.ctrlKey & & e.key === 'm') {
e.preventDefault();
showMagicPanel();
}
});
2025-12-03 18:42:22 -03:00
< / script >