Update dashboard, kb, memory +4 more (+28 ~18 -1)

This commit is contained in:
Echo
2026-02-06 14:25:10 +00:00
parent 7f64d5054a
commit 19d178268a
6767 changed files with 1346472 additions and 1282 deletions

View File

@@ -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>