Implement SSR-first book detail page at /media/:uuid with complete book information, progress tracking, and interactivity. Features: - Cover image (256x384px) with responsive layout - Complete metadata: title, author, description, publisher, ISBN, language, edition, page count, genre, copyright year, format - External service links (Goodreads, Open Library, Google Books, Amazon) with smart URL fallback: ID → ISBN → Title+Author - Reading progress display with device sources (web/kobo/koreader) - Sync progress modal for conflict resolution - Collections display as clickable badges - Notes/highlights counter with placeholder modal - Rating display (1-10 scale with star rendering) - HTML sanitization for book descriptions using bluemonday Data Structure: - handlers.MediaDetail embeds database.MediaItems for zero duplication - Uses existing database queries (GetMediaItem, GetMediaRating, etc.) - Follows project pattern: no parallel type systems Frontend: - TypeScript modal triggers (book-detail.ts) - Alpine.js for modal interactions - TailwindCSS styling with theme variables - Responsive: cover-left layout, mobile stacks vertically Backend: - Route: GET /media/:uuid (protected) - Handler: inline function in frontend.go following existing pattern - Template: SSR-first with progressive enhancement - Returns HTML only (API uses separate /api/media-items/:id endpoint) Files created: - internal/handlers/media_detail.go - templates/book_detail.templ - templates/book_detail_modals.templ - web/src/book-detail.ts Files modified: - internal/router/frontend.go (add route) - web/src/main.ts (import module)
22 lines
743 B
Go
22 lines
743 B
Go
package handlers
|
|
|
|
import "bookhoard/internal/database"
|
|
|
|
// MediaDetail embeds database.MediaItems for complete book metadata
|
|
// No field duplication - template gets direct access to all database fields
|
|
type MediaDetail struct {
|
|
database.MediaItems // Embedded - ALL book fields available
|
|
|
|
// User-specific data
|
|
Rating *database.MediaRatings `json:"rating,omitempty"`
|
|
Collections []database.Collections `json:"collections"`
|
|
ReadingProgress *database.ReadingProgress `json:"reading_progress,omitempty"`
|
|
|
|
// Conflict data (if exists)
|
|
ActiveConflict *ConflictDetailResponse `json:"active_conflict,omitempty"`
|
|
|
|
// Computed counts
|
|
NotesCount int `json:"notes_count"`
|
|
HighlightsCount int `json:"highlights_count"`
|
|
}
|