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
198 lines
8.4 KiB
Templ
198 lines
8.4 KiB
Templ
package templates
|
||
|
||
templ CollectionRules(user User, collection CollectionData) {
|
||
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8"/>
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||
<title>Collection Rules - { collection.Name } - Bookhoard</title>
|
||
<script src="/static/htmx.min.js"></script>
|
||
<link href="/static/style.css" rel="stylesheet"/>
|
||
</head>
|
||
<body class="theme-{ user.Theme }" x-data="collectionRules" x-init="initCollectionRules('{ collection.ID }')">
|
||
@Header(user, "/collections")
|
||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||
<div class="mb-6">
|
||
<button @click="backToCollection()" class="btn-secondary px-4 py-2 rounded-lg mb-4">
|
||
← Back to Collection
|
||
</button>
|
||
<div class="flex items-center gap-4">
|
||
<div class="text-4xl" style="color: { collection.Color }">{ collection.Icon }</div>
|
||
<div>
|
||
<h1 class="text-3xl font-bold" style="color: var(--text-primary)">{ collection.Name }</h1>
|
||
<p style="color: var(--text-secondary)">Auto-Assign Rules</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="mb-8">
|
||
<h2 class="text-2xl font-bold mb-4" style="color: var(--text-primary)">Existing Rules</h2>
|
||
<div id="rules-container" class="space-y-4">
|
||
<div id="no-rules" class="card p-6 rounded-lg border text-center" style="background-color: var(--bg-secondary); border-color: var(--border);">
|
||
<div class="text-4xl mb-2">📋</div>
|
||
<h3 class="text-lg font-semibold mb-2" style="color: var(--text-primary)">No Rules Yet</h3>
|
||
<p style="color: var(--text-secondary)">Create auto-assign rules to automatically add books to this collection</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="mb-8">
|
||
<h2 class="text-2xl font-bold mb-4" style="color: var(--text-primary)">Create New Rule</h2>
|
||
<form id="rule-form" @submit="handleCreateRule($event)">
|
||
<input type="hidden" id="collection-id" value="{ collection.ID }"/>
|
||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
|
||
<div>
|
||
<label class="block text-sm font-medium mb-2" style="color: var(--text-secondary)">Field</label>
|
||
<select
|
||
id="rule-field"
|
||
required
|
||
class="w-full px-4 py-2 border rounded-lg"
|
||
style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);"
|
||
>
|
||
<option value="">Select field...</option>
|
||
<option value="genre">Genre</option>
|
||
<option value="series">Series</option>
|
||
<option value="author">Author</option>
|
||
<option value="language">Language</option>
|
||
<option value="publisher">Publisher</option>
|
||
<option value="copyright_year">Copyright Year</option>
|
||
<option value="tags">Tags</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium mb-2" style="color: var(--text-secondary)">Operator</label>
|
||
<select
|
||
id="rule-operator"
|
||
required
|
||
class="w-full px-4 py-2 border rounded-lg"
|
||
style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);"
|
||
>
|
||
<option value="">Select operator...</option>
|
||
<option value="equals">equals</option>
|
||
<option value="not_equals">does not equal</option>
|
||
<option value="contains">contains</option>
|
||
<option value="not_contains">does not contain</option>
|
||
<option value="starts_with">starts with</option>
|
||
<option value="ends_with">ends with</option>
|
||
<option value="greater_than">greater than</option>
|
||
<option value="less_than">less than</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="mb-6">
|
||
<label class="block text-sm font-medium mb-2" style="color: var(--text-secondary)">Value</label>
|
||
<input
|
||
type="text"
|
||
id="rule-value"
|
||
required
|
||
class="w-full px-4 py-2 border rounded-lg"
|
||
style="background-color: var(--bg-primary); color: var(--text-primary); border-color: var(--border);"
|
||
placeholder="e.g., Science Fiction"
|
||
/>
|
||
</div>
|
||
<div class="mb-6">
|
||
<label class="flex items-center space-x-3 cursor-pointer">
|
||
<input
|
||
type="checkbox"
|
||
id="rule-enabled"
|
||
checked
|
||
class="w-5 h-5 rounded"
|
||
/>
|
||
<span style="color: var(--text-primary)">Enable Rule</span>
|
||
</label>
|
||
<p class="text-xs mt-1" style="color: var(--text-secondary)">Uncheck to disable without deleting</p>
|
||
</div>
|
||
<div class="mb-6">
|
||
<label class="block text-sm font-medium mb-2" style="color: var(--text-secondary)">Priority</label>
|
||
<div class="flex gap-4">
|
||
<label class="flex items-center space-x-2 cursor-pointer">
|
||
<input type="radio" name="priority" value="1" class="w-4 h-4"/>
|
||
<span style="color: var(--text-primary)">High</span>
|
||
</label>
|
||
<label class="flex items-center space-x-2 cursor-pointer">
|
||
<input
|
||
type="radio"
|
||
name="priority"
|
||
value="2"
|
||
checked
|
||
class="w-4 h-4"
|
||
/>
|
||
<span style="color: var(--text-primary)">Medium</span>
|
||
</label>
|
||
<label class="flex items-center space-x-2 cursor-pointer">
|
||
<input
|
||
type="radio"
|
||
name="priority"
|
||
value="3"
|
||
class="w-4 h-4"
|
||
/>
|
||
<span style="color: var(--text-primary)">Low</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
<div class="flex justify-end space-x-3">
|
||
<button type="button" @click="testRule()" class="btn-secondary px-4 py-2 rounded-lg">
|
||
🧪 Test Rule
|
||
</button>
|
||
<button type="button" @click="clearForm()" class="btn-secondary px-4 py-2 rounded-lg">
|
||
Clear
|
||
</button>
|
||
<button type="submit" class="btn-primary px-4 py-2 rounded-lg">
|
||
➕ Add Rule
|
||
</button>
|
||
</div>
|
||
</form>
|
||
<div id="test-results" class="hidden card p-6 rounded-lg border mb-6" style="background-color: var(--bg-primary); border-color: var(--border);">
|
||
<h3 class="font-semibold mb-3" style="color: var(--text-primary)">Rule Test Results</h3>
|
||
<p class="text-sm mb-2" style="color: var(--text-secondary)">Books that would be added by this rule:</p>
|
||
<div id="test-results-list" class="max-h-64 overflow-y-auto space-y-2"></div>
|
||
</div>
|
||
</div>
|
||
<div class="card p-6 rounded-lg border" style="background-color: var(--bg-primary); border-color: var(--border);">
|
||
<h2 class="text-xl font-bold mb-4" style="color: var(--text-primary)">Rule Examples</h2>
|
||
<div class="space-y-4">
|
||
<div class="flex items-start gap-4">
|
||
<div class="flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center font-bold" style="background-color: var(--accent);">1</div>
|
||
<div>
|
||
<p class="font-medium" style="color: var(--text-primary)">Add all Science Fiction books</p>
|
||
<code class="block mt-1 text-sm" style="color: var(--text-secondary); background-color: var(--bg-secondary); padding: 4px 8px; border-radius: 4px;">
|
||
Field: genre
|
||
<br/>
|
||
Operator: equals
|
||
<br/>
|
||
Value: Science Fiction
|
||
</code>
|
||
</div>
|
||
</div>
|
||
<div class="flex items-start gap-4">
|
||
<div class="flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center font-bold" style="background-color: var(--accent);">2</div>
|
||
<div>
|
||
<p class="font-medium" style="color: var(--text-primary)">Add books from a specific series</p>
|
||
<code class="block mt-1 text-sm" style="color: var(--text-secondary); background-color: var(--bg-secondary); padding: 4px 8px; border-radius: 4px;">
|
||
Field: series
|
||
<br/>
|
||
Operator: starts with
|
||
<br/>
|
||
Value: Harry Potter
|
||
</code>
|
||
</div>
|
||
</div>
|
||
<div class="flex items-start gap-4">
|
||
<div class="flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center font-bold" style="background-color: var(--accent);">3</div>
|
||
<div>
|
||
<p class="font-medium" style="color: var(--text-primary)">Add books published in a year range</p>
|
||
<code class="block mt-1 text-sm" style="color: var(--text-secondary); background-color: var(--bg-secondary); padding: 4px 8px; border-radius: 4px;">
|
||
Field: copyright_year
|
||
<br/>
|
||
Operator: greater than
|
||
<br/>
|
||
Value: 2020
|
||
</code>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</body>
|
||
</html>
|
||
}
|