Files
bookhoard/templates/types.go
T
John O'Keefe 28420d56ed feat(podcasts): chain playback + Continue section — sequence mode in the web player (phase 2 core)
D7 chain-by-default, delivered with minimal player surgery:

- ReaderMetadata gains PodcastSequence: the neighboring DOWNLOADED
  episodes in the caller's effective order (D5 override applied,
  feed-native fallback; computed in the reader route for
  source='podcast_episode' items — RLS already scoped the reads; gaps
  never enter the chain, invariant 6). ?single=1 escapes chaining (the
  'play single episode' affordance).
- Audio reader renders the sequence into body[data-json]
  (AudioReaderConfigJSON — books produce the identical shape minus the
  sequence key, so the reader path is unchanged for them) and an
  'Ep N/M' indicator + prev/next episode controls in the header.
- audio.ts: the last-track 'ended' handler chains into the next
  episode's reader page (final progress push precedes navigation);
  back button exits episodes to their feed page. Books are untouched —
  no sequence context, zero behavior change. Day-one chaining is
  page-navigation based (progress persists; position restores).

Continue aggregation (2.5): GetPodcastContinueForUser (in-progress
episodes, most-recent-first, ≥95% excluded) feeds a Continue section
on /podcasts — sorted most recently listened, cards show 'Ep N · title'
— ahead of All Feeds. Cards prefer the continue line over the
description.
2026-10-29 16:25:44 -04:00

381 lines
9.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package templates
import (
"time"
"github.com/a-h/templ"
)
type User struct {
ID string
Username string
Email string
Role string
Theme string
FirstName string
LastName string
CreatedAt time.Time
Token string
Timezone string
// User-level default audiobook playback speed (plan §8); 0 means
// "unset" and renders as the 1× option.
DefaultAudioSpeed float64
// Whether the web reader/player should hold a Screen Wake Lock.
KeepScreenAwake bool
// Sidebar /podcasts entry gate (plan D13): the user can see >=1
// podcasts-type library. Resolved centrally in
// getTemplateUserWithTheme so every page's Header is correct.
ShowPodcastsNav bool
}
// SpeedOption is one entry of the default-playback-speed select — the
// server-side mirror of the web player's SPEEDS list (plan §5/§8).
var SpeedOptions = []float64{0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.5, 3}
type PageData struct {
User User
CurrentPage string
}
type CollectionData struct {
ID string
Name string
Description string
Color string
Icon string
IsSystem bool
// A2c §3.3: playlist toggles (static collections only). Hidden fields
// preserve the current values on submit; absent from the create form.
HideFinished bool
ShowUpNext bool
// IsSmart marks rule-based ("dynamic") collections: membership is
// evaluated live at render, not stored in collection_items.
IsSmart bool
// BookCount is the LIVE count for the card (rule matches for smart
// collections, stored links for static ones).
BookCount int
}
type LibraryData struct {
ID string
Name string
Description string
TypeName string
TypeValue string
MediaCount int64
FolderCount int
// Offline is true when none of the library's folders exist on disk
// (e.g. an unmounted external drive). Contents stay intact and reappear
// when the storage returns.
Offline bool
}
type FolderData struct {
FolderPath string
}
// ArchivedItem feeds the admin archived-items page: media hidden from
// libraries because their files vanished, with their pending fate. When an
// active item with identical content (same SHA-256) exists, MatchID and
// friends are set so the UI can offer merging the archived row's reading
// data into its active twin.
type ArchivedItem struct {
ID string
Title string
Author string
LibraryName string
FilePath string
MissingScans int32
ArchivedAt *time.Time
PurgeAt *time.Time
Status string
MatchID string
MatchTitle string
MatchLibraryName string
}
type DirEntry struct {
Name string
Path string
}
type UserVisibilityData struct {
UserID string
Username string
Email string
IsVisible bool
}
type AdminStats struct {
LibraryCount int
MediaCount int
UserCount int
DeviceCount int
}
type ScanSettingsData struct {
AutoScanEnabled bool
ScanPollIntervalSeconds int
}
// SettingEntry mirrors database.SettingEntry for the admin UI. Kept as a
// template-local type so the templates package does not import database.
type SettingEntry struct {
Key string
Value string
Type string
Min string
Max string
RequiresRestart bool
Category string
Group string
Description string
IsDefault bool
}
// SettingGroup is a labeled cluster of related settings rendered as a
// sub-section within a tunable-settings card.
type SettingGroup struct {
Name string
Entries []SettingEntry
}
type SeriesCardData struct {
Name string
LibraryID string
BookCount int64
TotalInSeries int
CoverPaths []string
// Viewer's finished books in this series (drives the card's
// progress bar / "X of N read" line).
ReadCount int64
}
// SeriesPageState is the /series page's sort/filter state, echoed back
// into the toolbar controls and the pagination URLs (the URL is the
// single source of truth on this plain-GET page). Sort holds the BASE
// toolbar key (name/author/…); Dir is the effective direction.
type SeriesPageState struct {
Sort string
Dir string
Query string
Status string
}
type PendingRegistrationData struct {
RegistrationID string
DeviceName string
DeviceType string
ExpiresAt string
}
type ConflictData struct {
ID string
MediaItemID string
MediaItemTitle string
ConflictType string
ResolutionStatus string
CreatedAt string
ResolutionData map[string]interface{}
ResolvedBy string
ResolvedAt string
}
type QueueItemData struct {
ID string
DeviceID string
DeviceName string
DeviceType string
SyncType string
Status string
Priority int32
Attempts int32
CreatedAt string
}
type DeviceShelfMappingData struct {
ID string
CollectionID string
CollectionName string
DeviceShelfName string
SyncDirection string
CreatedAt string
}
type UnlinkedBookData struct {
ProgressID string
DeviceID string
DeviceType string
DeviceName string
FilePath string
SHA256 string
TitleFromDevice string
LastSyncTime string
ConfidenceScore float64
PotentialMatches []PotentialMatchData
}
type PotentialMatchData struct {
MediaItemID string
Title string
Author string
CoverImagePath string
MatchMethod string
Confidence float64
}
// Documentation types
type Navigation struct {
Sections []NavSection
}
// NavSection is a top-level documentation category. Items are documents that
// live directly in the section; Groups are subcategories (subdirectories).
type NavSection struct {
Title string
Icon string
Items []NavItem
Groups []NavGroup
Collapsed bool
}
// NavGroup is a subcategory inside a NavSection (a subdirectory of docs).
type NavGroup struct {
Title string
Icon string
Items []NavItem
Collapsed bool
}
type NavItem struct {
Title string
URL string
}
// SearchResultItem is one entry in the docs search results partial. The
// server renders it (templ); the client only swaps the markup in.
type SearchResultItem struct {
Title string
Category string
URL string
}
type Document struct {
Title string
Content string
TOC []TOCItem
Breadcrumb []BreadcrumbItem
Category string
SourceFile string
}
type TOCItem struct {
Level int
Title string
Anchor string
}
type BreadcrumbItem struct {
Title string
URL string
}
// UnsafeHTML is used for rendering raw HTML (like rendered markdown)
type UnsafeHTML string
// ToComponent converts UnsafeHTML to a templ Component
func (u UnsafeHTML) ToComponent() templ.Component {
return templ.Raw(string(u))
}
// Reader types
type ReaderMetadata struct {
MediaItemID string `json:"media_item_id"`
Title string `json:"title"`
Author string `json:"author"`
CoverImagePath string `json:"cover_image_path"`
LibraryType string `json:"library_type"`
MimeType string `json:"mime_type"`
FilePath string `json:"file_path"`
TotalPages int `json:"total_pages"`
ChapterCount int `json:"chapter_count"`
FormatGroup string `json:"format_group"`
MangaType string `json:"manga_type"`
ReadingDirection string `json:"reading_direction"`
FileURL string `json:"file_url"`
LibraryID string `json:"library_id"`
TotalCharacters int64 `json:"total_characters"`
EstimatedPages int `json:"estimated_pages"`
// TreatAsComic: fixed-layout item in a comics/manga library gets
// comic reader treatment (bookmarks only — no highlights, no search).
TreatAsComic bool `json:"treat_as_comic"`
// KeepScreenAwake: user preference — the reader holds a Screen Wake
// Lock while open when true.
KeepScreenAwake bool `json:"keep_screen_awake"`
// PodcastSequence (plan D7/Phase 2): set ONLY for podcast episodes —
// the neighboring downloaded episodes in the caller's effective
// order. Powers chain-by-default playback (auto-advance across
// episodes) and the player's episode prev/next. nil for books.
PodcastSequence *PodcastSequenceContext `json:"podcast_sequence,omitempty"`
}
// PodcastSequenceContext is the episode-level chain payload rendered
// into the audio reader's config JSON.
type PodcastSequenceContext struct {
FeedURL string `json:"feed_url,omitempty"`
FeedTitle string `json:"feed_title,omitempty"`
PrevURL string `json:"prev_url,omitempty"`
NextURL string `json:"next_url,omitempty"`
NextTitle string `json:"next_title,omitempty"`
// EpisodeIndex/IndexTotal render the "Ep N of M" indicator (D7).
EpisodeIndex int `json:"episode_index,omitempty"`
IndexTotal int `json:"index_total,omitempty"`
}
type ReadingProgress struct {
ID string
MediaItemID string
UserID string
CurrentPage int
TotalPages int
Percentage float64
EpubCfi string
LastReadAt time.Time
Chapter int
ChapterProgress float64
FormatGroup string
}
type Bookmark struct {
ID string
MediaItemID string
UserID string
PageNumber *int
ChapterNumber *int
CfiPosition string
Title string
Position string
Notes string
CreatedAt time.Time
}
// ProcessingIssueData - Processing issue types
type ProcessingIssueData struct {
ID string
MediaItemID string
Title string
FilePath string
FormatGroup string
LibraryTypeName string
IssueType string
IssueDescription string
Severity string
CreatedAt time.Time
}
type IssueStats struct {
ErrorCount int64
WarningCount int64
InfoCount int64
}