Files
bookhoard/templates/types.go
T
John O'Keefe a90e7debfd feat(podcasts): queue context source + player order control — item 2.2
Foundation rule R2 (§10): the queue context is the only queue, and
every producer emits the same shape. PodcastSequenceContext gains
source (feed|collection), feed_id, and order; the feed producer sets
them now — the collection producer (2.3) slots into the same shape
without the player ever special-casing beyond showing/hiding controls.

The player header gains the FEED-mode order control next to the
"Ep N/M" counter: a dropdown (native/reverse/oldest/newest) plus a
one-click reverse button. Both PUT the existing per-user feed order
override — the SAME state the feed page writes, so flipping it in the
player updates the feed page's dropdown too. The control renders only
when source=feed: a collection queue's order is curated content
(R1), never a listening preference. Behavior is hx-put +
hx-swap="none" (response discarded) with a module-level
htmx:afterRequest listener in audio.ts reloading the page — the
server re-renders prev/next/index in the new order; templ decides
what it looks like, JS only triggers the round trip (CSP-clean, no
inline handlers).

Pinned by TestPodcastPlayerOrderControl: the config JSON carries
source/feed_id/order/episode_index; the control renders feed-mode;
an order override re-renders with the dropdown reflecting it (data-
json pins must match templ's " attribute escaping).
2026-10-30 15:52:14 -04:00

394 lines
10 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"`
// Source is the queue's owner (§10 R2: every producer emits the
// same shape): "feed" = the feed's effective order (per-user
// override applied), "collection" = an ordered Up Next collection
// (2.3). The player shows/hides controls by source — the order
// control is FEED-mode only, because a collection's order is
// curated content, not a listening preference.
Source string `json:"source,omitempty"`
// Feed-mode fields: FeedID powers the player's order control (it
// PUTs /api/podcasts/feeds/:id/order — the same per-user override
// the feed page writes); Order is the resolved current value that
// the dropdown reflects. Empty in collection mode.
FeedID string `json:"feed_id,omitempty"`
Order string `json:"order,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
}