Remove redundant <script src="/static/main.js" defer></script> tags from 17+ templates that include the @Header component, eliminating duplicate script loading that was causing Alpine.js to initialize twice per page load. The header.templ component now serves as the single source of truth for main.js inclusion, following the DRY principle and ensuring consistent script loading across all pages that use the header navigation. Additionally, add type="button" attribute to all buttons in header navigation to prevent default form submission behavior when buttons are clicked. Changes: - Remove main.js script tag from templates using @Header component - Keep main.js in header.templ (line 279) as universal inclusion point - Preserve main.js in special pages: index.templ, login.templ, register.templ (these don't use @Header and are standalone entry points) - Add type="button" to theme toggle, theme selection, wood paneling, and user menu buttons to prevent unwanted form submissions or page navigation Benefits: - Eliminates Alpine.js double-initialization bug - Reduces HTTP requests (one script load instead of two) - Improves maintainability (add header, get scripts automatically) - Fixes broken @click handlers on collections, devices, and other pages - Prevents buttons from triggering default form submission behavior Technical notes: - Templates affected: admin, analytics, bookshelf, collection_rules, collections, conflicts, custom_section, dashboard, devices, docs, library, profile, progress, queue, unlinked_books - No changes to entry pages (index, login, register) which don't use @Header - HTMX script remains in individual templates (stateless, no double-load issue) - All interactive buttons in header now explicitly marked type="button" to prevent default browser form submission behavior Related to: previous commit fixing Vite code-splitting
156 lines
8.9 KiB
Templ
156 lines
8.9 KiB
Templ
package templates
|
|
|
|
templ AdminLibrary(user User, libraries []LibraryData, users []User) {
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8"/>
|
|
<title>Library Management - Bookhoard</title>
|
|
<link href="/static/style.css" rel="stylesheet"/>
|
|
</head>
|
|
<body x-data="library" x-init="initializeLibraryAdmin" class="theme-{ user.Theme }">
|
|
@Header(user, "/admin/library")
|
|
<div class="flex min-h-screen" style="background-color: var(--bg-primary)">
|
|
@AdminSidebar(user, "/admin/library")
|
|
<main class="flex-1 p-8">
|
|
<div class="w-full">
|
|
<div class="mb-8">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<a href="/admin" class="btn-secondary px-4 py-2 rounded-lg font-medium">
|
|
← Back to Dashboard
|
|
</a>
|
|
<button data-action="show-create-modal" class="btn-primary px-4 py-2 rounded-lg font-medium">
|
|
+ Create Library
|
|
</button>
|
|
</div>
|
|
<h1 class="text-3xl font-bold mb-2" style="color: var(--text-primary)">Library Management</h1>
|
|
<p style="color: var(--text-secondary)">Manage libraries and configure media scanning</p>
|
|
</div>
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
|
<!-- Libraries Section -->
|
|
<div class="card p-6 rounded-lg border" style="background-color: var(--bg-secondary); border-color: var(--border)">
|
|
<h3 class="text-xl font-semibold mb-6" style="color: var(--text-primary)">Libraries</h3>
|
|
<p style="color: var(--text-secondary)" class="mb-4">Manage media libraries and their folders</p>
|
|
<div id="libraries-list" class="space-y-3 mb-6">
|
|
if len(libraries) == 0 {
|
|
<p style="color: var(--text-secondary)" class="text-center py-8">
|
|
No libraries yet. Create your first library to get started.
|
|
</p>
|
|
} else {
|
|
for _, library := range libraries {
|
|
<div class="p-4 border rounded-lg" style="background-color: var(--bg-primary); border-color: var(--border)">
|
|
<div class="flex justify-between items-start mb-2">
|
|
<div>
|
|
<h4 class="font-semibold" style="color: var(--text-primary)">{ library.Name }</h4>
|
|
if library.Description != "" {
|
|
<p class="text-sm" style="color: var(--text-secondary)">{ library.Description }</p>
|
|
}
|
|
<span class="inline-block px-2 py-1 text-xs rounded" style="background-color: var(--accent); color: var(--bg-primary)">
|
|
{ library.TypeName }
|
|
</span>
|
|
</div>
|
|
<div class="flex space-x-2">
|
|
<button data-library-id={ library.ID } data-action="show-folders" class="text-xs px-2 py-1 rounded" style="background-color: var(--bg-secondary); color: var(--text-primary)">Folders</button>
|
|
<button data-library-id={ library.ID } data-action="edit" class="text-xs px-2 py-1 rounded" style="background-color: var(--accent); color: var(--bg-primary)">Edit</button>
|
|
<button data-library-id={ library.ID } data-action="delete" class="text-xs px-2 py-1 rounded text-red-500">Delete</button>
|
|
</div>
|
|
</div>
|
|
<div id={ "library-folders-" + library.ID } class="hidden mt-3 space-y-2"></div>
|
|
</div>
|
|
}
|
|
}
|
|
</div>
|
|
</div>
|
|
<!-- User Library Visibility Section -->
|
|
<div class="card p-6 rounded-lg border" style="background-color: var(--bg-secondary); border-color: var(--border)">
|
|
<h3 class="text-xl font-semibold mb-6" style="color: var(--text-primary)">Library Visibility</h3>
|
|
<p style="color: var(--text-secondary)" class="mb-4">Control which libraries are visible to users</p>
|
|
<div id="visibility-controls" class="space-y-4">
|
|
<!-- Visibility controls will be loaded here -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- User Visibility Management -->
|
|
<div class="mt-8 card p-6 rounded-lg border" style="background-color: var(--bg-secondary); border-color: var(--border)">
|
|
<h3 class="text-xl font-semibold mb-6" style="color: var(--text-primary)">User Library Access</h3>
|
|
<p style="color: var(--text-secondary)" class="mb-4">Manage individual user access to specific libraries</p>
|
|
<div class="mb-4">
|
|
<select id="user-select" onchange="loadUserVisibility()" class="px-3 py-2 border rounded" style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border)">
|
|
<option value="">Select a user...</option>
|
|
for _, user := range users {
|
|
<option value="{ user.ID }">{ user.Username } ({ user.Email })</option>
|
|
}
|
|
</select>
|
|
</div>
|
|
<div id="user-libraries" class="space-y-3">
|
|
<!-- User library checkboxes will be loaded here -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
<!-- Create Library Modal -->
|
|
<div id="create-library-modal" class="hidden fixed inset-0 z-50 flex items-center justify-center" style="background-color: rgba(0, 0, 0, 0.7);">
|
|
<div class="card rounded-lg p-6 w-full max-w-md mx-4" style="background-color: var(--bg-secondary); border-color: var(--border);">
|
|
<div class="flex justify-between items-center mb-6">
|
|
<h2 class="text-xl font-bold" style="color: var(--text-primary)">Create Library</h2>
|
|
<button type="button" data-action="hide-create-modal" class="p-2 hover:opacity-80 rounded" style="color: var(--text-primary)">✕</button>
|
|
</div>
|
|
<form id="create-library-form">
|
|
<input type="hidden" id="library-id" name="id"/>
|
|
<div class="mb-4">
|
|
<label class="block text-sm font-medium mb-2" style="color: var(--text-primary)">Library Name</label>
|
|
<input type="text" name="name" placeholder="My Ebook Library" class="w-full px-3 py-2 border rounded-lg" style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border)" required/>
|
|
</div>
|
|
<div class="mb-4">
|
|
<label class="block text-sm font-medium mb-2" style="color: var(--text-primary)">Description</label>
|
|
<textarea name="description" placeholder="Optional description" rows="3" class="w-full px-3 py-2 border rounded-lg" style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border)"></textarea>
|
|
</div>
|
|
<div class="mb-4">
|
|
<label class="block text-sm font-medium mb-2" style="color: var(--text-primary)">Library Type</label>
|
|
<select name="type" class="w-full px-3 py-2 border rounded-lg" style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border)" required>
|
|
<option value="ebooks">📚 Ebooks</option>
|
|
<option value="comics">📖 Comics</option>
|
|
<option value="manga">🗾 Manga</option>
|
|
</select>
|
|
</div>
|
|
<div class="flex justify-end space-x-3">
|
|
<button type="button" data-action="hide-create-modal" class="btn-secondary px-4 py-2 rounded-lg">Cancel</button>
|
|
<button type="submit" class="btn-primary px-4 py-2 rounded-lg">Create</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<!-- Folder Browser Modal -->
|
|
<div id="folder-browser-modal" class="hidden fixed inset-0 z-50 flex items-center justify-center" style="background-color: rgba(0, 0, 0, 0.7);">
|
|
<div class="card rounded-lg p-6 w-full max-w-md mx-4" style="background-color: var(--bg-secondary); border-color: var(--border);">
|
|
<div class="flex justify-between items-center mb-4">
|
|
<h2 class="text-xl font-bold" style="color: var(--text-primary)">Browse Folders</h2>
|
|
<button type="button" data-action="browse-cancel" class="p-2 hover:opacity-80 rounded" style="color: var(--text-primary)">✕</button>
|
|
</div>
|
|
<div id="folder-browser-content">
|
|
<!-- Directory listings will be rendered here -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Delete Library Confirmation Modal -->
|
|
<div id="delete-library-modal" class="hidden fixed inset-0 z-50 flex items-center justify-center" style="background-color: rgba(0, 0, 0, 0.7);">
|
|
<div class="card rounded-lg p-6 w-full max-w-md mx-4" style="background-color: var(--bg-secondary); border-color: var(--border);">
|
|
<div class="flex justify-between items-center mb-4">
|
|
<h2 class="text-xl font-bold" style="color: var(--text-primary)">Delete Library</h2>
|
|
<button type="button" data-action="hide-delete-modal" class="p-2 hover:opacity-80 rounded" style="color: var(--text-primary)">✕</button>
|
|
</div>
|
|
<div id="delete-modal-content" class="mb-6" style="color: var(--text-primary)">
|
|
<!-- Dynamic content will be injected here -->
|
|
</div>
|
|
<div class="flex justify-end space-x-3">
|
|
<button type="button" data-action="hide-delete-modal" class="btn-secondary px-4 py-2 rounded-lg">Cancel</button>
|
|
<button type="button" data-action="confirm-delete" class="btn-primary px-4 py-2 rounded-lg bg-red-500 hover:bg-red-600">Delete</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<script src="/static/htmx.min.js"></script>
|
|
</body>
|
|
</html>
|
|
}
|