botserver/ui/suite/editor.html
Rodrigo Rodriguez (Pragmatismo) 46d6ff6268 Add desktop tools, antivirus, and editor modules
- Add desktop tools module with drive cleaner, Windows optimizer, and
  Brave browser installer
- Add antivirus module with ClamAV integration and Windows Defender
  management
- Add tools.html template for settings page integration
- Add standalone editor.html for file editing
- Re-export new types from desktop and security
2025-11-29 21:23:19 -03:00

519 lines
17 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Editor - General Bots</title>
<link rel="stylesheet" href="css/app.css" />
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--bg-primary: #0f172a;
--bg-secondary: #1e293b;
--bg-tertiary: #334155;
--text-primary: #f1f5f9;
--text-secondary: #94a3b8;
--accent-color: #3b82f6;
--accent-hover: #2563eb;
--border-color: #475569;
--success: #22c55e;
--warning: #f59e0b;
--error: #ef4444;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI",
Roboto, sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
height: 100vh;
overflow: hidden;
}
.editor-container {
display: flex;
flex-direction: column;
height: 100vh;
}
.editor-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 20px;
background: var(--bg-secondary);
border-bottom: 1px solid var(--border-color);
}
.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;
color: var(--text-secondary);
}
.editor-toolbar {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 20px;
background: var(--bg-secondary);
border-bottom: 1px solid var(--border-color);
}
.toolbar-group {
display: flex;
align-items: center;
gap: 4px;
padding-right: 12px;
border-right: 1px solid var(--border-color);
}
.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;
background: var(--bg-tertiary);
color: var(--text-primary);
}
.btn:hover {
background: var(--border-color);
}
.btn-primary {
background: var(--accent-color);
color: white;
}
.btn-primary:hover {
background: var(--accent-hover);
}
.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;
background: var(--bg-secondary);
border-right: 1px solid var(--border-color);
padding: 16px 8px;
overflow: hidden;
text-align: right;
user-select: none;
font-family: "Consolas", monospace;
font-size: 13px;
line-height: 1.6;
color: var(--text-secondary);
}
.text-editor {
flex: 1;
background: var(--bg-primary);
color: var(--text-primary);
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 {
border: 1px solid var(--border-color);
padding: 0;
min-width: 120px;
}
.csv-table th {
background: var(--bg-tertiary);
font-weight: 600;
}
.csv-table .row-num {
width: 40px;
min-width: 40px;
background: var(--bg-secondary);
color: var(--text-secondary);
text-align: center;
padding: 8px 4px;
font-size: 12px;
}
.csv-input {
width: 100%;
background: transparent;
border: none;
color: var(--text-primary);
padding: 8px 12px;
font-size: 13px;
outline: none;
}
.csv-input:focus {
background: var(--bg-secondary);
}
.status-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 6px 16px;
background: var(--bg-secondary);
border-top: 1px solid var(--border-color);
font-size: 12px;
color: var(--text-secondary);
}
.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;
background: var(--bg-tertiary);
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
border-left: 4px solid var(--accent-color);
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);
}
</style>
</head>
<body>
<div class="editor-container">
<!-- Header -->
<div class="editor-header">
<div class="editor-title">
<span class="editor-title-icon">📝</span>
<div>
<span
class="editor-title-text"
id="editor-filename"
hx-get="/api/v1/editor/filename"
hx-trigger="load"
hx-swap="innerHTML"></div>
Untitled
</span>
<div
class="editor-path"
id="editor-filepath"
hx-get="/api/v1/editor/filepath"
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"
hx-post="/api/v1/editor/save"
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"
hx-get="/api/v1/editor/save-as"
hx-target="#save-dialog"
hx-swap="innerHTML">
Save As
</button>
</div>
<div class="toolbar-group">
<button
class="btn btn-small"
hx-post="/api/v1/editor/undo"
hx-target="#editor-content"
hx-swap="innerHTML">
↩️ Undo
</button>
<button
class="btn btn-small"
hx-post="/api/v1/editor/redo"
hx-target="#editor-content"
hx-swap="innerHTML">
↪️ Redo
</button>
</div>
<div class="toolbar-group" id="text-tools">
<button
class="btn btn-small"
hx-post="/api/v1/editor/format"
hx-include="#text-editor"
hx-target="#text-editor"
hx-swap="innerHTML">
{ } Format
</button>
</div>
<div class="toolbar-group" id="csv-tools" style="display: none;">
<button
class="btn btn-small"
hx-post="/api/v1/editor/csv/add-row"
hx-target="#csv-table-body"
hx-swap="beforeend">
Row
</button>
<button
class="btn btn-small"
hx-post="/api/v1/editor/csv/add-column"
hx-target="#csv-editor"
hx-swap="innerHTML">
Column
</button>
</div>
</div>
<!-- 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"
hx-get="/api/v1/editor/line-numbers"
hx-trigger="keyup from:#text-editor delay:100ms"
hx-swap="innerHTML">
1
</div>
<textarea
class="text-editor"
id="text-editor"
name="content"
spellcheck="false"
hx-post="/api/v1/editor/autosave"
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"
hx-post="/api/v1/editor/csv/update-header"
hx-trigger="change"
hx-swap="none">
</th>
</tr>
</thead>
<tbody
id="csv-table-body"
hx-get="/api/v1/editor/csv/rows"
hx-trigger="load"
hx-swap="innerHTML">
</tbody>
</table>
</div>
</div>
<!-- Status Bar -->
<div class="status-bar">
<div class="status-left">
<span
id="file-type"
hx-get="/api/v1/editor/filetype"
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"
hx-get="/api/v1/editor/position"
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();
htmx.trigger(document.querySelector('[hx-post="/api/v1/editor/save"]'), 'click');
}
});
</script>
</body>
</html>