Initial commit - workspace setup
- AGENTS.md, SOUL.md, USER.md, IDENTITY.md - ANAF monitor (declarații fiscale) - Kanban board + Notes UI - Email tools - Memory system
This commit is contained in:
633
kanban/files.html
Normal file
633
kanban/files.html
Normal file
@@ -0,0 +1,633 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ro">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Echo · Files</title>
|
||||
<link rel="stylesheet" href="common.css">
|
||||
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.min.js"></script>
|
||||
<script src="swipe-nav.js"></script>
|
||||
<style>
|
||||
.main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - 52px);
|
||||
}
|
||||
|
||||
/* Toolbar */
|
||||
.toolbar {
|
||||
padding: var(--space-3) var(--space-5);
|
||||
background: var(--bg-surface);
|
||||
border-bottom: 1px solid var(--border);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: var(--space-4);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
font-size: var(--text-sm);
|
||||
color: var(--text-muted);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.breadcrumb-item {
|
||||
color: var(--text-muted);
|
||||
text-decoration: none;
|
||||
padding: var(--space-1) var(--space-2);
|
||||
border-radius: var(--radius-sm);
|
||||
transition: all var(--transition-fast);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.breadcrumb-item:hover {
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-surface-hover);
|
||||
}
|
||||
|
||||
.breadcrumb-item.current {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.breadcrumb-sep {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.breadcrumb-sep svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.toolbar-actions {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
/* View modes */
|
||||
.view-toggle {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
background: var(--bg-base);
|
||||
border-radius: var(--radius-md);
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.view-btn {
|
||||
padding: var(--space-2);
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
border-radius: var(--radius-sm);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.view-btn:hover {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.view-btn.active {
|
||||
background: var(--accent);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.view-btn svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
/* Content area */
|
||||
.content-area {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Browse panel */
|
||||
.browse-panel {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
padding: var(--space-5);
|
||||
}
|
||||
|
||||
.browse-panel.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.file-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.file-item {
|
||||
background: var(--bg-surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--space-4);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.file-item:hover {
|
||||
border-color: var(--accent);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.file-item.active {
|
||||
border-color: var(--accent);
|
||||
background: var(--accent-subtle);
|
||||
}
|
||||
|
||||
.file-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.file-icon svg {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.file-icon.folder svg {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.file-name {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--text-primary);
|
||||
word-break: break-word;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.file-size {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* Editor panel */
|
||||
.editor-panel {
|
||||
flex: 1;
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
background: var(--bg-base);
|
||||
}
|
||||
|
||||
.editor-panel.active {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.editor-header {
|
||||
padding: var(--space-3) var(--space-5);
|
||||
background: var(--bg-surface);
|
||||
border-bottom: 1px solid var(--border);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.editor-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
color: var(--text-primary);
|
||||
font-size: var(--text-sm);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.editor-title svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.editor-actions {
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.editor-body {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#codeEditor {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: transparent;
|
||||
color: var(--text-secondary);
|
||||
border: none;
|
||||
padding: var(--space-5);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
resize: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.editor-footer {
|
||||
padding: var(--space-2) var(--space-5);
|
||||
background: var(--bg-surface);
|
||||
border-top: 1px solid var(--border);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.status-saved { color: var(--success); }
|
||||
.status-modified { color: var(--warning); }
|
||||
.status-error { color: var(--error); }
|
||||
|
||||
/* Empty state */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
color: var(--text-muted);
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.empty-state svg {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.toolbar {
|
||||
padding: var(--space-3);
|
||||
}
|
||||
|
||||
.file-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header class="header">
|
||||
<a href="index.html" class="logo">
|
||||
<i data-lucide="circle-dot"></i>
|
||||
Echo
|
||||
</a>
|
||||
<nav class="nav">
|
||||
<a href="index.html" class="nav-item">
|
||||
<i data-lucide="layout-list"></i>
|
||||
<span>Tasks</span>
|
||||
</a>
|
||||
<a href="notes.html" class="nav-item">
|
||||
<i data-lucide="file-text"></i>
|
||||
<span>Notes</span>
|
||||
</a>
|
||||
<a href="files.html" class="nav-item active">
|
||||
<i data-lucide="folder"></i>
|
||||
<span>Files</span>
|
||||
</a>
|
||||
<button class="theme-toggle" onclick="toggleTheme()" title="Schimbă tema">
|
||||
<i data-lucide="sun" id="themeIcon"></i>
|
||||
</button>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main class="main">
|
||||
<div class="toolbar">
|
||||
<div class="breadcrumb" id="breadcrumb">
|
||||
<span class="breadcrumb-item current" onclick="loadPath('')">~/clawd</span>
|
||||
</div>
|
||||
<div class="toolbar-actions">
|
||||
<div class="view-toggle">
|
||||
<button class="view-btn active" id="browseBtn" onclick="showBrowse()" title="Browse">
|
||||
<i data-lucide="layout-grid"></i>
|
||||
</button>
|
||||
<button class="view-btn" id="editorBtn" onclick="showEditor()" title="Editor">
|
||||
<i data-lucide="code"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content-area">
|
||||
<div class="browse-panel" id="browsePanel">
|
||||
<div class="file-grid" id="fileGrid">
|
||||
<div class="empty-state">
|
||||
<i data-lucide="loader"></i>
|
||||
<p>Se încarcă...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="editor-panel" id="editorPanel">
|
||||
<div class="editor-header">
|
||||
<div class="editor-title">
|
||||
<i data-lucide="file"></i>
|
||||
<span id="editorFileName">Niciun fișier</span>
|
||||
</div>
|
||||
<div class="editor-actions">
|
||||
<button class="btn btn-ghost" onclick="showBrowse()" title="Înapoi">
|
||||
<i data-lucide="arrow-left"></i>
|
||||
</button>
|
||||
<button class="btn btn-ghost" onclick="reloadFile()" id="reloadBtn" disabled title="Reload">
|
||||
<i data-lucide="refresh-cw"></i>
|
||||
</button>
|
||||
<button class="btn btn-primary" onclick="saveFile()" id="saveBtn" disabled>
|
||||
<i data-lucide="save"></i>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="editor-body">
|
||||
<textarea id="codeEditor" placeholder="Selectează un fișier..."></textarea>
|
||||
</div>
|
||||
<div class="editor-footer">
|
||||
<span id="statusText">Ready</span>
|
||||
<span id="fileInfo"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
// Theme
|
||||
function initTheme() {
|
||||
const saved = localStorage.getItem('theme') || 'dark';
|
||||
document.documentElement.setAttribute('data-theme', saved);
|
||||
updateThemeIcon(saved);
|
||||
}
|
||||
|
||||
function toggleTheme() {
|
||||
const current = document.documentElement.getAttribute('data-theme') || 'dark';
|
||||
const next = current === 'dark' ? 'light' : 'dark';
|
||||
document.documentElement.setAttribute('data-theme', next);
|
||||
localStorage.setItem('theme', next);
|
||||
updateThemeIcon(next);
|
||||
}
|
||||
|
||||
function updateThemeIcon(theme) {
|
||||
const icon = document.getElementById('themeIcon');
|
||||
if (icon) {
|
||||
icon.setAttribute('data-lucide', theme === 'dark' ? 'sun' : 'moon');
|
||||
lucide.createIcons();
|
||||
}
|
||||
}
|
||||
|
||||
initTheme();
|
||||
lucide.createIcons();
|
||||
|
||||
const API_BASE = window.location.pathname.includes('/echo/') ? '/echo' : '';
|
||||
let currentPath = '';
|
||||
let currentFile = null;
|
||||
let originalContent = '';
|
||||
let isModified = false;
|
||||
|
||||
function showBrowse() {
|
||||
if (isModified && !confirm('Ai modificări nesalvate. Continui?')) return;
|
||||
document.getElementById('browsePanel').classList.remove('hidden');
|
||||
document.getElementById('editorPanel').classList.remove('active');
|
||||
document.getElementById('browseBtn').classList.add('active');
|
||||
document.getElementById('editorBtn').classList.remove('active');
|
||||
}
|
||||
|
||||
function showEditor() {
|
||||
document.getElementById('browsePanel').classList.add('hidden');
|
||||
document.getElementById('editorPanel').classList.add('active');
|
||||
document.getElementById('browseBtn').classList.remove('active');
|
||||
document.getElementById('editorBtn').classList.add('active');
|
||||
}
|
||||
|
||||
async function loadPath(path = '') {
|
||||
currentPath = path;
|
||||
updateBreadcrumb();
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/api/files?path=${encodeURIComponent(path)}&action=list`);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.error) {
|
||||
showError(data.error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.type === 'dir') {
|
||||
renderFileGrid(data.items);
|
||||
updateURL(path);
|
||||
} else if (data.type === 'file') {
|
||||
openFile(path, data);
|
||||
}
|
||||
} catch (e) {
|
||||
showError('Eroare: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
function updateBreadcrumb() {
|
||||
const breadcrumb = document.getElementById('breadcrumb');
|
||||
const parts = currentPath.split('/').filter(p => p);
|
||||
|
||||
let html = `<span class="breadcrumb-item ${parts.length === 0 ? 'current' : ''}" onclick="loadPath('')">~/clawd</span>`;
|
||||
let buildPath = '';
|
||||
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
const part = parts[i];
|
||||
buildPath += (buildPath ? '/' : '') + part;
|
||||
const p = buildPath;
|
||||
const isCurrent = i === parts.length - 1;
|
||||
html += `
|
||||
<span class="breadcrumb-sep"><i data-lucide="chevron-right"></i></span>
|
||||
<span class="breadcrumb-item ${isCurrent ? 'current' : ''}" onclick="loadPath('${p}')">${part}</span>
|
||||
`;
|
||||
}
|
||||
|
||||
breadcrumb.innerHTML = html;
|
||||
lucide.createIcons();
|
||||
}
|
||||
|
||||
function renderFileGrid(items) {
|
||||
const grid = document.getElementById('fileGrid');
|
||||
|
||||
if (items.length === 0) {
|
||||
grid.innerHTML = `
|
||||
<div class="empty-state">
|
||||
<i data-lucide="folder-open"></i>
|
||||
<p>Folder gol</p>
|
||||
</div>
|
||||
`;
|
||||
lucide.createIcons();
|
||||
return;
|
||||
}
|
||||
|
||||
items.sort((a, b) => {
|
||||
if (a.type !== b.type) return a.type === 'dir' ? -1 : 1;
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
|
||||
grid.innerHTML = items.map(item => `
|
||||
<div class="file-item ${currentFile === item.path ? 'active' : ''}" onclick="handleClick('${item.path}', '${item.type}')">
|
||||
<div class="file-icon ${item.type === 'dir' ? 'folder' : ''}">
|
||||
<i data-lucide="${item.type === 'dir' ? 'folder' : getFileIcon(item.name)}"></i>
|
||||
</div>
|
||||
<div class="file-name">${item.name}</div>
|
||||
${item.size ? `<div class="file-size">${formatSize(item.size)}</div>` : ''}
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
lucide.createIcons();
|
||||
}
|
||||
|
||||
function getFileIcon(name) {
|
||||
const ext = name.split('.').pop().toLowerCase();
|
||||
const icons = {
|
||||
'md': 'file-text',
|
||||
'txt': 'file-text',
|
||||
'json': 'file-json',
|
||||
'js': 'file-code',
|
||||
'py': 'file-code',
|
||||
'html': 'file-code',
|
||||
'css': 'file-code',
|
||||
'sh': 'terminal',
|
||||
'yml': 'file-cog',
|
||||
'yaml': 'file-cog',
|
||||
'log': 'file-text',
|
||||
'xsd': 'file-code',
|
||||
'pdf': 'file-text'
|
||||
};
|
||||
return icons[ext] || 'file';
|
||||
}
|
||||
|
||||
function formatSize(bytes) {
|
||||
if (bytes < 1024) return bytes + ' B';
|
||||
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
|
||||
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
|
||||
}
|
||||
|
||||
function handleClick(path, type) {
|
||||
if (type === 'dir') {
|
||||
loadPath(path);
|
||||
} else {
|
||||
if (isModified && !confirm('Ai modificări nesalvate. Continui?')) return;
|
||||
loadPath(path);
|
||||
}
|
||||
}
|
||||
|
||||
function openFile(path, data) {
|
||||
currentFile = path;
|
||||
originalContent = data.content;
|
||||
updateURL(path);
|
||||
|
||||
document.getElementById('editorFileName').textContent = data.name;
|
||||
document.getElementById('codeEditor').value = data.content;
|
||||
document.getElementById('saveBtn').disabled = false;
|
||||
document.getElementById('reloadBtn').disabled = false;
|
||||
document.getElementById('fileInfo').textContent = formatSize(data.size);
|
||||
|
||||
if (data.truncated) {
|
||||
setStatus('Fișier trunchiat', 'error');
|
||||
} else {
|
||||
setStatus('Loaded', 'saved');
|
||||
}
|
||||
|
||||
isModified = false;
|
||||
showEditor();
|
||||
}
|
||||
|
||||
async function saveFile() {
|
||||
if (!currentFile) return;
|
||||
|
||||
const content = document.getElementById('codeEditor').value;
|
||||
|
||||
try {
|
||||
document.getElementById('saveBtn').disabled = true;
|
||||
setStatus('Se salvează...', 'modified');
|
||||
|
||||
const response = await fetch(`${API_BASE}/api/files`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ path: currentFile, content })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.error) {
|
||||
setStatus('Eroare: ' + data.error, 'error');
|
||||
} else {
|
||||
originalContent = content;
|
||||
isModified = false;
|
||||
setStatus('Salvat ✓', 'saved');
|
||||
}
|
||||
} catch (e) {
|
||||
setStatus('Eroare: ' + e.message, 'error');
|
||||
}
|
||||
|
||||
document.getElementById('saveBtn').disabled = false;
|
||||
}
|
||||
|
||||
function reloadFile() {
|
||||
if (currentFile && confirm('Renunți la modificări?')) {
|
||||
loadPath(currentFile);
|
||||
}
|
||||
}
|
||||
|
||||
function setStatus(text, type) {
|
||||
const status = document.getElementById('statusText');
|
||||
status.textContent = text;
|
||||
status.className = 'status-' + type;
|
||||
}
|
||||
|
||||
function showError(msg) {
|
||||
document.getElementById('fileGrid').innerHTML = `
|
||||
<div class="empty-state">
|
||||
<i data-lucide="alert-circle"></i>
|
||||
<p style="color: var(--error)">${msg}</p>
|
||||
</div>
|
||||
`;
|
||||
lucide.createIcons();
|
||||
}
|
||||
|
||||
function updateURL(path) {
|
||||
if (path) {
|
||||
history.replaceState(null, '', `#${path}`);
|
||||
} else {
|
||||
history.replaceState(null, '', window.location.pathname);
|
||||
}
|
||||
}
|
||||
|
||||
function getPathFromURL() {
|
||||
const hash = window.location.hash;
|
||||
return hash ? decodeURIComponent(hash.slice(1)) : '';
|
||||
}
|
||||
|
||||
window.addEventListener('hashchange', () => loadPath(getPathFromURL()));
|
||||
|
||||
document.getElementById('codeEditor').addEventListener('input', function() {
|
||||
isModified = this.value !== originalContent;
|
||||
setStatus(isModified ? 'Modified' : 'Ready', isModified ? 'modified' : 'saved');
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', e => {
|
||||
if (e.ctrlKey && e.key === 's') {
|
||||
e.preventDefault();
|
||||
saveFile();
|
||||
}
|
||||
});
|
||||
|
||||
// Init
|
||||
loadPath(getPathFromURL());
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user