From 1cd7c7a63082e63ed7ade6947e13029635060ad0 Mon Sep 17 00:00:00 2001 From: Jackie Li Date: Thu, 25 Jun 2026 11:45:46 +0100 Subject: [PATCH 01/13] =?UTF-8?q?examples:=20migrate=20templ=20=E2=86=92?= =?UTF-8?q?=20gsx=20(source)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{components.templ => components.gsx} | 63 ++-- examples/blog/admin/dashboard.gsx | 78 +++++ examples/blog/admin/dashboard.templ | 79 ----- .../blog/admin/{login.templ => login.gsx} | 13 +- .../blog/admin/{posts.templ => posts.gsx} | 70 +++-- .../blog/admin/{users.templ => users.gsx} | 37 ++- .../blog/{category.templ => category.gsx} | 42 ++- examples/blog/blog/components.templ | 47 --- examples/blog/blog/home.gsx | 49 ++++ examples/blog/blog/home.templ | 52 ---- examples/blog/blog/post.gsx | 69 +++++ examples/blog/blog/post.templ | 72 ----- examples/blog/blog/search.gsx | 69 +++++ examples/blog/blog/search.templ | 73 ----- .../{components.templ => components.gsx} | 89 +++--- .../ui/layout/{layout.templ => layout.gsx} | 16 +- examples/htmx-render-target/pages.gsx | 149 ++++++++++ examples/htmx-render-target/pages.templ | 270 ------------------ examples/htmx/pages.gsx | 80 ++++++ examples/htmx/pages.templ | 104 ------- examples/lint-misuse/pages.gsx | 38 +++ examples/lint-misuse/pages.templ | 19 -- examples/simple/{pages.templ => pages.gsx} | 47 ++- examples/todo/{pages.templ => pages.gsx} | 89 +++--- 24 files changed, 762 insertions(+), 952 deletions(-) rename examples/blog/admin/{components.templ => components.gsx} (65%) create mode 100644 examples/blog/admin/dashboard.gsx delete mode 100644 examples/blog/admin/dashboard.templ rename examples/blog/admin/{login.templ => login.gsx} (77%) rename examples/blog/admin/{posts.templ => posts.gsx} (54%) rename examples/blog/admin/{users.templ => users.gsx} (71%) rename examples/blog/blog/{category.templ => category.gsx} (58%) delete mode 100644 examples/blog/blog/components.templ create mode 100644 examples/blog/blog/home.gsx delete mode 100644 examples/blog/blog/home.templ create mode 100644 examples/blog/blog/post.gsx delete mode 100644 examples/blog/blog/post.templ create mode 100644 examples/blog/blog/search.gsx delete mode 100644 examples/blog/blog/search.templ rename examples/blog/ui/components/{components.templ => components.gsx} (60%) rename examples/blog/ui/layout/{layout.templ => layout.gsx} (89%) create mode 100644 examples/htmx-render-target/pages.gsx delete mode 100644 examples/htmx-render-target/pages.templ create mode 100644 examples/htmx/pages.gsx delete mode 100644 examples/htmx/pages.templ create mode 100644 examples/lint-misuse/pages.gsx delete mode 100644 examples/lint-misuse/pages.templ rename examples/simple/{pages.templ => pages.gsx} (74%) rename examples/todo/{pages.templ => pages.gsx} (80%) diff --git a/examples/blog/admin/components.templ b/examples/blog/admin/components.gsx similarity index 65% rename from examples/blog/admin/components.templ rename to examples/blog/admin/components.gsx index 4100748..c0a15d0 100644 --- a/examples/blog/admin/components.templ +++ b/examples/blog/admin/components.gsx @@ -1,6 +1,7 @@ -// Admin-local templ functions. StatsGrid and RecentPostsCard are standalone -// function components so the dashboard's Props+RenderTarget switch can refresh -// each widget independently with target.Is(StatsGrid) / target.Is(RecentPostsCard). +// Admin-local gsx functions. StatsGrid, RecentPostsCard and PostsTable are +// standalone function components so the dashboard's Props+RenderTarget switch +// can refresh each widget independently with target.Is(StatsGrid) / +// target.Is(RecentPostsCard). package admin import ( @@ -9,47 +10,49 @@ import ( "github.com/jackielii/structpages/examples/blog/ui/components" ) -templ statCell(label string, value int) { +// StatCell — capitalized (gsx components must be Capitalized; the templ name +// was lowercase `statCell`). +component StatCell(label string, value int) {
-
{ value }
-
{ label }
+
{value}
+
{label}
} -templ StatsGrid(stats store.Stats) { +component StatsGrid(stats store.Stats) {
- @statCell("Posts", stats.Posts) - @statCell("Drafts", stats.Drafts) - @statCell("Comments", stats.Comments) - @statCell("Categories", stats.Categories) + + + +
} -templ RecentPostsCard(posts []store.Post) { +component RecentPostsCard(posts []store.Post) {
- @components.Card("Recent posts") { +
    - if len(posts) == 0 { + { if len(posts) == 0 {
  • No posts yet.
  • - } - for _, p := range posts { + } } + { for _, p := range posts {
  • - { p.Title } + {p.Title} - if p.Published { + { if p.Published { live } else { draft - } + } }
  • - } + } }
- } +
} -templ PostsTable(posts []store.Post) { +component PostsTable(posts []store.Post) {
@@ -61,19 +64,19 @@ templ PostsTable(posts []store.Post) { - for _, p := range posts { + { for _, p := range posts { - + - } - if len(posts) == 0 { + } } + { if len(posts) == 0 { - } + } }
- { p.Title } + {p.Title} - if p.Published { + { if p.Published { published } else { draft - } + } } { p.CreatedAt.Format("Jan 2, 2006") }{p.CreatedAt.Format("Jan 2, 2006")}
No posts yet.
diff --git a/examples/blog/admin/dashboard.gsx b/examples/blog/admin/dashboard.gsx new file mode 100644 index 0000000..fe95051 --- /dev/null +++ b/examples/blog/admin/dashboard.gsx @@ -0,0 +1,78 @@ +package admin + +import ( + "net/http" + + "github.com/jackielii/structpages" + "github.com/jackielii/structpages/examples/blog/auth" + "github.com/jackielii/structpages/examples/blog/store" + "github.com/jackielii/structpages/examples/blog/ui/components" + "github.com/jackielii/structpages/examples/blog/ui/layout" +) + +type dashboardPage struct{} + +type dashboardProps struct { + User store.User + Stats store.Stats + RecentPosts []store.Post +} + +// Props demonstrates the Props + RenderTarget pattern. Each widget is a +// standalone gsx function (StatsGrid, RecentPostsCard) so HTMX refresh +// requests with HX-Target: #stats-grid or #recent-posts-card resolve here +// and only the touched data is loaded — no full page work. +// +// Returns dashboardProps directly — gsx now emits method components as +// func (p dashboardPage) Page(props dashboardProps) gsx.Node without a wrapper struct. +func (p dashboardPage) Props(r *http.Request, s *store.Store, target structpages.RenderTarget) (dashboardProps, error) { + switch { + case target.Is(StatsGrid): + return dashboardProps{}, structpages.RenderComponent(StatsGrid(StatsGridProps{Stats: s.Stats()})) + + case target.Is(RecentPostsCard): + posts, _ := s.ListPosts(store.PostFilter{IncludeDraft: true, PageSize: 5}) + return dashboardProps{}, structpages.RenderComponent(RecentPostsCard(RecentPostsCardProps{Posts: posts})) + } + + user, _ := auth.UserFromContext(r.Context()) + posts, _ := s.ListPosts(store.PostFilter{IncludeDraft: true, PageSize: 5}) + return dashboardProps{ + User: user, + Stats: s.Stats(), + RecentPosts: posts, + }, nil +} + +component (p dashboardPage) Page(props dashboardProps) { + +
+

Dashboard

+
+ + +
+
+
+ + + +
    +
  • Click ↻ Stats — only the StatsGrid widget refreshes (check Network tab).
  • +
  • Click ↻ Recent posts — only that card reloads, with its own DB query.
  • +
  • Hard refresh — the full document re-renders via Page().
  • +
+
+
+
+} diff --git a/examples/blog/admin/dashboard.templ b/examples/blog/admin/dashboard.templ deleted file mode 100644 index 4d692dc..0000000 --- a/examples/blog/admin/dashboard.templ +++ /dev/null @@ -1,79 +0,0 @@ -package admin - -import ( - "net/http" - - "github.com/jackielii/structpages" - "github.com/jackielii/structpages/examples/blog/auth" - "github.com/jackielii/structpages/examples/blog/store" - "github.com/jackielii/structpages/examples/blog/ui/components" - "github.com/jackielii/structpages/examples/blog/ui/layout" -) - -type dashboardPage struct{} - -type dashboardProps struct { - User store.User - Stats store.Stats - RecentPosts []store.Post -} - -// Props demonstrates the Props + RenderTarget pattern. Each widget is a -// standalone templ function (StatsGrid, RecentPostsCard) so HTMX refresh -// requests with HX-Target: #stats-grid or #recent-posts-card resolve here -// and only the touched data is loaded — no full page work. -func (p dashboardPage) Props(r *http.Request, s *store.Store, target structpages.RenderTarget) (dashboardProps, error) { - switch { - case target.Is(StatsGrid): - return dashboardProps{}, structpages.RenderComponent(StatsGrid(s.Stats())) - - case target.Is(RecentPostsCard): - posts, _ := s.ListPosts(store.PostFilter{IncludeDraft: true, PageSize: 5}) - return dashboardProps{}, structpages.RenderComponent(RecentPostsCard(posts)) - } - - user, _ := auth.UserFromContext(r.Context()) - posts, _ := s.ListPosts(store.PostFilter{IncludeDraft: true, PageSize: 5}) - return dashboardProps{ - User: user, - Stats: s.Stats(), - RecentPosts: posts, - }, nil -} - -templ (p dashboardPage) Page(props dashboardProps) { - @layout.AdminShell("Dashboard", props.User) { - @p.Content(props) - } -} - -templ (dashboardPage) Content(props dashboardProps) { -
-

Dashboard

-
- - -
-
-
- @StatsGrid(props.Stats) - @RecentPostsCard(props.RecentPosts) - @components.Card("Try it") { - - } -
-} diff --git a/examples/blog/admin/login.templ b/examples/blog/admin/login.gsx similarity index 77% rename from examples/blog/admin/login.templ rename to examples/blog/admin/login.gsx index 5bbdfb7..55091d0 100644 --- a/examples/blog/admin/login.templ +++ b/examples/blog/admin/login.gsx @@ -2,7 +2,10 @@ package admin import "github.com/jackielii/structpages/examples/blog/ui/components" -templ loginShell(username, errMsg string) { +// LoginShell is invoked from login.go (LoginPage.ServeHTTP). gsx requires +// component names to be Capitalized (lowercase = HTML element), so the templ +// `loginShell` becomes `LoginShell`. +component LoginShell(username, errMsg string) { @@ -12,14 +15,14 @@ templ loginShell(username, errMsg string) {
- @components.Card("Sign in") { + - @components.Alert(components.AlertError, errMsg) +
diff --git a/examples/blog/admin/posts.templ b/examples/blog/admin/posts.gsx similarity index 54% rename from examples/blog/admin/posts.templ rename to examples/blog/admin/posts.gsx index caf897f..65fa8a7 100644 --- a/examples/blog/admin/posts.templ +++ b/examples/blog/admin/posts.gsx @@ -5,18 +5,20 @@ import ( "net/http" "strconv" + "github.com/gsxhq/gsx" "github.com/jackielii/structpages/examples/blog/auth" "github.com/jackielii/structpages/examples/blog/store" "github.com/jackielii/structpages/examples/blog/ui/components" "github.com/jackielii/structpages/examples/blog/ui/layout" ) -// adminShellWith is a tiny templ wrapper used by handlers that need to -// render a custom body inside AdminShell from Go code. -templ adminShellWith(title string, user store.User, body templ.Component) { - @layout.AdminShell(title, user) { - @body - } +// AdminShellWith is a tiny gsx wrapper used by handlers that need to render a +// custom body inside AdminShell from Go code. The templ version took a +// `templ.Component`; gsx's equivalent renderable value type is `gsx.Node`. +component AdminShellWith(title string, user store.User, body gsx.Node) { + + {body} + } // --- List --- @@ -34,18 +36,14 @@ func (postListPage) Props(r *http.Request, s *store.Store) (postListProps, error return postListProps{User: user, Posts: posts}, nil } -templ (p postListPage) Page(props postListProps) { - @layout.AdminShell("Posts", props.User) { - @p.Content(props) - } -} - -templ (postListPage) Content(props postListProps) { -
-

All posts

- New post -
- @PostsTable(props.Posts) +component (p postListPage) Page(props postListProps) { + +
+

All posts

+ New post +
+ +
} // --- New --- @@ -63,11 +61,11 @@ func (postNewPage) Props(r *http.Request, s *store.Store) (postFormViewProps, er return postFormViewProps{User: user, Categories: s.ListCategories()}, nil } -templ (postNewPage) Page(props postFormViewProps) { - @layout.AdminShell("New post", props.User) { +component (p postNewPage) Page(props postFormViewProps) { +

New post

- @postForm(props.Post, props.Categories, "") - } + +
} // --- Edit --- @@ -87,36 +85,36 @@ func (postEditPage) Props(r *http.Request, s *store.Store) (postFormViewProps, e return postFormViewProps{User: user, Categories: s.ListCategories(), Post: p}, nil } -templ (postEditPage) Page(props postFormViewProps) { - @layout.AdminShell("Edit post", props.User) { +component (p postEditPage) Page(props postFormViewProps) { +

Edit post

- @postForm(props.Post, props.Categories, "") - } + +
} // --- Shared form --- -templ postForm(p store.Post, cats []store.Category, errMsg string) { +component PostForm(p store.Post, cats []store.Category, errMsg string) {
- @components.Alert(components.AlertError, errMsg) - @components.Input("title", "Title", p.Title, "") - @components.Input("slug", "Slug (auto if blank)", p.Slug, "") + + + - @components.Textarea("body", "Body", p.Body, "") +
- @components.Button("Save", templ.Attributes{"type": "submit"}) + Cancel
diff --git a/examples/blog/admin/users.templ b/examples/blog/admin/users.gsx similarity index 71% rename from examples/blog/admin/users.templ rename to examples/blog/admin/users.gsx index b6faa0c..f892bc1 100644 --- a/examples/blog/admin/users.templ +++ b/examples/blog/admin/users.gsx @@ -3,6 +3,7 @@ package admin import ( "net/http" + "github.com/gsxhq/gsx" "github.com/jackielii/structpages/examples/blog/auth" "github.com/jackielii/structpages/examples/blog/store" "github.com/jackielii/structpages/examples/blog/ui/components" @@ -21,24 +22,19 @@ func (userListPage) Props(r *http.Request, s *store.Store) (userListProps, error return userListProps{User: user, Users: s.ListUsers()}, nil } -templ (p userListPage) Page(props userListProps) { - @layout.AdminShell("Users", props.User) { - @p.Content(props) - } -} - -templ (userListPage) Content(props userListProps) { -

Users

-
- @components.Card("Existing users") { +component (p userListPage) Page(props userListProps) { + +

Users

+
+
    - for _, u := range props.Users { + { for _, u := range props.Users {
  • - { u.Username } - if u.IsAdmin { + {u.Username} + { if u.IsAdmin { admin - } + } }
    Delete
  • - } + } }
- } - @components.Card("Create user") { +
+
- @components.Input("username", "Username", "", "") + - @components.Button("Create", templ.Attributes{"type": "submit"}) + - } +
+
} diff --git a/examples/blog/blog/category.templ b/examples/blog/blog/category.gsx similarity index 58% rename from examples/blog/blog/category.templ rename to examples/blog/blog/category.gsx index 61e717e..fa89563 100644 --- a/examples/blog/blog/category.templ +++ b/examples/blog/blog/category.gsx @@ -15,7 +15,7 @@ type categoryPage struct{} type categoryProps struct { Category store.Category Posts []store.Post - Pagination components.PaginationProps + Pagination components.PageNav } func (categoryPage) Props(r *http.Request, s *store.Store) (categoryProps, error) { @@ -34,13 +34,11 @@ func (categoryPage) Props(r *http.Request, s *store.Store) (categoryProps, error PageSize: store.DefaultPageSize, }) - // URL closure captures the request context so links re-use the current - // route params (slug auto-fills from r.PathValue). ctx := r.Context() return categoryProps{ Category: cat, Posts: posts, - Pagination: components.PaginationProps{ + Pagination: components.PageNav{ Page: page, PageSize: store.DefaultPageSize, Total: total, @@ -54,24 +52,20 @@ func (categoryPage) Props(r *http.Request, s *store.Store) (categoryProps, error }, nil } -templ (p categoryPage) Page(props categoryProps) { - @layout.PublicShell(props.Category.Name) { - @p.Content(props) - } -} - -templ (categoryPage) Content(props categoryProps) { -

{ props.Category.Name }

-

Posts filed under this category.

-
- if len(props.Posts) == 0 { -

Nothing here yet.

- } - for _, post := range props.Posts { - @PostCard(post) - } -
-
- @components.Pagination(props.Pagination) -
+component (p categoryPage) Page(props categoryProps) { + +

{props.Category.Name}

+

Posts filed under this category.

+
+ { if len(props.Posts) == 0 { +

Nothing here yet.

+ } } + { for _, post := range props.Posts { + + } } +
+
+ { components.Pagination(props.Pagination) } +
+
} diff --git a/examples/blog/blog/components.templ b/examples/blog/blog/components.templ deleted file mode 100644 index 500cb42..0000000 --- a/examples/blog/blog/components.templ +++ /dev/null @@ -1,47 +0,0 @@ -// Feature-local templ components for the public blog. These are templ -// functions (not page methods) so other handlers in this package can target -// them via RenderComponent — most notably the comment handler, which -// re-renders CommentsList after a successful POST. -package blog - -import ( - "github.com/jackielii/structpages" - "github.com/jackielii/structpages/examples/blog/store" - "github.com/jackielii/structpages/examples/blog/ui/components" -) - -templ PostMeta(p store.Post) { -

- Posted { p.CreatedAt.Format("Jan 2, 2006") } -

-} - -templ PostCard(p store.Post) { - -} - -// CommentsList is a standalone function component so the commentHandler -// can re-render it from ServeHTTP via RenderComponent(CommentsList(...)). -// It owns its own wrapper id, which doubles as the HTMX hx-target. -templ CommentsList(comments []store.Comment) { -
- if len(comments) == 0 { -

No comments yet — be the first.

- } - for _, c := range comments { -
-
- { c.Author } · { c.CreatedAt.Format("15:04 Jan 2") } -
-

{ c.Body }

-
- } -

Total: { len(comments) }

-
-} diff --git a/examples/blog/blog/home.gsx b/examples/blog/blog/home.gsx new file mode 100644 index 0000000..566be3b --- /dev/null +++ b/examples/blog/blog/home.gsx @@ -0,0 +1,49 @@ +package blog + +import ( + "net/http" + + "github.com/jackielii/structpages/examples/blog/store" + "github.com/jackielii/structpages/examples/blog/ui/components" + "github.com/jackielii/structpages/examples/blog/ui/layout" +) + +type homePage struct{} + +type homeProps struct { + Posts []store.Post + Categories []store.Category +} + +// Props loads page data. Returns homeProps directly — gsx now emits method +// components as func (p homePage) Page(props homeProps) gsx.Node without a wrapper struct. +func (homePage) Props(_ *http.Request, s *store.Store) (homeProps, error) { + posts, _ := s.ListPosts(store.PostFilter{PageSize: 5}) + return homeProps{Posts: posts, Categories: s.ListCategories()}, nil +} + +// Page renders the full document. The former Content() method is inlined here: +// gsx's generated-props wrapping makes a separate Content(props) method +// undispatchable by structpages alongside Page(props) (both would need the same +// single Props-return type). See GAP notes. +component (p homePage) Page(props homeProps) { + +

Recent Posts

+
+ { for _, post := range props.Posts { + + } } +
+ + + +
+} diff --git a/examples/blog/blog/home.templ b/examples/blog/blog/home.templ deleted file mode 100644 index d8be302..0000000 --- a/examples/blog/blog/home.templ +++ /dev/null @@ -1,52 +0,0 @@ -package blog - -import ( - "net/http" - - "github.com/jackielii/structpages/examples/blog/store" - "github.com/jackielii/structpages/examples/blog/ui/components" - "github.com/jackielii/structpages/examples/blog/ui/layout" -) - -type homePage struct{} - -type homeProps struct { - Posts []store.Post - Categories []store.Category -} - -// Props demonstrates the simplest pattern: load data using a DI-injected -// *store.Store. The framework passes *http.Request and *store.Store by -// matching parameter types against the registry built in main. -func (homePage) Props(_ *http.Request, s *store.Store) (homeProps, error) { - posts, _ := s.ListPosts(store.PostFilter{PageSize: 5}) - return homeProps{Posts: posts, Categories: s.ListCategories()}, nil -} - -// Page wraps the layout for full-document loads. HTMX nav links target -// #content, which causes structpages to dispatch to Content() instead. -templ (p homePage) Page(props homeProps) { - @layout.PublicShell("Home") { - @p.Content(props) - } -} - -templ (homePage) Content(props homeProps) { -

Recent Posts

-
- for _, post := range props.Posts { - @PostCard(post) - } -
- @components.Card("Browse by category") { - - } -} diff --git a/examples/blog/blog/post.gsx b/examples/blog/blog/post.gsx new file mode 100644 index 0000000..88b100e --- /dev/null +++ b/examples/blog/blog/post.gsx @@ -0,0 +1,69 @@ +package blog + +import ( + "net/http" + + "github.com/gsxhq/gsx" + "github.com/jackielii/structpages" + "github.com/jackielii/structpages/examples/blog/store" + "github.com/jackielii/structpages/examples/blog/ui/components" + "github.com/jackielii/structpages/examples/blog/ui/layout" +) + +type postPage struct{} + +type postProps struct { + Post store.Post + Author store.User + Category store.Category + Comments []store.Comment +} + +func (postPage) Props(r *http.Request, s *store.Store) (postProps, error) { + slug := r.PathValue("slug") + p, err := s.GetPostBySlug(slug) + if err != nil { + return postProps{}, err + } + author, _ := s.GetUser(p.AuthorID) + category, _ := s.GetCategory(p.CategoryID) + return postProps{ + Post: p, + Author: author, + Category: category, + Comments: s.ListComments(p.ID), + }, nil +} + +component (p postPage) Page(props postProps) { + +
+

{props.Post.Title}

+

+ by {props.Author.Username} + { if props.Category.Slug != "" { + · {props.Category.Name} + } } +

+
+

{props.Post.Body}

+
+
+
+

Comments

+ +
+

Add a comment

+ + + + +
+
+} diff --git a/examples/blog/blog/post.templ b/examples/blog/blog/post.templ deleted file mode 100644 index c78f3ab..0000000 --- a/examples/blog/blog/post.templ +++ /dev/null @@ -1,72 +0,0 @@ -package blog - -import ( - "net/http" - - "github.com/jackielii/structpages" - "github.com/jackielii/structpages/examples/blog/store" - "github.com/jackielii/structpages/examples/blog/ui/components" - "github.com/jackielii/structpages/examples/blog/ui/layout" -) - -type postPage struct{} - -type postProps struct { - Post store.Post - Author store.User - Category store.Category - Comments []store.Comment -} - -func (postPage) Props(r *http.Request, s *store.Store) (postProps, error) { - slug := r.PathValue("slug") - p, err := s.GetPostBySlug(slug) - if err != nil { - return postProps{}, err - } - author, _ := s.GetUser(p.AuthorID) - category, _ := s.GetCategory(p.CategoryID) - return postProps{ - Post: p, - Author: author, - Category: category, - Comments: s.ListComments(p.ID), - }, nil -} - -templ (p postPage) Page(props postProps) { - @layout.PublicShell(props.Post.Title) { - @p.Content(props) - } -} - -templ (postPage) Content(props postProps) { -
-

{ props.Post.Title }

-

- by { props.Author.Username } - if props.Category.Slug != "" { - · { props.Category.Name } - } -

-
-

{ props.Post.Body }

-
-
-
-

Comments

- @CommentsList(props.Comments) -
-

Add a comment

- @components.Input("author", "Name", "", "") - @components.Textarea("body", "Comment", "", "") - @components.Button("Post comment", templ.Attributes{"type": "submit"}) -
-
-} diff --git a/examples/blog/blog/search.gsx b/examples/blog/blog/search.gsx new file mode 100644 index 0000000..72a84f0 --- /dev/null +++ b/examples/blog/blog/search.gsx @@ -0,0 +1,69 @@ +package blog + +import ( + "net/http" + + "github.com/jackielii/structpages" + "github.com/jackielii/structpages/examples/blog/store" + "github.com/jackielii/structpages/examples/blog/ui/layout" +) + +type searchPage struct{} + +type searchProps struct { + Query string + Posts []store.Post +} + +// Props uses RenderTarget to load less when HTMX only wants the results +// fragment. On the initial page load it returns the full searchProps; when the +// Results fragment is targeted it renders just that component via RenderComponent. +func (p searchPage) Props(r *http.Request, s *store.Store, target structpages.RenderTarget) (searchProps, error) { + q := r.URL.Query().Get("q") + sp := searchProps{Query: q} + if q != "" { + sp.Posts, _ = s.ListPosts(store.PostFilter{Search: q}) + } + if target.Is(p.Results) { + return searchProps{}, structpages.RenderComponent(p.Results(sp)) + } + return sp, nil +} + +component (p searchPage) Page(props searchProps) { + +

Search

+
+ +
+ +
+} + +component (p searchPage) Results(props searchProps) { +
+ { if props.Query == "" { +

Type a query to search.

+ } else if len(props.Posts) == 0 { +

No results for "{props.Query}".

+ } else { +

{resultsCount(len(props.Posts))}

+ { for _, post := range props.Posts { + + } } + } } +
+} diff --git a/examples/blog/blog/search.templ b/examples/blog/blog/search.templ deleted file mode 100644 index a7d15a5..0000000 --- a/examples/blog/blog/search.templ +++ /dev/null @@ -1,73 +0,0 @@ -package blog - -import ( - "net/http" - - "github.com/jackielii/structpages" - "github.com/jackielii/structpages/examples/blog/store" - "github.com/jackielii/structpages/examples/blog/ui/layout" -) - -type searchPage struct{} - -type searchProps struct { - Query string - Posts []store.Post -} - -// Props uses RenderTarget to load less when HTMX only wants the results -// fragment. On the initial page load we render the full layout; on -// subsequent keystrokes we only return the Results partial. -func (p searchPage) Props(r *http.Request, s *store.Store, target structpages.RenderTarget) (searchProps, error) { - q := r.URL.Query().Get("q") - props := searchProps{Query: q} - if q != "" { - props.Posts, _ = s.ListPosts(store.PostFilter{Search: q}) - } - if target.Is(p.Results) { - return searchProps{}, structpages.RenderComponent(p.Results, props) - } - return props, nil -} - -templ (p searchPage) Page(props searchProps) { - @layout.PublicShell("Search") { - @p.Content(props) - } -} - -templ (p searchPage) Content(props searchProps) { -

Search

-
- -
- @p.Results(props) -} - -templ (searchPage) Results(props searchProps) { -
- if props.Query == "" { -

Type a query to search.

- } else if len(props.Posts) == 0 { -

No results for "{ props.Query }".

- } else { -

{ resultsCount(len(props.Posts)) }

- for _, post := range props.Posts { - @PostCard(post) - } - } -
-} diff --git a/examples/blog/ui/components/components.templ b/examples/blog/ui/components/components.gsx similarity index 60% rename from examples/blog/ui/components/components.templ rename to examples/blog/ui/components/components.gsx index 626ed6c..1e84124 100644 --- a/examples/blog/ui/components/components.templ +++ b/examples/blog/ui/components/components.gsx @@ -2,7 +2,7 @@ // every feature package: buttons, form fields, alerts, cards, pagination, // and the styled error component used by the global error handler. // -// Each function is a standalone templ component (not a method on a page +// Each function is a standalone gsx component (not a method on a page // struct). That means they can be addressed by HTMX as targets via their // kebab-cased name — e.g. hx-target="#pagination" matches Pagination. package components @@ -26,109 +26,110 @@ func alertClasses(kind AlertKind) string { } } -templ Alert(kind AlertKind, msg string) { - if msg != "" { -
{ msg }
- } +component Alert(kind AlertKind, msg string) { + { if msg != "" { +
{msg}
+ } } } -templ Card(title string) { +component Card(title string) {
- if title != "" { -

{ title }

- } - { children... } + { if title != "" { +

{title}

+ } } + {children}
} -templ Button(label string, attrs templ.Attributes) { +// Button takes only its label; any extra attributes (type, hx-*, etc.) fall +// through to the root } -templ Input(name, label, value, errMsg string) { +component Input(name, label, value, errMsg string) { } -templ Textarea(name, label, value, errMsg string) { +component Textarea(name, label, value, errMsg string) { } // Pagination is rendered as a standalone function component so HTMX requests // with HX-Target: #pagination resolve here regardless of which page hosts it. -templ Pagination(p PaginationProps) { +component Pagination(p PageNav) { } // ErrorPage is the full document rendered by main.errorHandler for non-HTMX // errors. ErrorBlock is the partial used for HTMX requests. -templ ErrorPage(status int, msg string) { +component ErrorPage(status int, msg string) { - Error { status } + Error {status}
- @ErrorBlock(status, msg) +
} -templ ErrorBlock(status int, msg string) { +component ErrorBlock(status int, msg string) {
-
{ status }
-

{ msg }

+
{status}
+

{msg}

Back to home
} diff --git a/examples/blog/ui/layout/layout.templ b/examples/blog/ui/layout/layout.gsx similarity index 89% rename from examples/blog/ui/layout/layout.templ rename to examples/blog/ui/layout/layout.gsx index 4675a1f..b28e7de 100644 --- a/examples/blog/ui/layout/layout.templ +++ b/examples/blog/ui/layout/layout.gsx @@ -1,5 +1,5 @@ // Package layout exports the shared HTML shells used by every page. -// Feature packages call PublicShell or AdminShell with { children... } +// Feature packages call PublicShell or AdminShell with {children} // instead of writing their own document. package layout @@ -11,13 +11,13 @@ import ( // PublicShell wraps reader-facing pages. Cross-feature links (e.g. the admin // link) use structpages.Ref so this package never imports admin or blog — // keeping the dependency graph one-way (features → ui). -templ PublicShell(title string) { +component PublicShell(title string) { - { title } — structpages blog + {title} — structpages blog @@ -33,20 +33,20 @@ templ PublicShell(title string) {
- { children... } + {children}
} // AdminShell wraps the authenticated admin app. -templ AdminShell(title string, current store.User) { +component AdminShell(title string, current store.User) { - Admin — { title } + Admin — {title} @@ -62,7 +62,7 @@ templ AdminShell(title string, current store.User) { Posts Users | - { current.Username } + {current.Username}
@@ -70,7 +70,7 @@ templ AdminShell(title string, current store.User) {
- { children... } + {children}
diff --git a/examples/htmx-render-target/pages.gsx b/examples/htmx-render-target/pages.gsx new file mode 100644 index 0000000..76920b8 --- /dev/null +++ b/examples/htmx-render-target/pages.gsx @@ -0,0 +1,149 @@ +package main + +import ( + "context" + "fmt" + + "github.com/jackielii/structpages" +) + +// Shared standalone function components (can be used across multiple pages). +// These demonstrate the power of RenderTarget — no wrapper methods needed. + +component UserStatsWidget(stats UserStats) { +
+

User Statistics

+

Active Users: { fmt.Sprintf("%d", stats.ActiveUsers) }

+

New Today: { fmt.Sprintf("%d", stats.NewToday) }

+ +
+} + +component SalesChartWidget(data SalesData) { +
+

Sales Chart

+
+ { for _, point := range data.Points { +
+ } } +
+

Total Sales: ${ fmt.Sprintf("%.2f", data.Total) }

+ +
+} + +component NotificationsList(notifications []Notification) { +
+

Recent Notifications

+
    + { for _, n := range notifications { +
  • { n.Message } ({ n.Time.Format("15:04") })
  • + } } +
+ +
+} + +// Dashboard page. + +type dashboard struct{} + +component (p dashboard) Page(props dashboardData) { + +

Dashboard

+

This example demonstrates the RenderTarget API with standalone function components.

+

Click "Refresh" buttons to see HTMX partial updates — each widget loads only its own data!

+
+
+ +
+
+ +
+
+ +
+
+
+

How it works:

+
    +
  • Standalone functions — UserStatsWidget, SalesChartWidget, NotificationsList are shared components
  • +
  • Conditional loading — Props checks target.Is() and loads only needed data
  • +
  • RenderComponent (direct) — construct the gsx component with its props struct and pass directly
  • +
  • No wrapper methods — No need to create dashboard.UserStats() method!
  • +
  • HTMX integration — HTMXRenderTarget automatically handles partial updates
  • +
+
+ +} + +// Html is the full-page layout. +component Html() { + + + + + + RenderTarget API Example + + + +
+ {children} +
+ + +} + +component ErrorPage(err error) { + + + +} + +component ErrorComp(err error) { +

Error

+

{ err.Error() }

+} + +// Helper functions + +func urlFor(ctx context.Context, page any, args ...any) (string, error) { + return structpages.URLFor(ctx, page, args...) +} + +func idFor(ctx context.Context, v any) (string, error) { + return structpages.ID(ctx, v) +} + +func idForTarget(ctx context.Context, v any) (string, error) { + return structpages.IDTarget(ctx, v) +} diff --git a/examples/htmx-render-target/pages.templ b/examples/htmx-render-target/pages.templ deleted file mode 100644 index 9cb3b90..0000000 --- a/examples/htmx-render-target/pages.templ +++ /dev/null @@ -1,270 +0,0 @@ -package main - -import ( - "context" - "fmt" - "math/rand/v2" - "net/http" - "time" - - "github.com/jackielii/structpages" -) - -// Shared standalone function components (can be used across multiple pages) -// These demonstrate the power of RenderTarget - no need for wrapper methods! -templ UserStatsWidget(stats UserStats) { -
-

User Statistics

-

Active Users: { fmt.Sprintf("%d", stats.ActiveUsers) }

-

New Today: { fmt.Sprintf("%d", stats.NewToday) }

- -
-} - -templ SalesChartWidget(data SalesData) { -
-

Sales Chart

-
- for _, point := range data.Points { -
- } -
-

Total Sales: ${ fmt.Sprintf("%.2f", data.Total) }

- -
-} - -templ NotificationsList(notifications []Notification) { -
-

Recent Notifications

-
    - for _, n := range notifications { -
  • { n.Message } ({ n.Time.Format("15:04") })
  • - } -
- -
-} - -// Dashboard page using RenderTarget API - -type dashboard struct{} - -type DashboardProps struct { - Stats UserStats - Sales SalesData - Notifications []Notification -} - -type UserStats struct { - ActiveUsers int - NewToday int -} - -type SalesData struct { - Points []DataPoint - Total float64 -} - -type DataPoint struct { - Label string - Value int -} - -type Notification struct { - Message string - Time time.Time -} - -// Props demonstrates conditional data loading with RenderTarget -func (p dashboard) Props(r *http.Request, target structpages.RenderTarget) (DashboardProps, error) { - // Check which component is being requested and load only necessary data - switch { - case target.Is(UserStatsWidget): - // Only load user stats (lightweight query) - stats := loadUserStats() - comp := UserStatsWidget(stats) - // Use RenderComponent with target to render just this widget - return DashboardProps{}, structpages.RenderComponent(comp) - - case target.Is(SalesChartWidget): - // Only load sales data (expensive query - avoid loading unnecessarily) - sales := loadSalesData() - return DashboardProps{}, structpages.RenderComponent(target, sales) - - case target.Is(NotificationsList): - // Only load notifications - notifications := loadNotifications() - return DashboardProps{}, structpages.RenderComponent(target, notifications) - - case target.Is(p.Page): - // Full page load - load all data - return DashboardProps{ - Stats: loadUserStats(), - Sales: loadSalesData(), - Notifications: loadNotifications(), - }, nil - - default: - // Fallback to full page - return DashboardProps{ - Stats: loadUserStats(), - Sales: loadSalesData(), - Notifications: loadNotifications(), - }, nil - } -} - -templ (p dashboard) Page(props DashboardProps) { - @html() { -

Dashboard

-

This example demonstrates the new RenderTarget API with standalone function components.

-

Click "Refresh" buttons to see HTMX partial updates - each widget loads only its own data!

-
-
- @UserStatsWidget(props.Stats) -
-
- @SalesChartWidget(props.Sales) -
-
- @NotificationsList(props.Notifications) -
-
-
-

How it works:

-
    -
  • Standalone functions - UserStatsWidget, SalesChartWidget, NotificationsList are shared components
  • -
  • Conditional loading - Props checks target.Is() and loads only needed data
  • -
  • RenderComponent - Passes specific data to each widget
  • -
  • No wrapper methods - No need to create dashboard.UserStats() method!
  • -
  • HTMX integration - HTMXRenderTarget automatically handles partial updates
  • -
-
- } -} - -// Mock data loaders (simulating database queries) -// Using random data to showcase HTMX partial updates -func loadUserStats() UserStats { - return UserStats{ - ActiveUsers: 1000 + rand.IntN(500), - NewToday: 10 + rand.IntN(90), - } -} - -func loadSalesData() SalesData { - points := []DataPoint{ - {Label: "Mon", Value: 30 + rand.IntN(100)}, - {Label: "Tue", Value: 30 + rand.IntN(100)}, - {Label: "Wed", Value: 30 + rand.IntN(100)}, - {Label: "Thu", Value: 30 + rand.IntN(100)}, - {Label: "Fri", Value: 30 + rand.IntN(100)}, - } - total := 0.0 - for _, p := range points { - total += float64(p.Value) * 100.0 - } - return SalesData{ - Points: points, - Total: total, - } -} - -func loadNotifications() []Notification { - messages := []string{ - "New user registered", - "Payment received", - "System update available", - "New order placed", - "Report generated", - "Backup completed", - } - count := 3 + rand.IntN(3) - notifications := make([]Notification, count) - for i := 0; i < count; i++ { - notifications[i] = Notification{ - Message: messages[rand.IntN(len(messages))], - Time: time.Now().Add(-time.Duration(rand.IntN(120)) * time.Minute), - } - } - return notifications -} - -// HTML layout -templ html() { - - - - - - RenderTarget API Example - - - -
- { children... } -
- - -} - -// Error handling -templ errorPage(err error) { - @html() { - @errorComp(err) - } -} - -templ errorComp(err error) { -

Error

-

{ err.Error() }

-} - -// Helper functions -func urlFor(ctx context.Context, page any, args ...any) (string, error) { - s, err := structpages.URLFor(ctx, page, args...) - return s, err -} - -func idFor(ctx context.Context, v any) (string, error) { - return structpages.ID(ctx, v) -} - -func idForTarget(ctx context.Context, v any) (string, error) { - return structpages.IDTarget(ctx, v) -} diff --git a/examples/htmx/pages.gsx b/examples/htmx/pages.gsx new file mode 100644 index 0000000..88c31a2 --- /dev/null +++ b/examples/htmx/pages.gsx @@ -0,0 +1,80 @@ +package main + +import ( + "fmt" + + "github.com/jackielii/structpages" +) + +type index struct { + product `route:"/product Product"` + team `route:"/team Team"` + contact `route:"/contact Contact"` + throw `route:"/throw Throw"` +} +type product struct{} +type team struct{} +type contact struct{} +type throw struct{} + +component (p index) Page() { } +component (p index) Main() { +

Welcome to the Index Page

+

Navigate to the product, team, or contact pages using the links below:

+ Throw (Err) +} + +component (p product) Page() { } +component (p product) Main() { +

Product Page

+

This is the product page.

+} + +component (p team) Page() { } +component (p team) Main() { +

Team Page

+

This is the team page.

+} + +component (p contact) Page() { } +component (p contact) Main() { +

Contact Page

+

This is the contact page.

+} + +func errFunc() (string, error ) { return "", fmt.Errorf("this is an error") } +component (p throw) Page() {

{errFunc()}

} + + +component Layout() { + + + + + + HTMX Example + + + +
{children}
+ + +} + +component ErrorPage(err error) { } + +component ErrorComp(err error) { +

Error

+

{ err.Error() }

+} + +var urlFor = structpages.URLFor diff --git a/examples/htmx/pages.templ b/examples/htmx/pages.templ deleted file mode 100644 index 94c3ce5..0000000 --- a/examples/htmx/pages.templ +++ /dev/null @@ -1,104 +0,0 @@ -package main - -import ( - "context" - "github.com/jackielii/structpages" -) - -type index struct { - product `route:"/product Product"` - team `route:"/team Team"` - contact `route:"/contact Contact"` -} - -templ (p index) Page() { - @html() { - @p.Main() - } -} - -templ (index) Main() { -

Welcome to the Index Page

-

Navigate to the product, team, or contact pages using the links below:

-} - -type product struct{} - -templ (p product) Page() { - @html() { - @p.Main() - } -} - -templ (product) Main() { -

Product Page

-

This is the product page.

-} - -type team struct{} - -templ (p team) Page() { - @html() { - @p.Main() - } -} - -templ (team) Main() { -

Team Page

-

This is the team page.

-} - -type contact struct{} - -templ (p contact) Page() { - @html() { - @p.Main() - } -} - -templ (contact) Main() { -

Contact Page

-

This is the contact page.

-} - -templ html() { - - - - - - HTMX Example - - - -
- { children... } -
- - -} - -templ errorPage(err error) { - @html() { - @errorComp(err) - } -} - -templ errorComp(err error) { -

Error

-

{ err.Error() }

-} - -func urlFor(ctx context.Context, page any, args ...any) (templ.SafeURL, error) { - s, err := structpages.URLFor(ctx, page, args...) - return templ.SafeURL(s), err -} diff --git a/examples/lint-misuse/pages.gsx b/examples/lint-misuse/pages.gsx new file mode 100644 index 0000000..3cfa58f --- /dev/null +++ b/examples/lint-misuse/pages.gsx @@ -0,0 +1,38 @@ +package main + +import ( + "context" + "fmt" + "strconv" + + "github.com/jackielii/structpages" +) + +// BadLinks mirrors the templ original — it deliberately uses hard-coded +// internal URLs so the structpages-lint [url-attr] rule has targets to flag. +// In gsx the component uses inline params just like the templ version. +// +// NOTE ON gsx AUTO-ESCAPING: gsx escapes URL-context attributes +// (href, hx-get, action, …) by context. That means a dynamically- +// constructed javascript: or data: URL would be neutralised, preventing +// XSS via URL sinks. However, the [url-attr] lint rule catches a +// different problem — routing-correctness: hard-coded path strings that +// bypass structpages.URLFor break when routes are renamed. That class of +// bug is orthogonal to XSS escaping, so gsx's auto-escaping does NOT +// make these findings go away. The lint still has value in gsx projects. +component BadLinks(id int, name string) { + Hard-coded internal + Expression literal + Concat + Sprintf + Bad hx-get +
Bad action
+ External (allowed) +} + +// urlFor is the app-level convenience wrapper used by gsx templates +// (avoids repeating the package qualifier; returns (string, error) so +// gsx auto-unwraps the error at the call site in URL-context attrs). +func urlFor(ctx context.Context, page any, args ...any) (string, error) { + return structpages.URLFor(ctx, page, args...) +} diff --git a/examples/lint-misuse/pages.templ b/examples/lint-misuse/pages.templ deleted file mode 100644 index 52ce673..0000000 --- a/examples/lint-misuse/pages.templ +++ /dev/null @@ -1,19 +0,0 @@ -package main - -import ( - "fmt" - "strconv" -) - -templ BadLinks(id int, name string) { - Hard-coded internal - Expression literal - Concat - Sprintf - Bad hx-get -
Bad action
- External (allowed) - - - Suppressed -} diff --git a/examples/simple/pages.templ b/examples/simple/pages.gsx similarity index 74% rename from examples/simple/pages.templ rename to examples/simple/pages.gsx index 91157f8..aaa6b6f 100644 --- a/examples/simple/pages.templ +++ b/examples/simple/pages.gsx @@ -1,53 +1,50 @@ package main -import "context" import "github.com/jackielii/structpages" +// Page structs + route tags are plain Go — pass through unchanged. type index struct { product `route:"/product Product"` team `route:"/team Team"` contact `route:"/contact Contact"` } +type product struct{} +type team struct{} +type contact struct{} -templ (index) Page() { - @html() { +component (p index) Page() { +

Welcome to the Index Page

Navigate to the product, team, or contact pages using the links below:

- } +
} -type product struct{} - -templ (product) Page() { - @html() { +component (p product) Page() { +

Product Page

This is the product page.

- } +
} -type team struct{} - -templ (team) Page() { - @html() { +component (p team) Page() { +

Team Page

This is the team page.

- } +
} -type contact struct{} - -templ (contact) Page() { - @html() { +component (p contact) Page() { +

Contact Page

This is the contact page.

- } +
} -templ html() { +component Layout() { - + Simple Example @@ -61,13 +58,9 @@ templ html() { -
- { children... } -
+
{children}
} -func urlFor(ctx context.Context, page any, args ...any) (string, error) { - return structpages.URLFor(ctx, page, args...) -} +var urlFor = structpages.URLFor diff --git a/examples/todo/pages.templ b/examples/todo/pages.gsx similarity index 80% rename from examples/todo/pages.templ rename to examples/todo/pages.gsx index 10fee51..49e0fc2 100644 --- a/examples/todo/pages.templ +++ b/examples/todo/pages.gsx @@ -1,9 +1,10 @@ package main import ( - "github.com/jackielii/structpages" "net/http" "strconv" + + "github.com/jackielii/structpages" ) type index struct { @@ -12,13 +13,13 @@ type index struct { deleteTodo `route:"DELETE /delete/{id} DeleteTodo"` } -templ (p index) Page() { - @html() { +component (p index) Page() { +

TODO App

@@ -32,15 +33,15 @@ templ (p index) Page() {
-
- @p.TodoList() +
+
- } +
} -templ (p index) TodoList() { - @todoList() +component (p index) TodoList() { + } type add struct{} @@ -81,42 +82,42 @@ func (d deleteTodo) ServeHTTP(w http.ResponseWriter, r *http.Request) error { return structpages.RenderComponent(index.TodoList) } -templ todoList() { +component TodoList() {
    - for _, todo := range getTodos() { -
  • + { for _, todo := range getTodos() { +
  • { todo.Text }
  • - } + } }
- if len(getTodos()) == 0 { + { if len(getTodos()) == 0 {

No todos yet. Add one above!

- } + } } } -templ html() { +component Layout() { - + TODO App ") + _gsxgw.S("") +//line pages.gsx:115:3 + _gsxgw.S("") +//line pages.gsx:116:4 + _gsxgw.S("") +//line pages.gsx:117:5 + _gsxgw.Node(ctx, children) + _gsxgw.S("") + _gsxgw.S("") + _gsxgw.S("") + return _gsxgw.Err() + }) +} + +type ErrorPageProps struct { + Err error +} + +func ErrorPage(_gsxp ErrorPageProps) gsx.Node { + return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { + err := _gsxp.Err + _gsxgw := gsx.W(_gsxw) +//line pages.gsx:124:2 + _gsxgw.Node(ctx, Html(HtmlProps{Children: gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { + _gsxgw := gsx.W(_gsxw) +//line pages.gsx:125:3 + _gsxgw.Node(ctx, ErrorComp(ErrorCompProps{Err: err})) + return _gsxgw.Err() + })})) + return _gsxgw.Err() + }) +} + +type ErrorCompProps struct { + Err error +} + +func ErrorComp(_gsxp ErrorCompProps) gsx.Node { + return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { + err := _gsxp.Err + _gsxgw := gsx.W(_gsxw) +//line pages.gsx:130:2 + _gsxgw.S("") + _gsxgw.S("Error") + _gsxgw.S("") +//line pages.gsx:131:2 + _gsxgw.S("") +//line pages.gsx:131:5 + _gsxgw.Text(string(err.Error())) + _gsxgw.S("

") + return _gsxgw.Err() + }) +} diff --git a/examples/htmx/cmd/gen/main.go b/examples/htmx/cmd/gen/main.go new file mode 100644 index 0000000..a2a182a --- /dev/null +++ b/examples/htmx/cmd/gen/main.go @@ -0,0 +1,21 @@ +// Command gen runs gsx codegen for the htmx example with the structpages +// pipeline filters registered, so .gsx templates can write `{ page{} |> url }`, +// `{ x |> id }`, and `{ x |> target }` instead of the ctx-threading, +// error-returning structpages.URLFor / ID / IDTarget calls. +// +// Generate with: go run ./cmd/gen generate . +package main + +import ( + "github.com/gsxhq/gsx/gen" + + "github.com/jackielii/structpages" +) + +func main() { + gen.Main( + gen.WithFilter("url", structpages.URLFor), + gen.WithFilter("id", structpages.ID), + gen.WithFilter("target", structpages.IDTarget), + ) +} diff --git a/examples/htmx/go.sum b/examples/htmx/go.sum index c04a9f5..d06353a 100644 --- a/examples/htmx/go.sum +++ b/examples/htmx/go.sum @@ -31,6 +31,7 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tdewolff/parse/v2 v2.8.13 h1:si/8rLw5BZZTWCCiMm9A3f6x+RmqYfrkEeXCgpX5ick= github.com/tdewolff/parse/v2 v2.8.13/go.mod h1:XdsoSFThlVIRIajAuqz1evNY7bagZS8LBOPA3aVopwQ= +github.com/tdewolff/test v1.0.12 h1:7F21DqIajswxuche0geHdrUZRCWE4oko4b7bcmkkrxk= github.com/tdewolff/test v1.0.12/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8= golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= diff --git a/examples/htmx/pages.gsx b/examples/htmx/pages.gsx index 88c31a2..a3c5989 100644 --- a/examples/htmx/pages.gsx +++ b/examples/htmx/pages.gsx @@ -2,8 +2,6 @@ package main import ( "fmt" - - "github.com/jackielii/structpages" ) type index struct { @@ -21,7 +19,7 @@ component (p index) Page() { } component (p index) Main() {

Welcome to the Index Page

Navigate to the product, team, or contact pages using the links below:

- Throw (Err) + url } hx-target="#main">Throw (Err) } component (p product) Page() { } @@ -58,10 +56,10 @@ component Layout() { @@ -76,5 +74,3 @@ component ErrorComp(err error) {

Error

{ err.Error() }

} - -var urlFor = structpages.URLFor diff --git a/examples/htmx/pages.x.go b/examples/htmx/pages.x.go new file mode 100644 index 0000000..fc661a2 --- /dev/null +++ b/examples/htmx/pages.x.go @@ -0,0 +1,349 @@ +// Code generated by gsx; DO NOT EDIT. + +package main + +import ( + "context" + "fmt" + "io" + + "github.com/gsxhq/gsx" + _gsxf0 "github.com/jackielii/structpages" +) + +type index struct { + product `route:"/product Product"` + team `route:"/team Team"` + contact `route:"/contact Contact"` + throw `route:"/throw Throw"` +} +type product struct{} +type team struct{} +type contact struct{} +type throw struct{} + +func (p index) Page() gsx.Node { + return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { + _gsxgw := gsx.W(_gsxw) +//line pages.gsx:18:32 + _gsxgw.Node(ctx, Layout(LayoutProps{Children: gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { + _gsxgw := gsx.W(_gsxw) +//line pages.gsx:18:40 + _gsxgw.Node(ctx, p.Main()) + return _gsxgw.Err() + })})) + return _gsxgw.Err() + }) +} + +func (p index) Main() gsx.Node { + return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { + _gsxgw := gsx.W(_gsxw) +//line pages.gsx:20:2 + _gsxgw.S("") + _gsxgw.S("Welcome to the Index Page") + _gsxgw.S("") +//line pages.gsx:21:2 + _gsxgw.S("") + _gsxgw.S("Navigate to the product, team, or contact pages using the links below:") + _gsxgw.S("

") +//line pages.gsx:22:3 + _gsxgw.S("") + _gsxgw.S("Throw (Err)") + _gsxgw.S("") + return _gsxgw.Err() + }) +} + +func (p product) Page() gsx.Node { + return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { + _gsxgw := gsx.W(_gsxw) +//line pages.gsx:25:32 + _gsxgw.Node(ctx, Layout(LayoutProps{Children: gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { + _gsxgw := gsx.W(_gsxw) +//line pages.gsx:25:40 + _gsxgw.Node(ctx, p.Main()) + return _gsxgw.Err() + })})) + return _gsxgw.Err() + }) +} + +func (p product) Main() gsx.Node { + return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { + _gsxgw := gsx.W(_gsxw) +//line pages.gsx:27:2 + _gsxgw.S("") + _gsxgw.S("Product Page") + _gsxgw.S("") +//line pages.gsx:28:2 + _gsxgw.S("") + _gsxgw.S("This is the product page.") + _gsxgw.S("

") + return _gsxgw.Err() + }) +} + +func (p team) Page() gsx.Node { + return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { + _gsxgw := gsx.W(_gsxw) +//line pages.gsx:31:29 + _gsxgw.Node(ctx, Layout(LayoutProps{Children: gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { + _gsxgw := gsx.W(_gsxw) +//line pages.gsx:31:37 + _gsxgw.Node(ctx, p.Main()) + return _gsxgw.Err() + })})) + return _gsxgw.Err() + }) +} + +func (p team) Main() gsx.Node { + return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { + _gsxgw := gsx.W(_gsxw) +//line pages.gsx:33:2 + _gsxgw.S("") + _gsxgw.S("Team Page") + _gsxgw.S("") +//line pages.gsx:34:2 + _gsxgw.S("") + _gsxgw.S("This is the team page.") + _gsxgw.S("

") + return _gsxgw.Err() + }) +} + +func (p contact) Page() gsx.Node { + return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { + _gsxgw := gsx.W(_gsxw) +//line pages.gsx:37:32 + _gsxgw.Node(ctx, Layout(LayoutProps{Children: gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { + _gsxgw := gsx.W(_gsxw) +//line pages.gsx:37:40 + _gsxgw.Node(ctx, p.Main()) + return _gsxgw.Err() + })})) + return _gsxgw.Err() + }) +} + +func (p contact) Main() gsx.Node { + return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { + _gsxgw := gsx.W(_gsxw) +//line pages.gsx:39:2 + _gsxgw.S("") + _gsxgw.S("Contact Page") + _gsxgw.S("") +//line pages.gsx:40:2 + _gsxgw.S("") + _gsxgw.S("This is the contact page.") + _gsxgw.S("

") + return _gsxgw.Err() + }) +} + +func errFunc() (string, error) { return "", fmt.Errorf("this is an error") } + +func (p throw) Page() gsx.Node { + return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { + _gsxgw := gsx.W(_gsxw) +//line pages.gsx:44:30 + _gsxgw.S("") +//line pages.gsx:44:33 + _gsxv1, _gsxerr := errFunc() + if _gsxerr != nil { + return _gsxerr + } + _gsxgw.Text(string(_gsxv1)) + _gsxgw.S("

") + return _gsxgw.Err() + }) +} + +type LayoutProps struct { + Children gsx.Node +} + +func Layout(_gsxp LayoutProps) gsx.Node { + return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { + children := _gsxp.Children + _gsxgw := gsx.W(_gsxw) + _gsxgw.S("") +//line pages.gsx:49:2 + _gsxgw.S("") +//line pages.gsx:50:3 + _gsxgw.S("") +//line pages.gsx:51:4 + _gsxgw.S("") +//line pages.gsx:52:4 + _gsxgw.S("") + _gsxgw.S("") +//line pages.gsx:53:4 + _gsxgw.S("") + _gsxgw.S("HTMX Example") + _gsxgw.S("") + _gsxgw.S("") +//line pages.gsx:55:3 + _gsxgw.S("") +//line pages.gsx:56:4 + _gsxgw.S("") +//line pages.gsx:57:5 + _gsxgw.S("") +//line pages.gsx:58:6 + _gsxgw.S("") +//line pages.gsx:59:7 + _gsxgw.S("") +//line pages.gsx:59:11 + _gsxgw.S("") + _gsxgw.S("Home") + _gsxgw.S("") + _gsxgw.S("") +//line pages.gsx:60:7 + _gsxgw.S("") +//line pages.gsx:60:11 + _gsxgw.S("") + _gsxgw.S("Product") + _gsxgw.S("") + _gsxgw.S("") +//line pages.gsx:61:7 + _gsxgw.S("") +//line pages.gsx:61:11 + _gsxgw.S("") + _gsxgw.S("Team") + _gsxgw.S("") + _gsxgw.S("") +//line pages.gsx:62:7 + _gsxgw.S("") +//line pages.gsx:62:11 + _gsxgw.S("") + _gsxgw.S("Contact") + _gsxgw.S("") + _gsxgw.S("") + _gsxgw.S("") + _gsxgw.S("") + _gsxgw.S("") +//line pages.gsx:66:4 + _gsxgw.S("") +//line pages.gsx:66:20 + _gsxgw.Node(ctx, children) + _gsxgw.S("") + _gsxgw.S("") + _gsxgw.S("") + return _gsxgw.Err() + }) +} + +type ErrorPageProps struct { + Err error +} + +func ErrorPage(_gsxp ErrorPageProps) gsx.Node { + return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { + err := _gsxp.Err + _gsxgw := gsx.W(_gsxw) +//line pages.gsx:71:34 + _gsxgw.Node(ctx, Layout(LayoutProps{Children: gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { + _gsxgw := gsx.W(_gsxw) +//line pages.gsx:71:42 + _gsxgw.Node(ctx, ErrorComp(ErrorCompProps{Err: err})) + return _gsxgw.Err() + })})) + return _gsxgw.Err() + }) +} + +type ErrorCompProps struct { + Err error +} + +func ErrorComp(_gsxp ErrorCompProps) gsx.Node { + return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { + err := _gsxp.Err + _gsxgw := gsx.W(_gsxw) +//line pages.gsx:74:2 + _gsxgw.S("") + _gsxgw.S("Error") + _gsxgw.S("") +//line pages.gsx:75:2 + _gsxgw.S("") +//line pages.gsx:75:5 + _gsxgw.Text(string(err.Error())) + _gsxgw.S("

") + return _gsxgw.Err() + }) +} diff --git a/examples/lint-misuse/cmd/gen/main.go b/examples/lint-misuse/cmd/gen/main.go new file mode 100644 index 0000000..ff28a02 --- /dev/null +++ b/examples/lint-misuse/cmd/gen/main.go @@ -0,0 +1,21 @@ +// Command gen runs gsx codegen for the lint-misuse example with the structpages +// pipeline filters registered, so .gsx templates can write `{ page{} |> url }`, +// `{ x |> id }`, and `{ x |> target }` instead of the ctx-threading, +// error-returning structpages.URLFor / ID / IDTarget calls. +// +// Generate with: go run ./cmd/gen generate . +package main + +import ( + "github.com/gsxhq/gsx/gen" + + "github.com/jackielii/structpages" +) + +func main() { + gen.Main( + gen.WithFilter("url", structpages.URLFor), + gen.WithFilter("id", structpages.ID), + gen.WithFilter("target", structpages.IDTarget), + ) +} diff --git a/examples/lint-misuse/go.mod b/examples/lint-misuse/go.mod index f9cfd8b..c6ca70c 100644 --- a/examples/lint-misuse/go.mod +++ b/examples/lint-misuse/go.mod @@ -4,6 +4,13 @@ go 1.26.1 require github.com/jackielii/structpages v0.0.0-00010101000000-000000000000 +require ( + github.com/tdewolff/parse/v2 v2.8.13 // indirect + golang.org/x/mod v0.37.0 // indirect + golang.org/x/sync v0.21.0 // indirect + golang.org/x/tools v0.46.0 // indirect +) + require ( github.com/gsxhq/gsx v0.0.0 github.com/jackielii/ctxkey v1.0.1 // indirect diff --git a/examples/lint-misuse/go.sum b/examples/lint-misuse/go.sum index 2108c87..1fdd546 100644 --- a/examples/lint-misuse/go.sum +++ b/examples/lint-misuse/go.sum @@ -2,3 +2,13 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/jackielii/ctxkey v1.0.1 h1:CcgbR+fQbrzZJWxI/7Ec4EhzUbmTU1sfI1gV7MAgjIg= github.com/jackielii/ctxkey v1.0.1/go.mod h1:fo4HOwrvSnc3n8o5qZ5L+FVcSyQn+d67CCnlEbH24uc= +github.com/tdewolff/parse/v2 v2.8.13 h1:si/8rLw5BZZTWCCiMm9A3f6x+RmqYfrkEeXCgpX5ick= +github.com/tdewolff/parse/v2 v2.8.13/go.mod h1:XdsoSFThlVIRIajAuqz1evNY7bagZS8LBOPA3aVopwQ= +github.com/tdewolff/test v1.0.12 h1:7F21DqIajswxuche0geHdrUZRCWE4oko4b7bcmkkrxk= +github.com/tdewolff/test v1.0.12/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8= +golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= +golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= +golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= +golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/tools v0.46.0 h1:7jTurBkPZu4moS/Uy4OQT1M+QBlsj3wejyZwsT8Z7rk= +golang.org/x/tools v0.46.0/go.mod h1:FrD85F8l+NWL+9XWBSyVSHO6Ne4jutsfIFba7AWQ5Ys= diff --git a/examples/lint-misuse/pages.gsx b/examples/lint-misuse/pages.gsx index 3cfa58f..e4d28c1 100644 --- a/examples/lint-misuse/pages.gsx +++ b/examples/lint-misuse/pages.gsx @@ -1,11 +1,8 @@ package main import ( - "context" "fmt" "strconv" - - "github.com/jackielii/structpages" ) // BadLinks mirrors the templ original — it deliberately uses hard-coded @@ -29,10 +26,3 @@ component BadLinks(id int, name string) {
Bad action
External (allowed) } - -// urlFor is the app-level convenience wrapper used by gsx templates -// (avoids repeating the package qualifier; returns (string, error) so -// gsx auto-unwraps the error at the call site in URL-context attrs). -func urlFor(ctx context.Context, page any, args ...any) (string, error) { - return structpages.URLFor(ctx, page, args...) -} diff --git a/examples/lint-misuse/pages.x.go b/examples/lint-misuse/pages.x.go new file mode 100644 index 0000000..890edd8 --- /dev/null +++ b/examples/lint-misuse/pages.x.go @@ -0,0 +1,91 @@ +// Code generated by gsx; DO NOT EDIT. + +package main + +import ( + "context" + "fmt" + "io" + "strconv" + + "github.com/gsxhq/gsx" +) + +// BadLinks mirrors the templ original — it deliberately uses hard-coded +// internal URLs so the structpages-lint [url-attr] rule has targets to flag. +// In gsx the component uses inline params just like the templ version. +// +// NOTE ON gsx AUTO-ESCAPING: gsx escapes URL-context attributes +// (href, hx-get, action, …) by context. That means a dynamically- +// constructed javascript: or data: URL would be neutralised, preventing +// XSS via URL sinks. However, the [url-attr] lint rule catches a +// different problem — routing-correctness: hard-coded path strings that +// bypass structpages.URLFor break when routes are renamed. That class of +// bug is orthogonal to XSS escaping, so gsx's auto-escaping does NOT +// make these findings go away. The lint still has value in gsx projects. + +type BadLinksProps struct { + Id int + Name string +} + +func BadLinks(_gsxp BadLinksProps) gsx.Node { + return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { + id := _gsxp.Id + name := _gsxp.Name + _gsxgw := gsx.W(_gsxw) +//line pages.gsx:21:2 + _gsxgw.S("") + _gsxgw.S("Hard-coded internal") + _gsxgw.S("") +//line pages.gsx:22:2 + _gsxgw.S("") + _gsxgw.S("Expression literal") + _gsxgw.S("") +//line pages.gsx:23:2 + _gsxgw.S("") + _gsxgw.S("Concat") + _gsxgw.S("") +//line pages.gsx:24:2 + _gsxgw.S("") + _gsxgw.S("Sprintf") + _gsxgw.S("") +//line pages.gsx:25:2 + _gsxgw.S("") + _gsxgw.S("Bad hx-get") + _gsxgw.S("") +//line pages.gsx:26:2 + _gsxgw.S("") + _gsxgw.S("Bad action") + _gsxgw.S("") +//line pages.gsx:27:2 + _gsxgw.S("") + _gsxgw.S("External (allowed)") + _gsxgw.S("") + return _gsxgw.Err() + }) +} diff --git a/examples/simple/cmd/gen/main.go b/examples/simple/cmd/gen/main.go new file mode 100644 index 0000000..97fe469 --- /dev/null +++ b/examples/simple/cmd/gen/main.go @@ -0,0 +1,21 @@ +// Command gen runs gsx codegen for the simple example with the structpages +// pipeline filters registered, so .gsx templates can write `{ page{} |> url }`, +// `{ x |> id }`, and `{ x |> target }` instead of the ctx-threading, +// error-returning structpages.URLFor / ID / IDTarget calls. +// +// Generate with: go run ./cmd/gen generate . +package main + +import ( + "github.com/gsxhq/gsx/gen" + + "github.com/jackielii/structpages" +) + +func main() { + gen.Main( + gen.WithFilter("url", structpages.URLFor), + gen.WithFilter("id", structpages.ID), + gen.WithFilter("target", structpages.IDTarget), + ) +} diff --git a/examples/simple/go.mod b/examples/simple/go.mod index 688aa22..37fca37 100644 --- a/examples/simple/go.mod +++ b/examples/simple/go.mod @@ -4,7 +4,10 @@ go 1.26.1 require github.com/jackielii/structpages v0.0.0-00010101000000-000000000000 -require github.com/a-h/templ v0.3.1020 // indirect +require ( + github.com/a-h/templ v0.3.1020 // indirect + github.com/tdewolff/parse/v2 v2.8.13 // indirect +) require ( github.com/a-h/parse v0.0.0-20250122154542-74294addb73e // indirect diff --git a/examples/simple/go.sum b/examples/simple/go.sum index df0b1bd..d06353a 100644 --- a/examples/simple/go.sum +++ b/examples/simple/go.sum @@ -29,6 +29,10 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tdewolff/parse/v2 v2.8.13 h1:si/8rLw5BZZTWCCiMm9A3f6x+RmqYfrkEeXCgpX5ick= +github.com/tdewolff/parse/v2 v2.8.13/go.mod h1:XdsoSFThlVIRIajAuqz1evNY7bagZS8LBOPA3aVopwQ= +github.com/tdewolff/test v1.0.12 h1:7F21DqIajswxuche0geHdrUZRCWE4oko4b7bcmkkrxk= +github.com/tdewolff/test v1.0.12/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8= golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= diff --git a/examples/simple/pages.gsx b/examples/simple/pages.gsx index aaa6b6f..69ff909 100644 --- a/examples/simple/pages.gsx +++ b/examples/simple/pages.gsx @@ -1,7 +1,5 @@ package main -import "github.com/jackielii/structpages" - // Page structs + route tags are plain Go — pass through unchanged. type index struct { product `route:"/product Product"` @@ -51,10 +49,10 @@ component Layout() { @@ -62,5 +60,3 @@ component Layout() { } - -var urlFor = structpages.URLFor diff --git a/examples/simple/pages.x.go b/examples/simple/pages.x.go new file mode 100644 index 0000000..5f05b95 --- /dev/null +++ b/examples/simple/pages.x.go @@ -0,0 +1,229 @@ +// Code generated by gsx; DO NOT EDIT. + +package main + +import ( + "context" + "io" + + "github.com/gsxhq/gsx" + _gsxf0 "github.com/jackielii/structpages" +) + +// Page structs + route tags are plain Go — pass through unchanged. +type index struct { + product `route:"/product Product"` + team `route:"/team Team"` + contact `route:"/contact Contact"` +} +type product struct{} +type team struct{} +type contact struct{} + +func (p index) Page() gsx.Node { + return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { + _gsxgw := gsx.W(_gsxw) +//line pages.gsx:14:2 + _gsxgw.Node(ctx, Layout(LayoutProps{Children: gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { + _gsxgw := gsx.W(_gsxw) +//line pages.gsx:15:3 + _gsxgw.S("") + _gsxgw.S("Welcome to the Index Page") + _gsxgw.S("") +//line pages.gsx:16:3 + _gsxgw.S("") + _gsxgw.S("Navigate to the product, team, or contact pages using the links below:") + _gsxgw.S("

") + return _gsxgw.Err() + })})) + return _gsxgw.Err() + }) +} + +func (p product) Page() gsx.Node { + return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { + _gsxgw := gsx.W(_gsxw) +//line pages.gsx:21:2 + _gsxgw.Node(ctx, Layout(LayoutProps{Children: gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { + _gsxgw := gsx.W(_gsxw) +//line pages.gsx:22:3 + _gsxgw.S("") + _gsxgw.S("Product Page") + _gsxgw.S("") +//line pages.gsx:23:3 + _gsxgw.S("") + _gsxgw.S("This is the product page.") + _gsxgw.S("

") + return _gsxgw.Err() + })})) + return _gsxgw.Err() + }) +} + +func (p team) Page() gsx.Node { + return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { + _gsxgw := gsx.W(_gsxw) +//line pages.gsx:28:2 + _gsxgw.Node(ctx, Layout(LayoutProps{Children: gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { + _gsxgw := gsx.W(_gsxw) +//line pages.gsx:29:3 + _gsxgw.S("") + _gsxgw.S("Team Page") + _gsxgw.S("") +//line pages.gsx:30:3 + _gsxgw.S("") + _gsxgw.S("This is the team page.") + _gsxgw.S("

") + return _gsxgw.Err() + })})) + return _gsxgw.Err() + }) +} + +func (p contact) Page() gsx.Node { + return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { + _gsxgw := gsx.W(_gsxw) +//line pages.gsx:35:2 + _gsxgw.Node(ctx, Layout(LayoutProps{Children: gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { + _gsxgw := gsx.W(_gsxw) +//line pages.gsx:36:3 + _gsxgw.S("") + _gsxgw.S("Contact Page") + _gsxgw.S("") +//line pages.gsx:37:3 + _gsxgw.S("") + _gsxgw.S("This is the contact page.") + _gsxgw.S("

") + return _gsxgw.Err() + })})) + return _gsxgw.Err() + }) +} + +type LayoutProps struct { + Children gsx.Node +} + +func Layout(_gsxp LayoutProps) gsx.Node { + return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { + children := _gsxp.Children + _gsxgw := gsx.W(_gsxw) + _gsxgw.S("") +//line pages.gsx:43:2 + _gsxgw.S("") +//line pages.gsx:44:3 + _gsxgw.S("") +//line pages.gsx:45:4 + _gsxgw.S("") +//line pages.gsx:46:4 + _gsxgw.S("") + _gsxgw.S("Simple Example") + _gsxgw.S("") + _gsxgw.S("") +//line pages.gsx:48:3 + _gsxgw.S("") +//line pages.gsx:49:4 + _gsxgw.S("") +//line pages.gsx:50:5 + _gsxgw.S("") +//line pages.gsx:51:6 + _gsxgw.S("") +//line pages.gsx:52:7 + _gsxgw.S("") +//line pages.gsx:52:11 + _gsxgw.S("") + _gsxgw.S("Home") + _gsxgw.S("") + _gsxgw.S("") +//line pages.gsx:53:7 + _gsxgw.S("") +//line pages.gsx:53:11 + _gsxgw.S("") + _gsxgw.S("Product") + _gsxgw.S("") + _gsxgw.S("") +//line pages.gsx:54:7 + _gsxgw.S("") +//line pages.gsx:54:11 + _gsxgw.S("") + _gsxgw.S("Team") + _gsxgw.S("") + _gsxgw.S("") +//line pages.gsx:55:7 + _gsxgw.S("") +//line pages.gsx:55:11 + _gsxgw.S("") + _gsxgw.S("Contact") + _gsxgw.S("") + _gsxgw.S("") + _gsxgw.S("") + _gsxgw.S("") + _gsxgw.S("") +//line pages.gsx:59:4 + _gsxgw.S("") +//line pages.gsx:59:10 + _gsxgw.Node(ctx, children) + _gsxgw.S("") + _gsxgw.S("") + _gsxgw.S("") + return _gsxgw.Err() + }) +} diff --git a/examples/todo/cmd/gen/main.go b/examples/todo/cmd/gen/main.go new file mode 100644 index 0000000..3696a3a --- /dev/null +++ b/examples/todo/cmd/gen/main.go @@ -0,0 +1,21 @@ +// Command gen runs gsx codegen for the todo example with the structpages +// pipeline filters registered, so .gsx templates can write `{ page{} |> url }`, +// `{ x |> id }`, and `{ x |> target }` instead of the ctx-threading, +// error-returning structpages.URLFor / ID / IDTarget calls. +// +// Generate with: go run ./cmd/gen generate . +package main + +import ( + "github.com/gsxhq/gsx/gen" + + "github.com/jackielii/structpages" +) + +func main() { + gen.Main( + gen.WithFilter("url", structpages.URLFor), + gen.WithFilter("id", structpages.ID), + gen.WithFilter("target", structpages.IDTarget), + ) +} diff --git a/examples/todo/go.mod b/examples/todo/go.mod index 7ccebcf..a2b4ba8 100644 --- a/examples/todo/go.mod +++ b/examples/todo/go.mod @@ -4,7 +4,10 @@ go 1.26.1 require github.com/jackielii/structpages v0.0.0 -require github.com/a-h/templ v0.3.1020 // indirect +require ( + github.com/a-h/templ v0.3.1020 // indirect + github.com/tdewolff/parse/v2 v2.8.13 // indirect +) require ( github.com/a-h/parse v0.0.0-20250122154542-74294addb73e // indirect diff --git a/examples/todo/go.sum b/examples/todo/go.sum index df0b1bd..d06353a 100644 --- a/examples/todo/go.sum +++ b/examples/todo/go.sum @@ -29,6 +29,10 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tdewolff/parse/v2 v2.8.13 h1:si/8rLw5BZZTWCCiMm9A3f6x+RmqYfrkEeXCgpX5ick= +github.com/tdewolff/parse/v2 v2.8.13/go.mod h1:XdsoSFThlVIRIajAuqz1evNY7bagZS8LBOPA3aVopwQ= +github.com/tdewolff/test v1.0.12 h1:7F21DqIajswxuche0geHdrUZRCWE4oko4b7bcmkkrxk= +github.com/tdewolff/test v1.0.12/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8= golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= diff --git a/examples/todo/pages.gsx b/examples/todo/pages.gsx index 49e0fc2..4f7294e 100644 --- a/examples/todo/pages.gsx +++ b/examples/todo/pages.gsx @@ -18,8 +18,8 @@ component (p index) Page() {

TODO App

url } + hx-target={ index.TodoList |> target } hx-swap="innerHTML" hx-on:htmx:after-request="this.reset()" > @@ -33,7 +33,7 @@ component (p index) Page() {
-
+
id }>
@@ -90,16 +90,16 @@ component TodoList() { url("id", todo.ID) } + hx-target={ index.TodoList |> target } hx-swap="innerHTML" /> { todo.Text } ") + _gsxgw.S("") + _gsxgw.S("") +//line pages.gsx:36:4 + _gsxgw.S("") +//line pages.gsx:37:5 + _gsxgw.Node(ctx, p.TodoList()) + _gsxgw.S("") + _gsxgw.S("") + return _gsxgw.Err() + })})) + return _gsxgw.Err() + }) +} + +func (p index) TodoList() gsx.Node { + return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { + _gsxgw := gsx.W(_gsxw) +//line pages.gsx:44:2 + _gsxgw.Node(ctx, TodoList()) + return _gsxgw.Err() + }) +} + +type add struct{} + +func (a add) ServeHTTP(w http.ResponseWriter, r *http.Request) error { + if r.Method == "POST" { + text := r.FormValue("text") + if text != "" { + addTodo(text) + } + } + return structpages.RenderComponent(index.TodoList) +} + +type toggle struct{} + +func (t toggle) ServeHTTP(w http.ResponseWriter, r *http.Request) error { + id, err := strconv.Atoi(r.PathValue("id")) + if err != nil { + return err + } + if r.Method == "POST" { + toggleTodo(id) + } + return structpages.RenderComponent(index.TodoList) +} + +type deleteTodo struct{} + +func (d deleteTodo) ServeHTTP(w http.ResponseWriter, r *http.Request) error { + id, err := strconv.Atoi(r.PathValue("id")) + if err != nil { + return err + } + if r.Method == "DELETE" { + removeTodo(id) + } + return structpages.RenderComponent(index.TodoList) +} + +func TodoList() gsx.Node { + return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { + _gsxgw := gsx.W(_gsxw) +//line pages.gsx:86:2 + _gsxgw.S("") +//line pages.gsx:87:3 + for _, todo := range getTodos() { +//line pages.gsx:88:4 + _gsxgw.S("") +//line pages.gsx:89:5 + _gsxgw.S("") +//line pages.gsx:90:6 + _gsxgw.S("") +//line pages.gsx:97:6 + _gsxgw.S("") +//line pages.gsx:97:30 + _gsxgw.Text(string(todo.Text)) + _gsxgw.S("") + _gsxgw.S("") +//line pages.gsx:99:5 + _gsxgw.S("") + _gsxgw.S("×") + _gsxgw.S("") + _gsxgw.S("") + } + _gsxgw.S("") +//line pages.gsx:111:2 + if len(getTodos()) == 0 { +//line pages.gsx:112:3 + _gsxgw.S("") + _gsxgw.S("No todos yet. Add one above!") + _gsxgw.S("

") + } + return _gsxgw.Err() + }) +} + +type LayoutProps struct { + Children gsx.Node +} + +func Layout(_gsxp LayoutProps) gsx.Node { + return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { + children := _gsxp.Children + _gsxgw := gsx.W(_gsxw) + _gsxgw.S("") +//line pages.gsx:118:2 + _gsxgw.S("") +//line pages.gsx:119:3 + _gsxgw.S("") +//line pages.gsx:120:4 + _gsxgw.S("") +//line pages.gsx:121:4 + _gsxgw.S("") + _gsxgw.S("") +//line pages.gsx:122:4 + _gsxgw.S("") + _gsxgw.S("TODO App") + _gsxgw.S("") +//line pages.gsx:123:4 + _gsxgw.S("") + _gsxgw.S(".todo-app{max-width: 600px;margin: 2rem auto;padding: 2rem}.form-group{display: flex;gap: 0.5rem;margin-bottom: 2rem}.form-group input{flex: 1;padding: 0.75rem;border: 1px solid #ddd;border-radius: 4px}.form-group button{padding: 0.75rem 1.5rem;background: #007bff;color: white;border: none;border-radius: 4px;cursor: pointer}.form-group button:hover{background: #0056b3}.todo-list{list-style: none;padding: 0}.todo-item{display: flex;align-items: center;justify-content: space-between;padding: 1rem;border: 1px solid #eee;border-radius: 4px;margin-bottom: 0.5rem;background: white}.todo-item.completed{opacity: 0.6}.todo-item.completed .todo-text{text-decoration: line-through}.todo-content{display: flex;align-items: center;gap: 0.75rem;flex: 1}.todo-text{flex: 1}.delete-btn{background: #dc3545;color: white;border: none;border-radius: 50%;width: 2rem;height: 2rem;cursor: pointer;font-size: 1.2rem;display: flex;align-items: center;justify-content: center}.delete-btn:hover{background: #c82333}.empty-state{text-align: center;color: #666;font-style: italic;padding: 2rem}") + _gsxgw.S("") + _gsxgw.S("") +//line pages.gsx:217:3 + _gsxgw.S("") +//line pages.gsx:218:4 + _gsxgw.S("") +//line pages.gsx:219:5 + _gsxgw.Node(ctx, children) + _gsxgw.S("") + _gsxgw.S("") + _gsxgw.S("") + return _gsxgw.Err() + }) +} + +type ErrorPageProps struct { + Err error +} + +func ErrorPage(_gsxp ErrorPageProps) gsx.Node { + return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { + err := _gsxp.Err + _gsxgw := gsx.W(_gsxw) +//line pages.gsx:226:2 + _gsxgw.Node(ctx, Layout(LayoutProps{Children: gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { + _gsxgw := gsx.W(_gsxw) +//line pages.gsx:227:3 + _gsxgw.Node(ctx, ErrorComp(ErrorCompProps{Err: err})) + return _gsxgw.Err() + })})) + return _gsxgw.Err() + }) +} + +type ErrorCompProps struct { + Err error +} + +func ErrorComp(_gsxp ErrorCompProps) gsx.Node { + return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { + err := _gsxp.Err + _gsxgw := gsx.W(_gsxw) +//line pages.gsx:232:2 + _gsxgw.S("") + _gsxgw.S("Error") + _gsxgw.S("") +//line pages.gsx:233:2 + _gsxgw.S("") +//line pages.gsx:233:5 + _gsxgw.Text(string(err.Error())) + _gsxgw.S("

") + return _gsxgw.Err() + }) +} From 5a5f6ec1093ee60a80e5780de2ae5ce9494994e0 Mon Sep 17 00:00:00 2001 From: Jackie Li Date: Thu, 25 Jun 2026 17:37:26 +0100 Subject: [PATCH 03/13] examples/blog: pipeline the components.URL template calls (= { X |> url }) Claude-Session: https://claude.ai/code/session_01AVv1VYM5pjrSTVfYtBDzBG --- examples/blog/admin/components.gsx | 6 +- examples/blog/admin/components.x.go | 8 +- examples/blog/admin/posts.gsx | 4 +- examples/blog/admin/posts.x.go | 5 +- examples/blog/admin/users.gsx | 4 +- examples/blog/admin/users.x.go | 5 +- examples/blog/blog/components.gsx | 45 ++++++++ examples/blog/blog/components.x.go | 160 ++++++++++++++++++++++++++++ examples/blog/blog/home.gsx | 2 +- examples/blog/blog/home.x.go | 3 +- examples/blog/blog/post.gsx | 2 +- examples/blog/blog/post.x.go | 4 +- 12 files changed, 228 insertions(+), 20 deletions(-) create mode 100644 examples/blog/blog/components.gsx create mode 100644 examples/blog/blog/components.x.go diff --git a/examples/blog/admin/components.gsx b/examples/blog/admin/components.gsx index 48bb963..5f31138 100644 --- a/examples/blog/admin/components.gsx +++ b/examples/blog/admin/components.gsx @@ -36,7 +36,7 @@ component RecentPostsCard(posts []store.Post) { } } { for _, p := range posts {
  • - + url("id", p.ID) }> {p.Title} { if p.Published { @@ -66,7 +66,7 @@ component PostsTable(posts []store.Post) { { for _, p := range posts { - {p.Title} + url("id", p.ID) }>{p.Title} { if p.Published { @@ -79,7 +79,7 @@ component PostsTable(posts []store.Post) {
    url("id", p.ID) } hx-post={ postDeleteHandler{} |> url("id", p.ID) } hx-target={ PostsTable |> target } hx-swap="outerHTML" diff --git a/examples/blog/admin/components.x.go b/examples/blog/admin/components.x.go index 3dbaa5d..ddb9e44 100644 --- a/examples/blog/admin/components.x.go +++ b/examples/blog/admin/components.x.go @@ -142,7 +142,7 @@ func RecentPostsCard(_gsxp RecentPostsCardProps) gsx.Node { //line components.gsx:39:7 _gsxgw.S("") -//line components.gsx:69:92 +//line components.gsx:69:78 _gsxgw.Text(string(p.Title)) _gsxgw.S("") _gsxgw.S("") @@ -304,7 +304,7 @@ func PostsTable(_gsxp PostsTableProps) gsx.Node { //line components.gsx:80:8 _gsxgw.S("

    All posts

    - New post + url }>New post
    @@ -115,7 +115,7 @@ component PostForm(p store.Post, cats []store.Category, errMsg string) { } diff --git a/examples/blog/admin/posts.x.go b/examples/blog/admin/posts.x.go index eac06c4..6d41020 100644 --- a/examples/blog/admin/posts.x.go +++ b/examples/blog/admin/posts.x.go @@ -10,6 +10,7 @@ import ( "strconv" "github.com/gsxhq/gsx" + _gsxf0 "github.com/jackielii/structpages" "github.com/jackielii/structpages/examples/blog/auth" "github.com/jackielii/structpages/examples/blog/store" "github.com/jackielii/structpages/examples/blog/ui/components" @@ -77,7 +78,7 @@ func (p postListPage) Page(props postListProps) gsx.Node { //line posts.gsx:43:4 _gsxgw.S("
    url("id", u.ID) } class="m-0" > @@ -48,7 +48,7 @@ component (p userListPage) Page(props userListProps) { - + url } class="space-y-3">
  • ") -//line dashboard.gsx:72:6 - _gsxgw.S("") - _gsxgw.S("Click ↻ Recent posts — only that card reloads, with its own DB query.") - _gsxgw.S("") -//line dashboard.gsx:73:6 - _gsxgw.S("") - _gsxgw.S("Hard refresh — the full document re-renders via Page().") - _gsxgw.S("") - _gsxgw.S("") +//line dashboard.gsx:74:5 + _gsxgw.S("
      ") +//line dashboard.gsx:75:6 + _gsxgw.S("
    • Click ↻ Stats — only the StatsGrid widget refreshes (check Network tab).
    • ") +//line dashboard.gsx:78:6 + _gsxgw.S("
    • Click ↻ Recent posts — only that card reloads, with its own DB query.
    • ") +//line dashboard.gsx:81:6 + _gsxgw.S("
    • Hard refresh — the full document re-renders via Page().
    ") return _gsxgw.Err() })})) _gsxgw.S("") diff --git a/examples/blog/admin/login.gsx b/examples/blog/admin/login.gsx index 55091d0..b65a597 100644 --- a/examples/blog/admin/login.gsx +++ b/examples/blog/admin/login.gsx @@ -17,9 +17,14 @@ component LoginShell(username, errMsg string) {
    - +
    diff --git a/examples/blog/admin/login.x.go b/examples/blog/admin/login.x.go index e956a2e..73e7105 100644 --- a/examples/blog/admin/login.x.go +++ b/examples/blog/admin/login.x.go @@ -26,114 +26,56 @@ func LoginShell(_gsxp LoginShellProps) gsx.Node { _gsxgw := gsx.W(_gsxw) _gsxgw.S("") //line login.gsx:10:2 - _gsxgw.S("") + _gsxgw.S("") //line login.gsx:11:3 - _gsxgw.S("") + _gsxgw.S("") //line login.gsx:12:4 - _gsxgw.S("") + _gsxgw.S("") //line login.gsx:13:4 - _gsxgw.S("") - _gsxgw.S("Sign in — blog admin") - _gsxgw.S("") + _gsxgw.S("Sign in — blog admin") //line login.gsx:14:4 - _gsxgw.S("") - _gsxgw.S("") - _gsxgw.S("") + _gsxgw.S("") //line login.gsx:16:3 - _gsxgw.S("") + _gsxgw.S("") //line login.gsx:17:4 - _gsxgw.S("") + _gsxgw.S("
    ") //line login.gsx:18:5 _gsxgw.Node(ctx, components.Card(components.CardProps{Title: "Sign in", Children: gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) //line login.gsx:19:6 - _gsxgw.S("") + _gsxgw.S("
    ") //line login.gsx:20:7 _gsxgw.Node(ctx, components.Alert(components.AlertProps{Kind: components.AlertError, Msg: errMsg})) -//line login.gsx:21:7 - _gsxgw.S("") -//line login.gsx:22:8 - _gsxgw.S("") - _gsxgw.S("Username") - _gsxgw.S("") -//line login.gsx:23:8 - _gsxgw.S("") +//line login.gsx:25:8 + _gsxgw.S("Username") +//line login.gsx:28:8 + _gsxgw.S("") - _gsxgw.S("") -//line login.gsx:31:7 - _gsxgw.S("") -//line login.gsx:32:8 - _gsxgw.S("") - _gsxgw.S("Password") - _gsxgw.S("") -//line login.gsx:33:8 - _gsxgw.S("") +//line login.gsx:36:7 + _gsxgw.S("") -//line login.gsx:40:7 - _gsxgw.S("") - _gsxgw.S("Sign in") - _gsxgw.S("") -//line login.gsx:46:7 - _gsxgw.S("") - _gsxgw.S("Demo credentials: ") -//line login.gsx:46:59 - _gsxgw.S("") - _gsxgw.S("admin") - _gsxgw.S("") - _gsxgw.S(" / ") -//line login.gsx:46:80 - _gsxgw.S("") - _gsxgw.S("admin") - _gsxgw.S("") - _gsxgw.S("

    ") - _gsxgw.S("") + _gsxgw.S(" class=\"w-full rounded border border-slate-300 px-2 py-1.5 text-sm\"/>") +//line login.gsx:47:7 + _gsxgw.S("") +//line login.gsx:53:7 + _gsxgw.S("

    Demo credentials: ") +//line login.gsx:54:26 + _gsxgw.S("admin / ") +//line login.gsx:54:47 + _gsxgw.S("admin

    ") return _gsxgw.Err() })})) - _gsxgw.S("
    ") - _gsxgw.S("") - _gsxgw.S("") + _gsxgw.S("") return _gsxgw.Err() }) } diff --git a/examples/blog/admin/posts.gsx b/examples/blog/admin/posts.gsx index 1ea8a37..c2f96a8 100644 --- a/examples/blog/admin/posts.gsx +++ b/examples/blog/admin/posts.gsx @@ -18,7 +18,7 @@ import ( // User: …, Children: body}). component AdminShellWith(title string, user store.User) { - {children} + { children } } @@ -41,7 +41,12 @@ component (p postListPage) Page(props postListProps) {

    All posts

    - url }>New post + url} + > + New post +
    @@ -96,16 +101,26 @@ component (p postEditPage) Page(props postFormViewProps) { // --- Shared form --- component PostForm(p store.Post, cats []store.Category, errMsg string) { -
    + - + @@ -116,7 +131,12 @@ component PostForm(p store.Post, cats []store.Category, errMsg string) { } diff --git a/examples/blog/admin/posts.x.go b/examples/blog/admin/posts.x.go index 540b0c7..73ce318 100644 --- a/examples/blog/admin/posts.x.go +++ b/examples/blog/admin/posts.x.go @@ -67,30 +67,19 @@ func (p postListPage) Page(props postListProps) gsx.Node { _gsxgw.Node(ctx, layout.AdminShell(layout.AdminShellProps{Title: "Posts", Current: props.User, Children: gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) //line posts.gsx:42:3 - _gsxgw.S("") + _gsxgw.S("
    ") //line posts.gsx:43:4 - _gsxgw.S("") - _gsxgw.S("All posts") - _gsxgw.S("") + _gsxgw.S("

    All posts

    ") //line posts.gsx:44:4 - _gsxgw.S("") - _gsxgw.S("New post") - _gsxgw.S("") - _gsxgw.S("
    ") -//line posts.gsx:46:3 + _gsxgw.S("\">New post") +//line posts.gsx:51:3 _gsxgw.Node(ctx, PostsTable(PostsTableProps{Posts: props.Posts})) return _gsxgw.Err() })})) @@ -116,16 +105,12 @@ func (postNewPage) Props(r *http.Request, s *store.Store) (postFormViewProps, er func (p postNewPage) Page(props postFormViewProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) -//line posts.gsx:66:2 +//line posts.gsx:71:2 _gsxgw.Node(ctx, layout.AdminShell(layout.AdminShellProps{Title: "New post", Current: props.User, Children: gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) -//line posts.gsx:67:3 - _gsxgw.S("") - _gsxgw.S("New post") - _gsxgw.S("") -//line posts.gsx:68:3 +//line posts.gsx:72:3 + _gsxgw.S("

    New post

    ") +//line posts.gsx:73:3 _gsxgw.Node(ctx, PostForm(PostFormProps{P: props.Post, Cats: props.Categories, ErrMsg: ""})) return _gsxgw.Err() })})) @@ -153,16 +138,12 @@ func (postEditPage) Props(r *http.Request, s *store.Store) (postFormViewProps, e func (p postEditPage) Page(props postFormViewProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) -//line posts.gsx:90:2 +//line posts.gsx:95:2 _gsxgw.Node(ctx, layout.AdminShell(layout.AdminShellProps{Title: "Edit post", Current: props.User, Children: gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) -//line posts.gsx:91:3 - _gsxgw.S("") - _gsxgw.S("Edit post") - _gsxgw.S("") -//line posts.gsx:92:3 +//line posts.gsx:96:3 + _gsxgw.S("

    Edit post

    ") +//line posts.gsx:97:3 _gsxgw.Node(ctx, PostForm(PostFormProps{P: props.Post, Cats: props.Categories, ErrMsg: ""})) return _gsxgw.Err() })})) @@ -185,7 +166,7 @@ func PostForm(_gsxp PostFormProps) gsx.Node { cats := _gsxp.Cats errMsg := _gsxp.ErrMsg _gsxgw := gsx.W(_gsxw) -//line posts.gsx:99:2 +//line posts.gsx:104:2 _gsxgw.S("") -//line posts.gsx:100:3 +//line posts.gsx:105:3 _gsxgw.Node(ctx, components.Alert(components.AlertProps{Kind: components.AlertError, Msg: errMsg})) -//line posts.gsx:101:3 +//line posts.gsx:106:3 _gsxgw.Node(ctx, components.Input(components.InputProps{Name: "title", Label: "Title", Value: p.Title, ErrMsg: ""})) -//line posts.gsx:102:3 +//line posts.gsx:107:3 _gsxgw.Node(ctx, components.Input(components.InputProps{Name: "slug", Label: "Slug (auto if blank)", Value: p.Slug, ErrMsg: ""})) -//line posts.gsx:103:3 - _gsxgw.S("") -//line posts.gsx:104:4 - _gsxgw.S("") - _gsxgw.S("Category") - _gsxgw.S("") -//line posts.gsx:105:4 - _gsxgw.S("") -//line posts.gsx:106:5 - _gsxgw.S("") - _gsxgw.S("— pick one —") - _gsxgw.S("") -//line posts.gsx:107:5 +//line posts.gsx:113:3 + _gsxgw.S("") -//line posts.gsx:112:3 + _gsxgw.S("") +//line posts.gsx:127:3 _gsxgw.Node(ctx, components.Textarea(components.TextareaProps{Name: "body", Label: "Body", Value: p.Body, ErrMsg: ""})) -//line posts.gsx:113:3 - _gsxgw.S("") -//line posts.gsx:114:4 - _gsxgw.S("") +//line posts.gsx:129:4 + _gsxgw.S("") - _gsxgw.S("Publish immediately") - _gsxgw.S("") -//line posts.gsx:117:3 - _gsxgw.S("") -//line posts.gsx:118:4 + _gsxgw.S("/>Publish immediately") +//line posts.gsx:132:3 + _gsxgw.S("
    ") +//line posts.gsx:133:4 _gsxgw.Node(ctx, components.Button(components.ButtonProps{Label: "Save", Attrs: gsx.Attrs{}.Merge(gsx.Attrs{"type": "submit"})})) -//line posts.gsx:119:4 - _gsxgw.S("") - _gsxgw.S("Cancel") - _gsxgw.S("") - _gsxgw.S("
    ") - _gsxgw.S("") + _gsxgw.S("\">Cancel") return _gsxgw.Err() }) } diff --git a/examples/blog/admin/users.gsx b/examples/blog/admin/users.gsx index 34add61..87ef00c 100644 --- a/examples/blog/admin/users.gsx +++ b/examples/blog/admin/users.gsx @@ -26,46 +26,69 @@ component (p userListPage) Page(props userListProps) {

    Users

    - -
      - { for _, u := range props.Users { -
    • - - {u.Username} - { if u.IsAdmin { - admin - } } + +
        + { for _, u := range props.Users { +
      • + + { u.Username } + { if u.IsAdmin { + + admin + + } } + +
        url("id", u.ID)} + class="m-0" + > + +
        +
      • + } } +
      +
      + +
      url} + class="space-y-3" + > + +
    • - } } -
    -
    - -
    url } class="space-y-3"> - - + + - - - - -
    -
    + + +
    } diff --git a/examples/blog/admin/users.x.go b/examples/blog/admin/users.x.go index a9078e6..d8a2a9f 100644 --- a/examples/blog/admin/users.x.go +++ b/examples/blog/admin/users.x.go @@ -34,115 +34,70 @@ func (p userListPage) Page(props userListProps) gsx.Node { _gsxgw.Node(ctx, layout.AdminShell(layout.AdminShellProps{Title: "Users", Current: props.User, Children: gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) //line users.gsx:27:3 - _gsxgw.S("") - _gsxgw.S("Users") - _gsxgw.S("") + _gsxgw.S("

    Users

    ") //line users.gsx:28:3 - _gsxgw.S("") -//line users.gsx:29:3 + _gsxgw.S("
    ") +//line users.gsx:29:4 _gsxgw.Node(ctx, components.Card(components.CardProps{Title: "Existing users", Children: gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) -//line users.gsx:30:4 - _gsxgw.S("") -//line users.gsx:31:5 +//line users.gsx:30:5 + _gsxgw.S("
      ") +//line users.gsx:31:6 for _, u := range props.Users { -//line users.gsx:32:6 - _gsxgw.S("") -//line users.gsx:33:7 - _gsxgw.S("") -//line users.gsx:34:8 +//line users.gsx:32:7 + _gsxgw.S("
    • ") +//line users.gsx:33:8 + _gsxgw.S("") +//line users.gsx:34:9 _gsxgw.Text(string(u.Username)) -//line users.gsx:35:8 +//line users.gsx:35:9 if u.IsAdmin { -//line users.gsx:36:9 - _gsxgw.S("") - _gsxgw.S("admin") - _gsxgw.S("") +//line users.gsx:36:10 + _gsxgw.S("admin") } _gsxgw.S("") -//line users.gsx:39:7 - _gsxgw.S("") -//line users.gsx:44:8 - _gsxgw.S("") - _gsxgw.S("Delete") - _gsxgw.S("") - _gsxgw.S("") - _gsxgw.S("
    • ") + _gsxgw.S("\" class=\"m-0\">") +//line users.gsx:48:9 + _gsxgw.S("") } _gsxgw.S("
    ") return _gsxgw.Err() })})) -//line users.gsx:50:3 +//line users.gsx:59:4 _gsxgw.Node(ctx, components.Card(components.CardProps{Title: "Create user", Children: gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) -//line users.gsx:51:4 - _gsxgw.S("") -//line users.gsx:52:5 + _gsxgw.S("\" class=\"space-y-3\">") +//line users.gsx:65:6 _gsxgw.Node(ctx, components.Input(components.InputProps{Name: "username", Label: "Username", Value: "", ErrMsg: ""})) -//line users.gsx:53:5 - _gsxgw.S("") -//line users.gsx:54:6 - _gsxgw.S("") - _gsxgw.S("Password") - _gsxgw.S("") -//line users.gsx:55:6 - _gsxgw.S("") +//line users.gsx:72:7 + _gsxgw.S("Password") +//line users.gsx:75:7 + _gsxgw.S("") - _gsxgw.S("") -//line users.gsx:62:5 - _gsxgw.S("") -//line users.gsx:63:6 - _gsxgw.S("") - _gsxgw.S("Grant admin") - _gsxgw.S("") -//line users.gsx:66:5 + _gsxgw.S(" class=\"w-full rounded border border-slate-300 px-2 py-1.5 text-sm\"/>") +//line users.gsx:82:6 + _gsxgw.S("") +//line users.gsx:86:6 _gsxgw.Node(ctx, components.Button(components.ButtonProps{Label: "Create", Attrs: gsx.Attrs{}.Merge(gsx.Attrs{"type": "submit"})})) _gsxgw.S("") return _gsxgw.Err() diff --git a/examples/blog/blog/category.gsx b/examples/blog/blog/category.gsx index 8586d97..4527eb6 100644 --- a/examples/blog/blog/category.gsx +++ b/examples/blog/blog/category.gsx @@ -53,8 +53,10 @@ func (categoryPage) Props(r *http.Request, s *store.Store) (categoryProps, error component (p categoryPage) Page(props categoryProps) { -

    {props.Category.Name}

    -

    Posts filed under this category.

    +

    { props.Category.Name }

    +

    + Posts filed under this category. +

    { if len(props.Posts) == 0 {

    Nothing here yet.

    @@ -63,8 +65,6 @@ component (p categoryPage) Page(props categoryProps) { } }
    -
    - { components.Pagination(props.Pagination) } -
    +
    { components.Pagination(props.Pagination) }
    } diff --git a/examples/blog/blog/category.x.go b/examples/blog/blog/category.x.go index 5d09775..2faa8e3 100644 --- a/examples/blog/blog/category.x.go +++ b/examples/blog/blog/category.x.go @@ -63,42 +63,28 @@ func (p categoryPage) Page(props categoryProps) gsx.Node { _gsxgw.Node(ctx, layout.PublicShell(layout.PublicShellProps{Title: props.Category.Name, Children: gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) //line category.gsx:56:3 - _gsxgw.S("") + _gsxgw.S("

    ") //line category.gsx:56:43 _gsxgw.Text(string(props.Category.Name)) _gsxgw.S("

    ") //line category.gsx:57:3 - _gsxgw.S("") - _gsxgw.S("Posts filed under this category.") - _gsxgw.S("

    ") -//line category.gsx:58:3 - _gsxgw.S("") -//line category.gsx:59:4 + _gsxgw.S("

    Posts filed under this category.

    ") +//line category.gsx:60:3 + _gsxgw.S("
    ") +//line category.gsx:61:4 if len(props.Posts) == 0 { -//line category.gsx:60:5 - _gsxgw.S("") - _gsxgw.S("Nothing here yet.") - _gsxgw.S("

    ") +//line category.gsx:62:5 + _gsxgw.S("

    Nothing here yet.

    ") } -//line category.gsx:62:4 +//line category.gsx:64:4 for _, post := range props.Posts { -//line category.gsx:63:5 +//line category.gsx:65:5 _gsxgw.Node(ctx, PostCard(PostCardProps{P: post})) } _gsxgw.S("
    ") -//line category.gsx:66:3 - _gsxgw.S("") -//line category.gsx:67:4 +//line category.gsx:68:3 + _gsxgw.S("
    ") +//line category.gsx:68:21 _gsxgw.Node(ctx, components.Pagination(props.Pagination)) _gsxgw.S("
    ") return _gsxgw.Err() diff --git a/examples/blog/blog/components.gsx b/examples/blog/blog/components.gsx index 6b214ad..16b6e9c 100644 --- a/examples/blog/blog/components.gsx +++ b/examples/blog/blog/components.gsx @@ -10,17 +10,20 @@ import ( component PostMeta(p store.Post) {

    - Posted {p.CreatedAt.Format("Jan 2, 2006")} + Posted { p.CreatedAt.Format("Jan 2, 2006") }

    } component PostCard(p store.Post) { } @@ -28,18 +31,20 @@ component PostCard(p store.Post) { // can re-render it from ServeHTTP via RenderComponent(CommentsList(...)). // It owns its own wrapper id, which doubles as the HTMX hx-target. component CommentsList(comments []store.Comment) { -
    id } class="space-y-3"> +
    id} class="space-y-3"> { if len(comments) == 0 { -

    No comments yet — be the first.

    +

    + No comments yet — be the first. +

    } } { for _, c := range comments {
    - {c.Author} · {c.CreatedAt.Format("15:04 Jan 2")} + { c.Author } · { c.CreatedAt.Format("15:04 Jan 2") }
    -

    {c.Body}

    +

    { c.Body }

    } } -

    Total: {len(comments)}

    +

    Total: { len(comments) }

    } diff --git a/examples/blog/blog/components.x.go b/examples/blog/blog/components.x.go index e43fb10..b45e9a5 100644 --- a/examples/blog/blog/components.x.go +++ b/examples/blog/blog/components.x.go @@ -22,14 +22,12 @@ func PostMeta(_gsxp PostMetaProps) gsx.Node { p := _gsxp.P _gsxgw := gsx.W(_gsxw) //line components.gsx:12:2 - _gsxgw.S("") - _gsxgw.S("Posted ") + _gsxgw.S(">Posted ") //line components.gsx:13:10 _gsxgw.Text(string(p.CreatedAt.Format("Jan 2, 2006"))) _gsxgw.S("

    ") @@ -47,37 +45,31 @@ func PostCard(_gsxp PostCardProps) gsx.Node { p := _gsxp.P _gsxgw := gsx.W(_gsxw) //line components.gsx:18:2 - _gsxgw.S("") //line components.gsx:19:3 - _gsxgw.S("") -//line components.gsx:20:4 + _gsxgw.S("\">") +//line components.gsx:23:4 _gsxgw.Text(string(p.Title)) _gsxgw.S("") -//line components.gsx:22:3 +//line components.gsx:25:3 _gsxgw.Node(ctx, PostMeta(PostMetaProps{P: p})) -//line components.gsx:23:3 - _gsxgw.S("") -//line components.gsx:23:55 +//line components.gsx:26:3 + _gsxgw.S("

    ") +//line components.gsx:26:55 _gsxgw.Text(string(p.Body)) - _gsxgw.S("

    ") - _gsxgw.S("") + _gsxgw.S("

    ") return _gsxgw.Err() }) } @@ -95,7 +87,7 @@ func CommentsList(_gsxp CommentsListProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { comments := _gsxp.Comments _gsxgw := gsx.W(_gsxw) -//line components.gsx:31:2 +//line components.gsx:34:2 _gsxgw.S("") -//line components.gsx:32:3 +//line components.gsx:35:3 if len(comments) == 0 { -//line components.gsx:33:4 - _gsxgw.S("") - _gsxgw.S("No comments yet — be the first.") - _gsxgw.S("

    ") +//line components.gsx:36:4 + _gsxgw.S("

    No comments yet — be the first.

    ") } -//line components.gsx:35:3 +//line components.gsx:40:3 for _, c := range comments { -//line components.gsx:36:4 - _gsxgw.S("") -//line components.gsx:37:5 - _gsxgw.S("") -//line components.gsx:38:6 +//line components.gsx:41:4 + _gsxgw.S("
    ") +//line components.gsx:42:5 + _gsxgw.S("
    ") +//line components.gsx:43:6 _gsxgw.Text(string(c.Author)) _gsxgw.S(" · ") -//line components.gsx:38:20 +//line components.gsx:43:22 _gsxgw.Text(string(c.CreatedAt.Format("15:04 Jan 2"))) _gsxgw.S("
    ") -//line components.gsx:40:5 - _gsxgw.S("") -//line components.gsx:40:44 +//line components.gsx:45:5 + _gsxgw.S("

    ") +//line components.gsx:45:44 _gsxgw.Text(string(c.Body)) - _gsxgw.S("

    ") - _gsxgw.S("
    ") + _gsxgw.S("

    ") } -//line components.gsx:43:3 - _gsxgw.S("") - _gsxgw.S("Total: ") -//line components.gsx:43:44 +//line components.gsx:48:3 + _gsxgw.S("

    Total: ") +//line components.gsx:48:44 _gsxgw.Text(strconv.FormatInt(int64(len(comments)), 10)) - _gsxgw.S("

    ") - _gsxgw.S("
    ") + _gsxgw.S("

    ") return _gsxgw.Err() }) } diff --git a/examples/blog/blog/home.gsx b/examples/blog/blog/home.gsx index 874ebe3..4fc5d6e 100644 --- a/examples/blog/blog/home.gsx +++ b/examples/blog/blog/home.gsx @@ -38,8 +38,11 @@ component (p homePage) Page(props homeProps) {
      { for _, c := range props.Categories {
    • - url("slug", c.Slug) }> - {c.Name} + url("slug", c.Slug)} + > + { c.Name }
    • } } diff --git a/examples/blog/blog/home.x.go b/examples/blog/blog/home.x.go index 45beebf..944dc86 100644 --- a/examples/blog/blog/home.x.go +++ b/examples/blog/blog/home.x.go @@ -40,15 +40,9 @@ func (p homePage) Page(props homeProps) gsx.Node { _gsxgw.Node(ctx, layout.PublicShell(layout.PublicShellProps{Title: "Home", Children: gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) //line home.gsx:31:3 - _gsxgw.S("") - _gsxgw.S("Recent Posts") - _gsxgw.S("") + _gsxgw.S("

      Recent Posts

      ") //line home.gsx:32:3 - _gsxgw.S("") + _gsxgw.S("
      ") //line home.gsx:33:4 for _, post := range props.Posts { //line home.gsx:34:5 @@ -59,29 +53,23 @@ func (p homePage) Page(props homeProps) gsx.Node { _gsxgw.Node(ctx, components.Card(components.CardProps{Title: "Browse by category", Children: gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) //line home.gsx:38:4 - _gsxgw.S("") + _gsxgw.S("
        ") //line home.gsx:39:5 for _, c := range props.Categories { //line home.gsx:40:6 - _gsxgw.S("") + _gsxgw.S("
      • ") //line home.gsx:41:7 - _gsxgw.S("") -//line home.gsx:42:8 + _gsxgw.S("\">") +//line home.gsx:45:8 _gsxgw.Text(string(c.Name)) - _gsxgw.S("") - _gsxgw.S("
      • ") + _gsxgw.S("") } _gsxgw.S("
      ") return _gsxgw.Err() diff --git a/examples/blog/blog/post.gsx b/examples/blog/blog/post.gsx index 755f364..cef8761 100644 --- a/examples/blog/blog/post.gsx +++ b/examples/blog/blog/post.gsx @@ -37,15 +37,22 @@ func (postPage) Props(r *http.Request, s *store.Store) (postProps, error) { component (p postPage) Page(props postProps) {
      @@ -53,15 +60,28 @@ component (p postPage) Page(props postProps) {
      url("slug", props.Post.Slug) } - hx-target={ CommentsList |> target } + hx-post={commentHandler{} |> url("slug", props.Post.Slug)} + hx-target={CommentsList |> target} hx-swap="outerHTML" hx-on:htmx:after-request="this.reset()" >

      Add a comment

      - - - + + +
      diff --git a/examples/blog/blog/post.x.go b/examples/blog/blog/post.x.go index de94be3..7cfb5b5 100644 --- a/examples/blog/blog/post.x.go +++ b/examples/blog/blog/post.x.go @@ -46,69 +46,48 @@ func (p postPage) Page(props postProps) gsx.Node { _gsxgw.Node(ctx, layout.PublicShell(layout.PublicShellProps{Title: props.Post.Title, Children: gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) //line post.gsx:39:3 - _gsxgw.S("") + _gsxgw.S("
      ") //line post.gsx:40:4 - _gsxgw.S("") + _gsxgw.S("

      ") //line post.gsx:40:39 _gsxgw.Text(string(props.Post.Title)) _gsxgw.S("

      ") //line post.gsx:41:4 - _gsxgw.S("") - _gsxgw.S("by ") + _gsxgw.S("

      by ") //line post.gsx:42:8 _gsxgw.Text(string(props.Author.Username)) //line post.gsx:43:5 if props.Category.Slug != "" { _gsxgw.S("· ") //line post.gsx:44:9 - _gsxgw.S("") -//line post.gsx:44:96 + _gsxgw.S("\">") +//line post.gsx:50:7 _gsxgw.Text(string(props.Category.Name)) _gsxgw.S("") } _gsxgw.S("

      ") -//line post.gsx:47:4 - _gsxgw.S("") -//line post.gsx:48:5 - _gsxgw.S("") -//line post.gsx:48:8 +//line post.gsx:54:4 + _gsxgw.S("
      ") +//line post.gsx:55:5 + _gsxgw.S("

      ") +//line post.gsx:55:8 _gsxgw.Text(string(props.Post.Body)) - _gsxgw.S("

      ") - _gsxgw.S("
      ") - _gsxgw.S("
      ") -//line post.gsx:51:3 - _gsxgw.S("") -//line post.gsx:52:4 - _gsxgw.S("") - _gsxgw.S("Comments") - _gsxgw.S("") -//line post.gsx:53:4 + _gsxgw.S("

      ") +//line post.gsx:58:3 + _gsxgw.S("
      ") +//line post.gsx:59:4 + _gsxgw.S("

      Comments

      ") +//line post.gsx:60:4 _gsxgw.Node(ctx, CommentsList(CommentsListProps{Comments: props.Comments})) -//line post.gsx:54:4 - _gsxgw.S("") -//line post.gsx:61:5 - _gsxgw.S("") - _gsxgw.S("Add a comment") - _gsxgw.S("") -//line post.gsx:62:5 + _gsxgw.S("\" hx-swap=\"outerHTML\" hx-on:htmx:after-request=\"this.reset()\">") +//line post.gsx:68:5 + _gsxgw.S("

      Add a comment

      ") +//line post.gsx:69:5 _gsxgw.Node(ctx, components.Input(components.InputProps{Name: "author", Label: "Name", Value: "", ErrMsg: ""})) -//line post.gsx:63:5 +//line post.gsx:75:5 _gsxgw.Node(ctx, components.Textarea(components.TextareaProps{Name: "body", Label: "Comment", Value: "", ErrMsg: ""})) -//line post.gsx:64:5 +//line post.gsx:81:5 _gsxgw.Node(ctx, components.Button(components.ButtonProps{Label: "Post comment", Attrs: gsx.Attrs{}.Merge(gsx.Attrs{"type": "submit"})})) - _gsxgw.S("") - _gsxgw.S("
      ") + _gsxgw.S("") return _gsxgw.Err() })})) return _gsxgw.Err() diff --git a/examples/blog/blog/search.gsx b/examples/blog/blog/search.gsx index a6efeb5..afa51fe 100644 --- a/examples/blog/blog/search.gsx +++ b/examples/blog/blog/search.gsx @@ -35,8 +35,8 @@ component (p searchPage) Page(props searchProps) {

      Search

      url } - hx-target={ p.Results |> target } + hx-get={searchPage{} |> url} + hx-target={p.Results |> target} hx-swap="outerHTML" hx-trigger="input changed delay:250ms from:input, submit" hx-push-url="true" @@ -54,13 +54,19 @@ component (p searchPage) Page(props searchProps) { } component (p searchPage) Results(props searchProps) { -
      id } class="space-y-4"> +
      id} class="space-y-4"> { if props.Query == "" {

      Type a query to search.

      } else if len(props.Posts) == 0 { -

      No results for "{props.Query}".

      +

      + No results for " + { props.Query } + ". +

      } else { -

      {resultsCount(len(props.Posts))}

      +

      + { resultsCount(len(props.Posts)) } +

      { for _, post := range props.Posts { } } diff --git a/examples/blog/blog/search.x.go b/examples/blog/blog/search.x.go index 0b19de3..4c92d7e 100644 --- a/examples/blog/blog/search.x.go +++ b/examples/blog/blog/search.x.go @@ -43,14 +43,9 @@ func (p searchPage) Page(props searchProps) gsx.Node { _gsxgw.Node(ctx, layout.PublicShell(layout.PublicShellProps{Title: "Search", Children: gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) //line search.gsx:35:3 - _gsxgw.S("") - _gsxgw.S("Search") - _gsxgw.S("") + _gsxgw.S("

      Search

      ") //line search.gsx:36:3 - _gsxgw.S("") + _gsxgw.S("\" hx-swap=\"outerHTML\" hx-trigger=\"input changed delay:250ms from:input, submit\" hx-push-url=\"true\">") //line search.gsx:44:4 - _gsxgw.S("") - _gsxgw.S("") + _gsxgw.S("/>") //line search.gsx:52:3 _gsxgw.Node(ctx, p.Results(props)) return _gsxgw.Err() @@ -99,40 +85,28 @@ func (p searchPage) Results(props searchProps) gsx.Node { } _gsxgw.S(" id=\"") _gsxgw.AttrValue(string(_gsxv2)) - _gsxgw.S("\"") - _gsxgw.S(" class=\"space-y-4\"") - _gsxgw.S(">") + _gsxgw.S("\" class=\"space-y-4\">") //line search.gsx:58:3 if props.Query == "" { //line search.gsx:59:4 - _gsxgw.S("") - _gsxgw.S("Type a query to search.") - _gsxgw.S("

      ") + _gsxgw.S("

      Type a query to search.

      ") } else { //line search.gsx:60:10 if len(props.Posts) == 0 { //line search.gsx:61:4 - _gsxgw.S("") - _gsxgw.S("No results for \"") -//line search.gsx:61:54 + _gsxgw.S("

      No results for \"") +//line search.gsx:63:5 _gsxgw.Text(string(props.Query)) - _gsxgw.S("\".") - _gsxgw.S("

      ") + _gsxgw.S("\".

      ") } else { -//line search.gsx:63:4 - _gsxgw.S("") -//line search.gsx:63:38 +//line search.gsx:67:4 + _gsxgw.S("

      ") +//line search.gsx:68:5 _gsxgw.Text(string(resultsCount(len(props.Posts)))) _gsxgw.S("

      ") -//line search.gsx:64:4 +//line search.gsx:70:4 for _, post := range props.Posts { -//line search.gsx:65:5 +//line search.gsx:71:5 _gsxgw.Node(ctx, PostCard(PostCardProps{P: post})) } } diff --git a/examples/blog/ui/components/components.gsx b/examples/blog/ui/components/components.gsx index 1e84124..423096b 100644 --- a/examples/blog/ui/components/components.gsx +++ b/examples/blog/ui/components/components.gsx @@ -28,16 +28,20 @@ func alertClasses(kind AlertKind) string { component Alert(kind AlertKind, msg string) { { if msg != "" { -
      {msg}
      +
      + { msg } +
      } } } component Card(title string) {
      { if title != "" { -

      {title}

      +

      + { title } +

      } } - {children} + { children }
      } @@ -47,42 +51,38 @@ component Button(label string) { } component Input(name, label, value, errMsg string) { } component Textarea(name, label, value, errMsg string) { } @@ -91,18 +91,34 @@ component Textarea(name, label, value, errMsg string) { // with HX-Target: #pagination resolve here regardless of which page hosts it. component Pagination(p PageNav) { @@ -115,7 +131,7 @@ component ErrorPage(status int, msg string) { - Error {status} + Error { status } @@ -128,8 +144,10 @@ component ErrorPage(status int, msg string) { component ErrorBlock(status int, msg string) {
      -
      {status}
      -

      {msg}

      - Back to home +
      { status }
      +

      { msg }

      + + Back to home +
      } diff --git a/examples/blog/ui/components/components.x.go b/examples/blog/ui/components/components.x.go index 876cce7..dfad0d8 100644 --- a/examples/blog/ui/components/components.x.go +++ b/examples/blog/ui/components/components.x.go @@ -42,12 +42,10 @@ func Alert(_gsxp AlertProps) gsx.Node { //line components.gsx:30:2 if msg != "" { //line components.gsx:31:3 - _gsxgw.S("") -//line components.gsx:31:73 + _gsxgw.S("\">") +//line components.gsx:32:4 _gsxgw.Text(string(msg)) _gsxgw.S("
      ") } @@ -66,25 +64,22 @@ func Card(_gsxp CardProps) gsx.Node { title := _gsxp.Title children := _gsxp.Children _gsxgw := gsx.W(_gsxw) -//line components.gsx:36:2 - _gsxgw.S("") -//line components.gsx:37:3 +//line components.gsx:39:3 if title != "" { -//line components.gsx:38:4 - _gsxgw.S("") -//line components.gsx:38:60 +//line components.gsx:40:4 + _gsxgw.S("

      ") +//line components.gsx:41:5 _gsxgw.Text(string(title)) _gsxgw.S("

      ") } -//line components.gsx:40:3 +//line components.gsx:44:3 _gsxgw.Node(ctx, children) _gsxgw.S("") return _gsxgw.Err() @@ -103,15 +98,14 @@ func Button(_gsxp ButtonProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { label := _gsxp.Label _gsxgw := gsx.W(_gsxw) -//line components.gsx:47:2 - _gsxgw.S("") -//line components.gsx:50:3 +//line components.gsx:54:3 _gsxgw.Text(string(label)) _gsxgw.S("") return _gsxgw.Err() @@ -133,40 +127,31 @@ func Input(_gsxp InputProps) gsx.Node { value := _gsxp.Value errMsg := _gsxp.ErrMsg _gsxgw := gsx.W(_gsxw) -//line components.gsx:55:2 - _gsxgw.S("") -//line components.gsx:56:3 - _gsxgw.S("") -//line components.gsx:56:55 +//line components.gsx:60:3 + _gsxgw.S("") +//line components.gsx:60:55 _gsxgw.Text(string(label)) _gsxgw.S("") -//line components.gsx:57:3 - _gsxgw.S("") -//line components.gsx:66:3 + _gsxgw.S("\"/>") +//line components.gsx:68:3 if errMsg != "" { -//line components.gsx:67:4 - _gsxgw.S("") -//line components.gsx:67:50 +//line components.gsx:69:4 + _gsxgw.S("") +//line components.gsx:69:50 _gsxgw.Text(string(errMsg)) _gsxgw.S("") } @@ -190,40 +175,31 @@ func Textarea(_gsxp TextareaProps) gsx.Node { value := _gsxp.Value errMsg := _gsxp.ErrMsg _gsxgw := gsx.W(_gsxw) -//line components.gsx:73:2 - _gsxgw.S("") -//line components.gsx:74:3 - _gsxgw.S("") -//line components.gsx:74:55 +//line components.gsx:76:3 + _gsxgw.S("") +//line components.gsx:76:55 _gsxgw.Text(string(label)) _gsxgw.S("") -//line components.gsx:75:3 - _gsxgw.S("") + _gsxgw.S("\">") //line components.gsx:83:4 _gsxgw.Text(string(value)) _gsxgw.S("") //line components.gsx:84:3 if errMsg != "" { //line components.gsx:85:4 - _gsxgw.S("") + _gsxgw.S("") //line components.gsx:85:50 _gsxgw.Text(string(errMsg)) _gsxgw.S("") @@ -240,80 +216,53 @@ func Pagination(p PageNav) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) //line components.gsx:93:2 - _gsxgw.S("") + _gsxgw.S("") + _gsxgw.S("
      ") return _gsxgw.Err() }) } @@ -332,43 +281,26 @@ func ErrorPage(_gsxp ErrorPageProps) gsx.Node { msg := _gsxp.Msg _gsxgw := gsx.W(_gsxw) _gsxgw.S("") -//line components.gsx:115:2 - _gsxgw.S("") -//line components.gsx:116:3 - _gsxgw.S("") -//line components.gsx:117:4 - _gsxgw.S("") -//line components.gsx:118:4 - _gsxgw.S("") - _gsxgw.S("Error ") -//line components.gsx:118:17 +//line components.gsx:131:2 + _gsxgw.S("") +//line components.gsx:132:3 + _gsxgw.S("") +//line components.gsx:133:4 + _gsxgw.S("") +//line components.gsx:134:4 + _gsxgw.S("Error ") +//line components.gsx:134:17 _gsxgw.Text(strconv.FormatInt(int64(status), 10)) _gsxgw.S("") -//line components.gsx:119:4 - _gsxgw.S("") - _gsxgw.S("") - _gsxgw.S("") -//line components.gsx:121:3 - _gsxgw.S("") -//line components.gsx:122:4 - _gsxgw.S("") -//line components.gsx:123:5 +//line components.gsx:135:4 + _gsxgw.S("") +//line components.gsx:137:3 + _gsxgw.S("") +//line components.gsx:138:4 + _gsxgw.S("
      ") +//line components.gsx:139:5 _gsxgw.Node(ctx, ErrorBlock(ErrorBlockProps{Status: status, Msg: msg})) - _gsxgw.S("
      ") - _gsxgw.S("") - _gsxgw.S("") + _gsxgw.S("") return _gsxgw.Err() }) } @@ -384,36 +316,25 @@ func ErrorBlock(_gsxp ErrorBlockProps) gsx.Node { status := _gsxp.Status msg := _gsxp.Msg _gsxgw := gsx.W(_gsxw) -//line components.gsx:130:2 - _gsxgw.S("") -//line components.gsx:131:3 - _gsxgw.S("") -//line components.gsx:131:52 +//line components.gsx:147:3 + _gsxgw.S("
      ") +//line components.gsx:147:52 _gsxgw.Text(strconv.FormatInt(int64(status), 10)) _gsxgw.S("
      ") -//line components.gsx:132:3 - _gsxgw.S("") -//line components.gsx:132:32 +//line components.gsx:148:3 + _gsxgw.S("

      ") +//line components.gsx:148:32 _gsxgw.Text(string(msg)) _gsxgw.S("

      ") -//line components.gsx:133:3 - _gsxgw.S("") - _gsxgw.S("Back to home") - _gsxgw.S("") - _gsxgw.S("") +//line components.gsx:149:3 + _gsxgw.S("Back to home") return _gsxgw.Err() }) } diff --git a/examples/blog/ui/layout/layout.gsx b/examples/blog/ui/layout/layout.gsx index 6a261cb..14c91a2 100644 --- a/examples/blog/ui/layout/layout.gsx +++ b/examples/blog/ui/layout/layout.gsx @@ -16,24 +16,49 @@ component PublicShell(title string) { - - {title} — structpages blog + + { title } — structpages blog
      -
      - {children} + { children }
      @@ -45,32 +70,68 @@ component AdminShell(title string, current store.User) { - - Admin — {title} + + Admin — { title }
      -
      - url }> - +
      + url} + > + blog admin
      - {children} + { children }
      diff --git a/examples/blog/ui/layout/layout.x.go b/examples/blog/ui/layout/layout.x.go index db55845..fb0715b 100644 --- a/examples/blog/ui/layout/layout.x.go +++ b/examples/blog/ui/layout/layout.x.go @@ -28,120 +28,71 @@ func PublicShell(_gsxp PublicShellProps) gsx.Node { _gsxgw := gsx.W(_gsxw) _gsxgw.S("") //line layout.gsx:16:2 - _gsxgw.S("") + _gsxgw.S("") //line layout.gsx:17:3 - _gsxgw.S("") + _gsxgw.S("") //line layout.gsx:18:4 - _gsxgw.S("") + _gsxgw.S("") //line layout.gsx:19:4 - _gsxgw.S("") -//line layout.gsx:20:4 - _gsxgw.S("") -//line layout.gsx:20:11 + _gsxgw.S("") +//line layout.gsx:23:4 + _gsxgw.S("") +//line layout.gsx:23:11 _gsxgw.Text(string(title)) - _gsxgw.S(" — structpages blog") - _gsxgw.S("") -//line layout.gsx:21:4 - _gsxgw.S("") - _gsxgw.S("") -//line layout.gsx:22:4 - _gsxgw.S("") - _gsxgw.S("") - _gsxgw.S("") -//line layout.gsx:24:3 - _gsxgw.S("") + _gsxgw.S(" — structpages blog") +//line layout.gsx:24:4 + _gsxgw.S("") //line layout.gsx:25:4 - _gsxgw.S("") -//line layout.gsx:26:5 - _gsxgw.S("") -//line layout.gsx:27:6 - _gsxgw.S("") +//line layout.gsx:27:3 + _gsxgw.S("") +//line layout.gsx:28:4 + _gsxgw.S("
      ") +//line layout.gsx:29:5 + _gsxgw.S("
      ") +//line layout.gsx:32:6 + _gsxgw.S("") - _gsxgw.S("structpages blog") - _gsxgw.S("") -//line layout.gsx:28:6 - _gsxgw.S("") -//line layout.gsx:29:7 - _gsxgw.S("structpages blog") +//line layout.gsx:38:6 + _gsxgw.S("") - _gsxgw.S("
      ") - _gsxgw.S("
      ") -//line layout.gsx:35:4 - _gsxgw.S("") -//line layout.gsx:36:5 + _gsxgw.S("\">Admin") +//line layout.gsx:60:4 + _gsxgw.S("
      ") +//line layout.gsx:61:5 _gsxgw.Node(ctx, children) - _gsxgw.S("
      ") - _gsxgw.S("") - _gsxgw.S("") + _gsxgw.S("") return _gsxgw.Err() }) } @@ -161,160 +112,92 @@ func AdminShell(_gsxp AdminShellProps) gsx.Node { children := _gsxp.Children _gsxgw := gsx.W(_gsxw) _gsxgw.S("") -//line layout.gsx:45:2 - _gsxgw.S("") -//line layout.gsx:46:3 - _gsxgw.S("") -//line layout.gsx:47:4 - _gsxgw.S("") -//line layout.gsx:48:4 - _gsxgw.S("") -//line layout.gsx:49:4 - _gsxgw.S("") - _gsxgw.S("Admin — ") -//line layout.gsx:49:21 +//line layout.gsx:70:2 + _gsxgw.S("") +//line layout.gsx:71:3 + _gsxgw.S("") +//line layout.gsx:72:4 + _gsxgw.S("") +//line layout.gsx:73:4 + _gsxgw.S("") +//line layout.gsx:77:4 + _gsxgw.S("Admin — ") +//line layout.gsx:77:21 _gsxgw.Text(string(title)) _gsxgw.S("") -//line layout.gsx:50:4 - _gsxgw.S("") - _gsxgw.S("") -//line layout.gsx:51:4 - _gsxgw.S("") - _gsxgw.S("") - _gsxgw.S("") -//line layout.gsx:53:3 - _gsxgw.S("") -//line layout.gsx:54:4 - _gsxgw.S("") -//line layout.gsx:55:5 - _gsxgw.S("") -//line layout.gsx:56:6 - _gsxgw.S("") +//line layout.gsx:79:4 + _gsxgw.S("") +//line layout.gsx:81:3 + _gsxgw.S("") +//line layout.gsx:82:4 + _gsxgw.S("
      ") +//line layout.gsx:83:5 + _gsxgw.S("
      ") +//line layout.gsx:86:6 + _gsxgw.S("") -//line layout.gsx:57:7 - _gsxgw.S("") - _gsxgw.S("blog admin") - _gsxgw.S("") -//line layout.gsx:60:6 - _gsxgw.S("") -//line layout.gsx:61:7 - _gsxgw.S("") +//line layout.gsx:90:7 + _gsxgw.S("\"\"blog admin") +//line layout.gsx:97:6 + _gsxgw.S("") - _gsxgw.S("
      ") - _gsxgw.S("
      ") -//line layout.gsx:72:4 - _gsxgw.S("") -//line layout.gsx:73:5 + _gsxgw.S("\" class=\"m-0\">") +//line layout.gsx:123:8 + _gsxgw.S("") +//line layout.gsx:133:4 + _gsxgw.S("
      ") +//line layout.gsx:134:5 _gsxgw.Node(ctx, children) - _gsxgw.S("
      ") - _gsxgw.S("") - _gsxgw.S("") + _gsxgw.S("") return _gsxgw.Err() }) } diff --git a/examples/htmx-render-target/pages.gsx b/examples/htmx-render-target/pages.gsx index aaee61e..332f3b8 100644 --- a/examples/htmx-render-target/pages.gsx +++ b/examples/htmx-render-target/pages.gsx @@ -10,9 +10,11 @@ component UserStatsWidget(stats UserStats) {

      New Today: { stats.NewToday }

      + hx-get={dashboard{} |> url} + hx-target={UserStatsWidget |> target} + > + Refresh Stats + } @@ -21,15 +23,21 @@ component SalesChartWidget(data SalesData) {

      Sales Chart

      { for _, point := range data.Points { -
      +
      } }

      Total Sales: ${ data.Total |> format("%.2f") }

      + hx-get={dashboard{} |> url} + hx-target={SalesChartWidget |> target} + > + Refresh Sales + } @@ -38,14 +46,18 @@ component NotificationsList(notifications []Notification) {

      Recent Notifications

        { for _, n := range notifications { -
      • { n.Message } ({ n.Time.Format("15:04") })
      • +
      • + { n.Message } ({ n.Time.Format("15:04") }) +
      • } }
      + hx-get={dashboard{} |> url} + hx-target={NotificationsList |> target} + > + Refresh Notifications + } @@ -56,27 +68,55 @@ type dashboard struct{} component (p dashboard) Page(props dashboardData) {

      Dashboard

      -

      This example demonstrates the RenderTarget API with standalone function components.

      -

      Click "Refresh" buttons to see HTMX partial updates — each widget loads only its own data!

      -
      -
      id }> - +

      + This example demonstrates the RenderTarget API with standalone function components. +

      +

      + Click "Refresh" buttons to see HTMX partial updates — each widget loads only its own data! +

      +
      +
      id}> +
      -
      id }> - +
      id}> +
      -
      id }> - +
      id}> +
      -
      +

      How it works:

        -
      • Standalone functions — UserStatsWidget, SalesChartWidget, NotificationsList are shared components
      • -
      • Conditional loading — Props checks target.Is() and loads only needed data
      • -
      • RenderComponent (direct) — construct the gsx component with its props struct and pass directly
      • -
      • No wrapper methods — No need to create dashboard.UserStats() method!
      • -
      • HTMX integration — HTMXRenderTarget automatically handles partial updates
      • +
      • + ✅ + Standalone functions + — UserStatsWidget, SalesChartWidget, NotificationsList are shared components +
      • +
      • + ✅ + Conditional loading + — Props checks target.Is() and loads only needed data +
      • +
      • + ✅ + RenderComponent (direct) + — construct the gsx component with its props struct and pass directly +
      • +
      • + ✅ + No wrapper methods + — No need to create dashboard.UserStats() method! +
      • +
      • + ✅ + HTMX integration + — HTMXRenderTarget automatically handles partial updates +
      @@ -87,7 +127,7 @@ component Html() { - + RenderTarget API Example -
      - {children} -
      +
      { children }
      } component ErrorPage(err error) { - + } diff --git a/examples/htmx-render-target/pages.x.go b/examples/htmx-render-target/pages.x.go index 7922f49..8d78e3c 100644 --- a/examples/htmx-render-target/pages.x.go +++ b/examples/htmx-render-target/pages.x.go @@ -15,7 +15,6 @@ import ( // Shared standalone function components (can be used across multiple pages). // These demonstrate the power of RenderTarget — no wrapper methods needed. -//line pages.gsx:6:1 func UserStatsWidget(stats UserStats) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) @@ -53,30 +52,29 @@ func UserStatsWidget(stats UserStats) gsx.Node { }) } -//line pages.gsx:19:1 func SalesChartWidget(data SalesData) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) -//line pages.gsx:20:2 +//line pages.gsx:22:2 _gsxgw.S("
      ") -//line pages.gsx:21:3 +//line pages.gsx:23:3 _gsxgw.S("

      Sales Chart

      ") -//line pages.gsx:22:3 +//line pages.gsx:24:3 _gsxgw.S("
      ") -//line pages.gsx:23:4 +//line pages.gsx:25:4 for _, point := range data.Points { -//line pages.gsx:24:5 +//line pages.gsx:26:5 _gsxgw.S("
      ") } _gsxgw.S("
      ") -//line pages.gsx:27:3 +//line pages.gsx:33:3 _gsxgw.S("

      Total Sales: $") -//line pages.gsx:27:20 +//line pages.gsx:33:20 _gsxgw.Text(string(_gsxstd.Format((data.Total), "%.2f"))) _gsxgw.S("

      ") -//line pages.gsx:28:3 +//line pages.gsx:34:3 _gsxgw.S("
      ") -//line pages.gsx:68:4 +//line pages.gsx:86:4 _gsxgw.S("") -//line pages.gsx:69:5 +//line pages.gsx:87:5 _gsxgw.Node(ctx, NotificationsList(NotificationsListProps{Notifications: props.Notifications})) _gsxgw.S("
      ") -//line pages.gsx:72:3 +//line pages.gsx:90:3 _gsxgw.S("
      ") -//line pages.gsx:73:4 +//line pages.gsx:93:4 _gsxgw.S("

      How it works:

      ") -//line pages.gsx:74:4 +//line pages.gsx:94:4 _gsxgw.S("
        ") -//line pages.gsx:75:5 +//line pages.gsx:95:5 _gsxgw.S("
      • ✅ ") -//line pages.gsx:75:13 +//line pages.gsx:96:10 _gsxgw.S("Standalone functions — UserStatsWidget, SalesChartWidget, NotificationsList are shared components
      • ") -//line pages.gsx:76:5 +//line pages.gsx:100:5 _gsxgw.S("
      • ✅ ") -//line pages.gsx:76:13 +//line pages.gsx:101:10 _gsxgw.S("Conditional loading — Props checks target.Is() and loads only needed data
      • ") -//line pages.gsx:77:5 +//line pages.gsx:105:5 _gsxgw.S("
      • ✅ ") -//line pages.gsx:77:13 +//line pages.gsx:106:10 _gsxgw.S("RenderComponent (direct) — construct the gsx component with its props struct and pass directly
      • ") -//line pages.gsx:78:5 +//line pages.gsx:110:5 _gsxgw.S("
      • ✅ ") -//line pages.gsx:78:13 +//line pages.gsx:111:10 _gsxgw.S("No wrapper methods — No need to create dashboard.UserStats() method!
      • ") -//line pages.gsx:79:5 +//line pages.gsx:115:5 _gsxgw.S("
      • ✅ ") -//line pages.gsx:79:13 +//line pages.gsx:116:10 _gsxgw.S("HTMX integration — HTMXRenderTarget automatically handles partial updates
      ") return _gsxgw.Err() })})) @@ -244,29 +240,28 @@ type HtmlProps struct { Children gsx.Node } -//line pages.gsx:86:1 func Html(_gsxp HtmlProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { children := _gsxp.Children _gsxgw := gsx.W(_gsxw) _gsxgw.S("") -//line pages.gsx:88:2 +//line pages.gsx:128:2 _gsxgw.S("") -//line pages.gsx:89:3 +//line pages.gsx:129:3 _gsxgw.S("") -//line pages.gsx:90:4 +//line pages.gsx:130:4 _gsxgw.S("") -//line pages.gsx:91:4 +//line pages.gsx:131:4 _gsxgw.S("") -//line pages.gsx:92:4 +//line pages.gsx:132:4 _gsxgw.S("RenderTarget API Example") -//line pages.gsx:93:4 +//line pages.gsx:133:4 _gsxgw.S("") -//line pages.gsx:111:3 +//line pages.gsx:151:3 _gsxgw.S("") -//line pages.gsx:112:4 +//line pages.gsx:152:4 _gsxgw.S("
      ") -//line pages.gsx:113:5 +//line pages.gsx:152:10 _gsxgw.Node(ctx, children) _gsxgw.S("
      ") return _gsxgw.Err() @@ -277,15 +272,14 @@ type ErrorPageProps struct { Err error } -//line pages.gsx:119:1 func ErrorPage(_gsxp ErrorPageProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { err := _gsxp.Err _gsxgw := gsx.W(_gsxw) -//line pages.gsx:120:2 +//line pages.gsx:158:2 _gsxgw.Node(ctx, Html(HtmlProps{Children: gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) -//line pages.gsx:121:3 +//line pages.gsx:159:3 _gsxgw.Node(ctx, ErrorComp(ErrorCompProps{Err: err})) return _gsxgw.Err() })})) @@ -297,16 +291,15 @@ type ErrorCompProps struct { Err error } -//line pages.gsx:125:1 func ErrorComp(_gsxp ErrorCompProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { err := _gsxp.Err _gsxgw := gsx.W(_gsxw) -//line pages.gsx:126:2 +//line pages.gsx:164:2 _gsxgw.S("

      Error

      ") -//line pages.gsx:127:2 +//line pages.gsx:165:2 _gsxgw.S("

      ") -//line pages.gsx:127:5 +//line pages.gsx:165:5 _gsxgw.Text(string(err.Error())) _gsxgw.S("

      ") return _gsxgw.Err() diff --git a/examples/htmx/pages.gsx b/examples/htmx/pages.gsx index a3c5989..be1a6da 100644 --- a/examples/htmx/pages.gsx +++ b/examples/htmx/pages.gsx @@ -1,54 +1,77 @@ package main import ( - "fmt" + "fmt" ) type index struct { product `route:"/product Product"` team `route:"/team Team"` contact `route:"/contact Contact"` - throw `route:"/throw Throw"` + throw `route:"/throw Throw"` } type product struct{} type team struct{} type contact struct{} type throw struct{} -component (p index) Page() { } +component (p index) Page() { + + + +} + component (p index) Main() {

      Welcome to the Index Page

      -

      Navigate to the product, team, or contact pages using the links below:

      - url } hx-target="#main">Throw (Err) +

      + Navigate to the product, team, or contact pages using the links below: +

      + url} hx-target="#main">Throw (Err) +} + +component (p product) Page() { + + + } -component (p product) Page() { } component (p product) Main() {

      Product Page

      This is the product page.

      } -component (p team) Page() { } +component (p team) Page() { + + + +} + component (p team) Main() {

      Team Page

      This is the team page.

      } -component (p contact) Page() { } +component (p contact) Page() { + + + +} + component (p contact) Main() {

      Contact Page

      This is the contact page.

      } -func errFunc() (string, error ) { return "", fmt.Errorf("this is an error") } -component (p throw) Page() {

      {errFunc()}

      } - +func errFunc() (string, error) { return "", fmt.Errorf("this is an error") } +component (p throw) Page() { +

      { errFunc() }

      +} component Layout() { - + HTMX Example @@ -56,19 +79,31 @@ component Layout() { -
      {children}
      +
      { children }
      } -component ErrorPage(err error) { } +component ErrorPage(err error) { + + + +} component ErrorComp(err error) {

      Error

      diff --git a/examples/htmx/pages.x.go b/examples/htmx/pages.x.go index fc661a2..1779d22 100644 --- a/examples/htmx/pages.x.go +++ b/examples/htmx/pages.x.go @@ -25,10 +25,10 @@ type throw struct{} func (p index) Page() gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) -//line pages.gsx:18:32 +//line pages.gsx:19:2 _gsxgw.Node(ctx, Layout(LayoutProps{Children: gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) -//line pages.gsx:18:40 +//line pages.gsx:20:3 _gsxgw.Node(ctx, p.Main()) return _gsxgw.Err() })})) @@ -39,17 +39,11 @@ func (p index) Page() gsx.Node { func (p index) Main() gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) -//line pages.gsx:20:2 - _gsxgw.S("") - _gsxgw.S("Welcome to the Index Page") - _gsxgw.S("") -//line pages.gsx:21:2 - _gsxgw.S("") - _gsxgw.S("Navigate to the product, team, or contact pages using the links below:") - _gsxgw.S("

      ") -//line pages.gsx:22:3 +//line pages.gsx:25:2 + _gsxgw.S("

      Welcome to the Index Page

      ") +//line pages.gsx:26:2 + _gsxgw.S("

      Navigate to the product, team, or contact pages using the links below:

      ") +//line pages.gsx:29:2 _gsxgw.S("") - _gsxgw.S("Throw (Err)") - _gsxgw.S("") + _gsxgw.S("\" hx-target=\"#main\">Throw (Err)") return _gsxgw.Err() }) } @@ -69,10 +59,10 @@ func (p index) Main() gsx.Node { func (p product) Page() gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) -//line pages.gsx:25:32 +//line pages.gsx:33:2 _gsxgw.Node(ctx, Layout(LayoutProps{Children: gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) -//line pages.gsx:25:40 +//line pages.gsx:34:3 _gsxgw.Node(ctx, p.Main()) return _gsxgw.Err() })})) @@ -83,16 +73,10 @@ func (p product) Page() gsx.Node { func (p product) Main() gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) -//line pages.gsx:27:2 - _gsxgw.S("") - _gsxgw.S("Product Page") - _gsxgw.S("") -//line pages.gsx:28:2 - _gsxgw.S("") - _gsxgw.S("This is the product page.") - _gsxgw.S("

      ") +//line pages.gsx:39:2 + _gsxgw.S("

      Product Page

      ") +//line pages.gsx:40:2 + _gsxgw.S("

      This is the product page.

      ") return _gsxgw.Err() }) } @@ -100,10 +84,10 @@ func (p product) Main() gsx.Node { func (p team) Page() gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) -//line pages.gsx:31:29 +//line pages.gsx:44:2 _gsxgw.Node(ctx, Layout(LayoutProps{Children: gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) -//line pages.gsx:31:37 +//line pages.gsx:45:3 _gsxgw.Node(ctx, p.Main()) return _gsxgw.Err() })})) @@ -114,16 +98,10 @@ func (p team) Page() gsx.Node { func (p team) Main() gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) -//line pages.gsx:33:2 - _gsxgw.S("") - _gsxgw.S("Team Page") - _gsxgw.S("") -//line pages.gsx:34:2 - _gsxgw.S("") - _gsxgw.S("This is the team page.") - _gsxgw.S("

      ") +//line pages.gsx:50:2 + _gsxgw.S("

      Team Page

      ") +//line pages.gsx:51:2 + _gsxgw.S("

      This is the team page.

      ") return _gsxgw.Err() }) } @@ -131,10 +109,10 @@ func (p team) Main() gsx.Node { func (p contact) Page() gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) -//line pages.gsx:37:32 +//line pages.gsx:55:2 _gsxgw.Node(ctx, Layout(LayoutProps{Children: gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) -//line pages.gsx:37:40 +//line pages.gsx:56:3 _gsxgw.Node(ctx, p.Main()) return _gsxgw.Err() })})) @@ -145,16 +123,10 @@ func (p contact) Page() gsx.Node { func (p contact) Main() gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) -//line pages.gsx:39:2 - _gsxgw.S("") - _gsxgw.S("Contact Page") - _gsxgw.S("") -//line pages.gsx:40:2 - _gsxgw.S("") - _gsxgw.S("This is the contact page.") - _gsxgw.S("

      ") +//line pages.gsx:61:2 + _gsxgw.S("

      Contact Page

      ") +//line pages.gsx:62:2 + _gsxgw.S("

      This is the contact page.

      ") return _gsxgw.Err() }) } @@ -164,10 +136,9 @@ func errFunc() (string, error) { return "", fmt.Errorf("this is an error") } func (p throw) Page() gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) -//line pages.gsx:44:30 - _gsxgw.S("") -//line pages.gsx:44:33 +//line pages.gsx:67:2 + _gsxgw.S("

      ") +//line pages.gsx:67:5 _gsxv1, _gsxerr := errFunc() if _gsxerr != nil { return _gsxerr @@ -187,49 +158,27 @@ func Layout(_gsxp LayoutProps) gsx.Node { children := _gsxp.Children _gsxgw := gsx.W(_gsxw) _gsxgw.S("") -//line pages.gsx:49:2 - _gsxgw.S("") -//line pages.gsx:50:3 - _gsxgw.S("") -//line pages.gsx:51:4 - _gsxgw.S("") -//line pages.gsx:52:4 - _gsxgw.S("") - _gsxgw.S("") -//line pages.gsx:53:4 - _gsxgw.S("") - _gsxgw.S("HTMX Example") - _gsxgw.S("") - _gsxgw.S("") -//line pages.gsx:55:3 - _gsxgw.S("") -//line pages.gsx:56:4 - _gsxgw.S("") -//line pages.gsx:57:5 - _gsxgw.S("") -//line pages.gsx:58:6 - _gsxgw.S("") -//line pages.gsx:59:7 - _gsxgw.S("") -//line pages.gsx:59:11 +//line pages.gsx:72:2 + _gsxgw.S("") +//line pages.gsx:73:3 + _gsxgw.S("") +//line pages.gsx:74:4 + _gsxgw.S("") +//line pages.gsx:75:4 + _gsxgw.S("") +//line pages.gsx:76:4 + _gsxgw.S("HTMX Example") +//line pages.gsx:78:3 + _gsxgw.S("") +//line pages.gsx:79:4 + _gsxgw.S("

      ") +//line pages.gsx:80:5 + _gsxgw.S("") - _gsxgw.S("
      ") -//line pages.gsx:66:4 - _gsxgw.S("") -//line pages.gsx:66:20 + _gsxgw.S("\">Contact
    ") +//line pages.gsx:97:4 + _gsxgw.S("
    ") +//line pages.gsx:97:20 _gsxgw.Node(ctx, children) - _gsxgw.S("
    ") - _gsxgw.S("") - _gsxgw.S("") + _gsxgw.S("") return _gsxgw.Err() }) } @@ -314,10 +237,10 @@ func ErrorPage(_gsxp ErrorPageProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { err := _gsxp.Err _gsxgw := gsx.W(_gsxw) -//line pages.gsx:71:34 +//line pages.gsx:103:2 _gsxgw.Node(ctx, Layout(LayoutProps{Children: gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) -//line pages.gsx:71:42 +//line pages.gsx:104:3 _gsxgw.Node(ctx, ErrorComp(ErrorCompProps{Err: err})) return _gsxgw.Err() })})) @@ -333,15 +256,11 @@ func ErrorComp(_gsxp ErrorCompProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { err := _gsxp.Err _gsxgw := gsx.W(_gsxw) -//line pages.gsx:74:2 - _gsxgw.S("") - _gsxgw.S("Error") - _gsxgw.S("") -//line pages.gsx:75:2 - _gsxgw.S("") -//line pages.gsx:75:5 +//line pages.gsx:109:2 + _gsxgw.S("

    Error

    ") +//line pages.gsx:110:2 + _gsxgw.S("

    ") +//line pages.gsx:110:5 _gsxgw.Text(string(err.Error())) _gsxgw.S("

    ") return _gsxgw.Err() diff --git a/examples/lint-misuse/pages.gsx b/examples/lint-misuse/pages.gsx index e4d28c1..8c5391b 100644 --- a/examples/lint-misuse/pages.gsx +++ b/examples/lint-misuse/pages.gsx @@ -19,10 +19,10 @@ import ( // make these findings go away. The lint still has value in gsx projects. component BadLinks(id int, name string) { Hard-coded internal - Expression literal - Concat - Sprintf - Bad hx-get -
    Bad action
    + Expression literal + Concat + Sprintf + Bad hx-get +
    Bad action
    External (allowed) } diff --git a/examples/lint-misuse/pages.x.go b/examples/lint-misuse/pages.x.go index 890edd8..9fb07de 100644 --- a/examples/lint-misuse/pages.x.go +++ b/examples/lint-misuse/pages.x.go @@ -35,57 +35,29 @@ func BadLinks(_gsxp BadLinksProps) gsx.Node { name := _gsxp.Name _gsxgw := gsx.W(_gsxw) //line pages.gsx:21:2 - _gsxgw.S("") - _gsxgw.S("Hard-coded internal") - _gsxgw.S("") + _gsxgw.S("Hard-coded internal") //line pages.gsx:22:2 - _gsxgw.S("") - _gsxgw.S("Expression literal") - _gsxgw.S("") + _gsxgw.S("\">Expression literal") //line pages.gsx:23:2 - _gsxgw.S("") - _gsxgw.S("Concat") - _gsxgw.S("") + _gsxgw.S("\">Concat") //line pages.gsx:24:2 - _gsxgw.S("") - _gsxgw.S("Sprintf") - _gsxgw.S("") + _gsxgw.S("\">Sprintf") //line pages.gsx:25:2 - _gsxgw.S("") - _gsxgw.S("Bad hx-get") - _gsxgw.S("") + _gsxgw.S("\">Bad hx-get") //line pages.gsx:26:2 - _gsxgw.S("") - _gsxgw.S("Bad action") - _gsxgw.S("") + _gsxgw.S("\">Bad action") //line pages.gsx:27:2 - _gsxgw.S("") - _gsxgw.S("External (allowed)") - _gsxgw.S("") + _gsxgw.S("External (allowed)") return _gsxgw.Err() }) } diff --git a/examples/simple/pages.gsx b/examples/simple/pages.gsx index 69ff909..94861ae 100644 --- a/examples/simple/pages.gsx +++ b/examples/simple/pages.gsx @@ -13,7 +13,9 @@ type contact struct{} component (p index) Page() {

    Welcome to the Index Page

    -

    Navigate to the product, team, or contact pages using the links below:

    +

    + Navigate to the product, team, or contact pages using the links below: +

    } @@ -42,21 +44,29 @@ component Layout() { - + Simple Example -
    {children}
    +
    { children }
    } diff --git a/examples/simple/pages.x.go b/examples/simple/pages.x.go index 5f05b95..4fce15f 100644 --- a/examples/simple/pages.x.go +++ b/examples/simple/pages.x.go @@ -27,15 +27,9 @@ func (p index) Page() gsx.Node { _gsxgw.Node(ctx, Layout(LayoutProps{Children: gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) //line pages.gsx:15:3 - _gsxgw.S("") - _gsxgw.S("Welcome to the Index Page") - _gsxgw.S("") + _gsxgw.S("

    Welcome to the Index Page

    ") //line pages.gsx:16:3 - _gsxgw.S("") - _gsxgw.S("Navigate to the product, team, or contact pages using the links below:") - _gsxgw.S("

    ") + _gsxgw.S("

    Navigate to the product, team, or contact pages using the links below:

    ") return _gsxgw.Err() })})) return _gsxgw.Err() @@ -45,19 +39,13 @@ func (p index) Page() gsx.Node { func (p product) Page() gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) -//line pages.gsx:21:2 +//line pages.gsx:23:2 _gsxgw.Node(ctx, Layout(LayoutProps{Children: gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) -//line pages.gsx:22:3 - _gsxgw.S("") - _gsxgw.S("Product Page") - _gsxgw.S("") -//line pages.gsx:23:3 - _gsxgw.S("") - _gsxgw.S("This is the product page.") - _gsxgw.S("

    ") +//line pages.gsx:24:3 + _gsxgw.S("

    Product Page

    ") +//line pages.gsx:25:3 + _gsxgw.S("

    This is the product page.

    ") return _gsxgw.Err() })})) return _gsxgw.Err() @@ -67,19 +55,13 @@ func (p product) Page() gsx.Node { func (p team) Page() gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) -//line pages.gsx:28:2 +//line pages.gsx:30:2 _gsxgw.Node(ctx, Layout(LayoutProps{Children: gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) -//line pages.gsx:29:3 - _gsxgw.S("") - _gsxgw.S("Team Page") - _gsxgw.S("") -//line pages.gsx:30:3 - _gsxgw.S("") - _gsxgw.S("This is the team page.") - _gsxgw.S("

    ") +//line pages.gsx:31:3 + _gsxgw.S("

    Team Page

    ") +//line pages.gsx:32:3 + _gsxgw.S("

    This is the team page.

    ") return _gsxgw.Err() })})) return _gsxgw.Err() @@ -89,19 +71,13 @@ func (p team) Page() gsx.Node { func (p contact) Page() gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) -//line pages.gsx:35:2 +//line pages.gsx:37:2 _gsxgw.Node(ctx, Layout(LayoutProps{Children: gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) -//line pages.gsx:36:3 - _gsxgw.S("") - _gsxgw.S("Contact Page") - _gsxgw.S("") -//line pages.gsx:37:3 - _gsxgw.S("") - _gsxgw.S("This is the contact page.") - _gsxgw.S("

    ") +//line pages.gsx:38:3 + _gsxgw.S("

    Contact Page

    ") +//line pages.gsx:39:3 + _gsxgw.S("

    This is the contact page.

    ") return _gsxgw.Err() })})) return _gsxgw.Err() @@ -117,42 +93,25 @@ func Layout(_gsxp LayoutProps) gsx.Node { children := _gsxp.Children _gsxgw := gsx.W(_gsxw) _gsxgw.S("") -//line pages.gsx:43:2 - _gsxgw.S("") -//line pages.gsx:44:3 - _gsxgw.S("") -//line pages.gsx:45:4 - _gsxgw.S("") -//line pages.gsx:46:4 - _gsxgw.S("") - _gsxgw.S("Simple Example") - _gsxgw.S("") - _gsxgw.S("") -//line pages.gsx:48:3 - _gsxgw.S("") -//line pages.gsx:49:4 - _gsxgw.S("") -//line pages.gsx:50:5 - _gsxgw.S("") -//line pages.gsx:51:6 - _gsxgw.S("") -//line pages.gsx:52:7 - _gsxgw.S("") -//line pages.gsx:52:11 +//line pages.gsx:45:2 + _gsxgw.S("") +//line pages.gsx:46:3 + _gsxgw.S("") +//line pages.gsx:47:4 + _gsxgw.S("") +//line pages.gsx:48:4 + _gsxgw.S("Simple Example") +//line pages.gsx:50:3 + _gsxgw.S("") +//line pages.gsx:51:4 + _gsxgw.S("
    ") +//line pages.gsx:52:5 + _gsxgw.S("") - _gsxgw.S("
    ") -//line pages.gsx:59:4 - _gsxgw.S("") -//line pages.gsx:59:10 + _gsxgw.S("\">Contact") +//line pages.gsx:69:4 + _gsxgw.S("
    ") +//line pages.gsx:69:10 _gsxgw.Node(ctx, children) - _gsxgw.S("
    ") - _gsxgw.S("") - _gsxgw.S("") + _gsxgw.S("") return _gsxgw.Err() }) } diff --git a/examples/todo/pages.gsx b/examples/todo/pages.gsx index 4f7294e..c90223f 100644 --- a/examples/todo/pages.gsx +++ b/examples/todo/pages.gsx @@ -18,8 +18,8 @@ component (p index) Page() {

    TODO App

    url } - hx-target={ index.TodoList |> target } + hx-post={add{} |> url} + hx-target={index.TodoList |> target} hx-swap="innerHTML" hx-on:htmx:after-request="this.reset()" > @@ -33,7 +33,7 @@ component (p index) Page() {
    -
    id }> +
    id}>
    @@ -90,16 +90,16 @@ component TodoList() { url("id", todo.ID) } - hx-target={ index.TodoList |> target } + hx-post={toggle{} |> url("id", todo.ID)} + hx-target={index.TodoList |> target} hx-swap="innerHTML" /> { todo.Text } ") - _gsxgw.S("") - _gsxgw.S("") + _gsxgw.S("") //line pages.gsx:36:4 _gsxgw.S("") + _gsxgw.S("\">") //line pages.gsx:37:5 _gsxgw.Node(ctx, p.TodoList()) - _gsxgw.S("") - _gsxgw.S("") + _gsxgw.S("") return _gsxgw.Err() })})) return _gsxgw.Err() @@ -143,24 +122,17 @@ func TodoList() gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) //line pages.gsx:86:2 - _gsxgw.S("") + _gsxgw.S("
      ") //line pages.gsx:87:3 for _, todo := range getTodos() { //line pages.gsx:88:4 - _gsxgw.S("") + _gsxgw.S("\">") //line pages.gsx:89:5 - _gsxgw.S("") + _gsxgw.S("
      ") //line pages.gsx:90:6 - _gsxgw.S("") + _gsxgw.S("\" hx-swap=\"innerHTML\"/>") //line pages.gsx:97:6 - _gsxgw.S("") + _gsxgw.S("") //line pages.gsx:97:30 _gsxgw.Text(string(todo.Text)) - _gsxgw.S("") - _gsxgw.S("
      ") + _gsxgw.S("") //line pages.gsx:99:5 - _gsxgw.S("") - _gsxgw.S("×") - _gsxgw.S("") - _gsxgw.S("") + _gsxgw.S("\" hx-swap=\"innerHTML\" hx-confirm=\"Are you sure you want to delete this todo?\">×") } _gsxgw.S("
    ") //line pages.gsx:111:2 if len(getTodos()) == 0 { //line pages.gsx:112:3 - _gsxgw.S("") - _gsxgw.S("No todos yet. Add one above!") - _gsxgw.S("

    ") + _gsxgw.S("

    No todos yet. Add one above!

    ") } return _gsxgw.Err() }) @@ -234,45 +190,24 @@ func Layout(_gsxp LayoutProps) gsx.Node { _gsxgw := gsx.W(_gsxw) _gsxgw.S("") //line pages.gsx:118:2 - _gsxgw.S("") + _gsxgw.S("") //line pages.gsx:119:3 - _gsxgw.S("") + _gsxgw.S("") //line pages.gsx:120:4 - _gsxgw.S("") + _gsxgw.S("") //line pages.gsx:121:4 - _gsxgw.S("") - _gsxgw.S("") + _gsxgw.S("") //line pages.gsx:122:4 - _gsxgw.S("") - _gsxgw.S("TODO App") - _gsxgw.S("") + _gsxgw.S("TODO App") //line pages.gsx:123:4 - _gsxgw.S("") - _gsxgw.S(".todo-app{max-width: 600px;margin: 2rem auto;padding: 2rem}.form-group{display: flex;gap: 0.5rem;margin-bottom: 2rem}.form-group input{flex: 1;padding: 0.75rem;border: 1px solid #ddd;border-radius: 4px}.form-group button{padding: 0.75rem 1.5rem;background: #007bff;color: white;border: none;border-radius: 4px;cursor: pointer}.form-group button:hover{background: #0056b3}.todo-list{list-style: none;padding: 0}.todo-item{display: flex;align-items: center;justify-content: space-between;padding: 1rem;border: 1px solid #eee;border-radius: 4px;margin-bottom: 0.5rem;background: white}.todo-item.completed{opacity: 0.6}.todo-item.completed .todo-text{text-decoration: line-through}.todo-content{display: flex;align-items: center;gap: 0.75rem;flex: 1}.todo-text{flex: 1}.delete-btn{background: #dc3545;color: white;border: none;border-radius: 50%;width: 2rem;height: 2rem;cursor: pointer;font-size: 1.2rem;display: flex;align-items: center;justify-content: center}.delete-btn:hover{background: #c82333}.empty-state{text-align: center;color: #666;font-style: italic;padding: 2rem}") - _gsxgw.S("") - _gsxgw.S("") + _gsxgw.S("") //line pages.gsx:217:3 - _gsxgw.S("") + _gsxgw.S("") //line pages.gsx:218:4 - _gsxgw.S("") -//line pages.gsx:219:5 + _gsxgw.S("
    ") +//line pages.gsx:218:23 _gsxgw.Node(ctx, children) - _gsxgw.S("
    ") - _gsxgw.S("") - _gsxgw.S("") + _gsxgw.S("") return _gsxgw.Err() }) } @@ -285,10 +220,10 @@ func ErrorPage(_gsxp ErrorPageProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { err := _gsxp.Err _gsxgw := gsx.W(_gsxw) -//line pages.gsx:226:2 +//line pages.gsx:224:2 _gsxgw.Node(ctx, Layout(LayoutProps{Children: gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) -//line pages.gsx:227:3 +//line pages.gsx:225:3 _gsxgw.Node(ctx, ErrorComp(ErrorCompProps{Err: err})) return _gsxgw.Err() })})) @@ -304,15 +239,11 @@ func ErrorComp(_gsxp ErrorCompProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { err := _gsxp.Err _gsxgw := gsx.W(_gsxw) -//line pages.gsx:232:2 - _gsxgw.S("") - _gsxgw.S("Error") - _gsxgw.S("") -//line pages.gsx:233:2 - _gsxgw.S("") -//line pages.gsx:233:5 +//line pages.gsx:230:2 + _gsxgw.S("

    Error

    ") +//line pages.gsx:231:2 + _gsxgw.S("

    ") +//line pages.gsx:231:5 _gsxgw.Text(string(err.Error())) _gsxgw.S("

    ") return _gsxgw.Err() From 6f5477db9680c8fee70b47921add00c89716dda7 Mon Sep 17 00:00:00 2001 From: Jackie Li Date: Fri, 26 Jun 2026 21:03:23 +0100 Subject: [PATCH 11/13] regen --- examples/htmx-render-target/pages.x.go | 2 +- examples/todo/pages.x.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/htmx-render-target/pages.x.go b/examples/htmx-render-target/pages.x.go index 8d78e3c..194ffb8 100644 --- a/examples/htmx-render-target/pages.x.go +++ b/examples/htmx-render-target/pages.x.go @@ -256,7 +256,7 @@ func Html(_gsxp HtmlProps) gsx.Node { //line pages.gsx:132:4 _gsxgw.S("RenderTarget API Example") //line pages.gsx:133:4 - _gsxgw.S("") + _gsxgw.S("") //line pages.gsx:151:3 _gsxgw.S("") //line pages.gsx:152:4 diff --git a/examples/todo/pages.x.go b/examples/todo/pages.x.go index 7097f38..923f08e 100644 --- a/examples/todo/pages.x.go +++ b/examples/todo/pages.x.go @@ -200,7 +200,7 @@ func Layout(_gsxp LayoutProps) gsx.Node { //line pages.gsx:122:4 _gsxgw.S("TODO App") //line pages.gsx:123:4 - _gsxgw.S("") + _gsxgw.S("") //line pages.gsx:217:3 _gsxgw.S("") //line pages.gsx:218:4 From ac9e46a6d832587b0935424202a61672482d6f16 Mon Sep 17 00:00:00 2001 From: Jackie Li Date: Fri, 26 Jun 2026 21:21:46 +0100 Subject: [PATCH 12/13] . --- examples/htmx-render-target/pages.gsx | 9 +++++++++ examples/htmx-render-target/pages.x.go | 21 +++++++++++++++------ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/examples/htmx-render-target/pages.gsx b/examples/htmx-render-target/pages.gsx index 332f3b8..72149c8 100644 --- a/examples/htmx-render-target/pages.gsx +++ b/examples/htmx-render-target/pages.gsx @@ -150,6 +150,15 @@ component Html() {
    { children }
    + } diff --git a/examples/htmx-render-target/pages.x.go b/examples/htmx-render-target/pages.x.go index 194ffb8..85c5820 100644 --- a/examples/htmx-render-target/pages.x.go +++ b/examples/htmx-render-target/pages.x.go @@ -15,6 +15,7 @@ import ( // Shared standalone function components (can be used across multiple pages). // These demonstrate the power of RenderTarget — no wrapper methods needed. +//line pages.gsx:6:1 func UserStatsWidget(stats UserStats) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) @@ -52,6 +53,7 @@ func UserStatsWidget(stats UserStats) gsx.Node { }) } +//line pages.gsx:21:1 func SalesChartWidget(data SalesData) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) @@ -99,6 +101,7 @@ type NotificationsListProps struct { Attrs gsx.Attrs } +//line pages.gsx:44:1 func NotificationsList(_gsxp NotificationsListProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { notifications := _gsxp.Notifications @@ -152,6 +155,7 @@ func NotificationsList(_gsxp NotificationsListProps) gsx.Node { type dashboard struct{} +//line pages.gsx:68:1 func (p dashboard) Page(props dashboardData) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) @@ -240,6 +244,7 @@ type HtmlProps struct { Children gsx.Node } +//line pages.gsx:126:1 func Html(_gsxp HtmlProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { children := _gsxp.Children @@ -263,7 +268,9 @@ func Html(_gsxp HtmlProps) gsx.Node { _gsxgw.S("
    ") //line pages.gsx:152:10 _gsxgw.Node(ctx, children) - _gsxgw.S("
    ") + _gsxgw.S("") +//line pages.gsx:153:4 + _gsxgw.S("") return _gsxgw.Err() }) } @@ -272,14 +279,15 @@ type ErrorPageProps struct { Err error } +//line pages.gsx:166:1 func ErrorPage(_gsxp ErrorPageProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { err := _gsxp.Err _gsxgw := gsx.W(_gsxw) -//line pages.gsx:158:2 +//line pages.gsx:167:2 _gsxgw.Node(ctx, Html(HtmlProps{Children: gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) -//line pages.gsx:159:3 +//line pages.gsx:168:3 _gsxgw.Node(ctx, ErrorComp(ErrorCompProps{Err: err})) return _gsxgw.Err() })})) @@ -291,15 +299,16 @@ type ErrorCompProps struct { Err error } +//line pages.gsx:172:1 func ErrorComp(_gsxp ErrorCompProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { err := _gsxp.Err _gsxgw := gsx.W(_gsxw) -//line pages.gsx:164:2 +//line pages.gsx:173:2 _gsxgw.S("

    Error

    ") -//line pages.gsx:165:2 +//line pages.gsx:174:2 _gsxgw.S("

    ") -//line pages.gsx:165:5 +//line pages.gsx:174:5 _gsxgw.Text(string(err.Error())) _gsxgw.S("

    ") return _gsxgw.Err() From bf3cb0c8f0a8937548ea4ca99e44ca45a5e9e4c7 Mon Sep 17 00:00:00 2001 From: Jackie Li Date: Sat, 27 Jun 2026 21:13:45 +0100 Subject: [PATCH 13/13] regen --- examples/blog/admin/components.x.go | 4 ++++ examples/blog/admin/dashboard.x.go | 1 + examples/blog/admin/login.x.go | 1 + examples/blog/admin/posts.x.go | 5 +++++ examples/blog/admin/users.x.go | 1 + examples/blog/blog/category.x.go | 1 + examples/blog/blog/components.x.go | 3 +++ examples/blog/blog/home.x.go | 1 + examples/blog/blog/post.x.go | 1 + examples/blog/blog/search.x.go | 2 ++ examples/blog/ui/components/components.x.go | 8 ++++++++ examples/blog/ui/layout/layout.x.go | 2 ++ examples/htmx/pages.x.go | 12 ++++++++++++ examples/lint-misuse/pages.x.go | 1 + examples/simple/pages.x.go | 5 +++++ examples/todo/pages.x.go | 6 ++++++ 16 files changed, 54 insertions(+) diff --git a/examples/blog/admin/components.x.go b/examples/blog/admin/components.x.go index c670bcd..c8d0c70 100644 --- a/examples/blog/admin/components.x.go +++ b/examples/blog/admin/components.x.go @@ -22,6 +22,7 @@ type StatCellProps struct { Attrs gsx.Attrs } +//line components.gsx:14:1 func StatCell(_gsxp StatCellProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { label := _gsxp.Label @@ -53,6 +54,7 @@ type StatsGridProps struct { Attrs gsx.Attrs } +//line components.gsx:23:1 func StatsGrid(_gsxp StatsGridProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { stats := _gsxp.Stats @@ -92,6 +94,7 @@ type RecentPostsCardProps struct { Attrs gsx.Attrs } +//line components.gsx:32:1 func RecentPostsCard(_gsxp RecentPostsCardProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { posts := _gsxp.Posts @@ -160,6 +163,7 @@ type PostsTableProps struct { Attrs gsx.Attrs } +//line components.gsx:67:1 func PostsTable(_gsxp PostsTableProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { posts := _gsxp.Posts diff --git a/examples/blog/admin/dashboard.x.go b/examples/blog/admin/dashboard.x.go index 290ee42..6f4a914 100644 --- a/examples/blog/admin/dashboard.x.go +++ b/examples/blog/admin/dashboard.x.go @@ -50,6 +50,7 @@ func (p dashboardPage) Props(r *http.Request, s *store.Store, target structpages }, nil } +//line dashboard.gsx:47:1 func (p dashboardPage) Page(props dashboardProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) diff --git a/examples/blog/admin/login.x.go b/examples/blog/admin/login.x.go index 73e7105..ce7480c 100644 --- a/examples/blog/admin/login.x.go +++ b/examples/blog/admin/login.x.go @@ -19,6 +19,7 @@ type LoginShellProps struct { ErrMsg string } +//line login.gsx:8:1 func LoginShell(_gsxp LoginShellProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { username := _gsxp.Username diff --git a/examples/blog/admin/posts.x.go b/examples/blog/admin/posts.x.go index 73ce318..0eecf25 100644 --- a/examples/blog/admin/posts.x.go +++ b/examples/blog/admin/posts.x.go @@ -28,6 +28,7 @@ type AdminShellWithProps struct { Children gsx.Node } +//line posts.gsx:19:1 func AdminShellWith(_gsxp AdminShellWithProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { title := _gsxp.Title @@ -60,6 +61,7 @@ func (postListPage) Props(r *http.Request, s *store.Store) (postListProps, error return postListProps{User: user, Posts: posts}, nil } +//line posts.gsx:40:1 func (p postListPage) Page(props postListProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) @@ -102,6 +104,7 @@ func (postNewPage) Props(r *http.Request, s *store.Store) (postFormViewProps, er return postFormViewProps{User: user, Categories: s.ListCategories()}, nil } +//line posts.gsx:70:1 func (p postNewPage) Page(props postFormViewProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) @@ -135,6 +138,7 @@ func (postEditPage) Props(r *http.Request, s *store.Store) (postFormViewProps, e return postFormViewProps{User: user, Categories: s.ListCategories(), Post: p}, nil } +//line posts.gsx:94:1 func (p postEditPage) Page(props postFormViewProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) @@ -160,6 +164,7 @@ type PostFormProps struct { Attrs gsx.Attrs } +//line posts.gsx:103:1 func PostForm(_gsxp PostFormProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { p := _gsxp.P diff --git a/examples/blog/admin/users.x.go b/examples/blog/admin/users.x.go index d8a2a9f..a591514 100644 --- a/examples/blog/admin/users.x.go +++ b/examples/blog/admin/users.x.go @@ -27,6 +27,7 @@ func (userListPage) Props(r *http.Request, s *store.Store) (userListProps, error return userListProps{User: user, Users: s.ListUsers()}, nil } +//line users.gsx:25:1 func (p userListPage) Page(props userListProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) diff --git a/examples/blog/blog/category.x.go b/examples/blog/blog/category.x.go index 2faa8e3..d72660f 100644 --- a/examples/blog/blog/category.x.go +++ b/examples/blog/blog/category.x.go @@ -56,6 +56,7 @@ func (categoryPage) Props(r *http.Request, s *store.Store) (categoryProps, error }, nil } +//line category.gsx:54:1 func (p categoryPage) Page(props categoryProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) diff --git a/examples/blog/blog/components.x.go b/examples/blog/blog/components.x.go index b45e9a5..039cabd 100644 --- a/examples/blog/blog/components.x.go +++ b/examples/blog/blog/components.x.go @@ -17,6 +17,7 @@ type PostMetaProps struct { Attrs gsx.Attrs } +//line components.gsx:11:1 func PostMeta(_gsxp PostMetaProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { p := _gsxp.P @@ -40,6 +41,7 @@ type PostCardProps struct { Attrs gsx.Attrs } +//line components.gsx:17:1 func PostCard(_gsxp PostCardProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { p := _gsxp.P @@ -83,6 +85,7 @@ type CommentsListProps struct { Attrs gsx.Attrs } +//line components.gsx:33:1 func CommentsList(_gsxp CommentsListProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { comments := _gsxp.Comments diff --git a/examples/blog/blog/home.x.go b/examples/blog/blog/home.x.go index 944dc86..a5def35 100644 --- a/examples/blog/blog/home.x.go +++ b/examples/blog/blog/home.x.go @@ -33,6 +33,7 @@ func (homePage) Props(_ *http.Request, s *store.Store) (homeProps, error) { // undispatchable by structpages alongside Page(props) (both would need the same // single Props-return type). See GAP notes. +//line home.gsx:29:1 func (p homePage) Page(props homeProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) diff --git a/examples/blog/blog/post.x.go b/examples/blog/blog/post.x.go index 7cfb5b5..7dafc62 100644 --- a/examples/blog/blog/post.x.go +++ b/examples/blog/blog/post.x.go @@ -39,6 +39,7 @@ func (postPage) Props(r *http.Request, s *store.Store) (postProps, error) { }, nil } +//line post.gsx:37:1 func (p postPage) Page(props postProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) diff --git a/examples/blog/blog/search.x.go b/examples/blog/blog/search.x.go index 4c92d7e..94c6513 100644 --- a/examples/blog/blog/search.x.go +++ b/examples/blog/blog/search.x.go @@ -36,6 +36,7 @@ func (p searchPage) Props(r *http.Request, s *store.Store, target structpages.Re return sp, nil } +//line search.gsx:33:1 func (p searchPage) Page(props searchProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) @@ -74,6 +75,7 @@ func (p searchPage) Page(props searchProps) gsx.Node { }) } +//line search.gsx:56:1 func (p searchPage) Results(props searchProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) diff --git a/examples/blog/ui/components/components.x.go b/examples/blog/ui/components/components.x.go index dfad0d8..cf1f0f4 100644 --- a/examples/blog/ui/components/components.x.go +++ b/examples/blog/ui/components/components.x.go @@ -34,6 +34,7 @@ type AlertProps struct { Msg string } +//line components.gsx:29:1 func Alert(_gsxp AlertProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { kind := _gsxp.Kind @@ -59,6 +60,7 @@ type CardProps struct { Attrs gsx.Attrs } +//line components.gsx:37:1 func Card(_gsxp CardProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { title := _gsxp.Title @@ -94,6 +96,7 @@ type ButtonProps struct { Attrs gsx.Attrs } +//line components.gsx:50:1 func Button(_gsxp ButtonProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { label := _gsxp.Label @@ -120,6 +123,7 @@ type InputProps struct { Attrs gsx.Attrs } +//line components.gsx:58:1 func Input(_gsxp InputProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { name := _gsxp.Name @@ -168,6 +172,7 @@ type TextareaProps struct { Attrs gsx.Attrs } +//line components.gsx:74:1 func Textarea(_gsxp TextareaProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { name := _gsxp.Name @@ -212,6 +217,7 @@ func Textarea(_gsxp TextareaProps) gsx.Node { // Pagination is rendered as a standalone function component so HTMX requests // with HX-Target: #pagination resolve here regardless of which page hosts it. +//line components.gsx:92:1 func Pagination(p PageNav) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) @@ -275,6 +281,7 @@ type ErrorPageProps struct { Msg string } +//line components.gsx:129:1 func ErrorPage(_gsxp ErrorPageProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { status := _gsxp.Status @@ -311,6 +318,7 @@ type ErrorBlockProps struct { Attrs gsx.Attrs } +//line components.gsx:145:1 func ErrorBlock(_gsxp ErrorBlockProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { status := _gsxp.Status diff --git a/examples/blog/ui/layout/layout.x.go b/examples/blog/ui/layout/layout.x.go index fb0715b..12462e4 100644 --- a/examples/blog/ui/layout/layout.x.go +++ b/examples/blog/ui/layout/layout.x.go @@ -21,6 +21,7 @@ type PublicShellProps struct { Children gsx.Node } +//line layout.gsx:14:1 func PublicShell(_gsxp PublicShellProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { title := _gsxp.Title @@ -105,6 +106,7 @@ type AdminShellProps struct { Children gsx.Node } +//line layout.gsx:68:1 func AdminShell(_gsxp AdminShellProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { title := _gsxp.Title diff --git a/examples/htmx/pages.x.go b/examples/htmx/pages.x.go index 1779d22..e16c952 100644 --- a/examples/htmx/pages.x.go +++ b/examples/htmx/pages.x.go @@ -22,6 +22,7 @@ type team struct{} type contact struct{} type throw struct{} +//line pages.gsx:18:1 func (p index) Page() gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) @@ -36,6 +37,7 @@ func (p index) Page() gsx.Node { }) } +//line pages.gsx:24:1 func (p index) Main() gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) @@ -56,6 +58,7 @@ func (p index) Main() gsx.Node { }) } +//line pages.gsx:32:1 func (p product) Page() gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) @@ -70,6 +73,7 @@ func (p product) Page() gsx.Node { }) } +//line pages.gsx:38:1 func (p product) Main() gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) @@ -81,6 +85,7 @@ func (p product) Main() gsx.Node { }) } +//line pages.gsx:43:1 func (p team) Page() gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) @@ -95,6 +100,7 @@ func (p team) Page() gsx.Node { }) } +//line pages.gsx:49:1 func (p team) Main() gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) @@ -106,6 +112,7 @@ func (p team) Main() gsx.Node { }) } +//line pages.gsx:54:1 func (p contact) Page() gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) @@ -120,6 +127,7 @@ func (p contact) Page() gsx.Node { }) } +//line pages.gsx:60:1 func (p contact) Main() gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) @@ -133,6 +141,7 @@ func (p contact) Main() gsx.Node { func errFunc() (string, error) { return "", fmt.Errorf("this is an error") } +//line pages.gsx:66:1 func (p throw) Page() gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) @@ -153,6 +162,7 @@ type LayoutProps struct { Children gsx.Node } +//line pages.gsx:70:1 func Layout(_gsxp LayoutProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { children := _gsxp.Children @@ -233,6 +243,7 @@ type ErrorPageProps struct { Err error } +//line pages.gsx:102:1 func ErrorPage(_gsxp ErrorPageProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { err := _gsxp.Err @@ -252,6 +263,7 @@ type ErrorCompProps struct { Err error } +//line pages.gsx:108:1 func ErrorComp(_gsxp ErrorCompProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { err := _gsxp.Err diff --git a/examples/lint-misuse/pages.x.go b/examples/lint-misuse/pages.x.go index 9fb07de..6420e6c 100644 --- a/examples/lint-misuse/pages.x.go +++ b/examples/lint-misuse/pages.x.go @@ -29,6 +29,7 @@ type BadLinksProps struct { Name string } +//line pages.gsx:20:1 func BadLinks(_gsxp BadLinksProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { id := _gsxp.Id diff --git a/examples/simple/pages.x.go b/examples/simple/pages.x.go index 4fce15f..7380abf 100644 --- a/examples/simple/pages.x.go +++ b/examples/simple/pages.x.go @@ -20,6 +20,7 @@ type product struct{} type team struct{} type contact struct{} +//line pages.gsx:13:1 func (p index) Page() gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) @@ -36,6 +37,7 @@ func (p index) Page() gsx.Node { }) } +//line pages.gsx:22:1 func (p product) Page() gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) @@ -52,6 +54,7 @@ func (p product) Page() gsx.Node { }) } +//line pages.gsx:29:1 func (p team) Page() gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) @@ -68,6 +71,7 @@ func (p team) Page() gsx.Node { }) } +//line pages.gsx:36:1 func (p contact) Page() gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) @@ -88,6 +92,7 @@ type LayoutProps struct { Children gsx.Node } +//line pages.gsx:43:1 func Layout(_gsxp LayoutProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { children := _gsxp.Children diff --git a/examples/todo/pages.x.go b/examples/todo/pages.x.go index 923f08e..8a3cdb1 100644 --- a/examples/todo/pages.x.go +++ b/examples/todo/pages.x.go @@ -19,6 +19,7 @@ type index struct { deleteTodo `route:"DELETE /delete/{id} DeleteTodo"` } +//line pages.gsx:16:1 func (p index) Page() gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) @@ -71,6 +72,7 @@ func (p index) Page() gsx.Node { }) } +//line pages.gsx:43:1 func (p index) TodoList() gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) @@ -118,6 +120,7 @@ func (d deleteTodo) ServeHTTP(w http.ResponseWriter, r *http.Request) error { return structpages.RenderComponent(index.TodoList) } +//line pages.gsx:85:1 func TodoList() gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { _gsxgw := gsx.W(_gsxw) @@ -184,6 +187,7 @@ type LayoutProps struct { Children gsx.Node } +//line pages.gsx:116:1 func Layout(_gsxp LayoutProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { children := _gsxp.Children @@ -216,6 +220,7 @@ type ErrorPageProps struct { Err error } +//line pages.gsx:223:1 func ErrorPage(_gsxp ErrorPageProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { err := _gsxp.Err @@ -235,6 +240,7 @@ type ErrorCompProps struct { Err error } +//line pages.gsx:229:1 func ErrorComp(_gsxp ErrorCompProps) gsx.Node { return gsx.Func(func(ctx context.Context, _gsxw io.Writer) error { err := _gsxp.Err