diff --git a/web/components/anime/pending.templ b/web/components/anime/pending.templ
deleted file mode 100644
index 0937ed8..0000000
--- a/web/components/anime/pending.templ
+++ /dev/null
@@ -1,23 +0,0 @@
-package anime
-
-import (
- "fmt"
- "mal/web/shared/layout"
-)
-
-templ Pending(id int) {
- @layout.Layout("mal - anime pending", true) {
-
-
-
- Anime data is being fetched
- We could not load this anime right now. A background worker is retrying data fetch for anime #{ fmt.Sprintf("%d", id) }.
- Refresh this page in a few seconds.
-
-
-
-
- }
-}
diff --git a/web/components/anime/recommendations.templ b/web/components/anime/recommendations.templ
deleted file mode 100644
index 53d0430..0000000
--- a/web/components/anime/recommendations.templ
+++ /dev/null
@@ -1,27 +0,0 @@
-package anime
-
-import (
- "mal/integrations/jikan"
- ui "mal/web/components"
-)
-
-templ Recommendations(recs []jikan.Anime, watchlistStatuses map[int]string) {
- if len(recs) > 0 {
-
- for _, anime := range recs {
- @ui.AnimeCard(ui.AnimeCardProps{
- ID: anime.MalID,
- Title: anime.DisplayTitle(),
- ImageURL: anime.ImageURL(),
- TitleEnglish: anime.TitleEnglish,
- TitleJapanese: anime.TitleJapanese,
- Airing: anime.Airing,
- Synopsis: anime.Synopsis,
- WatchlistStatus: watchlistStatuses[anime.MalID],
- })
- }
-
- } else {
- No recommendations available.
- }
-}
diff --git a/web/components/anime/relations.templ b/web/components/anime/relations.templ
deleted file mode 100644
index 7a56cdf..0000000
--- a/web/components/anime/relations.templ
+++ /dev/null
@@ -1,34 +0,0 @@
-package anime
-
-import (
- "mal/integrations/jikan"
- ui "mal/web/components"
-)
-
-templ RelationsList(relations []jikan.RelationEntry, watchlistStatuses map[int]string) {
- if len(relations) > 1 {
-
- for _, rel := range relations {
- @ui.AnimeCard(ui.AnimeCardProps{
- ID: rel.Anime.MalID,
- Title: rel.Anime.DisplayTitle(),
- ImageURL: rel.Anime.ImageURL(),
- TitleEnglish: rel.Anime.TitleEnglish,
- TitleJapanese: rel.Anime.TitleJapanese,
- Airing: rel.Anime.Airing,
- CurrentNode: rel.IsCurrent,
- WatchlistStatus: watchlistStatuses[rel.Anime.MalID],
- }) {
- if rel.IsCurrent {
-
- }
- if rel.Relation != "" && rel.Relation != "Current" {
-
{ rel.Relation }
- }
- }
- }
-
- } else {
- No related anime found.
- }
-}
diff --git a/web/components/anime/studios.templ b/web/components/anime/studios.templ
deleted file mode 100644
index 2ccabe4..0000000
--- a/web/components/anime/studios.templ
+++ /dev/null
@@ -1,18 +0,0 @@
-package anime
-
-import (
- "fmt"
- "mal/integrations/jikan"
-)
-
-templ StudioLinks(studios []jikan.NamedEntity) {
- for i, studio := range studios {
- { studio.Name }
- if i < len(studios)-1 {
- ,
- }
- }
-}
diff --git a/web/components/anime_card.templ b/web/components/anime_card.templ
deleted file mode 100644
index 4414a82..0000000
--- a/web/components/anime_card.templ
+++ /dev/null
@@ -1,117 +0,0 @@
-package ui
-
-import (
- "fmt"
-
- "mal/web/components/watchlist"
-)
-
-type AnimeCardProps struct {
- ID int
- Title string
- ImageURL string
- Href string
- Class string
- ImgClass string
- TitleClass string
- HideTitle bool
- CurrentNode bool
- Synopsis string
- PlayHref string
- TitleEnglish string
- TitleJapanese string
- Airing bool
- WatchlistStatus string
- DisableWatchlist bool
-}
-
-templ AnimeCard(props AnimeCardProps) {
-
- @animeCardPoster(props)
- if !props.HideTitle {
- if props.CurrentNode {
-
- { props.Title }
-
- } else {
-
-
- { props.Title }
-
-
- }
- }
- { children... }
-
-}
-
-func cardHref(props AnimeCardProps) string {
- if props.Href != "" {
- return props.Href
- }
- return fmt.Sprintf("/anime/%d", props.ID)
-}
-
-templ animeCardPoster(props AnimeCardProps) {
-
-
- @animeCardImage(props)
-
-
- if props.Synopsis != "" {
-
-
-
{ props.Title }
-
{ props.Synopsis }
-
-
- }
- if !props.CurrentNode {
-
- }
- if props.PlayHref != "" || !props.CurrentNode && !props.DisableWatchlist {
-
-
- if props.PlayHref != "" {
-
-
- Play
-
-
-
- }
- if !props.CurrentNode && !props.DisableWatchlist {
- @watchlist.CardButton(
- props.ID,
- props.Title,
- props.TitleEnglish,
- props.TitleJapanese,
- props.ImageURL,
- props.Airing,
- props.WatchlistStatus != "",
- )
- }
-
-
- }
-
-}
-
-templ animeCardImage(props AnimeCardProps) {
- if props.ImageURL != "" {
-
- } else {
- No image
- }
-}
-
-func defaultString(val, fallback string) string {
- if val == "" {
- return fallback
- }
- return val
-}
diff --git a/web/components/anime_list.templ b/web/components/anime_list.templ
deleted file mode 100644
index 2d46b0d..0000000
--- a/web/components/anime_list.templ
+++ /dev/null
@@ -1,32 +0,0 @@
-package ui
-
-import (
- "fmt"
- "mal/integrations/jikan"
-)
-
-templ InfiniteAnimeList(animes []jikan.Anime, watchlistStatuses map[int]string, hasNext bool, nextURL string, containerID string) {
- for _, anime := range animes {
-
- @CatalogItem(anime, watchlistStatuses[anime.MalID])
-
- }
- if hasNext {
-
- }
-
-}
-
-templ CatalogItem(anime jikan.Anime, watchlistStatus string) {
- @AnimeCard(AnimeCardProps{
- ID: anime.MalID,
- Title: anime.DisplayTitle(),
- ImageURL: anime.ImageURL(),
- TitleEnglish: anime.TitleEnglish,
- TitleJapanese: anime.TitleJapanese,
- Airing: anime.Airing,
- Synopsis: anime.Synopsis,
- PlayHref: fmt.Sprintf("/watch/%d/1", anime.MalID),
- WatchlistStatus: watchlistStatus,
- })
-}
diff --git a/web/components/empty_state.templ b/web/components/empty_state.templ
deleted file mode 100644
index be4a38a..0000000
--- a/web/components/empty_state.templ
+++ /dev/null
@@ -1,10 +0,0 @@
-package ui
-
-templ EmptyState(title string) {
-
-
{ title }
-
- { children... }
-
-
-}
diff --git a/web/components/icons/icons.templ b/web/components/icons/icons.templ
deleted file mode 100644
index 8223f84..0000000
--- a/web/components/icons/icons.templ
+++ /dev/null
@@ -1,9 +0,0 @@
-package icons
-
-templ LogoIcon(class string) {
-
-
-
-
-
-}
diff --git a/web/components/loading.templ b/web/components/loading.templ
deleted file mode 100644
index 9ec1c8e..0000000
--- a/web/components/loading.templ
+++ /dev/null
@@ -1,12 +0,0 @@
-package ui
-
-templ LoadingIndicator(text string) {
-
-}
diff --git a/web/components/sort_filter.templ b/web/components/sort_filter.templ
deleted file mode 100644
index c599c2c..0000000
--- a/web/components/sort_filter.templ
+++ /dev/null
@@ -1,34 +0,0 @@
-package ui
-
-type SortFilterOptions struct {
- Sort string // "title", "date"
- Order string // "asc", "desc"
- Status string // for watchlist: "all", "watching", etc
-}
-
-templ SortFilter(opts SortFilterOptions) {
-
-
- Sort by
-
- Date added
- Title
-
-
-
- Order
-
- Descending
- Ascending
-
-
-
-
-
-}
diff --git a/web/components/ui/loading_small.templ b/web/components/ui/loading_small.templ
deleted file mode 100644
index 9e8e0d6..0000000
--- a/web/components/ui/loading_small.templ
+++ /dev/null
@@ -1,7 +0,0 @@
-package ui
-
-templ LoadingIndicatorSmall() {
-
-}
diff --git a/web/components/watch/episodes.templ b/web/components/watch/episodes.templ
deleted file mode 100644
index c5df7a6..0000000
--- a/web/components/watch/episodes.templ
+++ /dev/null
@@ -1,58 +0,0 @@
-package watch
-
-import (
- "fmt"
- "mal/integrations/jikan"
-)
-
-templ EpisodeList(episodes []jikan.Episode, currentEpisode string, animeID int) {
- if len(episodes) == 0 {
- No episodes available
- } else {
-
- for _, ep := range episodes {
- @EpisodeItem(ep, currentEpisode, animeID)
- }
-
- }
-}
-
-templ EpisodeItem(episode jikan.Episode, currentEpisode string, animeID int) {
- {{ isCurrent := fmt.Sprintf("%d", episode.MalID) == currentEpisode }}
-
-
- { fmt.Sprintf("%d", episode.MalID) }
-
-
- if episode.Title != "" {
- { episode.Title }
- } else {
- Episode { fmt.Sprintf("%d", episode.MalID) }
- }
-
-
- if episode.Filler {
-
Filler
- }
- if episode.Recap {
-
Recap
- }
- if isCurrent {
-
- }
-
-
-}
diff --git a/web/components/watch/video_player.templ b/web/components/watch/video_player.templ
deleted file mode 100644
index f2c5434..0000000
--- a/web/components/watch/video_player.templ
+++ /dev/null
@@ -1,272 +0,0 @@
-package watch
-
-import (
- "fmt"
- "mal/web/shared"
-)
-
-templ VideoPlayer(data shared.WatchPageData, displayTitle string) {
- {{ streamToken := shared.ModeToken(data.InitialMode, data.ModeSources) }}
- {{ hasDub := shared.ModeAvailable(data.AvailableModes, "dub") }}
- {{ hasSub := shared.ModeAvailable(data.AvailableModes, "sub") }}
- {{ episodeTitle := data.EpisodeTitle }}
-
-
-
-
-
{ displayTitle }
-
- if episodeTitle != "" {
- { fmt.Sprintf("Episode %s, %s", data.CurrentEpisode, episodeTitle) }
- } else {
- { fmt.Sprintf("Episode %s", data.CurrentEpisode) }
- }
-
-
-
-
-
- Skip intro
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 00:00 / 00:00
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-}
diff --git a/web/components/watchlist/card_button.templ b/web/components/watchlist/card_button.templ
deleted file mode 100644
index aef6646..0000000
--- a/web/components/watchlist/card_button.templ
+++ /dev/null
@@ -1,57 +0,0 @@
-package watchlist
-
-import (
- "fmt"
-
- "mal/web/shared"
-)
-
-templ CardButton(
- animeID int,
- title string,
- titleEnglish string,
- titleJapanese string,
- imageURL string,
- airing bool,
- inWatchlist bool,
-) {
-
-
- { getWatchlistLabel(inWatchlist) }
-
-
-
-}
-
-func getWatchlistFill(inWatchlist bool) string {
- if inWatchlist {
- return "currentColor"
- }
- return "none"
-}
-
-func getWatchlistLabel(inWatchlist bool) string {
- if inWatchlist {
- return "In watchlist"
- }
- return "Add to watchlist"
-}
diff --git a/web/components/watchlist/dropdown.templ b/web/components/watchlist/dropdown.templ
deleted file mode 100644
index e3ee600..0000000
--- a/web/components/watchlist/dropdown.templ
+++ /dev/null
@@ -1,99 +0,0 @@
-package watchlist
-
-import (
- "fmt"
-
- "mal/web/shared"
-)
-
-templ WatchlistDropdown(
- animeID int,
- animeTitle string,
- animeTitleEnglish string,
- animeTitleJapanese string,
- animeImage string,
- currentStatus string,
- airing bool,
-) {
-
-
- if currentStatus != "" {
- { formatStatus(currentStatus) }
- } else {
- Add to watchlist
- }
- ▾
-
-
- @StatusOption(animeID, animeTitle, animeTitleEnglish, animeTitleJapanese, animeImage, "completed", currentStatus, airing)
- @StatusOption(animeID, animeTitle, animeTitleEnglish, animeTitleJapanese, animeImage, "dropped", currentStatus, airing)
- @StatusOption(animeID, animeTitle, animeTitleEnglish, animeTitleJapanese, animeImage, "plan_to_watch", currentStatus, airing)
- if currentStatus != "" {
-
- Remove from list
-
- }
-
-
-}
-
-templ StatusOption(
- animeID int,
- animeTitle string,
- animeTitleEnglish string,
- animeTitleJapanese string,
- animeImage string,
- status string,
- currentStatus string,
- airing bool,
-) {
-
- { formatStatus(status) }
-
-}
-
-func formatStatus(status string) string {
- switch status {
- case "completed":
- return "Completed"
- case "dropped":
- return "Dropped"
- case "plan_to_watch":
- return "Plan to watch"
- default:
- return status
- }
-}
diff --git a/web/components/watchlist/progress.templ b/web/components/watchlist/progress.templ
deleted file mode 100644
index 4a069cf..0000000
--- a/web/components/watchlist/progress.templ
+++ /dev/null
@@ -1,18 +0,0 @@
-package watchlist
-
-import (
- "fmt"
- db "mal/internal/db"
- "mal/web/shared"
-)
-
-templ Progress(entry db.GetUserWatchListRow) {
- if entry.CurrentEpisode.Valid && entry.CurrentEpisode.Int64 > 0 && entry.Status != "completed" {
-
- Continue ep { fmt.Sprintf("%d", entry.CurrentEpisode.Int64) }
- if entry.CurrentTimeSeconds > 0 {
- { fmt.Sprintf(" · %s", shared.FormatProgressTime(entry.CurrentTimeSeconds)) }
- }
-
- }
-}
diff --git a/web/context/context.go b/web/context/context.go
deleted file mode 100644
index 2ba54c7..0000000
--- a/web/context/context.go
+++ /dev/null
@@ -1,3 +0,0 @@
-package context
-
-const UserKey = "mal:user"
diff --git a/web/shared/anime.go b/web/shared/anime.go
deleted file mode 100644
index 1c7cf6c..0000000
--- a/web/shared/anime.go
+++ /dev/null
@@ -1,41 +0,0 @@
-package shared
-
-import (
- "mal/integrations/jikan"
- "strings"
-)
-
-func JoinNames(entities []jikan.NamedEntity) string {
- names := make([]string, len(entities))
- for i, e := range entities {
- names[i] = e.Name
- }
- return strings.Join(names, ", ")
-}
-
-func JoinStreamingNames(anime jikan.Anime) string {
- names := make([]string, len(anime.Streaming))
- for i, s := range anime.Streaming {
- names[i] = s.Name
- }
- return strings.Join(names, ", ")
-}
-
-func WatchTargetEpisode(currentStatus string, currentEpisode int) int {
- if currentStatus != "" && currentEpisode > 0 {
- return currentEpisode
- }
- return 1
-}
-
-func HasExtraSidebarDetails(anime jikan.Anime) bool {
- return anime.TitleJapanese != "" ||
- len(anime.TitleSynonyms) > 0 ||
- len(anime.Studios) > 0 ||
- len(anime.Producers) > 0 ||
- anime.Source != "" ||
- len(anime.Demographics) > 0 ||
- len(anime.Themes) > 0 ||
- anime.Broadcast.String != "" ||
- len(anime.Streaming) > 0
-}
diff --git a/web/shared/format.go b/web/shared/format.go
deleted file mode 100644
index 5e8ab76..0000000
--- a/web/shared/format.go
+++ /dev/null
@@ -1,43 +0,0 @@
-package shared
-
-import (
- "fmt"
- "net/url"
-)
-
-// BuildStreamURL constructs a stream URL from mode and token
-func BuildStreamURL(mode string, token string) string {
- if token == "" {
- return ""
- }
- return fmt.Sprintf("/watch/proxy/stream?mode=%s&token=%s", url.QueryEscape(mode), url.QueryEscape(token))
-}
-
-// FormatProgressTime formats seconds into MM:SS format
-func FormatProgressTime(seconds float64) string {
- total := int(seconds)
- if total < 0 {
- total = 0
- }
- minutes := total / 60
- remainingSeconds := total % 60
- return fmt.Sprintf("%02d:%02d", minutes, remainingSeconds)
-}
-
-// FormatEstablishedDate extracts YYYY-MM-DD from ISO date string
-func FormatEstablishedDate(date string) string {
- if len(date) >= 10 {
- return date[:10]
- }
- return date
-}
-
-// WatchlistURL builds the watchlist URL with query parameters
-func WatchlistURL(status string, sortBy string, sortOrder string) string {
- return fmt.Sprintf("/watchlist?status=%s&sort=%s&order=%s", status, sortBy, sortOrder)
-}
-
-// AnimeURL builds the anime detail URL
-func AnimeURL(animeID int) string {
- return fmt.Sprintf("/anime/%d", animeID)
-}
diff --git a/web/shared/hx_vals.go b/web/shared/hx_vals.go
deleted file mode 100644
index 886ecf8..0000000
--- a/web/shared/hx_vals.go
+++ /dev/null
@@ -1,11 +0,0 @@
-package shared
-
-import "encoding/json"
-
-func HxVals(v map[string]any) string {
- b, err := json.Marshal(v)
- if err != nil {
- return "{}"
- }
- return string(b)
-}
diff --git a/web/shared/layout/layout.templ b/web/shared/layout/layout.templ
deleted file mode 100644
index c83ff4f..0000000
--- a/web/shared/layout/layout.templ
+++ /dev/null
@@ -1,126 +0,0 @@
-package layout
-
-import (
- "mal/web/components/icons"
- "time"
-)
-
-templ Layout(title string, showHeader bool) {
-
-
-
-
-
- { title }
-
-
-
-
-
-
-
-
-
-
-
- if showHeader {
-
- }
-
- { children... }
-
-
-
-
- © {time.Now().Year()} mal. Open source anime tracking.
-
-
-
-
-
-
-
-}
diff --git a/web/shared/studio.go b/web/shared/studio.go
deleted file mode 100644
index d5ba77f..0000000
--- a/web/shared/studio.go
+++ /dev/null
@@ -1,13 +0,0 @@
-package shared
-
-import "mal/integrations/jikan"
-
-// GetProducerName extracts the default title from producer response
-func GetProducerName(producer jikan.ProducerResponse) string {
- for _, title := range producer.Data.Titles {
- if title.Type == "Default" {
- return title.Title
- }
- }
- return "Studio"
-}
diff --git a/web/shared/ui.go b/web/shared/ui.go
deleted file mode 100644
index 0fcb1f9..0000000
--- a/web/shared/ui.go
+++ /dev/null
@@ -1,10 +0,0 @@
-package shared
-
-// TabClass returns the CSS class for watchlist filter tabs
-func TabClass(active bool) string {
- base := "shrink-0 whitespace-nowrap bg-(--panel-soft) px-2 py-1 text-xs text-(--text-muted) no-underline hover:bg-(--surface-tab-hover) hover:text-(--text) hover:no-underline"
- if active {
- return "shrink-0 whitespace-nowrap bg-(--surface-tab-active) px-2 py-1 text-xs text-(--text-tab-active) no-underline hover:no-underline"
- }
- return base
-}
diff --git a/web/shared/watch.go b/web/shared/watch.go
deleted file mode 100644
index 4429729..0000000
--- a/web/shared/watch.go
+++ /dev/null
@@ -1,124 +0,0 @@
-package shared
-
-import (
- "encoding/json"
- "fmt"
- "log"
- "strconv"
-)
-
-// WatchPageData holds the data needed for the watch page
-type WatchPageData struct {
- MalID int
- Title string
- TitleEnglish string
- TitleJapanese string
- ImageURL string
- Airing bool
- CurrentEpisode string
- TotalEpisodes int
- StartTimeSeconds float64
- CurrentStatus string
- InitialMode string
- AvailableModes []string
- ModeSources map[string]ModeSource
- Segments []SkipSegment
- EpisodeTitle string
-}
-
-// ModeSource represents a stream source for a specific mode (dub/sub)
-type ModeSource struct {
- Token string `json:"token"`
- Subtitles []SubtitleItem `json:"subtitles"`
-}
-
-// SubtitleItem represents a subtitle track
-type SubtitleItem struct {
- Lang string `json:"lang"`
- Token string `json:"token"`
-}
-
-// SkipSegment represents a skippable segment (intro/outro)
-type SkipSegment struct {
- Type string `json:"type"`
- Start float64 `json:"start"`
- End float64 `json:"end"`
-}
-
-func ModeToken(mode string, modeSources map[string]ModeSource) string {
- normalizedMode := mode
- if _, ok := modeSources[normalizedMode]; !ok {
- if _, ok := modeSources["dub"]; ok {
- normalizedMode = "dub"
- } else if _, ok := modeSources["sub"]; ok {
- normalizedMode = "sub"
- } else {
- for key := range modeSources {
- normalizedMode = key
- break
- }
- }
- }
-
- source, ok := modeSources[normalizedMode]
- if !ok {
- return ""
- }
- return source.Token
-}
-
-func ToJSON(v any) string {
- b, err := json.Marshal(v)
- if err != nil {
- log.Printf("ToJSON error: %v", err)
- return "{}"
- }
- return string(b)
-}
-
-func EpisodeWithOffsetURL(animeID int, currentEpisode string, offset int) string {
- episodeID, err := strconv.Atoi(currentEpisode)
- if err != nil {
- episodeID = 1
- }
- nextEpisode := episodeID + offset
- if nextEpisode < 1 {
- nextEpisode = 1
- }
- return fmt.Sprintf("/watch/%d/%d", animeID, nextEpisode)
-}
-
-func CanGoPrevEpisode(currentEpisode string) bool {
- episodeID, err := strconv.Atoi(currentEpisode)
- if err != nil {
- return false
- }
- return episodeID > 1
-}
-
-func CanGoNextEpisode(currentEpisode string, totalEpisodes int) bool {
- if totalEpisodes <= 0 {
- return true
- }
- episodeID, err := strconv.Atoi(currentEpisode)
- if err != nil {
- return false
- }
- return episodeID < totalEpisodes
-}
-
-func ModeAvailable(modes []string, mode string) bool {
- for _, value := range modes {
- if value == mode {
- return true
- }
- }
- return false
-}
-
-func ModeButtonTitle(label string, enabled bool) string {
- if enabled {
- return label
- }
- return label + " unavailable for this episode"
-}
diff --git a/web/templates/anime.templ b/web/templates/anime.templ
deleted file mode 100644
index de39014..0000000
--- a/web/templates/anime.templ
+++ /dev/null
@@ -1,175 +0,0 @@
-package templates
-
-import (
- "fmt"
- "strings"
-
- "mal/integrations/jikan"
- animecomponents "mal/web/components/anime"
- components "mal/web/components"
- watchlistcomponents "mal/web/components/watchlist"
- "mal/web/shared"
- "mal/web/shared/layout"
-)
-
-templ AnimeDetails(anime jikan.Anime, currentStatus string, nextEpisode int) {
- @layout.Layout("mal - " + anime.DisplayTitle(), true) {
-
-
-
-
- if anime.ImageURL() != "" {
-
- } else {
-
No image
- }
-
-
-
{ anime.DisplayTitle() }
- if anime.TitleJapanese != "" {
-
{ anime.TitleJapanese }
- }
-
- if anime.ShortRating() != "" {
- { anime.ShortRating() }
- }
- if anime.Type != "" {
- { anime.Type }
- }
- if anime.Episodes > 0 {
- { fmt.Sprintf("%d ep", anime.Episodes) }
- }
- if anime.ShortDuration() != "" {
- { anime.ShortDuration() }
- }
-
-
-
- @watchlistcomponents.WatchlistDropdown(anime.MalID, anime.Title, anime.TitleEnglish, anime.TitleJapanese, anime.ImageURL(), currentStatus, anime.Airing)
-
Watch
-
-
-
- if anime.Synopsis != "" {
- { anime.Synopsis }
- } else {
- No synopsis available.
- }
-
-
-
-
- Related
-
- @components.LoadingIndicator("Loading relations")
-
-
-
- Recommendations
-
- @components.LoadingIndicator("Loading recommendations")
-
-
-
-
-
-
Details
- if anime.Aired.String != "" {
-
- Aired
- { anime.Aired.String }
-
- }
- if anime.Premiered() != "" {
-
- Premiered
- { anime.Premiered() }
-
- }
- if anime.Status != "" {
-
- Status
- { anime.Status }
-
- }
- if anime.Duration != "" {
-
- Duration
- { anime.Duration }
-
- }
- if len(anime.Genres) > 0 {
-
- Genres
- { shared.JoinNames(anime.Genres) }
-
- }
-
- if shared.HasExtraSidebarDetails(anime) {
-
- More metadata
- if anime.TitleJapanese != "" {
-
- Japanese
- { anime.TitleJapanese }
-
- }
- if len(anime.TitleSynonyms) > 0 {
-
- Synonyms
- { strings.Join(anime.TitleSynonyms, ", ") }
-
- }
- if len(anime.Studios) > 0 {
-
- Studios
-
- @animecomponents.StudioLinks(anime.Studios)
-
-
- }
- if len(anime.Producers) > 0 {
-
- Producers
- { shared.JoinNames(anime.Producers) }
-
- }
- if anime.Source != "" {
-
- Source
- { anime.Source }
-
- }
- if len(anime.Demographics) > 0 {
-
- Demographics
- { shared.JoinNames(anime.Demographics) }
-
- }
- if len(anime.Themes) > 0 {
-
- Themes
- { shared.JoinNames(anime.Themes) }
-
- }
- if anime.Broadcast.String != "" {
-
- Broadcast
- { anime.Broadcast.String }
-
- }
- if len(anime.Streaming) > 0 {
-
- Streaming
- { shared.JoinStreamingNames(anime) }
-
- }
-
- }
-
-
- }
-}
diff --git a/web/templates/auth.templ b/web/templates/auth.templ
deleted file mode 100644
index 5df9ef9..0000000
--- a/web/templates/auth.templ
+++ /dev/null
@@ -1,54 +0,0 @@
-package templates
-
-import "mal/web/shared/layout"
-
-templ Login(formError string, username string) {
- @layout.Layout("Login", false) {
-
-
-
Sign in
-
Enter your credentials to continue.
-
-
-
- }
-}
diff --git a/web/templates/catalog.templ b/web/templates/catalog.templ
deleted file mode 100644
index fdf9789..0000000
--- a/web/templates/catalog.templ
+++ /dev/null
@@ -1,55 +0,0 @@
-package templates
-
-import "mal/integrations/jikan"
-import ui "mal/web/components"
-import "fmt"
-import "mal/web/shared/layout"
-
-templ Catalog() {
- @layout.Layout("mal - catalog", true) {
-
-
- @ui.LoadingIndicator("Loading catalog")
-
-
- }
-}
-
-templ CatalogItems(animes []jikan.Anime, watchlistStatuses map[int]string, nextPage int, hasNext bool) {
- @ui.InfiniteAnimeList(animes, watchlistStatuses, hasNext, string(templ.URL(fmt.Sprintf("/api/catalog?page=%d", nextPage))), "catalog-content")
-}
-
-templ CatalogPlaceholderItems(count int) {
- for i := 0; i < count; i++ {
-
- }
-}
-
-templ CatalogError(message string) {
-
-
- if message != "" {
- { message }
- } else {
- Unable to load data
- }
-
-
- The anime catalog is temporarily unavailable. Please wait a moment and refresh the page.
-
-
-
-
-
- Try again
-
-
-}
diff --git a/web/templates/continue_watching.templ b/web/templates/continue_watching.templ
deleted file mode 100644
index 07b5eee..0000000
--- a/web/templates/continue_watching.templ
+++ /dev/null
@@ -1,80 +0,0 @@
-package templates
-
-import (
- "database/sql"
- "fmt"
- db "mal/internal/db"
- ui "mal/web/components"
- "mal/web/shared"
- "mal/web/shared/layout"
-)
-
-templ ContinueWatching(entries []db.GetContinueWatchingEntriesRow) {
- @layout.Layout("mal - continue watching", true) {
-
-
Continue watching
-
Pick up where you left off.
- if len(entries) == 0 {
- @ui.EmptyState("Nothing to continue yet") {
- Start watching any anime and your progress will show up here.
- }
- } else {
-
- for _, entry := range entries {
-
- @ui.AnimeCard(ui.AnimeCardProps{
- ID: int(entry.AnimeID),
- Title: displayContinueWatchingTitle(entry),
- ImageURL: entry.ImageUrl,
- Href: continueWatchingURL(entry),
- TitleEnglish: nullString(entry.TitleEnglish),
- TitleJapanese: nullString(entry.TitleJapanese),
- DisableWatchlist: true,
- Class: "notification-card min-w-0 flex flex-col bg-transparent text-inherit no-underline",
- HideTitle: true,
- }) {
-
-
{ displayContinueWatchingTitle(entry) }
-
- if entry.CurrentEpisode.Valid && entry.CurrentEpisode.Int64 > 0 {
- Continue ep { fmt.Sprintf("%d", entry.CurrentEpisode.Int64) }
- }
- if entry.CurrentTimeSeconds > 0 {
- { shared.FormatProgressTime(entry.CurrentTimeSeconds) }
- }
-
-
- }
-
×
-
- }
-
- }
-
- }
-}
-
-func continueWatchingURL(entry db.GetContinueWatchingEntriesRow) string {
- episode := 1
- if entry.CurrentEpisode.Valid && entry.CurrentEpisode.Int64 > 0 {
- episode = int(entry.CurrentEpisode.Int64)
- }
-
- return fmt.Sprintf("/watch/%d/%d", entry.AnimeID, episode)
-}
-
-func displayContinueWatchingTitle(entry db.GetContinueWatchingEntriesRow) string {
- return db.DisplayTitle(entry.TitleEnglish, entry.TitleJapanese, entry.TitleOriginal)
-}
-
-func nullString(s sql.NullString) string {
- if s.Valid {
- return s.String
- }
- return ""
-}
diff --git a/web/templates/discovery.templ b/web/templates/discovery.templ
deleted file mode 100644
index 2e4dc0c..0000000
--- a/web/templates/discovery.templ
+++ /dev/null
@@ -1,52 +0,0 @@
-package templates
-
-import "mal/integrations/jikan"
-import ui "mal/web/components"
-import "fmt"
-import "mal/web/shared/layout"
-
-templ Discover() {
- @layout.Layout("mal - discover", true) {
-
-
-
Discover
-
Browse what's airing now and what is coming soon.
-
-
-
- airing now
-
-
- upcoming
-
-
-
-
- @ui.LoadingIndicator("Loading discover")
-
-
-
- }
-}
-
-templ DiscoverItems(animes []jikan.Anime, watchlistStatuses map[int]string, listType string, nextPage int, hasNext bool) {
- @ui.InfiniteAnimeList(animes, watchlistStatuses, hasNext, string(templ.URL(fmt.Sprintf("/api/discover/%s?page=%d", listType, nextPage))), "discover-content")
-}
diff --git a/web/templates/index.templ b/web/templates/index.templ
deleted file mode 100644
index 0dcb0c3..0000000
--- a/web/templates/index.templ
+++ /dev/null
@@ -1,40 +0,0 @@
-package templates
-
-import (
- "fmt"
- "mal/integrations/jikan"
- ui "mal/web/components"
- "mal/web/shared/layout"
- "net/url"
-)
-
-templ Search(q string) {
- @layout.Layout("mal - search", true) {
- if q != "" {
-
- @ui.LoadingIndicator("Searching...")
-
-
- } else {
- @ui.EmptyState("Search for anime") {
- Use the search bar above to find anime to add to your watchlist.
- }
- }
- }
-}
-
-templ SearchResultsWrapper(query string, animes []jikan.Anime, watchlistStatuses map[int]string, nextPage int, hasNext bool) {
- if len(animes) == 0 {
- @ui.EmptyState("No results found.") {
- Try a different search term.
- }
- } else {
-
- @SearchItems(query, animes, watchlistStatuses, nextPage, hasNext)
-
- }
-}
-
-templ SearchItems(query string, animes []jikan.Anime, watchlistStatuses map[int]string, nextPage int, hasNext bool) {
- @ui.InfiniteAnimeList(animes, watchlistStatuses, hasNext, string(templ.URL(fmt.Sprintf("/api/search?q=%s&page=%d", url.QueryEscape(query), nextPage))), "results")
-}
diff --git a/web/templates/not_found.templ b/web/templates/not_found.templ
deleted file mode 100644
index 9c76ef5..0000000
--- a/web/templates/not_found.templ
+++ /dev/null
@@ -1,14 +0,0 @@
-package templates
-
-import "mal/web/shared/layout"
-
-templ NotFoundPage() {
- @layout.Layout("mal - not found", false) {
-
- 404
- Page not found
- The page you requested does not exist, or it was moved.
- Back to catalog
-
- }
-}
diff --git a/web/templates/studio.templ b/web/templates/studio.templ
deleted file mode 100644
index 88ad3d1..0000000
--- a/web/templates/studio.templ
+++ /dev/null
@@ -1,94 +0,0 @@
-package templates
-
-import (
- "fmt"
-
- "mal/integrations/jikan"
- components "mal/web/components"
- "mal/web/shared"
- "mal/web/shared/layout"
-)
-
-templ StudioDetails(producer jikan.ProducerResponse, animes []jikan.Anime, watchlistStatuses map[int]string, hasNext bool, nextPage int) {
- @layout.Layout("mal - "+shared.GetProducerName(producer), true) {
-
-
-
- if producer.Data.Images.Jpg.ImageURL != "" {
-
- }
-
-
{ shared.GetProducerName(producer) }
- if producer.Data.Established != "" {
-
- Established: { shared.FormatEstablishedDate(producer.Data.Established) }
-
- }
- if producer.Data.Count > 0 {
-
- { fmt.Sprintf("%d anime", producer.Data.Count) }
-
- }
-
-
- if producer.Data.About != "" {
-
{ producer.Data.About }
- }
-
-
-
Anime
-
- for _, anime := range animes {
-
- @components.AnimeCard(components.AnimeCardProps{
- ID: anime.MalID,
- Title: anime.DisplayTitle(),
- ImageURL: anime.ImageURL(),
- TitleEnglish: anime.TitleEnglish,
- TitleJapanese: anime.TitleJapanese,
- Airing: anime.Airing,
- WatchlistStatus: watchlistStatuses[anime.MalID],
- })
-
- }
- if hasNext {
- @StudioLoadMore(producer.Data.MalID, nextPage)
- }
-
-
-
- }
-}
-
-templ StudioLoadMore(studioID int, nextPage int) {
-
-}
-
-templ StudioAnimeItems(animes []jikan.Anime, watchlistStatuses map[int]string, hasNext bool, studioID int, nextPage int) {
- for _, anime := range animes {
-
- @components.AnimeCard(components.AnimeCardProps{
- ID: anime.MalID,
- Title: anime.DisplayTitle(),
- ImageURL: anime.ImageURL(),
- TitleEnglish: anime.TitleEnglish,
- TitleJapanese: anime.TitleJapanese,
- Airing: anime.Airing,
- WatchlistStatus: watchlistStatuses[anime.MalID],
- })
-
- }
- if hasNext {
- @StudioLoadMore(studioID, nextPage)
- }
-
-}
diff --git a/web/templates/watch.templ b/web/templates/watch.templ
deleted file mode 100644
index 65c9b58..0000000
--- a/web/templates/watch.templ
+++ /dev/null
@@ -1,143 +0,0 @@
-package templates
-
-import (
- "fmt"
-
- "mal/integrations/jikan"
- components "mal/web/components"
- "mal/web/components/ui"
- "mal/web/components/watch"
- "mal/web/components/watchlist"
- "mal/web/shared"
- "mal/web/shared/layout"
-)
-
-templ WatchPage(anime jikan.Anime, data shared.WatchPageData) {
- @layout.Layout(fmt.Sprintf("%s - episode %s", anime.DisplayTitle(), data.CurrentEpisode), true) {
-
-
-
-
-
-
-
- @watch.VideoPlayer(data, anime.DisplayTitle())
-
-
-
-
-
-
- Autoplay: On
-
-
- if shared.CanGoPrevEpisode(data.CurrentEpisode) {
-
- ◀ Prev
-
- } else {
-
- ◀ Prev
-
- }
- if shared.CanGoNextEpisode(data.CurrentEpisode, anime.Episodes) {
-
- Next ▶
-
- } else {
-
- Next ▶
-
- }
-
- @watchlist.WatchlistDropdown(
- anime.MalID,
- anime.Title,
- anime.TitleEnglish,
- anime.TitleJapanese,
- anime.ImageURL(),
- data.CurrentStatus,
- anime.Airing,
- )
-
-
-
-
-
- Watch more seasons of this anime
-
-
- @components.LoadingIndicator("Loading relations")
-
-
-
-
-
-
-
-
- }
-}
diff --git a/web/templates/watchlist.templ b/web/templates/watchlist.templ
deleted file mode 100644
index ebe23f0..0000000
--- a/web/templates/watchlist.templ
+++ /dev/null
@@ -1,111 +0,0 @@
-package templates
-
-import (
- "fmt"
-
- db "mal/internal/db"
- components "mal/web/components"
- "mal/web/components/watchlist"
- "mal/web/shared"
- "mal/web/shared/layout"
-)
-
-templ Watchlist(
- entries []db.GetUserWatchListRow,
- currentStatus string,
- sortBy string,
- sortOrder string,
-) {
- @layout.Layout("mal - watchlist", true) {
-
-
-
Watchlist
-
- Track what you're watching with less noise.
-
-
-
-
- @components.SortFilter(components.SortFilterOptions{
- Sort: sortBy,
- Order: sortOrder,
- Status: currentStatus,
- })
- if len(entries) == 0 {
- @components.EmptyState("Nothing here yet") {
- if currentStatus == "all" {
- Your watchlist is empty. Search for anime to get started.
- } else {
- No anime in this category.
- }
- }
- } else {
-
- for _, entry := range entries {
-
- }
-
- }
- }
-}