From ce111ec03d6abcbda0f1b0f4c6b0e53f3f9965d2 Mon Sep 17 00:00:00 2001 From: maddalax Date: Thu, 26 Sep 2024 21:15:04 -0500 Subject: [PATCH] fix github button from being glitchy --- framework/h/attribute.go | 8 +++ framework/h/tag.go | 8 +++ htmgo-site/internal/cache/simplecache.go | 57 ++++++++++++++++++++ htmgo-site/internal/httpjson/client.go | 27 ++++++++++ htmgo-site/main.go | 2 + htmgo-site/pages/base/root.go | 7 +-- htmgo-site/pages/docs.go | 1 + htmgo-site/pages/examples.go | 2 +- htmgo-site/pages/index.go | 2 +- htmgo-site/pages/time.go | 1 + htmgo-site/partials/navbar.go | 69 +++++++++++++++--------- 11 files changed, 153 insertions(+), 31 deletions(-) create mode 100644 htmgo-site/internal/cache/simplecache.go create mode 100644 htmgo-site/internal/httpjson/client.go diff --git a/framework/h/attribute.go b/framework/h/attribute.go index 309aca5..9232776 100644 --- a/framework/h/attribute.go +++ b/framework/h/attribute.go @@ -117,6 +117,14 @@ func Href(path string) Ren { return Attribute("href", path) } +func Target(target string) Ren { + return Attribute("target", target) +} + +func D(value string) Ren { + return Attribute("d", value) +} + func Type(name string) Ren { return Attribute("type", name) } diff --git a/framework/h/tag.go b/framework/h/tag.go index 8d6de4d..59ace19 100644 --- a/framework/h/tag.go +++ b/framework/h/tag.go @@ -25,6 +25,14 @@ func (node *Element) AppendChildren(children ...Ren) *Element { return node } +func Svg(children ...Ren) *Element { + return Tag("svg", children...) +} + +func Path(children ...Ren) *Element { + return Tag("path", children...) +} + func TextF(format string, args ...interface{}) *TextContent { return Text(fmt.Sprintf(format, args...)) } diff --git a/htmgo-site/internal/cache/simplecache.go b/htmgo-site/internal/cache/simplecache.go new file mode 100644 index 0000000..2131299 --- /dev/null +++ b/htmgo-site/internal/cache/simplecache.go @@ -0,0 +1,57 @@ +package cache + +import ( + "sync" + "time" +) + +type SimpleCache struct { + data map[string]Entry + lock sync.RWMutex +} + +type Entry struct { + Value any + Expiration time.Time +} + +func NewSimpleCache() *SimpleCache { + return &SimpleCache{ + data: make(map[string]Entry), + lock: sync.RWMutex{}, + } +} + +func (c *SimpleCache) Get(key string) (any, bool) { + c.lock.RLock() + defer c.lock.RUnlock() + entry, ok := c.data[key] + if !ok { + return nil, false + } + if entry.Expiration.Before(time.Now()) { + delete(c.data, key) + return nil, false + } + return entry.Value, true +} + +func (c *SimpleCache) Set(key string, value any, expiration time.Duration) { + c.lock.Lock() + defer c.lock.Unlock() + c.data[key] = Entry{ + Value: value, + Expiration: time.Now().Add(expiration), + } +} + +func GetOrSet[T any](cache *SimpleCache, key string, expiration time.Duration, cb func() (T, bool)) T { + if val, ok := cache.Get(key); ok { + return val.(T) + } + value, should := cb() + if should { + cache.Set(key, value, expiration) + } + return value +} diff --git a/htmgo-site/internal/httpjson/client.go b/htmgo-site/internal/httpjson/client.go new file mode 100644 index 0000000..4a36bbd --- /dev/null +++ b/htmgo-site/internal/httpjson/client.go @@ -0,0 +1,27 @@ +package httpjson + +import ( + "encoding/json" + "fmt" + "net/http" +) + +// Get sends a GET request and decodes the response JSON into a generic type T +func Get[T any](url string) (*T, error) { + resp, err := http.Get(url) + if err != nil { + return nil, fmt.Errorf("failed to make GET request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("received non-OK status code: %d", resp.StatusCode) + } + + var result T + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + return nil, fmt.Errorf("failed to decode response: %w", err) + } + + return &result, nil +} diff --git a/htmgo-site/main.go b/htmgo-site/main.go index 6dbd38f..50b0b30 100644 --- a/htmgo-site/main.go +++ b/htmgo-site/main.go @@ -4,6 +4,7 @@ import ( "github.com/maddalax/htmgo/framework/h" "github.com/maddalax/htmgo/framework/service" "htmgo-site/__htmgo" + "htmgo-site/internal/cache" "htmgo-site/internal/markdown" "io/fs" "net/http" @@ -15,6 +16,7 @@ func main() { markdownAssets := GetMarkdownAssets() service.Set(locator, service.Singleton, markdown.NewRenderer) + service.Set(locator, service.Singleton, cache.NewSimpleCache) h.Start(h.AppOpts{ ServiceLocator: locator, diff --git a/htmgo-site/pages/base/root.go b/htmgo-site/pages/base/root.go index 352593d..04b79a6 100644 --- a/htmgo-site/pages/base/root.go +++ b/htmgo-site/pages/base/root.go @@ -8,7 +8,7 @@ import ( var Version = uuid.NewString()[0:6] -func RootPage(children ...h.Ren) *h.Element { +func RootPage(ctx *h.RequestContext, children ...h.Ren) *h.Element { title := "htmgo" description := "build simple and scalable systems with go + htmx" @@ -26,9 +26,6 @@ func RootPage(children ...h.Ren) *h.Element { h.Meta("og:description", description), h.LinkWithVersion("/public/main.css", "stylesheet", Version), h.ScriptWithVersion("/public/htmgo.js", Version), - h.Raw(` - - `), h.Style(` html { scroll-behavior: smooth; @@ -37,7 +34,7 @@ func RootPage(children ...h.Ren) *h.Element { ), h.Body( h.Class("bg-stone-50 min-h-screen overflow-x-hidden"), - partials.NavBar(false), + partials.NavBar(ctx, false), h.Fragment(children...), ), ) diff --git a/htmgo-site/pages/docs.go b/htmgo-site/pages/docs.go index c770336..4e2b1f2 100644 --- a/htmgo-site/pages/docs.go +++ b/htmgo-site/pages/docs.go @@ -13,6 +13,7 @@ func DocsPage(ctx *h.RequestContext) *h.Page { pages := dirwalk.WalkPages("md/docs", assets) return h.NewPage(base.RootPage( + ctx, h.Div( h.Class("flex flex-col md:flex-row gap-4 justify-center mb-12"), partials.DocSidebar(pages), diff --git a/htmgo-site/pages/examples.go b/htmgo-site/pages/examples.go index 9bb39eb..5bbf9c2 100644 --- a/htmgo-site/pages/examples.go +++ b/htmgo-site/pages/examples.go @@ -29,7 +29,7 @@ var examples = []Example{ func ExamplesPage(ctx *h.RequestContext) *h.Page { return h.NewPage( - base.RootPage(h.Div( + base.RootPage(ctx, h.Div( h.Class("flex items-center justify-center"), h.Div( h.Class("w-full px-4 flex flex-col prose max-w-[95vw] md:max-w-3xl mt-6"), diff --git a/htmgo-site/pages/index.go b/htmgo-site/pages/index.go index 3ee3c5a..d60283d 100644 --- a/htmgo-site/pages/index.go +++ b/htmgo-site/pages/index.go @@ -7,7 +7,7 @@ import ( func IndexPage(ctx *h.RequestContext) *h.Page { return h.NewPage( - base.RootPage(h.Div( + base.RootPage(ctx, h.Div( h.Class("flex items-center justify-center "), h.Div( h.Class("w-full px-4 flex flex-col prose md:max-w-3xl mt-6 mx-auto"), diff --git a/htmgo-site/pages/time.go b/htmgo-site/pages/time.go index d8a02c7..d6349dd 100644 --- a/htmgo-site/pages/time.go +++ b/htmgo-site/pages/time.go @@ -9,6 +9,7 @@ import ( func CurrentTimePage(ctx *h.RequestContext) *h.Page { return h.NewPage( base.RootPage( + ctx, h.GetPartial( partials.CurrentTimePartial, "load, every 1s"), diff --git a/htmgo-site/partials/navbar.go b/htmgo-site/partials/navbar.go index b957eeb..cd6ea8a 100644 --- a/htmgo-site/partials/navbar.go +++ b/htmgo-site/partials/navbar.go @@ -2,6 +2,10 @@ package partials import ( "github.com/maddalax/htmgo/framework/h" + "github.com/maddalax/htmgo/framework/service" + "htmgo-site/internal/cache" + "htmgo-site/internal/httpjson" + "time" ) type NavItem struct { @@ -12,7 +16,7 @@ type NavItem struct { func ToggleNavbar(ctx *h.RequestContext) *h.Partial { return h.SwapManyPartial( ctx, - MobileNav(h.GetQueryParam(ctx, "expanded") == "true"), + MobileNav(ctx, h.GetQueryParam(ctx, "expanded") == "true"), ) } @@ -21,26 +25,46 @@ var navItems = []NavItem{ {Name: "Examples", Url: "/examples"}, } -func Star() *h.Element { +func Star(ctx *h.RequestContext) *h.Element { - return h.Div( - h.Script("https://buttons.github.io/buttons.js"), - h.Id("github-star"), - h.Class("min-w-[100px]"), - h.Raw(` - - `), + type Repo struct { + StarCount int `json:"stargazers_count"` + } + + simpleCache := service.Get[cache.SimpleCache](ctx.ServiceLocator()) + count := cache.GetOrSet(simpleCache, "starCount", 10*time.Minute, func() (int, bool) { + response, err := httpjson.Get[Repo]("https://api.github.com/repos/maddalax/htmgo") + if err != nil { + return 0, false + } + return response.StarCount, true + }) + + return h.A( + h.Href("https://github.com/maddalax/htmgo"), + h.Target("_blank"), + h.Class("inline-flex items-center rounded overflow-hidden shadow-sm"), + h.Div( + h.Class("flex items-center px-2 py-1 bg-gray-800 text-white text-sm font-semibold hover:bg-gray-700 transition"), + h.Svg( + h.Class("w-4 h-4 -mt-0.5 mr-0.5 fill-current text-white"), + h.Attribute("xmlns", "http://www.w3.org/2000/svg"), + h.Attribute("viewBox", "0 0 24 24"), + h.Attribute("fill", "currentColor"), + h.Path( + h.D("M12 17.27l5.18 3.05-1.64-5.68 4.46-3.87-5.88-.5L12 3.5l-2.12 6.77-5.88.5 4.46 3.87-1.64 5.68L12 17.27z"), + ), + ), + h.Text("Star"), + ), + h.If(count > 0, h.Div( + h.Class("flex items-center px-3 py-1 bg-black text-white text-sm font-semibold"), + h.Pf("%d", count), + )), ) } -func NavBar(expanded bool) *h.Element { +func NavBar(ctx *h.RequestContext, expanded bool) *h.Element { prelease := h.A(h.Class("bg-yellow-200 text-yellow-800 text-center p-2 flex items-center justify-center"), h.Href("https://github.com/maddalax/htmgo/issues"), h.Attribute("target", "_blank"), @@ -72,10 +96,7 @@ func NavBar(expanded bool) *h.Element { ), ) }), - h.Div( - h.Class("ml-2 hidden md:block min-w-[99px]"), - Star(), - ), + Star(ctx), ), ), ), @@ -84,12 +105,12 @@ func NavBar(expanded bool) *h.Element { return h.Div( h.Id("navbar"), prelease, - MobileNav(expanded), + MobileNav(ctx, expanded), desktopNav, ) } -func MobileNav(expanded bool) *h.Element { +func MobileNav(ctx *h.RequestContext, expanded bool) *h.Element { return h.Nav( h.Id("mobile-nav"), h.Class("block sm:hidden bg-neutral-100 border border-b-slate-300 p-4 md:p-3"), @@ -107,7 +128,7 @@ func MobileNav(expanded bool) *h.Element { )), h.Div( h.Class("flex items-center gap-3"), - h.Div(h.Class("mt-1"), Star()), + h.Div(h.Class("mt-1"), Star(ctx)), h.Button( h.Boost(),