novela/containers/novela/templates/_sidebar.html

251 lines
11 KiB
HTML

<button class="sidebar-toggle" id="sidebar-toggle" onclick="toggleSidebar()" aria-label="Menu">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
<line x1="3" y1="6" x2="21" y2="6"/>
<line x1="3" y1="12" x2="21" y2="12"/>
<line x1="3" y1="18" x2="21" y2="18"/>
</svg>
</button>
<div class="sidebar-overlay" id="sidebar-overlay" onclick="closeSidebar()"></div>
<aside class="sidebar" id="sidebar">
<div class="sidebar-logo">
<a href="/home" style="text-decoration:none;color:inherit"><h1>No<span>vela</span></h1></a>
</div>
<ul class="sidebar-nav">
<li>
<a href="/home"{% if active == 'home' %} class="active"{% endif %}>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
<polyline points="9 22 9 12 15 12 15 22"/>
</svg>
Home
</a>
</li>
</ul>
<hr class="sidebar-divider"/>
<div class="sidebar-section-label">Library</div>
<ul class="sidebar-nav"{% if active == 'library' %} id="lib-nav"{% endif %}>
<li>
<a href="{% if active == 'library' %}#{% else %}/library{% endif %}"
{% if active == 'library' %}id="nav-all" class="active" onclick="switchView('all'); return false;"
{% elif active == 'book' %}class="active"{% endif %}>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/>
<rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/>
</svg>
All books
<span class="sidebar-count" id="count-all"></span>
</a>
</li>
<li>
<a href="{% if active == 'library' %}#{% elif active == 'book' %}/library#wtr{% else %}/library#wtr{% endif %}"
{% if active == 'library' %}id="nav-wtr" onclick="switchView('wtr'); return false;"{% endif %}>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/>
</svg>
Want to Read
<span class="sidebar-count" id="count-wtr"></span>
</a>
</li>
<li>
<a href="{% if active == 'library' %}#{% else %}/library#new{% endif %}"
{% if active == 'library' %}id="nav-new" onclick="switchView('new'); return false;"{% endif %}>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 3v18"/><path d="M3 12h18"/>
</svg>
New
<span class="sidebar-count" id="count-new"></span>
</a>
</li>
<li>
<a href="{% if active == 'library' %}#{% else %}/library#series{% endif %}"
{% if active == 'library' %}id="nav-series" onclick="switchView('series'); return false;"{% endif %}>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="2" y="3" width="6" height="18" rx="1"/>
<rect x="9" y="3" width="6" height="18" rx="1"/>
<rect x="16" y="3" width="6" height="18" rx="1"/>
</svg>
Series
<span class="sidebar-count" id="count-series"></span>
</a>
</li>
<li>
<a href="{% if active == 'library' %}#{% else %}/library#authors{% endif %}"
{% if active == 'library' %}id="nav-authors" onclick="switchView('authors'); return false;"{% endif %}>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/>
<circle cx="9" cy="7" r="4"/>
<path d="M23 21v-2a4 4 0 0 0-3-3.87"/>
<path d="M16 3.13a4 4 0 0 1 0 7.75"/>
</svg>
Authors
<span class="sidebar-count" id="count-authors"></span>
</a>
</li>
<li>
<a href="{% if active == 'library' %}#{% else %}/library#publishers{% endif %}"
{% if active == 'library' %}id="nav-publishers" onclick="switchView('publishers'); return false;"{% endif %}>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M3 21h18"/>
<path d="M5 21V7l7-4 7 4v14"/>
<path d="M9 21v-6h6v6"/>
</svg>
Publishers
<span class="sidebar-count" id="count-publishers"></span>
</a>
</li>
<li>
<a href="{% if active == 'library' %}#{% else %}/library#archived{% endif %}"
{% if active == 'library' %}id="nav-archived" onclick="switchView('archived'); return false;"{% endif %}>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="21 8 21 21 3 21 3 8"/>
<rect x="1" y="3" width="22" height="5"/>
<line x1="10" y1="12" x2="14" y2="12"/>
</svg>
Archived
<span class="sidebar-count" id="count-archived"></span>
</a>
</li>
<li>
<a href="/stats"{% if active == 'stats' %} class="active"{% endif %}>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="20" x2="18" y2="10"/>
<line x1="12" y1="20" x2="12" y2="4"/>
<line x1="6" y1="20" x2="6" y2="14"/>
</svg>
Statistics
</a>
</li>
</ul>
<hr class="sidebar-divider"/>
<div class="sidebar-section-label">Tools</div>
<ul class="sidebar-nav">
<li>
<a href="/convert"{% if active == 'convert' %} class="active"{% endif %}>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/>
</svg>
Convert
</a>
</li>
<li>
<a href="/credentials-manager"{% if active == 'credentials' %} class="active"{% endif %}>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="3"/><path d="M19.07 4.93a10 10 0 010 14.14M4.93 4.93a10 10 0 000 14.14"/>
</svg>
Credentials
</a>
</li>
<li>
<a href="/debug"{% if active == 'debug' %} class="active"{% endif %}>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/>
</svg>
Debug
</a>
</li>
<li>
<a href="/backup"{% if active == 'backup' %} class="active"{% endif %}>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 8v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8"/>
<polyline points="1 8 12 2 23 8"/>
<path d="M12 22v-8"/>
</svg>
Backup
</a>
</li>
<li>
<a href="/settings"{% if active == 'settings' %} class="active"{% endif %}>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="3"/>
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/>
</svg>
Settings
</a>
</li>
</ul>
<div class="sidebar-bottom">
<button class="btn-rescan" onclick="rescanLibraryGlobal()" id="rescan-btn">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
<polyline points="23 4 23 10 17 10"/>
<path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/>
</svg>
<span id="rescan-label">Rescan library</span>
</button>
</div>
</aside>
<script>
function toggleSidebar() {
document.getElementById('sidebar').classList.toggle('open');
document.getElementById('sidebar-overlay').classList.toggle('open');
}
function closeSidebar() {
document.getElementById('sidebar').classList.remove('open');
document.getElementById('sidebar-overlay').classList.remove('open');
}
// Close sidebar on any nav link click (mobile)
document.querySelectorAll('.sidebar-nav a').forEach(a => {
a.addEventListener('click', () => { if (window.innerWidth <= 768) closeSidebar(); });
});
function applyLibraryCounts(books) {
const active = books.filter(b => !b.archived);
const wtrCount = active.filter(b => b.want_to_read).length;
const newCount = active.filter(b => b.needs_review).length;
const seriesCount = new Set(active.filter(b => b.series).map(b => b.series)).size;
const authorCount = new Set(active.map(b => b.author).filter(Boolean)).size;
const publisherCount = new Set(active.map(b => b.publisher).filter(Boolean)).size;
const archivedCount = books.filter(b => b.archived).length;
const setCount = (id, value) => {
const el = document.getElementById(id);
if (el) el.textContent = value || '';
};
setCount('count-all', active.length);
setCount('count-wtr', wtrCount);
setCount('count-new', newCount);
setCount('count-series', seriesCount);
setCount('count-authors', authorCount);
setCount('count-publishers', publisherCount);
setCount('count-archived', archivedCount);
}
async function refreshLibraryCounts() {
try {
const resp = await fetch('/library/list');
if (!resp.ok) return;
const books = await resp.json();
applyLibraryCounts(books);
} catch (_) {
// silently ignore; sidebar remains usable without counts
}
}
async function rescanLibraryGlobal() {
const btn = document.getElementById('rescan-btn');
const label = document.getElementById('rescan-label');
if (btn) btn.disabled = true;
if (label) label.textContent = 'Scanning…';
try {
const resp = await fetch('/library/rescan', { method: 'POST' });
if (!resp.ok) throw new Error('rescan failed');
await refreshLibraryCounts();
} catch (_) {
alert('Rescan failed.');
} finally {
if (btn) btn.disabled = false;
if (label) label.textContent = 'Rescan library';
}
}
refreshLibraryCounts();
</script>