Update dashboard, kb, memory +4 more (+28 ~18 -1)
This commit is contained in:
@@ -30,6 +30,8 @@ class TaskBoardHandler(SimpleHTTPRequestHandler):
|
||||
self.handle_refresh_index()
|
||||
elif self.path == '/api/git-commit':
|
||||
self.handle_git_commit()
|
||||
elif self.path == '/api/pdf':
|
||||
self.handle_pdf_post()
|
||||
else:
|
||||
self.send_error(404)
|
||||
|
||||
@@ -129,6 +131,72 @@ class TaskBoardHandler(SimpleHTTPRequestHandler):
|
||||
except Exception as e:
|
||||
self.send_json({'error': str(e)}, 500)
|
||||
|
||||
def handle_pdf_post(self):
|
||||
"""Convert markdown to PDF (text-based, not image) using venv script."""
|
||||
try:
|
||||
content_length = int(self.headers['Content-Length'])
|
||||
post_data = self.rfile.read(content_length).decode('utf-8')
|
||||
data = json.loads(post_data)
|
||||
|
||||
markdown_content = data.get('markdown', '')
|
||||
filename = data.get('filename', 'document.pdf')
|
||||
|
||||
if not markdown_content:
|
||||
self.send_json({'error': 'No markdown content'}, 400)
|
||||
return
|
||||
|
||||
# Call PDF generator script in venv
|
||||
venv_python = BASE_DIR / 'venv' / 'bin' / 'python3'
|
||||
pdf_script = TOOLS_DIR / 'generate_pdf.py'
|
||||
|
||||
if not venv_python.exists():
|
||||
self.send_json({'error': 'Venv Python not found'}, 500)
|
||||
return
|
||||
|
||||
if not pdf_script.exists():
|
||||
self.send_json({'error': 'PDF generator script not found'}, 500)
|
||||
return
|
||||
|
||||
# Prepare input JSON
|
||||
input_data = json.dumps({
|
||||
'markdown': markdown_content,
|
||||
'filename': filename
|
||||
})
|
||||
|
||||
# Call script with stdin
|
||||
result = subprocess.run(
|
||||
[str(venv_python), str(pdf_script)],
|
||||
input=input_data.encode('utf-8'),
|
||||
capture_output=True,
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
# Error from script
|
||||
error_msg = result.stderr.decode('utf-8', errors='replace')
|
||||
try:
|
||||
error_json = json.loads(error_msg)
|
||||
self.send_json(error_json, 500)
|
||||
except:
|
||||
self.send_json({'error': error_msg}, 500)
|
||||
return
|
||||
|
||||
# PDF bytes from stdout
|
||||
pdf_bytes = result.stdout
|
||||
|
||||
# Send as file download
|
||||
self.send_response(200)
|
||||
self.send_header('Content-Type', 'application/pdf')
|
||||
self.send_header('Content-Disposition', f'attachment; filename="{filename}"')
|
||||
self.send_header('Content-Length', str(len(pdf_bytes)))
|
||||
self.end_headers()
|
||||
self.wfile.write(pdf_bytes)
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
self.send_json({'error': 'PDF generation timeout'}, 500)
|
||||
except Exception as e:
|
||||
self.send_json({'error': str(e)}, 500)
|
||||
|
||||
def do_GET(self):
|
||||
if self.path == '/api/status':
|
||||
self.send_json({'status': 'ok', 'time': datetime.now().isoformat()})
|
||||
@@ -193,15 +261,15 @@ class TaskBoardHandler(SimpleHTTPRequestHandler):
|
||||
|
||||
# Parse uncommitted into structured format
|
||||
# Format: XY PATH where XY is 2 chars (index + working tree status)
|
||||
# Examples: "M file" (staged), " M file" (unstaged), "?? file" (untracked)
|
||||
# Examples: "M AGENTS.md" (staged), " M tools.md" (unstaged), "?? file" (untracked)
|
||||
# The format varies: sometimes 1 space after status, sometimes 2
|
||||
uncommitted_parsed = []
|
||||
for line in uncommitted:
|
||||
if len(line) >= 3:
|
||||
status = line[:2].strip()
|
||||
# Path starts at position 3 for most cases, but we use lstrip
|
||||
# to handle edge cases where there's no separator space
|
||||
filepath = line[2:].lstrip().strip()
|
||||
uncommitted_parsed.append({'status': status, 'path': filepath})
|
||||
if len(line) >= 2:
|
||||
status = line[:2].strip() # Get 2 chars and strip whitespace
|
||||
filepath = line[2:].strip() # Get everything after position 2 and strip
|
||||
if filepath: # Only add if filepath is not empty
|
||||
uncommitted_parsed.append({'status': status, 'path': filepath})
|
||||
|
||||
self.send_json({
|
||||
'branch': branch,
|
||||
|
||||
@@ -32,6 +32,175 @@
|
||||
"created": "2025-01-30",
|
||||
"completed": "2025-01-30",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "task-029",
|
||||
"title": "Test sortare timestamp",
|
||||
"description": "Verificare sortare",
|
||||
"created": "2026-01-29T14:54:17Z",
|
||||
"priority": "medium",
|
||||
"completed": "2026-01-29T14:54:25Z"
|
||||
},
|
||||
{
|
||||
"id": "task-027",
|
||||
"title": "UI fixes: kanban icons + notes tags",
|
||||
"description": "Scos emoji din coloane kanban. Adăugat tag pills cu multi-select și count în notes.",
|
||||
"created": "2026-01-29",
|
||||
"priority": "high",
|
||||
"completed": "2026-01-29"
|
||||
},
|
||||
{
|
||||
"id": "task-026",
|
||||
"title": "Swipe navigation mobil",
|
||||
"description": "Swipe stânga/dreapta pentru navigare între Tasks ↔ Notes ↔ Files. Indicator dots pe mobil.",
|
||||
"created": "2026-01-29",
|
||||
"priority": "high",
|
||||
"completed": "2026-01-29"
|
||||
},
|
||||
{
|
||||
"id": "task-025",
|
||||
"title": "Notes: Accordion pe zile",
|
||||
"description": "Grupare: Azi (expanded), Ieri, Săptămâna aceasta, Mai vechi (collapsed). Click pentru expand/collapse.",
|
||||
"created": "2026-01-29",
|
||||
"priority": "high",
|
||||
"completed": "2026-01-29"
|
||||
},
|
||||
{
|
||||
"id": "task-024",
|
||||
"title": "Fix contrast dark/light mode",
|
||||
"description": "Text și borders mai vizibile, header alb în light mode, toggle temă funcțional",
|
||||
"created": "2026-01-29",
|
||||
"priority": "high",
|
||||
"completed": "2026-01-29"
|
||||
},
|
||||
{
|
||||
"id": "task-023",
|
||||
"title": "Design System Unificat",
|
||||
"description": "common.css + Lucide icons + UI modern pe toate paginile: Tasks, Notes, Files",
|
||||
"created": "2026-01-29",
|
||||
"priority": "high",
|
||||
"completed": "2026-01-29"
|
||||
},
|
||||
{
|
||||
"id": "task-022",
|
||||
"title": "Unificare stil navigare",
|
||||
"description": "Nav unificat pe toate paginile: 📋 Tasks | 📝 Notes | 📁 Files cu iconuri și stil consistent",
|
||||
"created": "2026-01-29",
|
||||
"priority": "high",
|
||||
"completed": "2026-01-29"
|
||||
},
|
||||
{
|
||||
"id": "task-021",
|
||||
"title": "UI/UX Redesign v2",
|
||||
"description": "Kanban: doar In Progress expandat. Notes: mobile tabs. Files: Browse/Editor tabs cu grid.",
|
||||
"created": "2026-01-29",
|
||||
"priority": "high",
|
||||
"completed": "2026-01-29"
|
||||
},
|
||||
{
|
||||
"id": "task-020",
|
||||
"title": "UI Responsive & Compact",
|
||||
"description": "Coloane colapsabile, task-uri compacte (click expand), sidebar toggle, Done minimizat by default",
|
||||
"created": "2026-01-29",
|
||||
"priority": "high",
|
||||
"completed": "2026-01-29"
|
||||
},
|
||||
{
|
||||
"id": "task-019",
|
||||
"title": "Comparare bilanț 12/2025 vs 12/2024",
|
||||
"description": "Doar S1002 modificat! Câmpuri noi: AN_CAEN, d_audit_intern. Raport: bilant_compare/2025_vs_2024/",
|
||||
"created": "2026-01-29",
|
||||
"priority": "high",
|
||||
"completed": "2026-01-29"
|
||||
},
|
||||
{
|
||||
"id": "task-018",
|
||||
"title": "Comparare bilanț ANAF 2024 vs 2023",
|
||||
"description": "Comparat XSD-uri S1002-S1005. Raport: anaf-monitor/bilant_compare/RAPORT_DIFERENTE_2024_vs_2023.md",
|
||||
"created": "2026-01-29",
|
||||
"priority": "high",
|
||||
"completed": "2026-01-29"
|
||||
},
|
||||
{
|
||||
"id": "task-017",
|
||||
"title": "Scrie un haiku",
|
||||
"description": "Biți în noaptea grea / Claude răspunde în liniște / Ecou digital",
|
||||
"created": "2026-01-29",
|
||||
"priority": "medium",
|
||||
"completed": "2026-01-29"
|
||||
},
|
||||
{
|
||||
"id": "task-005",
|
||||
"title": "Kanban board",
|
||||
"description": "Interfață web pentru vizualizare task-uri",
|
||||
"created": "2025-01-30",
|
||||
"priority": "high",
|
||||
"completed": "2026-01-29"
|
||||
},
|
||||
{
|
||||
"id": "task-008",
|
||||
"title": "YouTube Notes interface",
|
||||
"description": "Interfață pentru vizualizare notițe cu search",
|
||||
"created": "2026-01-29",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "task-009",
|
||||
"title": "Search în notițe",
|
||||
"description": "Căutare în titlu, tags și conținut",
|
||||
"created": "2026-01-29",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "task-010",
|
||||
"title": "Sumarizare: Claude Code Do Work Pattern",
|
||||
"description": "https://youtu.be/I9-tdhxiH7w",
|
||||
"created": "2026-01-29",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "task-011",
|
||||
"title": "File Explorer în Task Board",
|
||||
"description": "Interfață pentru browse/edit fișiere din workspace",
|
||||
"created": "2026-01-29",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "task-013",
|
||||
"title": "Kanban interactiv cu drag & drop",
|
||||
"description": "Adăugat: drag-drop, add/edit/delete tasks, priorități, salvare automată",
|
||||
"created": "2026-01-29",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "task-014",
|
||||
"title": "Sumarizare: It Got Worse (Clawdbot)...",
|
||||
"description": "https://youtu.be/rPAKq2oQVBs?si=6sJk41XsCrQQt6Lg",
|
||||
"created": "2026-01-29",
|
||||
"priority": "medium",
|
||||
"completed": "2026-01-29"
|
||||
},
|
||||
{
|
||||
"id": "task-015",
|
||||
"title": "Sumarizare: Greșeli post cu apă",
|
||||
"description": "https://youtu.be/4QjkI0sf64M",
|
||||
"created": "2026-01-29",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "task-016",
|
||||
"title": "Sumarizare: GSD Framework Claude Code",
|
||||
"description": "https://www.youtube.com/watch?v=l94A53kIUB0",
|
||||
"created": "2026-01-29",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "task-028",
|
||||
"title": "ANAF Monitor - verificare (test)",
|
||||
"description": "Testare manuală cron job",
|
||||
"created": "2026-01-29",
|
||||
"priority": "medium",
|
||||
"completed": "2026-01-29"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
<link rel="stylesheet" href="common.css">
|
||||
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
|
||||
<script src="swipe-nav.js"></script>
|
||||
<style>
|
||||
.main {
|
||||
@@ -418,6 +419,9 @@
|
||||
line-height: 1.6;
|
||||
resize: none;
|
||||
outline: none;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
#markdownPreview {
|
||||
@@ -426,8 +430,11 @@
|
||||
height: 100%;
|
||||
padding: var(--space-5);
|
||||
overflow-y: auto;
|
||||
overflow-x: auto;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.7;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
#markdownPreview h1, #markdownPreview h2, #markdownPreview h3 {
|
||||
@@ -490,14 +497,333 @@
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.toolbar {
|
||||
padding: var(--space-3);
|
||||
/* Hide view/sort controls in editor mode (both mobile and desktop) */
|
||||
body.editor-mode #viewModeToggle,
|
||||
body.editor-mode #sortBy,
|
||||
body.editor-mode #sortDirBtn {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Editor menu for mobile */
|
||||
.editor-menu-mobile {
|
||||
position: relative;
|
||||
display: none; /* Hidden by default, shown on mobile via media query */
|
||||
}
|
||||
|
||||
.editor-menu-dropdown {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
right: 0;
|
||||
background: var(--bg-surface);
|
||||
backdrop-filter: blur(8px);
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
min-width: 180px;
|
||||
z-index: 100;
|
||||
box-shadow: var(--shadow-md);
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* Ensure opaque background on both themes */
|
||||
[data-theme="dark"] .editor-menu-dropdown {
|
||||
background: #1a1a1aee;
|
||||
}
|
||||
|
||||
[data-theme="light"] .editor-menu-dropdown {
|
||||
background: #ffffffee;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
width: 100%;
|
||||
padding: var(--space-2) var(--space-3);
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
font-size: var(--text-sm);
|
||||
text-align: left;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.menu-item:hover {
|
||||
background: var(--bg-surface-hover);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.menu-item svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.menu-item:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.menu-item.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
/* Prevent horizontal overflow */
|
||||
.main,
|
||||
.content-area,
|
||||
.browse-panel,
|
||||
.editor-panel {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.file-grid {
|
||||
.toolbar {
|
||||
padding: var(--space-2) var(--space-3);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* Compact breadcrumb on mobile */
|
||||
.breadcrumb {
|
||||
font-size: 12px;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.breadcrumb-item {
|
||||
padding: var(--space-1);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Make toolbar actions wrap and stay visible */
|
||||
.toolbar-actions {
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-1);
|
||||
}
|
||||
|
||||
/* Collapse view/sort controls into dropdown on mobile */
|
||||
.view-sort-group {
|
||||
position: relative;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#viewModeToggle,
|
||||
.sort-select,
|
||||
#sortDirBtn {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.view-sort-dropdown-toggle {
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
/* Hide when in editor mode */
|
||||
body.editor-mode .view-sort-dropdown-toggle {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.view-sort-dropdown {
|
||||
position: fixed;
|
||||
top: 60px;
|
||||
left: var(--space-3);
|
||||
right: var(--space-3);
|
||||
max-width: 280px;
|
||||
background: #1a1a1aee;
|
||||
backdrop-filter: blur(8px);
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
z-index: 100;
|
||||
box-shadow: var(--shadow-md);
|
||||
padding: var(--space-2);
|
||||
}
|
||||
|
||||
[data-theme="light"] .view-sort-dropdown {
|
||||
background: #ffffffee;
|
||||
}
|
||||
|
||||
.view-sort-section {
|
||||
padding: var(--space-2) 0;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.view-sort-section:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.view-sort-section-title {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-muted);
|
||||
font-weight: 600;
|
||||
margin-bottom: var(--space-2);
|
||||
padding: 0 var(--space-2);
|
||||
}
|
||||
|
||||
.view-sort-options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.view-sort-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
font-size: var(--text-sm);
|
||||
text-align: left;
|
||||
border-radius: var(--radius-sm);
|
||||
transition: all var(--transition-fast);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.view-sort-option:hover {
|
||||
background: var(--bg-surface-hover);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.view-sort-option.active {
|
||||
background: var(--accent-subtle);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.view-sort-option svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Simplify details view for mobile - use single column layout */
|
||||
.file-grid.view-details .file-header {
|
||||
display: none; /* Hide header on mobile */
|
||||
}
|
||||
|
||||
.file-grid.view-details .file-item {
|
||||
display: grid;
|
||||
grid-template-columns: 24px 1fr auto;
|
||||
grid-template-rows: auto auto;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-2) var(--space-3);
|
||||
}
|
||||
|
||||
.file-grid.view-details .file-icon {
|
||||
grid-row: 1 / 3;
|
||||
}
|
||||
|
||||
.file-grid.view-details .file-name {
|
||||
grid-column: 2;
|
||||
grid-row: 1;
|
||||
font-size: var(--text-sm);
|
||||
}
|
||||
|
||||
.file-grid.view-details .file-meta {
|
||||
grid-column: 2 / 4;
|
||||
grid-row: 2;
|
||||
display: flex;
|
||||
gap: var(--space-2);
|
||||
font-size: var(--text-xs);
|
||||
}
|
||||
|
||||
.file-grid.view-details .file-type,
|
||||
.file-grid.view-details .file-size,
|
||||
.file-grid.view-details .file-date {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Reduce grid columns for tiles/list */
|
||||
.file-grid.view-tiles {
|
||||
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.file-grid.view-list {
|
||||
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
||||
}
|
||||
|
||||
/* Hide individual buttons on mobile - available in hamburger menu */
|
||||
.editor-header #previewBtn,
|
||||
.editor-header #downloadPdfBtn,
|
||||
.editor-header #diffBtn,
|
||||
.editor-header #reloadBtn {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#saveBtn {
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
/* Hamburger menu ALWAYS visible on mobile */
|
||||
.editor-menu-mobile {
|
||||
display: flex !important;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.editor-actions {
|
||||
gap: var(--space-1);
|
||||
flex-wrap: nowrap; /* Keep buttons in one line */
|
||||
}
|
||||
|
||||
/* Fix button sizes on mobile - prevent scaling */
|
||||
.editor-actions .btn {
|
||||
padding: var(--space-2) !important;
|
||||
min-width: auto !important;
|
||||
min-height: auto !important;
|
||||
font-size: 14px !important;
|
||||
}
|
||||
|
||||
.editor-actions .btn svg {
|
||||
width: 16px !important;
|
||||
height: 16px !important;
|
||||
}
|
||||
|
||||
/* Compact editor header */
|
||||
.editor-header {
|
||||
padding: var(--space-2) var(--space-3);
|
||||
}
|
||||
|
||||
.editor-title {
|
||||
font-size: 13px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
/* Override common.css mobile touch target sizes for editor buttons */
|
||||
.editor-actions .btn {
|
||||
min-height: auto !important;
|
||||
padding: var(--space-2) !important;
|
||||
}
|
||||
|
||||
.editor-header {
|
||||
padding: var(--space-2) !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1201px) {
|
||||
/* Hide hamburger menu on desktop */
|
||||
.editor-menu-mobile {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Hide view/sort dropdown toggle on desktop */
|
||||
.view-sort-dropdown-toggle {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Re-enable desktop buttons (override mobile hide) */
|
||||
#previewBtn,
|
||||
#downloadPdfBtn,
|
||||
#diffBtn,
|
||||
#reloadBtn {
|
||||
display: none; /* Default hidden, JS will show when needed */
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
@@ -539,20 +865,85 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- View Mode Toggle -->
|
||||
<div class="view-toggle" id="viewModeToggle">
|
||||
<button class="view-btn" data-view="list" onclick="setViewMode('list')" title="Listă">
|
||||
<i data-lucide="list"></i>
|
||||
</button>
|
||||
<button class="view-btn" data-view="details" onclick="setViewMode('details')" title="Detalii">
|
||||
<i data-lucide="layout-list"></i>
|
||||
</button>
|
||||
<button class="view-btn active" data-view="tiles" onclick="setViewMode('tiles')" title="Tiles">
|
||||
<i data-lucide="layout-grid"></i>
|
||||
</button>
|
||||
<!-- View/Sort Controls Group -->
|
||||
<div class="view-sort-group">
|
||||
<!-- Mobile: Collapsed dropdown toggle -->
|
||||
<div class="view-toggle">
|
||||
<button class="view-btn view-sort-dropdown-toggle" onclick="toggleViewSortMenu()" title="View & Sort" style="display:none;">
|
||||
<i data-lucide="settings-2"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Dropdown menu (mobile only) -->
|
||||
<div class="view-sort-dropdown" id="viewSortDropdown" style="display:none;">
|
||||
<div class="view-sort-section">
|
||||
<div class="view-sort-section-title">Mod vizualizare</div>
|
||||
<div class="view-sort-options">
|
||||
<button class="view-sort-option" data-view="list" onclick="setViewMode('list'); toggleViewSortMenu()">
|
||||
<i data-lucide="list"></i>
|
||||
<span>Listă</span>
|
||||
</button>
|
||||
<button class="view-sort-option" data-view="details" onclick="setViewMode('details'); toggleViewSortMenu()">
|
||||
<i data-lucide="layout-list"></i>
|
||||
<span>Detalii</span>
|
||||
</button>
|
||||
<button class="view-sort-option active" data-view="tiles" onclick="setViewMode('tiles'); toggleViewSortMenu()">
|
||||
<i data-lucide="layout-grid"></i>
|
||||
<span>Tiles</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="view-sort-section">
|
||||
<div class="view-sort-section-title">Sortare</div>
|
||||
<div class="view-sort-options">
|
||||
<button class="view-sort-option active" data-sort="name" onclick="setSortByMobile('name'); toggleViewSortMenu()">
|
||||
<i data-lucide="text"></i>
|
||||
<span>Nume</span>
|
||||
</button>
|
||||
<button class="view-sort-option" data-sort="type" onclick="setSortByMobile('type'); toggleViewSortMenu()">
|
||||
<i data-lucide="file-type"></i>
|
||||
<span>Tip</span>
|
||||
</button>
|
||||
<button class="view-sort-option" data-sort="size" onclick="setSortByMobile('size'); toggleViewSortMenu()">
|
||||
<i data-lucide="hard-drive"></i>
|
||||
<span>Mărime</span>
|
||||
</button>
|
||||
<button class="view-sort-option" data-sort="date" onclick="setSortByMobile('date'); toggleViewSortMenu()">
|
||||
<i data-lucide="calendar"></i>
|
||||
<span>Dată</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="view-sort-section">
|
||||
<div class="view-sort-section-title">Ordine</div>
|
||||
<div class="view-sort-options">
|
||||
<button class="view-sort-option active" data-dir="asc" onclick="setSortDirMobile('asc'); toggleViewSortMenu()">
|
||||
<i data-lucide="arrow-down-a-z"></i>
|
||||
<span>Crescător</span>
|
||||
</button>
|
||||
<button class="view-sort-option" data-dir="desc" onclick="setSortDirMobile('desc'); toggleViewSortMenu()">
|
||||
<i data-lucide="arrow-up-z-a"></i>
|
||||
<span>Descrescător</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Desktop: Original controls -->
|
||||
<div class="view-toggle" id="viewModeToggle">
|
||||
<button class="view-btn" data-view="list" onclick="setViewMode('list')" title="Listă">
|
||||
<i data-lucide="list"></i>
|
||||
</button>
|
||||
<button class="view-btn" data-view="details" onclick="setViewMode('details')" title="Detalii">
|
||||
<i data-lucide="layout-list"></i>
|
||||
</button>
|
||||
<button class="view-btn active" data-view="tiles" onclick="setViewMode('tiles')" title="Tiles">
|
||||
<i data-lucide="layout-grid"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sort Toggle -->
|
||||
<!-- Sort Toggle (desktop only) -->
|
||||
<div class="view-toggle">
|
||||
<select class="sort-select" id="sortBy" onchange="sortFiles()">
|
||||
<option value="name">Nume</option>
|
||||
@@ -565,15 +956,6 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Browse/Editor Toggle -->
|
||||
<div class="view-toggle">
|
||||
<button class="view-btn active" id="browseBtn" onclick="showBrowse()" title="Browse">
|
||||
<i data-lucide="folder"></i>
|
||||
</button>
|
||||
<button class="view-btn" id="editorBtn" onclick="showEditor()" title="Editor">
|
||||
<i data-lucide="code"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -600,15 +982,42 @@
|
||||
<button class="btn btn-ghost btn-preview" onclick="togglePreview()" id="previewBtn" style="display:none;" title="Preview Markdown">
|
||||
<i data-lucide="eye"></i>
|
||||
</button>
|
||||
<button class="btn btn-ghost" onclick="downloadPDF()" id="downloadPdfBtn" style="display:none;" title="Download as PDF">
|
||||
<i data-lucide="download"></i>
|
||||
</button>
|
||||
<button class="btn btn-ghost btn-diff" onclick="toggleDiff()" id="diffBtn" style="display:none;" title="Git Diff">
|
||||
<i data-lucide="git-compare"></i>
|
||||
</button>
|
||||
<button class="btn btn-ghost" onclick="reloadFile()" id="reloadBtn" disabled title="Reload">
|
||||
<!-- Hamburger menu for mobile -->
|
||||
<div class="editor-menu-mobile" id="editorMenuMobile">
|
||||
<button class="btn btn-ghost" onclick="toggleEditorMenu()" title="More">
|
||||
<i data-lucide="more-vertical"></i>
|
||||
</button>
|
||||
<div class="editor-menu-dropdown" id="editorMenuDropdown" style="display:none;">
|
||||
<button onclick="togglePreview(); toggleEditorMenu()" class="menu-item" id="previewMenuItem">
|
||||
<i data-lucide="eye"></i>
|
||||
<span id="previewLabel">Preview</span>
|
||||
</button>
|
||||
<button onclick="downloadPDF(); toggleEditorMenu()" class="menu-item" id="downloadPdfMenuItem">
|
||||
<i data-lucide="download"></i>
|
||||
<span>Download PDF</span>
|
||||
</button>
|
||||
<button onclick="toggleDiff(); toggleEditorMenu()" class="menu-item" id="diffMenuItem">
|
||||
<i data-lucide="git-compare"></i>
|
||||
<span>Git Diff</span>
|
||||
</button>
|
||||
<button onclick="reloadFile(); toggleEditorMenu()" class="menu-item">
|
||||
<i data-lucide="refresh-cw"></i>
|
||||
<span>Reload</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Desktop buttons -->
|
||||
<button class="btn btn-ghost" onclick="reloadFile()" id="reloadBtn" disabled title="Reload" style="display:flex;">
|
||||
<i data-lucide="refresh-cw"></i>
|
||||
</button>
|
||||
<button class="btn btn-primary" onclick="saveFile()" id="saveBtn" disabled>
|
||||
<button class="btn btn-primary" onclick="saveFile()" id="saveBtn" disabled style="display:flex;" title="Save">
|
||||
<i data-lucide="save"></i>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -668,17 +1077,30 @@
|
||||
setViewMode(currentViewMode, false);
|
||||
document.getElementById('sortBy').value = currentSortBy;
|
||||
updateSortIcon();
|
||||
|
||||
// Update mobile dropdown initial states
|
||||
document.querySelectorAll('.view-sort-option[data-sort]').forEach(btn => {
|
||||
btn.classList.toggle('active', btn.dataset.sort === currentSortBy);
|
||||
});
|
||||
document.querySelectorAll('.view-sort-option[data-dir]').forEach(btn => {
|
||||
btn.classList.toggle('active', btn.dataset.dir === currentSortDir);
|
||||
});
|
||||
}
|
||||
|
||||
function setViewMode(mode, reload = true) {
|
||||
currentViewMode = mode;
|
||||
localStorage.setItem('filesViewMode', mode);
|
||||
|
||||
// Update buttons
|
||||
// Update desktop buttons
|
||||
document.querySelectorAll('#viewModeToggle .view-btn').forEach(btn => {
|
||||
btn.classList.toggle('active', btn.dataset.view === mode);
|
||||
});
|
||||
|
||||
// Update mobile dropdown options
|
||||
document.querySelectorAll('.view-sort-option[data-view]').forEach(btn => {
|
||||
btn.classList.toggle('active', btn.dataset.view === mode);
|
||||
});
|
||||
|
||||
// Update grid class
|
||||
const grid = document.getElementById('fileGrid');
|
||||
grid.classList.remove('view-list', 'view-details', 'view-tiles');
|
||||
@@ -728,17 +1150,32 @@
|
||||
|
||||
function showBrowse() {
|
||||
if (isModified && !confirm('Ai modificări nesalvate. Continui?')) return;
|
||||
|
||||
// Get parent directory of current file
|
||||
let parentPath = '';
|
||||
if (currentFile) {
|
||||
const parts = currentFile.split('/');
|
||||
parts.pop(); // Remove filename
|
||||
parentPath = parts.join('/');
|
||||
}
|
||||
|
||||
// Switch to browse mode
|
||||
document.body.classList.remove('editor-mode');
|
||||
document.getElementById('browsePanel').classList.remove('hidden');
|
||||
document.getElementById('editorPanel').classList.remove('active');
|
||||
document.getElementById('browseBtn').classList.add('active');
|
||||
document.getElementById('editorBtn').classList.remove('active');
|
||||
|
||||
// Show git filter in browse mode
|
||||
document.getElementById('gitFilterBtn').style.display = 'flex';
|
||||
|
||||
// Reload directory listing
|
||||
loadPath(parentPath);
|
||||
}
|
||||
|
||||
function showEditor() {
|
||||
document.body.classList.add('editor-mode');
|
||||
document.getElementById('browsePanel').classList.add('hidden');
|
||||
document.getElementById('editorPanel').classList.add('active');
|
||||
document.getElementById('browseBtn').classList.remove('active');
|
||||
document.getElementById('editorBtn').classList.add('active');
|
||||
document.getElementById('gitFilterBtn').style.display = 'none';
|
||||
}
|
||||
|
||||
async function loadGitStatus() {
|
||||
@@ -748,14 +1185,39 @@
|
||||
gitStatus = {};
|
||||
if (data.uncommittedParsed) {
|
||||
data.uncommittedParsed.forEach(item => {
|
||||
gitStatus[item.path] = item.status;
|
||||
// Normalize path: remove ./ prefix, forward slashes for consistency
|
||||
const normalized = item.path.replace(/^\.\//, '').replace(/\\/g, '/');
|
||||
gitStatus[normalized] = item.status;
|
||||
});
|
||||
}
|
||||
console.log('📂 Git status loaded:', Object.keys(gitStatus).length, 'files');
|
||||
} catch (e) {
|
||||
console.error('Failed to load git status:', e);
|
||||
}
|
||||
}
|
||||
|
||||
function getGitStatusForPath(path) {
|
||||
// Try exact match first
|
||||
if (gitStatus[path]) return gitStatus[path];
|
||||
|
||||
// Normalize: remove ./ prefix, convert backslashes
|
||||
const normalized = path.replace(/^\.\//, '').replace(/\\/g, '/');
|
||||
if (gitStatus[normalized]) return gitStatus[normalized];
|
||||
|
||||
// Try without extension for edge cases
|
||||
const withoutExt = path.replace(/\.[^.]+$/, '');
|
||||
if (gitStatus[withoutExt]) return gitStatus[withoutExt];
|
||||
|
||||
// Try all keys that might match (case-insensitive)
|
||||
const lowerPath = path.toLowerCase();
|
||||
for (const [key, val] of Object.entries(gitStatus)) {
|
||||
if (key.toLowerCase() === lowerPath) return val;
|
||||
if (key.toLowerCase().endsWith('/' + lowerPath)) return val;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async function showDiff(filepath, event) {
|
||||
if (event) event.stopPropagation();
|
||||
try {
|
||||
@@ -815,7 +1277,17 @@
|
||||
await loadGitStatus(); // Refresh git status
|
||||
renderFileGrid(data.items);
|
||||
updateURL(path);
|
||||
|
||||
// If we're in editor mode and loading a directory, switch to browse mode
|
||||
const editorPanel = document.getElementById('editorPanel');
|
||||
if (editorPanel.classList.contains('active')) {
|
||||
document.body.classList.remove('editor-mode');
|
||||
document.getElementById('browsePanel').classList.remove('hidden');
|
||||
editorPanel.classList.remove('active');
|
||||
document.getElementById('gitFilterBtn').style.display = 'flex';
|
||||
}
|
||||
} else if (data.type === 'file') {
|
||||
await loadGitStatus(); // Load git status before opening file
|
||||
openFile(path, data);
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -906,9 +1378,9 @@
|
||||
const sizeStr = item.size !== undefined ? formatSize(item.size) : '-';
|
||||
|
||||
// Git status
|
||||
const gStatus = gitStatus[item.path] || '';
|
||||
const gStatus = getGitStatusForPath(item.path) || '';
|
||||
const gitBadge = gStatus ? getGitBadge(gStatus) : '';
|
||||
const hasGitChange = !!gStatus;
|
||||
const hasGitChange = gStatus && gStatus !== '??'; // Only show for tracked changes
|
||||
|
||||
if (currentViewMode === 'details') {
|
||||
return `
|
||||
@@ -1020,6 +1492,10 @@
|
||||
originalContent = data.content;
|
||||
updateURL(path);
|
||||
|
||||
// Switch to editor mode
|
||||
document.body.classList.add('editor-mode');
|
||||
document.getElementById('gitFilterBtn').style.display = 'none';
|
||||
|
||||
document.getElementById('editorFileName').textContent = data.name;
|
||||
document.getElementById('codeEditor').value = data.content;
|
||||
document.getElementById('saveBtn').disabled = false;
|
||||
@@ -1028,21 +1504,53 @@
|
||||
|
||||
// Show preview button for markdown files
|
||||
const isMarkdown = path.endsWith('.md');
|
||||
document.getElementById('previewBtn').style.display = isMarkdown ? 'flex' : 'none';
|
||||
const previewBtn = document.getElementById('previewBtn');
|
||||
const downloadPdfBtn = document.getElementById('downloadPdfBtn');
|
||||
const downloadPdfMenuItem = document.getElementById('downloadPdfMenuItem');
|
||||
const previewMenuItem = document.getElementById('previewMenuItem');
|
||||
|
||||
// Always show diff button - let user check if file has changes
|
||||
document.getElementById('diffBtn').style.display = 'flex';
|
||||
document.getElementById('diffBtn').classList.remove('active');
|
||||
previewBtn.style.display = isMarkdown ? 'flex' : 'none';
|
||||
downloadPdfBtn.style.display = isMarkdown ? 'flex' : 'none';
|
||||
downloadPdfMenuItem.classList.toggle('hidden', !isMarkdown);
|
||||
previewMenuItem.classList.toggle('hidden', !isMarkdown);
|
||||
|
||||
// Auto-activate preview for markdown files
|
||||
// Show diff button only if file has git changes
|
||||
const gitStatus_forFile = getGitStatusForPath(path);
|
||||
const hasGitChanges = gitStatus_forFile && gitStatus_forFile !== '??'; // Only show for tracked changes (M, A, D, etc), not untracked (??)
|
||||
|
||||
const diffBtn = document.getElementById('diffBtn');
|
||||
const diffMenuItem = document.getElementById('diffMenuItem');
|
||||
|
||||
// Desktop: show diff button only if git changes
|
||||
diffBtn.style.display = hasGitChanges ? 'flex' : 'none';
|
||||
|
||||
// Mobile menu: ALWAYS show diff item, but disable if no changes
|
||||
diffMenuItem.classList.remove('hidden');
|
||||
diffMenuItem.disabled = !hasGitChanges;
|
||||
if (!gitStatus_forFile) {
|
||||
diffMenuItem.title = 'File not in git repo';
|
||||
} else if (!hasGitChanges && gitStatus_forFile === '??') {
|
||||
diffMenuItem.title = 'File is untracked (new)';
|
||||
} else if (!hasGitChanges) {
|
||||
diffMenuItem.title = 'No tracked changes';
|
||||
} else {
|
||||
diffMenuItem.title = 'Show git changes';
|
||||
}
|
||||
diffBtn.classList.remove('active');
|
||||
|
||||
// Auto-activate preview for markdown files (hides diff button automatically)
|
||||
if (isMarkdown) {
|
||||
const preview = document.getElementById('markdownPreview');
|
||||
preview.innerHTML = marked.parse(data.content);
|
||||
document.getElementById('editorBody').classList.add('preview-active');
|
||||
document.getElementById('previewBtn').classList.add('active');
|
||||
previewBtn.classList.add('active');
|
||||
// Hide desktop diff button in preview mode, but keep menu item visible
|
||||
if (diffBtn.style.display !== 'none') {
|
||||
diffBtn.style.display = 'none';
|
||||
}
|
||||
} else {
|
||||
document.getElementById('editorBody').classList.remove('preview-active');
|
||||
document.getElementById('previewBtn').classList.remove('active');
|
||||
previewBtn.classList.remove('active');
|
||||
}
|
||||
|
||||
if (data.truncated) {
|
||||
@@ -1059,6 +1567,7 @@
|
||||
const editorBody = document.getElementById('editorBody');
|
||||
const previewBtn = document.getElementById('previewBtn');
|
||||
const diffBtn = document.getElementById('diffBtn');
|
||||
const diffMenuItem = document.getElementById('diffMenuItem');
|
||||
const preview = document.getElementById('markdownPreview');
|
||||
const content = document.getElementById('codeEditor').value;
|
||||
|
||||
@@ -1066,14 +1575,27 @@
|
||||
// Switch to edit mode
|
||||
editorBody.classList.remove('preview-active');
|
||||
previewBtn.classList.remove('active');
|
||||
if (diffBtn) diffBtn.classList.remove('active');
|
||||
if (diffBtn) {
|
||||
diffBtn.classList.remove('active');
|
||||
const gitStat = getGitStatusForPath(currentFile);
|
||||
const hasGitChanges = gitStat && gitStat !== '??';
|
||||
diffBtn.style.display = hasGitChanges ? 'flex' : 'none';
|
||||
diffMenuItem.disabled = !hasGitChanges;
|
||||
}
|
||||
setStatus('Edit mode', 'saved');
|
||||
} else {
|
||||
// Switch to preview mode
|
||||
preview.innerHTML = marked.parse(content);
|
||||
editorBody.classList.add('preview-active');
|
||||
previewBtn.classList.add('active');
|
||||
if (diffBtn) diffBtn.classList.remove('active');
|
||||
if (diffBtn) {
|
||||
diffBtn.classList.remove('active');
|
||||
diffBtn.style.display = 'none'; // Hide diff button in preview mode
|
||||
// Keep diff menu item enabled/disabled based on git status
|
||||
const gitStat = getGitStatusForPath(currentFile);
|
||||
const hasGitChanges = gitStat && gitStat !== '??';
|
||||
diffMenuItem.disabled = !hasGitChanges;
|
||||
}
|
||||
setStatus('Preview mode', 'saved');
|
||||
}
|
||||
}
|
||||
@@ -1081,8 +1603,17 @@
|
||||
async function toggleDiff() {
|
||||
if (!currentFile) return;
|
||||
|
||||
// Check if file has git changes (only for tracked changes, not untracked)
|
||||
const gitStat = getGitStatusForPath(currentFile);
|
||||
const hasGitChanges = gitStat && gitStat !== '??';
|
||||
if (!hasGitChanges) {
|
||||
setStatus('Nicio modificare git pentru acest fișier', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const editorBody = document.getElementById('editorBody');
|
||||
const diffBtn = document.getElementById('diffBtn');
|
||||
const diffMenuItem = document.getElementById('diffMenuItem');
|
||||
const previewBtn = document.getElementById('previewBtn');
|
||||
const preview = document.getElementById('markdownPreview');
|
||||
|
||||
@@ -1129,6 +1660,58 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadPDF() {
|
||||
if (!currentFile) {
|
||||
setStatus('Niciun fișier deschis', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!currentFile.endsWith('.md')) {
|
||||
setStatus('PDF download disponibil doar pentru fișiere Markdown', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setStatus('Se generează PDF...', 'modified');
|
||||
|
||||
// Get markdown content from editor
|
||||
const markdownContent = document.getElementById('codeEditor').value;
|
||||
const filename = currentFile.split('/').pop().replace('.md', '.pdf');
|
||||
|
||||
// Send to backend for conversion
|
||||
const response = await fetch(`${API_BASE}/api/pdf`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
markdown: markdownContent,
|
||||
filename: filename
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
setStatus('Eroare: ' + (error.error || 'Unknown error'), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Download the PDF
|
||||
const blob = await response.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = filename;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
window.URL.revokeObjectURL(url);
|
||||
|
||||
setStatus('PDF descărcat: ' + filename, 'saved');
|
||||
} catch (e) {
|
||||
setStatus('Eroare la descărcare PDF: ' + e.message, 'error');
|
||||
console.error('PDF generation error:', e);
|
||||
}
|
||||
}
|
||||
|
||||
async function saveFile() {
|
||||
if (!currentFile) return;
|
||||
|
||||
@@ -1190,6 +1773,61 @@
|
||||
}
|
||||
}
|
||||
|
||||
function toggleEditorMenu() {
|
||||
const dropdown = document.getElementById('editorMenuDropdown');
|
||||
dropdown.style.display = dropdown.style.display === 'none' ? 'block' : 'none';
|
||||
}
|
||||
|
||||
function toggleViewSortMenu() {
|
||||
const dropdown = document.getElementById('viewSortDropdown');
|
||||
dropdown.style.display = dropdown.style.display === 'none' ? 'block' : 'none';
|
||||
}
|
||||
|
||||
function setSortByMobile(field) {
|
||||
currentSortBy = field;
|
||||
document.getElementById('sortBy').value = field;
|
||||
localStorage.setItem('filesSortBy', currentSortBy);
|
||||
|
||||
// Update active state in dropdown
|
||||
document.querySelectorAll('.view-sort-option[data-sort]').forEach(btn => {
|
||||
btn.classList.toggle('active', btn.dataset.sort === field);
|
||||
});
|
||||
|
||||
if (currentItems.length > 0) {
|
||||
renderFileGrid(currentItems);
|
||||
}
|
||||
}
|
||||
|
||||
function setSortDirMobile(dir) {
|
||||
currentSortDir = dir;
|
||||
localStorage.setItem('filesSortDir', currentSortDir);
|
||||
updateSortIcon();
|
||||
|
||||
// Update active state in dropdown
|
||||
document.querySelectorAll('.view-sort-option[data-dir]').forEach(btn => {
|
||||
btn.classList.toggle('active', btn.dataset.dir === dir);
|
||||
});
|
||||
|
||||
if (currentItems.length > 0) {
|
||||
renderFileGrid(currentItems);
|
||||
}
|
||||
}
|
||||
|
||||
// Close menus when clicking outside
|
||||
document.addEventListener('click', (e) => {
|
||||
const editorMenu = document.getElementById('editorMenuMobile');
|
||||
const editorDropdown = document.getElementById('editorMenuDropdown');
|
||||
if (editorMenu && !editorMenu.contains(e.target) && editorDropdown) {
|
||||
editorDropdown.style.display = 'none';
|
||||
}
|
||||
|
||||
const viewSortGroup = document.querySelector('.view-sort-group');
|
||||
const viewSortDropdown = document.getElementById('viewSortDropdown');
|
||||
if (viewSortGroup && !viewSortGroup.contains(e.target) && viewSortDropdown) {
|
||||
viewSortDropdown.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
function getPathFromURL() {
|
||||
const hash = window.location.hash;
|
||||
return hash ? decodeURIComponent(hash.slice(1)) : '';
|
||||
@@ -1212,6 +1850,12 @@
|
||||
// Init
|
||||
initViewMode();
|
||||
|
||||
// Show git filter initially (browse mode by default)
|
||||
document.getElementById('gitFilterBtn').style.display = 'flex';
|
||||
|
||||
// Load git status on init
|
||||
loadGitStatus();
|
||||
|
||||
// Check for git mode
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
if (urlParams.get('git') === '1') {
|
||||
@@ -1237,7 +1881,7 @@
|
||||
|
||||
async function loadGitChangedFiles() {
|
||||
await loadGitStatus();
|
||||
const changedPaths = Object.keys(gitStatus);
|
||||
const changedPaths = Object.keys(gitStatus).filter(p => p);
|
||||
|
||||
// Update button state
|
||||
document.getElementById('gitFilterBtn').classList.add('active');
|
||||
@@ -1274,5 +1918,6 @@
|
||||
renderFileGrid(items);
|
||||
}
|
||||
</script>
|
||||
<!-- v2.0.2 - Fixed media query threshold (992px→1200px) for mobile hamburger menu -->
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"lastUpdated": "2026-02-03T17:20:07.199Z",
|
||||
"lastUpdated": "2026-02-05T21:53:55.397Z",
|
||||
"programs": [
|
||||
"ROACONT",
|
||||
"ROAGEST",
|
||||
@@ -34,10 +34,11 @@
|
||||
"program": "ROACONT",
|
||||
"owner": "marius",
|
||||
"priority": "important",
|
||||
"status": "todo",
|
||||
"status": "done",
|
||||
"created": "2026-01-30T15:10:00Z",
|
||||
"deadline": "2026-02-06",
|
||||
"updated": "2026-02-02T22:26:59.690Z"
|
||||
"updated": "2026-02-02T22:26:59.690Z",
|
||||
"completed": "2026-02-05T21:53:55.392Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -743,6 +743,9 @@
|
||||
<div class="note-viewer-header">
|
||||
<h2 id="viewerTitle">Titlu</h2>
|
||||
<a id="viewerPath" href="#" class="viewer-path" target="_blank"></a>
|
||||
<button class="btn btn-ghost" onclick="downloadNotePDF()" id="downloadNotePdfBtn" title="Download as PDF">
|
||||
<i data-lucide="download"></i>
|
||||
</button>
|
||||
<button class="btn btn-ghost" onclick="closeNote()">
|
||||
<i data-lucide="x"></i>
|
||||
</button>
|
||||
@@ -1214,6 +1217,52 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadNotePDF() {
|
||||
const title = document.getElementById('viewerTitle').textContent;
|
||||
const viewerPath = document.getElementById('viewerPath').textContent;
|
||||
const filename = title.replace(/[^a-zA-Z0-9_-]/g, '_') + '.pdf';
|
||||
|
||||
try {
|
||||
// Get the raw markdown content from cache (not the HTML)
|
||||
let markdownContent = '';
|
||||
const noteFile = Object.keys(notesCache).find(f => {
|
||||
const note = notesIndex.find(n => n.file === f && n.title === title);
|
||||
return !!note;
|
||||
});
|
||||
|
||||
if (noteFile && notesCache[noteFile]) {
|
||||
markdownContent = notesCache[noteFile];
|
||||
} else {
|
||||
alert('Markdown content not found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Send to backend API
|
||||
const response = await fetch('api/pdf', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ markdown: markdownContent, filename: filename })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
alert('Eroare: ' + (error.error || 'Unknown error'));
|
||||
return;
|
||||
}
|
||||
|
||||
const blob = await response.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = filename;
|
||||
link.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
} catch (e) {
|
||||
console.error('PDF error:', e);
|
||||
alert('Eroare: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
function closeNote() {
|
||||
document.getElementById('noteViewer').classList.remove('active');
|
||||
document.body.style.overflow = '';
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"ok": true,
|
||||
"status": "OK",
|
||||
"message": "Nicio modificare detectată",
|
||||
"lastCheck": "03 Feb 2026, 21:39",
|
||||
"lastCheck": "06 Feb 2026, 14:00",
|
||||
"changesCount": 0
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"lastUpdated": "2026-02-04T06:31:05.120920Z",
|
||||
"lastUpdated": "2026-02-06T03:00:02.959226",
|
||||
"columns": [
|
||||
{
|
||||
"id": "backlog",
|
||||
@@ -30,175 +30,6 @@
|
||||
"id": "done",
|
||||
"name": "Done",
|
||||
"tasks": [
|
||||
{
|
||||
"id": "task-029",
|
||||
"title": "Test sortare timestamp",
|
||||
"description": "Verificare sortare",
|
||||
"created": "2026-01-29T14:54:17Z",
|
||||
"priority": "medium",
|
||||
"completed": "2026-01-29T14:54:25Z"
|
||||
},
|
||||
{
|
||||
"id": "task-027",
|
||||
"title": "UI fixes: kanban icons + notes tags",
|
||||
"description": "Scos emoji din coloane kanban. Adăugat tag pills cu multi-select și count în notes.",
|
||||
"created": "2026-01-29",
|
||||
"priority": "high",
|
||||
"completed": "2026-01-29"
|
||||
},
|
||||
{
|
||||
"id": "task-026",
|
||||
"title": "Swipe navigation mobil",
|
||||
"description": "Swipe stânga/dreapta pentru navigare între Tasks ↔ Notes ↔ Files. Indicator dots pe mobil.",
|
||||
"created": "2026-01-29",
|
||||
"priority": "high",
|
||||
"completed": "2026-01-29"
|
||||
},
|
||||
{
|
||||
"id": "task-025",
|
||||
"title": "Notes: Accordion pe zile",
|
||||
"description": "Grupare: Azi (expanded), Ieri, Săptămâna aceasta, Mai vechi (collapsed). Click pentru expand/collapse.",
|
||||
"created": "2026-01-29",
|
||||
"priority": "high",
|
||||
"completed": "2026-01-29"
|
||||
},
|
||||
{
|
||||
"id": "task-024",
|
||||
"title": "Fix contrast dark/light mode",
|
||||
"description": "Text și borders mai vizibile, header alb în light mode, toggle temă funcțional",
|
||||
"created": "2026-01-29",
|
||||
"priority": "high",
|
||||
"completed": "2026-01-29"
|
||||
},
|
||||
{
|
||||
"id": "task-023",
|
||||
"title": "Design System Unificat",
|
||||
"description": "common.css + Lucide icons + UI modern pe toate paginile: Tasks, Notes, Files",
|
||||
"created": "2026-01-29",
|
||||
"priority": "high",
|
||||
"completed": "2026-01-29"
|
||||
},
|
||||
{
|
||||
"id": "task-022",
|
||||
"title": "Unificare stil navigare",
|
||||
"description": "Nav unificat pe toate paginile: 📋 Tasks | 📝 Notes | 📁 Files cu iconuri și stil consistent",
|
||||
"created": "2026-01-29",
|
||||
"priority": "high",
|
||||
"completed": "2026-01-29"
|
||||
},
|
||||
{
|
||||
"id": "task-021",
|
||||
"title": "UI/UX Redesign v2",
|
||||
"description": "Kanban: doar In Progress expandat. Notes: mobile tabs. Files: Browse/Editor tabs cu grid.",
|
||||
"created": "2026-01-29",
|
||||
"priority": "high",
|
||||
"completed": "2026-01-29"
|
||||
},
|
||||
{
|
||||
"id": "task-020",
|
||||
"title": "UI Responsive & Compact",
|
||||
"description": "Coloane colapsabile, task-uri compacte (click expand), sidebar toggle, Done minimizat by default",
|
||||
"created": "2026-01-29",
|
||||
"priority": "high",
|
||||
"completed": "2026-01-29"
|
||||
},
|
||||
{
|
||||
"id": "task-019",
|
||||
"title": "Comparare bilanț 12/2025 vs 12/2024",
|
||||
"description": "Doar S1002 modificat! Câmpuri noi: AN_CAEN, d_audit_intern. Raport: bilant_compare/2025_vs_2024/",
|
||||
"created": "2026-01-29",
|
||||
"priority": "high",
|
||||
"completed": "2026-01-29"
|
||||
},
|
||||
{
|
||||
"id": "task-018",
|
||||
"title": "Comparare bilanț ANAF 2024 vs 2023",
|
||||
"description": "Comparat XSD-uri S1002-S1005. Raport: anaf-monitor/bilant_compare/RAPORT_DIFERENTE_2024_vs_2023.md",
|
||||
"created": "2026-01-29",
|
||||
"priority": "high",
|
||||
"completed": "2026-01-29"
|
||||
},
|
||||
{
|
||||
"id": "task-017",
|
||||
"title": "Scrie un haiku",
|
||||
"description": "Biți în noaptea grea / Claude răspunde în liniște / Ecou digital",
|
||||
"created": "2026-01-29",
|
||||
"priority": "medium",
|
||||
"completed": "2026-01-29"
|
||||
},
|
||||
{
|
||||
"id": "task-005",
|
||||
"title": "Kanban board",
|
||||
"description": "Interfață web pentru vizualizare task-uri",
|
||||
"created": "2025-01-30",
|
||||
"priority": "high",
|
||||
"completed": "2026-01-29"
|
||||
},
|
||||
{
|
||||
"id": "task-008",
|
||||
"title": "YouTube Notes interface",
|
||||
"description": "Interfață pentru vizualizare notițe cu search",
|
||||
"created": "2026-01-29",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "task-009",
|
||||
"title": "Search în notițe",
|
||||
"description": "Căutare în titlu, tags și conținut",
|
||||
"created": "2026-01-29",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "task-010",
|
||||
"title": "Sumarizare: Claude Code Do Work Pattern",
|
||||
"description": "https://youtu.be/I9-tdhxiH7w",
|
||||
"created": "2026-01-29",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "task-011",
|
||||
"title": "File Explorer în Task Board",
|
||||
"description": "Interfață pentru browse/edit fișiere din workspace",
|
||||
"created": "2026-01-29",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "task-013",
|
||||
"title": "Kanban interactiv cu drag & drop",
|
||||
"description": "Adăugat: drag-drop, add/edit/delete tasks, priorități, salvare automată",
|
||||
"created": "2026-01-29",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "task-014",
|
||||
"title": "Sumarizare: It Got Worse (Clawdbot)...",
|
||||
"description": "https://youtu.be/rPAKq2oQVBs?si=6sJk41XsCrQQt6Lg",
|
||||
"created": "2026-01-29",
|
||||
"priority": "medium",
|
||||
"completed": "2026-01-29"
|
||||
},
|
||||
{
|
||||
"id": "task-015",
|
||||
"title": "Sumarizare: Greșeli post cu apă",
|
||||
"description": "https://youtu.be/4QjkI0sf64M",
|
||||
"created": "2026-01-29",
|
||||
"priority": "medium"
|
||||
},
|
||||
{
|
||||
"id": "task-016",
|
||||
"title": "Sumarizare: GSD Framework Claude Code",
|
||||
"description": "https://www.youtube.com/watch?v=l94A53kIUB0",
|
||||
"created": "2026-01-29",
|
||||
"priority": "high"
|
||||
},
|
||||
{
|
||||
"id": "task-028",
|
||||
"title": "ANAF Monitor - verificare (test)",
|
||||
"description": "Testare manuală cron job",
|
||||
"created": "2026-01-29",
|
||||
"priority": "medium",
|
||||
"completed": "2026-01-29"
|
||||
},
|
||||
{
|
||||
"id": "task-030",
|
||||
"title": "Test task tracking",
|
||||
|
||||
@@ -1,6 +1,32 @@
|
||||
{
|
||||
"lastUpdated": "2026-02-04T07:00:00.000Z",
|
||||
"lastUpdated": "2026-02-06T13:46:00.687Z",
|
||||
"items": [
|
||||
{
|
||||
"id": "prov-2026-02-06",
|
||||
"text": "Provocare: Observă 1 aliniere + 1 fricțiune - ce îți spun despre tine?",
|
||||
"context": "Observă azi UN moment când te simți energizat (aliniere) și UN moment când ești tras înapoi (fricțiune). Pentru fiecare notează: ce activitate, ce caracteristică (creativitate? rezolvare probleme? conexiune? vs repetitivitate? teamă de judecată?). Nu trebuie să faci nimic cu observațiile - doar să le vezi. Corpul știe adevărul înainte ca mintea să-l articuleze.",
|
||||
"example": "Aliniere: Când automatizezi ceva și simți satisfacție - observi că e creativitatea și controlul care te energizează. Fricțiune: Când amâni să suni un client nou - observi că nu e competența (știi să vorbești), ci teama de respingere. Pattern-ul arată: vrei autonomie creativă, nu vânzare agresivă.",
|
||||
"domain": "self",
|
||||
"dueDate": "2026-02-06",
|
||||
"done": true,
|
||||
"doneAt": "2026-02-06T13:46:00.687Z",
|
||||
"source": "Coaching Dimineață - Pattern-uri de Auto-Cunoaștere",
|
||||
"sourceUrl": "https://moltbot.tailf7372d.ts.net/echo/files.html#memory/kb/coaching/2026-02-06-dimineata.md",
|
||||
"createdAt": "2026-02-06T07:02:00.666161"
|
||||
},
|
||||
{
|
||||
"id": "prov-2026-02-05",
|
||||
"text": "Provocare: Vizualizare Prospecting - sună un client potențial (5 min)",
|
||||
"context": "Alege UN client potențial real. Găsește o amintire cu client entuziasmat. Vizualizează: tu suni, el răspunde, pui propunerea, el zice 'Sună bine'. Sparge imaginea - prin fissură vezi entuziasmul din amintirea reală. Repetă 2-3 ori. Apoi sun-l azi sau mâine (sau cel puțin prepară motivul).",
|
||||
"example": "Client potențial: X care ar fi perfect dar zici 'dar...'. Amintire: momentul când clientul A a zis 'da'. Vizualizezi: suni, răspunde, pui propunerea, el: 'Sună bine'. Apoi suni pe X.",
|
||||
"domain": "work",
|
||||
"dueDate": "2026-02-05",
|
||||
"done": true,
|
||||
"source": "Gândul de Seară - NLP Prospecting",
|
||||
"sourceUrl": "https://moltbot.tailf7372d.ts.net/echo/files.html#memory/kb/coaching/2026-02-05-seara.md",
|
||||
"createdAt": "2026-02-05T19:00:00.000Z",
|
||||
"doneAt": "2026-02-06T13:45:58.234Z"
|
||||
},
|
||||
{
|
||||
"id": "prov-2026-02-04",
|
||||
"text": "Provocare: Vizualizare NLP - transferă motivația (5 min)",
|
||||
@@ -8,8 +34,8 @@
|
||||
"example": "Acțiunea: să trimiți un email de prospecting către un potențial client. Amintirea: momentul când ai terminat un proiect mare și clientul era entuziasmat. Când 'spargi' imaginea și vezi entuziasmul din spate, creierul începe să asocieze email-ul cu acel sentiment de succes.",
|
||||
"domain": "self",
|
||||
"dueDate": "2026-02-04",
|
||||
"done": false,
|
||||
"doneAt": null,
|
||||
"done": true,
|
||||
"doneAt": "2026-02-04T14:38:17.505Z",
|
||||
"source": "Meditație NLP - Vizualizare pentru Motivație",
|
||||
"sourceUrl": "https://moltbot.tailf7372d.ts.net/echo/files.html#memory/kb/projects/grup-sprijin/biblioteca/meditatie-vizualizare-motivatie.md",
|
||||
"createdAt": "2026-02-04T07:00:00.000Z"
|
||||
|
||||
Reference in New Issue
Block a user