diff --git a/internal/anime/command_palette.go b/internal/anime/command_palette.go deleted file mode 100644 index 1bf9b6e..0000000 --- a/internal/anime/command_palette.go +++ /dev/null @@ -1,207 +0,0 @@ -package anime - -import ( - "fmt" - "mal/internal/db" - "mal/internal/domain" - "mal/internal/server" - "net/http" - "strconv" - "strings" - - "github.com/gin-gonic/gin" -) - -const commandPaletteAnimeLimit = 24 - -type commandPaletteItem struct { - ID string `json:"id"` - Type string `json:"type"` - Label string `json:"label"` - Subtitle string `json:"subtitle"` - Href string `json:"href"` - Image string `json:"image,omitempty"` - Icon string `json:"icon,omitempty"` - InWatchlist bool `json:"inWatchlist,omitempty"` -} - -type commandPaletteResponse struct { - Items []commandPaletteItem `json:"items"` - HasNextPage bool `json:"hasNextPage"` - NextPage int `json:"nextPage,omitempty"` -} - -func (h *AnimeHandler) HandleCommandPalette(c *gin.Context) { - user := server.CurrentUser(c) - if user == nil { - c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) - return - } - - query := strings.TrimSpace(c.Query("q")) - page, err := strconv.Atoi(c.DefaultQuery("page", "1")) - if err != nil || page < 1 { - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid page"}) - return - } - - items := make([]commandPaletteItem, 0, commandPaletteAnimeLimit) - - if query != "" { - hasNextPage := false - if len(query) >= 2 { - var animeItems []commandPaletteItem - animeItems, hasNextPage = h.commandPaletteAnimeResults(c, user.ID, query, page) - items = append(items, animeItems...) - } - - if page == 1 { - items = append(items, h.commandPaletteNavigationItems(query)...) - items = append(items, h.commandPaletteContinueItems(c, user.ID, query)...) - items = append(items, h.commandPalettePersonalItems(c, user.ID, query)...) - } - - c.JSON(http.StatusOK, commandPaletteResponse{ - Items: items, - HasNextPage: hasNextPage, - NextPage: page + 1, - }) - return - } - - items = append(items, h.commandPaletteContinueItems(c, user.ID, query)...) - items = append(items, h.commandPaletteNavigationItems(query)...) - items = append(items, h.commandPalettePersonalItems(c, user.ID, query)...) - c.JSON(http.StatusOK, commandPaletteResponse{Items: items}) -} - -func (h *AnimeHandler) commandPaletteNavigationItems(query string) []commandPaletteItem { - all := []commandPaletteItem{ - {ID: "nav:home", Type: "navigation", Label: "Go to Home", Subtitle: "Navigation", Href: "/", Icon: "home"}, - {ID: "nav:watchlist", Type: "navigation", Label: "Go to Watchlist", Subtitle: "Navigation", Href: "/watchlist", Icon: "bookmark"}, - {ID: "nav:top-picks", Type: "navigation", Label: "Open Top Picks", Subtitle: "Navigation", Href: "/top-picks", Icon: "sparkles"}, - {ID: "nav:popular", Type: "navigation", Label: "Browse popular", Subtitle: "Browse", Href: "/browse?order_by=popularity&sort=asc", Icon: "trending"}, - {ID: "nav:airing", Type: "navigation", Label: "Currently airing", Subtitle: "Browse", Href: "/browse?status=airing&order_by=popularity&sort=asc", Icon: "play"}, - } - if query == "" { - return all - } - - filtered := make([]commandPaletteItem, 0, len(all)) - for _, item := range all { - if commandPaletteMatches(query, item.Label, item.Subtitle) { - filtered = append(filtered, item) - } - } - return filtered -} - -func (h *AnimeHandler) commandPaletteAnimeResults(c *gin.Context, userID string, query string, page int) ([]commandPaletteItem, bool) { - res, err := h.svc.SearchAdvanced(c.Request.Context(), query, "", "", "", "", nil, 0, true, page, commandPaletteAnimeLimit) - if err != nil { - return nil, false - } - - animes := wrapAnimes(res.Animes) - watchlistMap := h.watchlistMapForAnimes(c.Request.Context(), userID, animes) - items := make([]commandPaletteItem, 0, len(animes)) - for _, anime := range animes { - items = append(items, commandPaletteItem{ - ID: fmt.Sprintf("anime:%d", anime.MalID), - Type: "anime", - Label: anime.DisplayTitle(), - Subtitle: strings.TrimSpace("Anime " + anime.Type), - Href: fmt.Sprintf("/anime/%d", anime.MalID), - Image: anime.ImageURL(), - InWatchlist: watchlistMap[int64(anime.MalID)], - }) - } - return items, res.HasNextPage -} - -func (h *AnimeHandler) commandPalettePersonalItems(c *gin.Context, userID string, query string) []commandPaletteItem { - items := make([]commandPaletteItem, 0, 5) - - watchlist, err := h.watchlistSvc.GetCommandPaletteWatchlist(c.Request.Context(), userID, query, 5) - if err != nil { - return items - } - - for _, entry := range watchlist { - title := watchlistTitle(entry) - items = append(items, commandPaletteItem{ - ID: fmt.Sprintf("watchlist:%d", entry.AnimeID), - Type: "watchlist", - Label: title, - Subtitle: watchlistStatusLabel(entry.Status), - Href: fmt.Sprintf("/anime/%d", entry.AnimeID), - Image: entry.ImageUrl, - }) - if len(items) >= 5 { - return items - } - } - - return items -} - -func (h *AnimeHandler) commandPaletteContinueItems(c *gin.Context, userID string, query string) []commandPaletteItem { - items := make([]commandPaletteItem, 0, 5) - - rows, err := h.watchlistSvc.GetCommandPaletteContinueWatching(c.Request.Context(), userID, query, 5) - if err != nil { - return items - } - - for _, row := range rows { - title := continueWatchingTitle(row) - episode := "" - href := fmt.Sprintf("/anime/%d/watch", row.AnimeID) - if row.CurrentEpisode.Valid { - episode = fmt.Sprintf(" episode %d", row.CurrentEpisode.Int64) - href = fmt.Sprintf("%s?ep=%d", href, row.CurrentEpisode.Int64) - } - items = append(items, commandPaletteItem{ - ID: fmt.Sprintf("continue:%d", row.AnimeID), - Type: "continue", - Label: "Continue watching " + title, - Subtitle: "Resume" + episode, - Href: href, - Image: row.ImageUrl, - }) - if len(items) >= 5 { - return items - } - } - - return items -} - -func commandPaletteMatches(query string, values ...string) bool { - needle := strings.ToLower(strings.TrimSpace(query)) - for _, value := range values { - if strings.Contains(strings.ToLower(value), needle) { - return true - } - } - return false -} - -func continueWatchingTitle(row db.GetContinueWatchingEntriesRow) string { - return row.DisplayTitle() -} - -func watchlistTitle(row domain.UserWatchListRow) string { - return row.DisplayTitle() -} - -func watchlistStatusLabel(status string) string { - switch status { - case "watching": - return "Watching" - case "plan_to_watch": - return "Plan to Watch" - default: - return "Watchlist" - } -}