Improve feeds page loading and remove user rec thereUnverified
68224be parent: 71d0cbb modified
internal/server/feeds_handler.go +10 -68 | @@ -10,10 +10,8 @@ import ( | ||
| 10 | 10 | "golang.org/x/sync/errgroup" |
| 11 | 11 | |
| 12 | 12 | "pkg.rbrt.fr/glean/internal/atproto" |
| 13 | - "pkg.rbrt.fr/glean/internal/cluster" | |
| 14 | 13 | "pkg.rbrt.fr/glean/internal/db" |
| 15 | 14 | "pkg.rbrt.fr/glean/internal/feed" |
| 16 | - "pkg.rbrt.fr/glean/internal/feedback" | |
| 17 | 15 | ) |
| 18 | 16 | |
| 19 | 17 | func (s *Server) handleFeeds(w http.ResponseWriter, r *http.Request) { |
| @@ -26,8 +24,6 @@ func (s *Server) handleFeeds(w http.ResponseWriter, r *http.Request) { | ||
| 26 | 24 | var ( |
| 27 | 25 | subs []*db.Subscription |
| 28 | 26 | subCount int |
| 29 | - feedRecs []*cluster.FeedRecommendation | |
| 30 | - peopleRecs []*cluster.PersonRecommendation | |
| 31 | 27 | deadFeeds []*db.Feed |
| 32 | 28 | categories []string |
| 33 | 29 | ) |
| @@ -58,24 +54,6 @@ func (s *Server) handleFeeds(w http.ResponseWriter, r *http.Request) { | ||
| 58 | 54 | return nil |
| 59 | 55 | }) |
| 60 | 56 | |
| 61 | - g.Go(func() error { | |
| 62 | - var err error | |
| 63 | - feedRecs, err = s.engine.GetFeedRecommendations(gCtx, user.DID, 6) | |
| 64 | - if err != nil { | |
| 65 | - s.logger.Warn("failed to get feed recommendations", "error", err, "did", user.DID) | |
| 66 | - } | |
| 67 | - return nil | |
| 68 | - }) | |
| 69 | - | |
| 70 | - g.Go(func() error { | |
| 71 | - var err error | |
| 72 | - peopleRecs, err = s.engine.GetPeopleRecommendations(gCtx, user.DID, 6) | |
| 73 | - if err != nil { | |
| 74 | - s.logger.Warn("failed to get people recommendations", "error", err, "did", user.DID) | |
| 75 | - } | |
| 76 | - return nil | |
| 77 | - }) | |
| 78 | - | |
| 79 | 57 | g.Go(func() error { |
| 80 | 58 | var err error |
| 81 | 59 | deadFeeds, err = s.dbs.Articles.ListDeadFeeds(gCtx, user.DID, 7) |
| @@ -95,55 +73,19 @@ func (s *Server) handleFeeds(w http.ResponseWriter, r *http.Request) { | ||
| 95 | 73 | }) |
| 96 | 74 | |
| 97 | 75 | if err := g.Wait(); err != nil { |
| 98 | - s.logger.Warn("feeds phase 1 error", "error", err, "did", user.DID) | |
| 99 | - } | |
| 100 | - | |
| 101 | - g2, gCtx2 := errgroup.WithContext(ctx) | |
| 102 | - | |
| 103 | - g2.Go(func() error { | |
| 104 | - resolvePeopleHandles(gCtx2, peopleRecs) | |
| 105 | - return nil | |
| 106 | - }) | |
| 107 | - | |
| 108 | - g2.Go(func() error { | |
| 109 | - if len(feedRecs) > 0 { | |
| 110 | - impressions := make([]feedback.Impression, len(feedRecs)) | |
| 111 | - for i, rec := range feedRecs { | |
| 112 | - impressions[i] = feedback.Impression{TargetType: "feed", TargetID: rec.FeedURL} | |
| 113 | - } | |
| 114 | - if err := s.feedback.RecordImpressions(gCtx2, user.DID, impressions); err != nil { | |
| 115 | - s.logger.Warn("failed to record impressions", "error", err) | |
| 116 | - } | |
| 117 | - } | |
| 118 | - return nil | |
| 119 | - }) | |
| 120 | - | |
| 121 | - if err := g2.Wait(); err != nil { | |
| 122 | - s.logger.Warn("feeds phase 2 error", "error", err, "did", user.DID) | |
| 123 | - } | |
| 124 | - | |
| 125 | - var followedPeople, discoverPeople []*cluster.PersonRecommendation | |
| 126 | - for _, p := range peopleRecs { | |
| 127 | - if p.IsFollowed { | |
| 128 | - followedPeople = append(followedPeople, p) | |
| 129 | - } else { | |
| 130 | - discoverPeople = append(discoverPeople, p) | |
| 131 | - } | |
| 76 | + s.logger.Warn("feeds error", "error", err, "did", user.DID) | |
| 132 | 77 | } |
| 133 | 78 | |
| 134 | 79 | s.render(w, r, "feeds.html", map[string]any{ |
| 135 | - "User": user, | |
| 136 | - "Subscriptions": subs, | |
| 137 | - "SubscriptionCount": subCount, | |
| 138 | - "Categories": categories, | |
| 139 | - "Category": category, | |
| 140 | - "FeedRecommendations": feedRecs, | |
| 141 | - "FollowedPeople": followedPeople, | |
| 142 | - "DiscoverPeople": discoverPeople, | |
| 143 | - "DeadFeeds": deadFeeds, | |
| 144 | - "Page": page, | |
| 145 | - "BaseURL": "/feeds", | |
| 146 | - "QueryParams": buildQueryParams(map[string]string{"category": category}), | |
| 80 | + "User": user, | |
| 81 | + "Subscriptions": subs, | |
| 82 | + "SubscriptionCount": subCount, | |
| 83 | + "Categories": categories, | |
| 84 | + "Category": category, | |
| 85 | + "DeadFeeds": deadFeeds, | |
| 86 | + "Page": page, | |
| 87 | + "BaseURL": "/feeds", | |
| 88 | + "QueryParams": buildQueryParams(map[string]string{"category": category}), | |
| 147 | 89 | }) |
| 148 | 90 | } |
| 149 | 91 | |
| @@ -10,10 +10,8 @@ import ( | |||
| 10 | "golang.org/x/sync/errgroup" | 10 | "golang.org/x/sync/errgroup" |
| 11 | 11 | ||
| 12 | "pkg.rbrt.fr/glean/internal/atproto" | 12 | "pkg.rbrt.fr/glean/internal/atproto" |
| 13 | - "pkg.rbrt.fr/glean/internal/cluster" | ||
| 14 | "pkg.rbrt.fr/glean/internal/db" | 13 | "pkg.rbrt.fr/glean/internal/db" |
| 15 | "pkg.rbrt.fr/glean/internal/feed" | 14 | "pkg.rbrt.fr/glean/internal/feed" |
| 16 | - "pkg.rbrt.fr/glean/internal/feedback" | ||
| 17 | ) | 15 | ) |
| 18 | 16 | ||
| 19 | func (s *Server) handleFeeds(w http.ResponseWriter, r *http.Request) { | 17 | func (s *Server) handleFeeds(w http.ResponseWriter, r *http.Request) { |
| @@ -26,8 +24,6 @@ func (s *Server) handleFeeds(w http.ResponseWriter, r *http.Request) { | |||
| 26 | var ( | 24 | var ( |
| 27 | subs []*db.Subscription | 25 | subs []*db.Subscription |
| 28 | subCount int | 26 | subCount int |
| 29 | - feedRecs []*cluster.FeedRecommendation | ||
| 30 | - peopleRecs []*cluster.PersonRecommendation | ||
| 31 | deadFeeds []*db.Feed | 27 | deadFeeds []*db.Feed |
| 32 | categories []string | 28 | categories []string |
| 33 | ) | 29 | ) |
| @@ -58,24 +54,6 @@ func (s *Server) handleFeeds(w http.ResponseWriter, r *http.Request) { | |||
| 58 | return nil | 54 | return nil |
| 59 | }) | 55 | }) |
| 60 | 56 | ||
| 61 | - g.Go(func() error { | ||
| 62 | - var err error | ||
| 63 | - feedRecs, err = s.engine.GetFeedRecommendations(gCtx, user.DID, 6) | ||
| 64 | - if err != nil { | ||
| 65 | - s.logger.Warn("failed to get feed recommendations", "error", err, "did", user.DID) | ||
| 66 | - } | ||
| 67 | - return nil | ||
| 68 | - }) | ||
| 69 | - | ||
| 70 | - g.Go(func() error { | ||
| 71 | - var err error | ||
| 72 | - peopleRecs, err = s.engine.GetPeopleRecommendations(gCtx, user.DID, 6) | ||
| 73 | - if err != nil { | ||
| 74 | - s.logger.Warn("failed to get people recommendations", "error", err, "did", user.DID) | ||
| 75 | - } | ||
| 76 | - return nil | ||
| 77 | - }) | ||
| 78 | - | ||
| 79 | g.Go(func() error { | 57 | g.Go(func() error { |
| 80 | var err error | 58 | var err error |
| 81 | deadFeeds, err = s.dbs.Articles.ListDeadFeeds(gCtx, user.DID, 7) | 59 | deadFeeds, err = s.dbs.Articles.ListDeadFeeds(gCtx, user.DID, 7) |
| @@ -95,55 +73,19 @@ func (s *Server) handleFeeds(w http.ResponseWriter, r *http.Request) { | |||
| 95 | }) | 73 | }) |
| 96 | 74 | ||
| 97 | if err := g.Wait(); err != nil { | 75 | if err := g.Wait(); err != nil { |
| 98 | - s.logger.Warn("feeds phase 1 error", "error", err, "did", user.DID) | 76 | + s.logger.Warn("feeds error", "error", err, "did", user.DID) |
| 99 | - } | ||
| 100 | - | ||
| 101 | - g2, gCtx2 := errgroup.WithContext(ctx) | ||
| 102 | - | ||
| 103 | - g2.Go(func() error { | ||
| 104 | - resolvePeopleHandles(gCtx2, peopleRecs) | ||
| 105 | - return nil | ||
| 106 | - }) | ||
| 107 | - | ||
| 108 | - g2.Go(func() error { | ||
| 109 | - if len(feedRecs) > 0 { | ||
| 110 | - impressions := make([]feedback.Impression, len(feedRecs)) | ||
| 111 | - for i, rec := range feedRecs { | ||
| 112 | - impressions[i] = feedback.Impression{TargetType: "feed", TargetID: rec.FeedURL} | ||
| 113 | - } | ||
| 114 | - if err := s.feedback.RecordImpressions(gCtx2, user.DID, impressions); err != nil { | ||
| 115 | - s.logger.Warn("failed to record impressions", "error", err) | ||
| 116 | - } | ||
| 117 | - } | ||
| 118 | - return nil | ||
| 119 | - }) | ||
| 120 | - | ||
| 121 | - if err := g2.Wait(); err != nil { | ||
| 122 | - s.logger.Warn("feeds phase 2 error", "error", err, "did", user.DID) | ||
| 123 | - } | ||
| 124 | - | ||
| 125 | - var followedPeople, discoverPeople []*cluster.PersonRecommendation | ||
| 126 | - for _, p := range peopleRecs { | ||
| 127 | - if p.IsFollowed { | ||
| 128 | - followedPeople = append(followedPeople, p) | ||
| 129 | - } else { | ||
| 130 | - discoverPeople = append(discoverPeople, p) | ||
| 131 | - } | ||
| 132 | } | 77 | } |
| 133 | 78 | ||
| 134 | s.render(w, r, "feeds.html", map[string]any{ | 79 | s.render(w, r, "feeds.html", map[string]any{ |
| 135 | - "User": user, | 80 | + "User": user, |
| 136 | - "Subscriptions": subs, | 81 | + "Subscriptions": subs, |
| 137 | - "SubscriptionCount": subCount, | 82 | + "SubscriptionCount": subCount, |
| 138 | - "Categories": categories, | 83 | + "Categories": categories, |
| 139 | - "Category": category, | 84 | + "Category": category, |
| 140 | - "FeedRecommendations": feedRecs, | 85 | + "DeadFeeds": deadFeeds, |
| 141 | - "FollowedPeople": followedPeople, | 86 | + "Page": page, |
| 142 | - "DiscoverPeople": discoverPeople, | 87 | + "BaseURL": "/feeds", |
| 143 | - "DeadFeeds": deadFeeds, | 88 | + "QueryParams": buildQueryParams(map[string]string{"category": category}), |
| 144 | - "Page": page, | ||
| 145 | - "BaseURL": "/feeds", | ||
| 146 | - "QueryParams": buildQueryParams(map[string]string{"category": category}), | ||
| 147 | }) | 89 | }) |
| 148 | } | 90 | } |
| 149 | 91 | ||
modified
internal/server/server.go +3 -3 | @@ -168,9 +168,6 @@ func (s *Server) setupRoutes() { | ||
| 168 | 168 | s.router.Route("/dashboard", func(r chi.Router) { |
| 169 | 169 | r.Use(s.requireAuth) |
| 170 | 170 | r.Get("/", s.handleDashboard) |
| 171 | - r.Get("/article-recommendations", s.handleArticleRecommendations) | |
| 172 | - r.Get("/feed-recommendations", s.handleFeedRecommendations) | |
| 173 | - r.Get("/people-recommendations", s.handlePeopleRecommendations) | |
| 174 | 171 | }) |
| 175 | 172 | |
| 176 | 173 | s.router.Route("/feeds", func(r chi.Router) { |
| @@ -217,6 +214,9 @@ func (s *Server) setupRoutes() { | ||
| 217 | 214 | |
| 218 | 215 | s.router.Route("/recs", func(r chi.Router) { |
| 219 | 216 | r.Use(s.requireAuth) |
| 217 | + r.Get("/articles", s.handleArticleRecommendations) | |
| 218 | + r.Get("/feeds", s.handleFeedRecommendations) | |
| 219 | + r.Get("/people", s.handlePeopleRecommendations) | |
| 220 | 220 | r.Post("/dismiss-feed", s.handleDismissFeedRecommendation) |
| 221 | 221 | r.Post("/dismiss-article", s.handleDismissArticleRecommendation) |
| 222 | 222 | r.Post("/dismiss-person", s.handleDismissPersonRecommendation) |
| @@ -168,9 +168,6 @@ func (s *Server) setupRoutes() { | |||
| 168 | s.router.Route("/dashboard", func(r chi.Router) { | 168 | s.router.Route("/dashboard", func(r chi.Router) { |
| 169 | r.Use(s.requireAuth) | 169 | r.Use(s.requireAuth) |
| 170 | r.Get("/", s.handleDashboard) | 170 | r.Get("/", s.handleDashboard) |
| 171 | - r.Get("/article-recommendations", s.handleArticleRecommendations) | ||
| 172 | - r.Get("/feed-recommendations", s.handleFeedRecommendations) | ||
| 173 | - r.Get("/people-recommendations", s.handlePeopleRecommendations) | ||
| 174 | }) | 171 | }) |
| 175 | 172 | ||
| 176 | s.router.Route("/feeds", func(r chi.Router) { | 173 | s.router.Route("/feeds", func(r chi.Router) { |
| @@ -217,6 +214,9 @@ func (s *Server) setupRoutes() { | |||
| 217 | 214 | ||
| 218 | s.router.Route("/recs", func(r chi.Router) { | 215 | s.router.Route("/recs", func(r chi.Router) { |
| 219 | r.Use(s.requireAuth) | 216 | r.Use(s.requireAuth) |
| 217 | + r.Get("/articles", s.handleArticleRecommendations) | ||
| 218 | + r.Get("/feeds", s.handleFeedRecommendations) | ||
| 219 | + r.Get("/people", s.handlePeopleRecommendations) | ||
| 220 | r.Post("/dismiss-feed", s.handleDismissFeedRecommendation) | 220 | r.Post("/dismiss-feed", s.handleDismissFeedRecommendation) |
| 221 | r.Post("/dismiss-article", s.handleDismissArticleRecommendation) | 221 | r.Post("/dismiss-article", s.handleDismissArticleRecommendation) |
| 222 | r.Post("/dismiss-person", s.handleDismissPersonRecommendation) | 222 | r.Post("/dismiss-person", s.handleDismissPersonRecommendation) |
modified
internal/tmpl/dashboard.html +4 -4 | @@ -28,7 +28,7 @@ | ||
| 28 | 28 | </div> |
| 29 | 29 | {{else if .Articles}} |
| 30 | 30 | {{if gt .SubscriptionCount 0}} |
| 31 | -<div id="article-recommendations" hx-get="/dashboard/article-recommendations" hx-trigger="load" hx-swap="innerHTML" hx-indicator="#article-recommendations"> | |
| 31 | +<div id="article-recommendations" hx-get="/recs/articles" hx-trigger="load" hx-swap="innerHTML" hx-indicator="#article-recommendations"> | |
| 32 | 32 | <div class="mb-8"> |
| 33 | 33 | <h2 class="text-lg font-semibold text-spot-text mb-4">Recommended for you</h2> |
| 34 | 34 | <div class="animate-pulse space-y-3"> |
| @@ -90,7 +90,7 @@ | ||
| 90 | 90 | </div> |
| 91 | 91 | {{else}} |
| 92 | 92 | {{if gt .SubscriptionCount 0}} |
| 93 | -<div id="article-recommendations" hx-get="/dashboard/article-recommendations" hx-trigger="load" hx-swap="innerHTML" hx-indicator="#article-recommendations"> | |
| 93 | +<div id="article-recommendations" hx-get="/recs/articles" hx-trigger="load" hx-swap="innerHTML" hx-indicator="#article-recommendations"> | |
| 94 | 94 | <div class="mb-8"> |
| 95 | 95 | <h2 class="text-lg font-semibold text-spot-text mb-4">Recommended for you</h2> |
| 96 | 96 | <div class="animate-pulse space-y-3"> |
| @@ -134,7 +134,7 @@ | ||
| 134 | 134 | </div> |
| 135 | 135 | {{end}} |
| 136 | 136 | |
| 137 | -<div id="people-recommendations" hx-get="/dashboard/people-recommendations" hx-trigger="load" hx-swap="innerHTML" hx-indicator="#people-recommendations"> | |
| 137 | +<div id="people-recommendations" hx-get="/recs/people" hx-trigger="load" hx-swap="innerHTML" hx-indicator="#people-recommendations"> | |
| 138 | 138 | <div class="mb-8"> |
| 139 | 139 | <div class="grid grid-cols-1 md:grid-cols-2 gap-6"> |
| 140 | 140 | <div> |
| @@ -172,7 +172,7 @@ | ||
| 172 | 172 | </div> |
| 173 | 173 | </div> |
| 174 | 174 | |
| 175 | -<div id="feed-recommendations" hx-get="/dashboard/feed-recommendations" hx-trigger="load" hx-swap="innerHTML" hx-indicator="#feed-recommendations"> | |
| 175 | +<div id="feed-recommendations" hx-get="/recs/feeds" hx-trigger="load" hx-swap="innerHTML" hx-indicator="#feed-recommendations"> | |
| 176 | 176 | <div class="mb-8"> |
| 177 | 177 | <h2 class="text-lg font-semibold text-spot-text mb-4">Recommended feeds</h2> |
| 178 | 178 | <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3"> |
| @@ -28,7 +28,7 @@ | |||
| 28 | </div> | 28 | </div> |
| 29 | {{else if .Articles}} | 29 | {{else if .Articles}} |
| 30 | {{if gt .SubscriptionCount 0}} | 30 | {{if gt .SubscriptionCount 0}} |
| 31 | -<div id="article-recommendations" hx-get="/dashboard/article-recommendations" hx-trigger="load" hx-swap="innerHTML" hx-indicator="#article-recommendations"> | 31 | +<div id="article-recommendations" hx-get="/recs/articles" hx-trigger="load" hx-swap="innerHTML" hx-indicator="#article-recommendations"> |
| 32 | <div class="mb-8"> | 32 | <div class="mb-8"> |
| 33 | <h2 class="text-lg font-semibold text-spot-text mb-4">Recommended for you</h2> | 33 | <h2 class="text-lg font-semibold text-spot-text mb-4">Recommended for you</h2> |
| 34 | <div class="animate-pulse space-y-3"> | 34 | <div class="animate-pulse space-y-3"> |
| @@ -90,7 +90,7 @@ | |||
| 90 | </div> | 90 | </div> |
| 91 | {{else}} | 91 | {{else}} |
| 92 | {{if gt .SubscriptionCount 0}} | 92 | {{if gt .SubscriptionCount 0}} |
| 93 | -<div id="article-recommendations" hx-get="/dashboard/article-recommendations" hx-trigger="load" hx-swap="innerHTML" hx-indicator="#article-recommendations"> | 93 | +<div id="article-recommendations" hx-get="/recs/articles" hx-trigger="load" hx-swap="innerHTML" hx-indicator="#article-recommendations"> |
| 94 | <div class="mb-8"> | 94 | <div class="mb-8"> |
| 95 | <h2 class="text-lg font-semibold text-spot-text mb-4">Recommended for you</h2> | 95 | <h2 class="text-lg font-semibold text-spot-text mb-4">Recommended for you</h2> |
| 96 | <div class="animate-pulse space-y-3"> | 96 | <div class="animate-pulse space-y-3"> |
| @@ -134,7 +134,7 @@ | |||
| 134 | </div> | 134 | </div> |
| 135 | {{end}} | 135 | {{end}} |
| 136 | 136 | ||
| 137 | -<div id="people-recommendations" hx-get="/dashboard/people-recommendations" hx-trigger="load" hx-swap="innerHTML" hx-indicator="#people-recommendations"> | 137 | +<div id="people-recommendations" hx-get="/recs/people" hx-trigger="load" hx-swap="innerHTML" hx-indicator="#people-recommendations"> |
| 138 | <div class="mb-8"> | 138 | <div class="mb-8"> |
| 139 | <div class="grid grid-cols-1 md:grid-cols-2 gap-6"> | 139 | <div class="grid grid-cols-1 md:grid-cols-2 gap-6"> |
| 140 | <div> | 140 | <div> |
| @@ -172,7 +172,7 @@ | |||
| 172 | </div> | 172 | </div> |
| 173 | </div> | 173 | </div> |
| 174 | 174 | ||
| 175 | -<div id="feed-recommendations" hx-get="/dashboard/feed-recommendations" hx-trigger="load" hx-swap="innerHTML" hx-indicator="#feed-recommendations"> | 175 | +<div id="feed-recommendations" hx-get="/recs/feeds" hx-trigger="load" hx-swap="innerHTML" hx-indicator="#feed-recommendations"> |
| 176 | <div class="mb-8"> | 176 | <div class="mb-8"> |
| 177 | <h2 class="text-lg font-semibold text-spot-text mb-4">Recommended feeds</h2> | 177 | <h2 class="text-lg font-semibold text-spot-text mb-4">Recommended feeds</h2> |
| 178 | <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3"> | 178 | <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3"> |
modified
internal/tmpl/feeds.html +23 -36 | @@ -21,21 +21,31 @@ function gleanToggleEdit(id){var i=document.getElementById('feed-edit-'+id);if(i | ||
| 21 | 21 | |
| 22 | 22 | {{template "dead-feeds.html" (dict "DeadFeeds" .DeadFeeds "CSRFToken" .CSRFToken)}} |
| 23 | 23 | |
| 24 | -{{if .FeedRecommendations}} | |
| 25 | -<div class="mb-6"> | |
| 26 | - <h2 class="text-sm font-bold text-spot-text uppercase tracking-wide mb-3">Recommended feeds</h2> | |
| 27 | - <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3"> | |
| 28 | - {{range .FeedRecommendations}} | |
| 29 | - {{template "recommendation-feed-card.html" (dict "title" .Title "feed_url" .FeedURL "description" .Description "favicon_url" .FaviconURL "subscriber_count" .SubscriberCount "CSRFToken" $.CSRFToken)}} | |
| 30 | - {{end}} | |
| 24 | +<div id="feed-recommendations" hx-get="/recs/feeds" hx-trigger="load" hx-swap="innerHTML" hx-indicator="#feed-recommendations"> | |
| 25 | + <div class="mb-6"> | |
| 26 | + <h2 class="text-sm font-bold text-spot-text uppercase tracking-wide mb-3">Recommended feeds</h2> | |
| 27 | + <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3"> | |
| 28 | + <div class="animate-pulse bg-spot-surface rounded-xl p-3.5"> | |
| 29 | + <div class="flex items-center gap-2.5"> | |
| 30 | + <div class="w-5 h-5 bg-spot-hover rounded"></div> | |
| 31 | + <div class="flex-1 space-y-2"> | |
| 32 | + <div class="h-4 bg-spot-hover rounded w-2/3"></div> | |
| 33 | + <div class="h-3 bg-spot-hover rounded w-full"></div> | |
| 34 | + </div> | |
| 35 | + </div> | |
| 36 | + </div> | |
| 37 | + <div class="animate-pulse bg-spot-surface rounded-xl p-3.5"> | |
| 38 | + <div class="flex items-center gap-2.5"> | |
| 39 | + <div class="w-5 h-5 bg-spot-hover rounded"></div> | |
| 40 | + <div class="flex-1 space-y-2"> | |
| 41 | + <div class="h-4 bg-spot-hover rounded w-1/2"></div> | |
| 42 | + <div class="h-3 bg-spot-hover rounded w-4/5"></div> | |
| 43 | + </div> | |
| 44 | + </div> | |
| 45 | + </div> | |
| 46 | + </div> | |
| 31 | 47 | </div> |
| 32 | 48 | </div> |
| 33 | -{{else if .SubscriptionCount}} | |
| 34 | -<div class="mb-6"> | |
| 35 | - <h2 class="text-sm font-bold text-spot-text uppercase tracking-wide mb-3">Recommended feeds</h2> | |
| 36 | - {{template "empty-state.html" (dict "icon" "icon-rss" "size" "compact" "title" "No feed suggestions yet" "subtitle" "We'll suggest feeds as you read more.")}} | |
| 37 | -</div> | |
| 38 | -{{end}} | |
| 39 | 49 | |
| 40 | 50 | <div class="grid grid-cols-1 lg:grid-cols-3 gap-6"> |
| 41 | 51 | <div class="lg:col-span-2"> |
| @@ -87,29 +97,6 @@ function gleanToggleEdit(id){var i=document.getElementById('feed-edit-'+id);if(i | ||
| 87 | 97 | </div> |
| 88 | 98 | </div> |
| 89 | 99 | |
| 90 | - {{if or .FollowedPeople .DiscoverPeople}} | |
| 91 | - <div> | |
| 92 | - {{if .FollowedPeople}} | |
| 93 | - <h2 class="text-lg font-semibold text-spot-text mb-4">Your network</h2> | |
| 94 | - <div class="space-y-3"> | |
| 95 | - {{range .FollowedPeople}} | |
| 96 | - {{template "profile-card.html" .}} | |
| 97 | - {{end}} | |
| 98 | - </div> | |
| 99 | - {{end}} | |
| 100 | - {{if .DiscoverPeople}} | |
| 101 | - <h2 class="text-lg font-semibold text-spot-text mb-4 {{if .FollowedPeople}}mt-8{{end}}">Discover new readers</h2> | |
| 102 | - <div class="space-y-3"> | |
| 103 | - {{range .DiscoverPeople}} | |
| 104 | - {{template "profile-card.html" .}} | |
| 105 | - {{end}} | |
| 106 | - </div> | |
| 107 | - {{end}} | |
| 108 | - </div> | |
| 109 | - {{else}} | |
| 110 | - {{template "empty-state.html" (dict "icon" "icon-people" "size" "compact" "title" "No connections yet" "subtitle" "Follow readers on the network to see them here.")}} | |
| 111 | - {{end}} | |
| 112 | - | |
| 113 | 100 | {{if .Subscriptions}} |
| 114 | 101 | <form hx-post="/feeds/clear" hx-confirm="Are you sure you want to unsubscribe from ALL feeds? This cannot be undone."> |
| 115 | 102 | {{csrfInput .CSRFToken}} |
| @@ -21,21 +21,31 @@ function gleanToggleEdit(id){var i=document.getElementById('feed-edit-'+id);if(i | |||
| 21 | 21 | ||
| 22 | {{template "dead-feeds.html" (dict "DeadFeeds" .DeadFeeds "CSRFToken" .CSRFToken)}} | 22 | {{template "dead-feeds.html" (dict "DeadFeeds" .DeadFeeds "CSRFToken" .CSRFToken)}} |
| 23 | 23 | ||
| 24 | -{{if .FeedRecommendations}} | 24 | +<div id="feed-recommendations" hx-get="/recs/feeds" hx-trigger="load" hx-swap="innerHTML" hx-indicator="#feed-recommendations"> |
| 25 | -<div class="mb-6"> | 25 | + <div class="mb-6"> |
| 26 | - <h2 class="text-sm font-bold text-spot-text uppercase tracking-wide mb-3">Recommended feeds</h2> | 26 | + <h2 class="text-sm font-bold text-spot-text uppercase tracking-wide mb-3">Recommended feeds</h2> |
| 27 | - <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3"> | 27 | + <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3"> |
| 28 | - {{range .FeedRecommendations}} | 28 | + <div class="animate-pulse bg-spot-surface rounded-xl p-3.5"> |
| 29 | - {{template "recommendation-feed-card.html" (dict "title" .Title "feed_url" .FeedURL "description" .Description "favicon_url" .FaviconURL "subscriber_count" .SubscriberCount "CSRFToken" $.CSRFToken)}} | 29 | + <div class="flex items-center gap-2.5"> |
| 30 | - {{end}} | 30 | + <div class="w-5 h-5 bg-spot-hover rounded"></div> |
| 31 | + <div class="flex-1 space-y-2"> | ||
| 32 | + <div class="h-4 bg-spot-hover rounded w-2/3"></div> | ||
| 33 | + <div class="h-3 bg-spot-hover rounded w-full"></div> | ||
| 34 | + </div> | ||
| 35 | + </div> | ||
| 36 | + </div> | ||
| 37 | + <div class="animate-pulse bg-spot-surface rounded-xl p-3.5"> | ||
| 38 | + <div class="flex items-center gap-2.5"> | ||
| 39 | + <div class="w-5 h-5 bg-spot-hover rounded"></div> | ||
| 40 | + <div class="flex-1 space-y-2"> | ||
| 41 | + <div class="h-4 bg-spot-hover rounded w-1/2"></div> | ||
| 42 | + <div class="h-3 bg-spot-hover rounded w-4/5"></div> | ||
| 43 | + </div> | ||
| 44 | + </div> | ||
| 45 | + </div> | ||
| 46 | + </div> | ||
| 31 | </div> | 47 | </div> |
| 32 | </div> | 48 | </div> |
| 33 | -{{else if .SubscriptionCount}} | ||
| 34 | -<div class="mb-6"> | ||
| 35 | - <h2 class="text-sm font-bold text-spot-text uppercase tracking-wide mb-3">Recommended feeds</h2> | ||
| 36 | - {{template "empty-state.html" (dict "icon" "icon-rss" "size" "compact" "title" "No feed suggestions yet" "subtitle" "We'll suggest feeds as you read more.")}} | ||
| 37 | -</div> | ||
| 38 | -{{end}} | ||
| 39 | 49 | ||
| 40 | <div class="grid grid-cols-1 lg:grid-cols-3 gap-6"> | 50 | <div class="grid grid-cols-1 lg:grid-cols-3 gap-6"> |
| 41 | <div class="lg:col-span-2"> | 51 | <div class="lg:col-span-2"> |
| @@ -87,29 +97,6 @@ function gleanToggleEdit(id){var i=document.getElementById('feed-edit-'+id);if(i | |||
| 87 | </div> | 97 | </div> |
| 88 | </div> | 98 | </div> |
| 89 | 99 | ||
| 90 | - {{if or .FollowedPeople .DiscoverPeople}} | ||
| 91 | - <div> | ||
| 92 | - {{if .FollowedPeople}} | ||
| 93 | - <h2 class="text-lg font-semibold text-spot-text mb-4">Your network</h2> | ||
| 94 | - <div class="space-y-3"> | ||
| 95 | - {{range .FollowedPeople}} | ||
| 96 | - {{template "profile-card.html" .}} | ||
| 97 | - {{end}} | ||
| 98 | - </div> | ||
| 99 | - {{end}} | ||
| 100 | - {{if .DiscoverPeople}} | ||
| 101 | - <h2 class="text-lg font-semibold text-spot-text mb-4 {{if .FollowedPeople}}mt-8{{end}}">Discover new readers</h2> | ||
| 102 | - <div class="space-y-3"> | ||
| 103 | - {{range .DiscoverPeople}} | ||
| 104 | - {{template "profile-card.html" .}} | ||
| 105 | - {{end}} | ||
| 106 | - </div> | ||
| 107 | - {{end}} | ||
| 108 | - </div> | ||
| 109 | - {{else}} | ||
| 110 | - {{template "empty-state.html" (dict "icon" "icon-people" "size" "compact" "title" "No connections yet" "subtitle" "Follow readers on the network to see them here.")}} | ||
| 111 | - {{end}} | ||
| 112 | - | ||
| 113 | {{if .Subscriptions}} | 100 | {{if .Subscriptions}} |
| 114 | <form hx-post="/feeds/clear" hx-confirm="Are you sure you want to unsubscribe from ALL feeds? This cannot be undone."> | 101 | <form hx-post="/feeds/clear" hx-confirm="Are you sure you want to unsubscribe from ALL feeds? This cannot be undone."> |
| 115 | {{csrfInput .CSRFToken}} | 102 | {{csrfInput .CSRFToken}} |