diff --git a/examples/hackernews/internal/news/news.go b/examples/hackernews/internal/news/news.go index a972e68..1b0c778 100644 --- a/examples/hackernews/internal/news/news.go +++ b/examples/hackernews/internal/news/news.go @@ -77,6 +77,15 @@ func GetStories(category string, page int, limit int) []Story { ids := *top start := page * limit end := start + limit + + if start > len(ids) { + return make([]Story, 0) + } + + if end > len(ids) { + end = len(ids) + } + return batch.ParallelProcess[int, Story]( ids[start:end], 50, diff --git a/examples/hackernews/internal/parse/parse.go b/examples/hackernews/internal/parse/parse.go new file mode 100644 index 0000000..ffd1281 --- /dev/null +++ b/examples/hackernews/internal/parse/parse.go @@ -0,0 +1,11 @@ +package parse + +import "strconv" + +func MustParseInt(s string, fallback int) int { + v, err := strconv.ParseInt(s, 10, 64) + if err != nil { + return fallback + } + return int(v) +} diff --git a/examples/hackernews/partials/sidebar.go b/examples/hackernews/partials/sidebar.go index e68325b..c803e43 100644 --- a/examples/hackernews/partials/sidebar.go +++ b/examples/hackernews/partials/sidebar.go @@ -6,6 +6,7 @@ import ( "github.com/maddalax/htmgo/framework/hx" "hackernews/components" "hackernews/internal/news" + "hackernews/internal/parse" "hackernews/internal/timeformat" "time" ) @@ -45,10 +46,23 @@ var ScrollJs = ` func StorySidebar(ctx *h.RequestContext) *h.Partial { category := h.GetQueryParam(ctx, "category") + pageRaw := h.GetQueryParam(ctx, "page") + mode := h.GetQueryParam(ctx, "mode") + + if pageRaw == "" { + pageRaw = "0" + } + if category == "" { category = "topstories" } + page := parse.MustParseInt(pageRaw, 0) + + fetchMorePath := h.GetPartialPathWithQs(StorySidebar, h.NewQs("mode", "infinite", "page", fmt.Sprintf("%d", page+1), "category", category)) + + list := CachedStoryList(category, page, 50, fetchMorePath) + body := h.Aside( h.Id("story-sidebar"), h.JoinExtensions(h.TriggerChildren()), @@ -56,13 +70,21 @@ func StorySidebar(ctx *h.RequestContext) *h.Partial { h.Div( h.Class("flex flex-col gap-1"), SidebarTitle(category), - CachedStoryList(category, 0, 50), + h.Id("story-list"), + list, ), ) + if mode == "infinite" { + return h.NewPartial( + list, + ) + } + if ctx.IsHxRequest() { return h.SwapManyPartial(ctx, body) } + return h.NewPartial(body) } @@ -99,7 +121,7 @@ func CategoryBadge(defaultCategory string, category news.Category) *h.Element { ) } -var CachedStoryList = h.CachedPerKeyT3(time.Minute*5, func(category string, page int, limit int) (string, h.GetElementFunc) { +var CachedStoryList = h.CachedPerKeyT4(time.Minute*5, func(category string, page int, limit int, fetchMorePath string) (string, h.GetElementFunc) { return fmt.Sprintf("%s-stories-%d-%d", category, page, limit), func() *h.Element { stories := news.GetStories(category, page, limit) return h.List(stories, func(item news.Story, index int) *h.Element { @@ -120,6 +142,12 @@ var CachedStoryList = h.CachedPerKeyT3(time.Minute*5, func(category string, page h.Class("text-sm text-gray-600"), h.UnsafeRaw(fmt.Sprintf("%d upvotes • %d comments", item.Score, item.Descendents)), ), + h.If(index == len(stories)-1, h.Div( + h.Id("load-more"), + h.Attribute("hx-swap", "beforeend"), + h.HxTarget("#story-list"), + h.Get(fetchMorePath, "intersect once"), + )), ) }) } diff --git a/examples/hackernews/partials/story.go b/examples/hackernews/partials/story.go index 43e742e..0aa6c05 100644 --- a/examples/hackernews/partials/story.go +++ b/examples/hackernews/partials/story.go @@ -75,7 +75,6 @@ func StoryBody(story *news.Story) *h.Element { h.TextF(" %s", timeformat.RelativeTime(story.Time)), ), ), - h.TriggerChildren(), h.Div( h.Id("comments-loader"), h.Class("flex justify-center items-center h-24"), diff --git a/framework/h/base.go b/framework/h/base.go index ec0030f..bbb8006 100644 --- a/framework/h/base.go +++ b/framework/h/base.go @@ -1,7 +1,6 @@ package h import ( - "html" "net/http" "reflect" "runtime" @@ -89,5 +88,5 @@ func GetPartialPath(partial PartialFunc) string { } func GetPartialPathWithQs(partial func(ctx *RequestContext) *Partial, qs *Qs) string { - return html.EscapeString(GetPartialPath(partial) + "?" + qs.ToString()) + return GetPartialPath(partial) + "?" + qs.ToString() }