diff --git a/cli/htmgo/runner.go b/cli/htmgo/runner.go index 2352d39a..0cfd5864 100644 --- a/cli/htmgo/runner.go +++ b/cli/htmgo/runner.go @@ -9,6 +9,7 @@ import ( "github.com/maddalax/htmgo/cli/htmgo/tasks/copyassets" "github.com/maddalax/htmgo/cli/htmgo/tasks/css" "github.com/maddalax/htmgo/cli/htmgo/tasks/downloadtemplate" + "github.com/maddalax/htmgo/cli/htmgo/tasks/formatter" "github.com/maddalax/htmgo/cli/htmgo/tasks/process" "github.com/maddalax/htmgo/cli/htmgo/tasks/reloader" "github.com/maddalax/htmgo/cli/htmgo/tasks/run" @@ -19,10 +20,10 @@ import ( ) func main() { - done := RegisterSignals() + needsSignals := true commandMap := make(map[string]*flag.FlagSet) - commands := []string{"template", "run", "watch", "build", "setup", "css", "schema", "generate"} + commands := []string{"template", "run", "watch", "build", "setup", "css", "schema", "generate", "format"} for _, command := range commands { commandMap[command] = flag.NewFlagSet(command, flag.ExitOnError) @@ -56,6 +57,15 @@ func main() { slog.Debug("Running task:", slog.String("task", taskName)) slog.Debug("working dir:", slog.String("dir", process.GetWorkingDir())) + if taskName == "format" { + needsSignals = false + } + + done := make(chan bool, 1) + if needsSignals { + done = RegisterSignals() + } + if taskName == "watch" { fmt.Printf("Running in watch mode\n") os.Setenv("ENV", "development") @@ -90,7 +100,18 @@ func main() { }() startWatcher(reloader.OnFileChange) } else { - if taskName == "schema" { + if taskName == "format" { + if len(os.Args) < 3 { + fmt.Println(fmt.Sprintf("Usage: htmgo format ")) + os.Exit(1) + } + file := os.Args[2] + if file == "." { + formatter.FormatDir(process.GetWorkingDir()) + } else { + formatter.FormatFile(os.Args[2]) + } + } else if taskName == "schema" { reader := bufio.NewReader(os.Stdin) fmt.Print("Enter entity name:") text, _ := reader.ReadString('\n') diff --git a/cli/htmgo/tasks/formatter/formatter.go b/cli/htmgo/tasks/formatter/formatter.go new file mode 100644 index 00000000..61a32058 --- /dev/null +++ b/cli/htmgo/tasks/formatter/formatter.go @@ -0,0 +1,50 @@ +package formatter + +import ( + "fmt" + "github.com/maddalax/htmgo/tools/html-to-htmgo/htmltogo" + "os" + "path/filepath" + "strings" +) + +func FormatDir(dir string) { + files, err := os.ReadDir(dir) + if err != nil { + fmt.Printf("error reading dir: %s\n", err.Error()) + return + } + for _, file := range files { + if file.IsDir() { + FormatDir(filepath.Join(dir, file.Name())) + } else { + FormatFile(filepath.Join(dir, file.Name())) + } + } +} + +func FormatFile(file string) { + if !strings.HasSuffix(file, ".go") { + return + } + + fmt.Printf("formatting file: %s\n", file) + + source, err := os.ReadFile(file) + if err != nil { + fmt.Printf("error reading file: %s\n", err.Error()) + return + } + + str := string(source) + + if !strings.Contains(str, "github.com/maddalax/htmgo/framework/h") { + return + } + + parsed := htmltogo.Indent(str) + + os.WriteFile(file, []byte(parsed), 0644) + + return +} diff --git a/cli/htmgo/tasks/process/process.go b/cli/htmgo/tasks/process/process.go index a31cd196..f3c1c41a 100644 --- a/cli/htmgo/tasks/process/process.go +++ b/cli/htmgo/tasks/process/process.go @@ -115,7 +115,7 @@ func OnShutdown() { } } // give it a second - time.Sleep(time.Second * 2) + time.Sleep(time.Second * 1) // force kill KillAll() } diff --git a/examples/chat/chat/component.go b/examples/chat/chat/component.go index f81adbf8..0840fe30 100644 --- a/examples/chat/chat/component.go +++ b/examples/chat/chat/component.go @@ -11,18 +11,25 @@ import ( func MessageRow(message *Message) *h.Element { return h.Div( h.Attribute("hx-swap-oob", "beforeend"), - h.Class("flex flex-col gap-4 w-full break-words whitespace-normal"), // Ensure container breaks long words + h.Class("flex flex-col gap-4 w-full break-words whitespace-normal"), + // Ensure container breaks long words h.Id("messages"), h.Div( h.Class("flex flex-col gap-1"), h.Div( h.Class("flex gap-2 items-center"), - h.Pf(message.UserName, h.Class("font-bold")), + h.Pf( + message.UserName, + h.Class("font-bold"), + ), h.Pf(message.CreatedAt.In(time.Local).Format("01/02 03:04 PM")), ), h.Article( - h.Class("break-words whitespace-normal"), // Ensure message text wraps correctly - h.P(h.Text(message.Message)), + h.Class("break-words whitespace-normal"), + // Ensure message text wraps correctly + h.P( + h.Text(message.Message), + ), ), ), ) diff --git a/examples/chat/components/error.go b/examples/chat/components/error.go index a20ba0d6..4b147ddd 100644 --- a/examples/chat/components/error.go +++ b/examples/chat/components/error.go @@ -6,6 +6,9 @@ func FormError(error string) *h.Element { return h.Div( h.Id("form-error"), h.Text(error), - h.If(error != "", h.Class("p-4 bg-rose-400 text-white rounded")), + h.If( + error != "", + h.Class("p-4 bg-rose-400 text-white rounded"), + ), ) } diff --git a/examples/chat/components/input.go b/examples/chat/components/input.go index fec13632..0013d4ef 100644 --- a/examples/chat/components/input.go +++ b/examples/chat/components/input.go @@ -19,11 +19,14 @@ type InputProps struct { } func Input(props InputProps) *h.Element { - validation := h.If(props.ValidationPath != "", h.Children( - h.Post(props.ValidationPath, hx.BlurEvent), - h.Attribute("hx-swap", "innerHTML transition:true"), - h.Attribute("hx-target", "next div"), - )) + validation := h.If( + props.ValidationPath != "", + h.Children( + h.Post(props.ValidationPath, hx.BlurEvent), + h.Attribute("hx-swap", "innerHTML transition:true"), + h.Attribute("hx-target", "next div"), + ), + ) if props.Type == "" { props.Type = "text" @@ -32,18 +35,41 @@ func Input(props InputProps) *h.Element { input := h.Input( props.Type, h.Class("border p-2 rounded focus:outline-none focus:ring focus:ring-slate-800"), - h.If(props.Name != "", h.Name(props.Name)), - h.If(props.Children != nil, h.Children(props.Children...)), - h.If(props.Required, h.Required()), - h.If(props.Placeholder != "", h.Placeholder(props.Placeholder)), - h.If(props.DefaultValue != "", h.Attribute("value", props.DefaultValue)), + h.If( + props.Name != "", + h.Name(props.Name), + ), + h.If( + props.Children != nil, + h.Children(props.Children...), + ), + h.If( + props.Required, + h.Required(), + ), + h.If( + props.Placeholder != "", + h.Placeholder(props.Placeholder), + ), + h.If( + props.DefaultValue != "", + h.Attribute("value", props.DefaultValue), + ), validation, ) wrapped := h.Div( - h.If(props.Id != "", h.Id(props.Id)), + h.If( + props.Id != "", + h.Id(props.Id), + ), h.Class("flex flex-col gap-1"), - h.If(props.Label != "", h.Label(h.Text(props.Label))), + h.If( + props.Label != "", + h.Label( + h.Text(props.Label), + ), + ), input, h.Div( h.Id(props.Id+"-error"), diff --git a/examples/chat/pages/chat.$id.go b/examples/chat/pages/chat.$id.go index 493a873c..a06367a8 100644 --- a/examples/chat/pages/chat.$id.go +++ b/examples/chat/pages/chat.$id.go @@ -17,13 +17,10 @@ func ChatRoom(ctx *h.RequestContext) *h.Page { RootPage( h.Div( h.TriggerChildren(), - h.Attribute("sse-connect", fmt.Sprintf("/sse/chat/%s", roomId)), - h.HxOnSseOpen( js.ConsoleLog("Connected to chat room"), ), - h.HxOnSseError( js.EvalJs(fmt.Sprintf(` const reason = e.detail.event.data @@ -38,35 +35,27 @@ func ChatRoom(ctx *h.RequestContext) *h.Page { } `, roomId, roomId)), ), - // Adjusted flex properties for responsive layout h.Class("flex flex-row h-screen bg-neutral-100 overflow-x-hidden"), - // Collapse Button for mobile CollapseButton(), - // Sidebar for connected users UserSidebar(), - h.Div( // Adjusted to fill height and width h.Class("flex flex-col h-full w-full bg-white p-4 overflow-hidden"), - // Room name at the top, fixed CachedRoomHeader(ctx), - h.HxAfterSseMessage( js.EvalJsOnSibling("#messages", `element.scrollTop = element.scrollHeight;`), ), - // Chat Messages h.Div( h.Id("messages"), // Adjusted flex properties and removed max-width h.Class("flex flex-col gap-4 mb-4 overflow-auto flex-grow w-full pt-[50px]"), ), - // Chat Input at the bottom Form(), ), @@ -91,7 +80,10 @@ func roomNameHeader(ctx *h.RequestContext) *h.Element { } return h.Div( h.Class("bg-neutral-700 text-white p-3 shadow-sm w-full fixed top-0 left-0 flex justify-center z-10"), - h.H2F(room.Name, h.Class("text-lg font-bold")), + h.H2F( + room.Name, + h.Class("text-lg font-bold"), + ), h.Div( h.Class("absolute right-5 top-3 cursor-pointer"), h.Text("Share"), @@ -108,7 +100,10 @@ func UserSidebar() *h.Element { return h.Div( h.Class("sidebar h-full pt-[67px] min-w-48 w-48 bg-neutral-200 p-4 flex-col justify-between gap-3 rounded-l-lg hidden md:flex"), h.Div( - h.H3F("Connected Users", h.Class("text-lg font-bold")), + h.H3F( + "Connected Users", + h.Class("text-lg font-bold"), + ), chat.ConnectedUsers(make([]db.User, 0), ""), ), h.A( @@ -121,9 +116,11 @@ func UserSidebar() *h.Element { func CollapseButton() *h.Element { return h.Div( - h.Class("fixed top-0 left-4 md:hidden z-50"), // Always visible on mobile + h.Class("fixed top-0 left-4 md:hidden z-50"), + // Always visible on mobile h.Button( - h.Class("p-2 text-2xl bg-neutral-700 text-white rounded-md"), // Styling the button + h.Class("p-2 text-2xl bg-neutral-700 text-white rounded-md"), + // Styling the button h.OnClick( js.EvalJs(` const sidebar = document.querySelector('.sidebar'); @@ -131,13 +128,15 @@ func CollapseButton() *h.Element { sidebar.classList.toggle('flex'); `), ), - h.UnsafeRaw("☰"), // The icon for collapsing the sidebar + h.UnsafeRaw("☰"), + // The icon for collapsing the sidebar ), ) } func MessageInput() *h.Element { - return h.Input("text", + return h.Input( + "text", h.Id("message-input"), h.Required(), h.Class("p-4 rounded-md border border-slate-200 w-full focus:outline-none focus:ring focus:ring-slate-200"), diff --git a/examples/chat/pages/index.go b/examples/chat/pages/index.go index 571d5b54..229c7d93 100644 --- a/examples/chat/pages/index.go +++ b/examples/chat/pages/index.go @@ -13,12 +13,14 @@ func ChatAppFirstScreen(ctx *h.RequestContext) *h.Page { h.Class("flex flex-col items-center justify-center min-h-screen bg-neutral-100"), h.Div( h.Class("bg-white p-8 rounded-lg shadow-lg w-full max-w-md"), - h.H2F("htmgo chat", h.Class("text-3xl font-bold text-center mb-6")), + h.H2F( + "htmgo chat", + h.Class("text-3xl font-bold text-center mb-6"), + ), h.Form( h.Attribute("hx-swap", "none"), h.PostPartial(partials.CreateOrJoinRoom), h.Class("flex flex-col gap-6"), - // Username input at the top components.Input(components.InputProps{ Id: "username", @@ -30,11 +32,9 @@ func ChatAppFirstScreen(ctx *h.RequestContext) *h.Page { h.MaxLength(15), }, }), - // Single box for Create or Join a Chat Room h.Div( h.Class("p-4 border border-gray-300 rounded-md flex flex-col gap-6"), - // Create New Chat Room input components.Input(components.InputProps{ Name: "new-chat-room", @@ -45,15 +45,20 @@ func ChatAppFirstScreen(ctx *h.RequestContext) *h.Page { h.MaxLength(20), }, }), - // OR divider h.Div( h.Class("flex items-center justify-center gap-4"), - h.Div(h.Class("border-t border-gray-300 flex-grow")), - h.P(h.Text("OR"), h.Class("text-gray-500")), - h.Div(h.Class("border-t border-gray-300 flex-grow")), + h.Div( + h.Class("border-t border-gray-300 flex-grow"), + ), + h.P( + h.Text("OR"), + h.Class("text-gray-500"), + ), + h.Div( + h.Class("border-t border-gray-300 flex-grow"), + ), ), - // Join Chat Room input components.Input(components.InputProps{ Id: "join-chat-room", @@ -67,10 +72,8 @@ func ChatAppFirstScreen(ctx *h.RequestContext) *h.Page { }, }), ), - // Error message components.FormError(""), - // Submit button at the bottom components.PrimaryButton(components.ButtonProps{ Type: "submit", diff --git a/examples/hackernews/partials/comments.go b/examples/hackernews/partials/comments.go index c0583fee..95ce923d 100644 --- a/examples/hackernews/partials/comments.go +++ b/examples/hackernews/partials/comments.go @@ -13,7 +13,12 @@ import ( func StoryComments(ctx *h.RequestContext) *h.Partial { return h.NewPartial( h.Fragment( - h.OobSwap(ctx, h.Div(h.Id("comments-loader"))), + h.OobSwap( + ctx, + h.Div( + h.Id("comments-loader"), + ), + ), h.Div( h.Class("flex flex-col gap-3 prose max-w-none"), CachedStoryComments(news.MustItemId(ctx)), @@ -57,9 +62,15 @@ func Comment(item news.Comment, nesting int) *h.Element { "border-b border-gray-200": nesting == 0, "border-l border-gray-200": nesting > 0, }), - h.If(nesting > 0, h.Attribute("style", fmt.Sprintf("margin-left: %dpx", (nesting-1)*15))), + h.If( + nesting > 0, + h.Attribute("style", fmt.Sprintf("margin-left: %dpx", (nesting-1)*15)), + ), h.Div( - h.If(nesting > 0, h.Class("pl-4")), + h.If( + nesting > 0, + h.Class("pl-4"), + ), h.Div( h.Class("flex gap-1 items-center"), h.Div( @@ -77,12 +88,15 @@ func Comment(item news.Comment, nesting int) *h.Element { h.UnsafeRaw(strings.TrimSpace(item.Text)), ), ), - h.If(len(children) > 0, h.List( - children, func(child news.Comment, index int) *h.Element { - return h.Div( - Comment(child, nesting+1), - ) - }, - )), + h.If( + len(children) > 0, + h.List( + children, func(child news.Comment, index int) *h.Element { + return h.Div( + Comment(child, nesting+1), + ) + }, + ), + ), ) } diff --git a/examples/hackernews/partials/sidebar.go b/examples/hackernews/partials/sidebar.go index c850d09b..d3bbef0b 100644 --- a/examples/hackernews/partials/sidebar.go +++ b/examples/hackernews/partials/sidebar.go @@ -57,13 +57,18 @@ func StorySidebar(ctx *h.RequestContext) *h.Partial { page := parse.MustParseInt(pageRaw, 0) - fetchMorePath := h.GetPartialPathWithQs(StorySidebar, h.NewQs("mode", "infinite", "page", fmt.Sprintf("%d", page+1), "category", category)) + fetchMorePath := h.GetPartialPathWithQs( + StorySidebar, + h.NewQs("mode", "infinite", "page", fmt.Sprintf("%d", page+1), "category", category), + ) list := CachedStoryList(category, page, 50, fetchMorePath) body := h.Aside( h.Id("story-sidebar"), - h.JoinExtensions(h.TriggerChildren()), + h.JoinExtensions( + h.TriggerChildren(), + ), h.Class("sticky top-0 h-screen p-1 bg-gray-100 overflow-y-auto max-w-80 min-w-80"), h.Div( h.Class("flex flex-col gap-1"), @@ -99,7 +104,9 @@ func SidebarTitle(defaultCategory string) *h.Element { h.Text("Hacker News"), ), h.Div( - h.OnLoad(h.EvalJs(ScrollJs)), + h.OnLoad( + h.EvalJs(ScrollJs), + ), h.Class("scroll-container mt-2 flex gap-1 no-scrollbar overflow-y-hidden whitespace-nowrap overflow-x-auto"), h.List(news.Categories, func(item news.Category, index int) *h.Element { return CategoryBadge(defaultCategory, item) @@ -114,7 +121,13 @@ func CategoryBadge(defaultCategory string, category news.Category) *h.Element { category.Name, selected, h.Attribute("hx-swap", "none"), - h.If(!selected, h.PostPartialOnClickQs(StorySidebar, h.NewQs("category", category.Path))), + h.If( + !selected, + h.PostPartialOnClickQs( + StorySidebar, + h.NewQs("category", category.Path), + ), + ), ) } diff --git a/examples/simple-auth/pages/index.go b/examples/simple-auth/pages/index.go index 364f0f3c..f159adce 100644 --- a/examples/simple-auth/pages/index.go +++ b/examples/simple-auth/pages/index.go @@ -24,14 +24,16 @@ func UserProfilePage(u db.User) *h.Element { return h.Div( h.Class("flex flex-col gap-6 items-center pt-10 min-h-screen bg-neutral-100"), - h.H3F("User Profile", h.Class("text-2xl font-bold")), + h.H3F( + "User Profile", + h.Class("text-2xl font-bold"), + ), h.Pf("Welcome, %s!", u.Email), h.Form( h.Attribute("hx-swap", "none"), h.PostPartial(partials.UpdateProfile), h.TriggerChildren(), h.Class("flex flex-col gap-4 w-full max-w-md p-6 bg-white rounded-md shadow-md"), - ui.Input(ui.InputProps{ Id: "email", Name: "email", @@ -42,26 +44,22 @@ func UserProfilePage(u db.User) *h.Element { h.Disabled(), }, }), - ui.Input(ui.InputProps{ Name: "birth-date", Label: "Birth Date", DefaultValue: user.GetMetaKey(meta, "birthDate"), Type: "date", }), - ui.Input(ui.InputProps{ Name: "favorite-color", Label: "Favorite Color", DefaultValue: user.GetMetaKey(meta, "favoriteColor"), }), - ui.Input(ui.InputProps{ Name: "occupation", Label: "Occupation", DefaultValue: user.GetMetaKey(meta, "occupation"), }), - ui.FormError(""), ui.SubmitButton("Save Changes"), ), diff --git a/examples/simple-auth/ui/error.go b/examples/simple-auth/ui/error.go index a410e133..47d1eac6 100644 --- a/examples/simple-auth/ui/error.go +++ b/examples/simple-auth/ui/error.go @@ -6,7 +6,10 @@ func FormError(error string) *h.Element { return h.Div( h.Id("form-error"), h.Text(error), - h.If(error != "", h.Class("p-4 bg-rose-400 text-white rounded")), + h.If( + error != "", + h.Class("p-4 bg-rose-400 text-white rounded"), + ), ) } diff --git a/examples/simple-auth/ui/input.go b/examples/simple-auth/ui/input.go index f4657662..6e302fbe 100644 --- a/examples/simple-auth/ui/input.go +++ b/examples/simple-auth/ui/input.go @@ -19,11 +19,14 @@ type InputProps struct { } func Input(props InputProps) *h.Element { - validation := h.If(props.ValidationPath != "", h.Children( - h.Post(props.ValidationPath, hx.BlurEvent), - h.Attribute("hx-swap", "innerHTML transition:true"), - h.Attribute("hx-target", "next div"), - )) + validation := h.If( + props.ValidationPath != "", + h.Children( + h.Post(props.ValidationPath, hx.BlurEvent), + h.Attribute("hx-swap", "innerHTML transition:true"), + h.Attribute("hx-target", "next div"), + ), + ) if props.Type == "" { props.Type = "text" @@ -32,18 +35,41 @@ func Input(props InputProps) *h.Element { input := h.Input( props.Type, h.Class("border p-2 rounded focus:outline-none focus:ring focus:ring-slate-800"), - h.If(props.Name != "", h.Name(props.Name)), - h.If(props.Children != nil, h.Children(props.Children...)), - h.If(props.Required, h.Required()), - h.If(props.Placeholder != "", h.Placeholder(props.Placeholder)), - h.If(props.DefaultValue != "", h.Attribute("value", props.DefaultValue)), + h.If( + props.Name != "", + h.Name(props.Name), + ), + h.If( + props.Children != nil, + h.Children(props.Children...), + ), + h.If( + props.Required, + h.Required(), + ), + h.If( + props.Placeholder != "", + h.Placeholder(props.Placeholder), + ), + h.If( + props.DefaultValue != "", + h.Attribute("value", props.DefaultValue), + ), validation, ) wrapped := h.Div( - h.If(props.Id != "", h.Id(props.Id)), + h.If( + props.Id != "", + h.Id(props.Id), + ), h.Class("flex flex-col gap-1"), - h.If(props.Label != "", h.Label(h.Text(props.Label))), + h.If( + props.Label != "", + h.Label( + h.Text(props.Label), + ), + ), input, h.Div( h.Id(props.Id+"-error"), diff --git a/examples/simple-auth/ui/login.go b/examples/simple-auth/ui/login.go index 93a2d973..50cf046c 100644 --- a/examples/simple-auth/ui/login.go +++ b/examples/simple-auth/ui/login.go @@ -16,7 +16,10 @@ func CenteredForm(props CenteredFormProps) *h.Element { h.Class("flex flex-col items-center justify-center min-h-screen bg-neutral-100"), h.Div( h.Class("bg-white p-8 rounded-lg shadow-lg w-full max-w-md"), - h.H2F(props.Title, h.Class("text-3xl font-bold text-center mb-6")), + h.H2F( + props.Title, + h.Class("text-3xl font-bold text-center mb-6"), + ), h.Form( h.TriggerChildren(), h.Post(props.PostUrl), diff --git a/examples/todo-list/pages/index.go b/examples/todo-list/pages/index.go index 65b5cacb..2cc736d0 100644 --- a/examples/todo-list/pages/index.go +++ b/examples/todo-list/pages/index.go @@ -10,7 +10,10 @@ import ( func TaskListPage(ctx *h.RequestContext) *h.Page { title := h.Div( - h.H1(h.Class("text-7xl font-extralight text-rose-500 tracking-wide"), h.Text("todos")), + h.H1( + h.Class("text-7xl font-extralight text-rose-500 tracking-wide"), + h.Text("todos"), + ), ) return h.NewPage(base.RootPage( @@ -21,7 +24,9 @@ func TaskListPage(ctx *h.RequestContext) *h.Page { title, task.Card(ctx), h.Children( - h.Div(h.Text("Double-click to edit a todo")), + h.Div( + h.Text("Double-click to edit a todo"), + ), ), ), ), diff --git a/examples/todo-list/partials/task/task.go b/examples/todo-list/partials/task/task.go index 29498ab3..f307a577 100644 --- a/examples/todo-list/partials/task/task.go +++ b/examples/todo-list/partials/task/task.go @@ -58,7 +58,9 @@ func Input(list []*ent.Task) *h.Element { h.Name("name"), h.Class("pl-12 text-xl p-4 w-full outline-none focus:outline-2 focus:outline-rose-400"), h.Placeholder("What needs to be done?"), - h.Post(h.GetPartialPath(Create)), + h.Post( + h.GetPartialPath(Create), + ), h.HxTrigger(hx.OnEvent(hx.TriggerKeyUpEnter)), ), CompleteAllIcon(list), @@ -66,23 +68,34 @@ func Input(list []*ent.Task) *h.Element { } func CompleteAllIcon(list []*ent.Task) *h.Element { - notCompletedCount := len(h.Filter(list, func(item *ent.Task) bool { - return item.CompletedAt == nil - })) + notCompletedCount := len( + h.Filter(list, func(item *ent.Task) bool { + return item.CompletedAt == nil + }), + ) return h.Div( h.ClassX("absolute top-1 left-5 p-2 rotate-90 text-3xl cursor-pointer", map[string]bool{ "text-slate-400": notCompletedCount > 0, - }), h.UnsafeRaw("›"), - h.PostPartialWithQs(CompleteAll, h.NewQs("complete", h.Ternary(notCompletedCount > 0, "true", "false"))), + }), + h.UnsafeRaw("›"), + h.PostPartialWithQs( + CompleteAll, + h.NewQs( + "complete", + h.Ternary(notCompletedCount > 0, "true", "false"), + ), + ), ) } func Footer(list []*ent.Task, activeTab Tab) *h.Element { - notCompletedCount := len(h.Filter(list, func(item *ent.Task) bool { - return item.CompletedAt == nil - })) + notCompletedCount := len( + h.Filter(list, func(item *ent.Task) bool { + return item.CompletedAt == nil + }), + ) tabs := []Tab{TabAll, TabActive, TabComplete} @@ -96,7 +109,12 @@ func Footer(list []*ent.Task, activeTab Tab) *h.Element { h.Class("flex items-center gap-4"), h.List(tabs, func(tab Tab, index int) *h.Element { return h.P( - h.PostOnClick(h.GetPartialPathWithQs(ChangeTab, h.NewQs("tab", tab))), + h.PostOnClick( + h.GetPartialPathWithQs( + ChangeTab, + h.NewQs("tab", tab), + ), + ), h.ClassX("cursor-pointer px-2 py-1 rounded", map[string]bool{ "border border-rose-600": activeTab == tab, }), @@ -139,12 +157,14 @@ func Task(task *ent.Task, editing bool) *h.Element { "border border-b-slate-100": !editing, }), CompleteIcon(task), - h.IfElse(editing, + h.IfElse( + editing, h.Div( h.Class("flex-1 h-full"), h.Form( h.Class("h-full"), - h.Input("text", + h.Input( + "text", h.Name("task"), h.Value(task.ID.String()), h.Class("hidden"), @@ -168,30 +188,43 @@ func Task(task *ent.Task, editing bool) *h.Element { ), ), h.P( - h.GetPartialWithQs(EditNameForm, h.NewQs("id", task.ID.String()), hx.TriggerDblClick), + h.GetPartialWithQs( + EditNameForm, + h.NewQs("id", task.ID.String()), + hx.TriggerDblClick, + ), h.ClassX("text-xl break-all text-wrap truncate", map[string]bool{ "line-through text-slate-400": task.CompletedAt != nil, }), h.Text(task.Name), - )), + ), + ), ) } func CompleteIcon(task *ent.Task) *h.Element { return h.Div( h.HxTrigger(hx.OnClick()), - h.Post(h.GetPartialPathWithQs(ToggleCompleted, h.NewQs("id", task.ID.String()))), + h.Post( + h.GetPartialPathWithQs( + ToggleCompleted, + h.NewQs("id", task.ID.String()), + ), + ), h.Class("flex items-center justify-center cursor-pointer"), h.Div( h.ClassX("w-10 h-10 border rounded-full flex items-center justify-center", map[string]bool{ "border-green-500": task.CompletedAt != nil, "border-slate-400": task.CompletedAt == nil, }), - h.If(task.CompletedAt != nil, h.UnsafeRaw(` + h.If( + task.CompletedAt != nil, + h.UnsafeRaw(` - `)), + `), + ), ), ) } @@ -199,46 +232,75 @@ func CompleteIcon(task *ent.Task) *h.Element { func UpdateName(ctx *h.RequestContext) *h.Partial { id, err := uuid.Parse(ctx.FormValue("task")) if err != nil { - return h.NewPartial(h.Div(h.Text("invalid id"))) + return h.NewPartial( + h.Div( + h.Text("invalid id"), + ), + ) } name := ctx.FormValue("name") if name == "" { - return h.NewPartial(h.Div(h.Text("name is required"))) + return h.NewPartial( + h.Div( + h.Text("name is required"), + ), + ) } if len(name) > 150 { - return h.NewPartial(h.Div(h.Text("task must be less than 150 characters"))) + return h.NewPartial( + h.Div( + h.Text("task must be less than 150 characters"), + ), + ) } service := tasks.NewService(ctx) task, err := service.Get(id) if task == nil { - return h.NewPartial(h.Div(h.Text("task not found"))) + return h.NewPartial( + h.Div( + h.Text("task not found"), + ), + ) } task, err = service.SetName(task.ID, name) if err != nil { - return h.NewPartial(h.Div(h.Text("failed to update"))) + return h.NewPartial( + h.Div( + h.Text("failed to update"), + ), + ) } return h.NewPartial( - h.OobSwap(ctx, Task(task, false))) + h.OobSwap(ctx, Task(task, false)), + ) } func EditNameForm(ctx *h.RequestContext) *h.Partial { id, err := uuid.Parse(ctx.QueryParam("id")) if err != nil { - return h.NewPartial(h.Div(h.Text("invalid id"))) + return h.NewPartial( + h.Div( + h.Text("invalid id"), + ), + ) } service := tasks.NewService(ctx) task, err := service.Get(id) if task == nil { - return h.NewPartial(h.Div(h.Text("task not found"))) + return h.NewPartial( + h.Div( + h.Text("task not found"), + ), + ) } return h.NewPartial( @@ -249,21 +311,36 @@ func EditNameForm(ctx *h.RequestContext) *h.Partial { func ToggleCompleted(ctx *h.RequestContext) *h.Partial { id, err := uuid.Parse(ctx.QueryParam("id")) if err != nil { - return h.NewPartial(h.Div(h.Text("invalid id"))) + return h.NewPartial( + h.Div( + h.Text("invalid id"), + ), + ) } service := tasks.NewService(ctx) task, err := service.Get(id) if task == nil { - return h.NewPartial(h.Div(h.Text("task not found"))) + return h.NewPartial( + h.Div( + h.Text("task not found"), + ), + ) } - task, err = service.SetCompleted(task.ID, h. - Ternary(task.CompletedAt == nil, true, false)) + task, err = service.SetCompleted( + task.ID, + h. + Ternary(task.CompletedAt == nil, true, false), + ) if err != nil { - return h.NewPartial(h.Div(h.Text("failed to update"))) + return h.NewPartial( + h.Div( + h.Text("failed to update"), + ), + ) } list, _ := service.List() @@ -282,7 +359,9 @@ func CompleteAll(ctx *h.RequestContext) *h.Partial { list, _ := service.List() - return h.NewPartial(h.OobSwap(ctx, CardBody(list, getActiveTab(ctx)))) + return h.NewPartial( + h.OobSwap(ctx, CardBody(list, getActiveTab(ctx))), + ) } func ClearCompleted(ctx *h.RequestContext) *h.Partial { @@ -291,7 +370,9 @@ func ClearCompleted(ctx *h.RequestContext) *h.Partial { list, _ := service.List() - return h.NewPartial(h.OobSwap(ctx, CardBody(list, getActiveTab(ctx)))) + return h.NewPartial( + h.OobSwap(ctx, CardBody(list, getActiveTab(ctx))), + ) } func Create(ctx *h.RequestContext) *h.Partial { @@ -300,7 +381,9 @@ func Create(ctx *h.RequestContext) *h.Partial { if len(name) > 150 { return h.NewPartial( h.Div( - h.HxOnLoad(js.Alert("Task must be less than 150 characters")), + h.HxOnLoad( + js.Alert("Task must be less than 150 characters"), + ), ), ) } @@ -312,7 +395,9 @@ func Create(ctx *h.RequestContext) *h.Partial { if list != nil && len(list) >= 100 { return h.NewPartial( h.Div( - h.HxOnLoad(js.Alert("There are too many tasks, please complete and clear some.")), + h.HxOnLoad( + js.Alert("There are too many tasks, please complete and clear some."), + ), ), ) } @@ -322,7 +407,11 @@ func Create(ctx *h.RequestContext) *h.Partial { }) if err != nil { - return h.NewPartial(h.Div(h.Text("failed to create"))) + return h.NewPartial( + h.Div( + h.Text("failed to create"), + ), + ) } list, err = service.List() @@ -338,8 +427,12 @@ func ChangeTab(ctx *h.RequestContext) *h.Partial { tab := ctx.QueryParam("tab") - return h.SwapManyPartialWithHeaders(ctx, - h.PushQsHeader(ctx, h.NewQs("tab", tab)), + return h.SwapManyPartialWithHeaders( + ctx, + h.PushQsHeader( + ctx, + h.NewQs("tab", tab), + ), List(list, tab), Footer(list, tab), ) diff --git a/htmgo-site/pages/base/root.go b/htmgo-site/pages/base/root.go index 4b0e1851..0fd37e46 100644 --- a/htmgo-site/pages/base/root.go +++ b/htmgo-site/pages/base/root.go @@ -13,7 +13,9 @@ func RootPage(ctx *h.RequestContext, children ...h.Ren) *h.Element { description := "build simple and scalable systems with go + htmx" return h.Html( - h.HxExtension(h.BaseExtensions()), + h.HxExtension( + h.BaseExtensions(), + ), h.Head( h.Meta("viewport", "width=device-width, initial-scale=1"), h.Meta("title", title), @@ -54,7 +56,8 @@ func RootPage(ctx *h.RequestContext, children ...h.Ren) *h.Element { } func PageWithNav(ctx *h.RequestContext, children ...h.Ren) *h.Element { - return RootPage(ctx, + return RootPage( + ctx, h.Fragment( partials.NavBar(ctx, partials.NavBarProps{ Expanded: false, diff --git a/htmgo-site/pages/docs.go b/htmgo-site/pages/docs.go index f85e689c..c8a35721 100644 --- a/htmgo-site/pages/docs.go +++ b/htmgo-site/pages/docs.go @@ -43,7 +43,8 @@ func DocsPage(ctx *h.RequestContext) *h.Page { MarkdownContent(ctx, page.FilePath, anchor), h.Div( h.Class("ml-4 pl-1 mt-2 bg-rose-200"), - h.If(anchor == "core-concepts-partials", + h.If( + anchor == "core-concepts-partials", h.GetPartial(partials.CurrentTimePartial, "load, every 1s"), ), ), diff --git a/htmgo-site/pages/examples.go b/htmgo-site/pages/examples.go index 719a87a9..ceefac99 100644 --- a/htmgo-site/pages/examples.go +++ b/htmgo-site/pages/examples.go @@ -57,31 +57,34 @@ var examples = []Example{ func ExamplesPage(ctx *h.RequestContext) *h.Page { return h.NewPage( - base.PageWithNav(ctx, h.Div( - h.Class("flex items-center justify-center"), + base.PageWithNav( + ctx, h.Div( - h.Class("w-full px-4 flex flex-col prose max-w-[95vw] md:max-w-3xl mt-6"), + h.Class("flex items-center justify-center"), h.Div( - h.Class("flex flex-col mb-6 md:mb-0 md:flex-row justify-between items-center"), + h.Class("w-full px-4 flex flex-col prose max-w-[95vw] md:max-w-3xl mt-6"), h.Div( - h.H1( - h.Class("text-center md:text-left"), - h.Text("htmgo examples"), - ), - h.H3( - h.Class("-mt-4"), - h.TextF("example projects built with htmgo"), + h.Class("flex flex-col mb-6 md:mb-0 md:flex-row justify-between items-center"), + h.Div( + h.H1( + h.Class("text-center md:text-left"), + h.Text("htmgo examples"), + ), + h.H3( + h.Class("-mt-4"), + h.TextF("example projects built with htmgo"), + ), ), ), - ), - h.Div( - h.Class("border-b border-b-slate-200 h-1"), h.Div( - h.Class("mt-4"), - ExampleCards(), + h.Class("border-b border-b-slate-200 h-1"), + h.Div( + h.Class("mt-4"), + ExampleCards(), + ), ), ), - )), + ), ), ) } @@ -91,26 +94,32 @@ func ExampleCards() *h.Element { h.Class("prose-h2:my-1 prose-img:my-1 grid grid-cols-1 gap-6 text-center pb-8"), h.List(examples, func(example Example, index int) *h.Element { return h.Div( - h.Class("border border-gray-200 shadow-sm rounded-md px-4 pb-4 bg-neutral-100"), // Removed specific width, handled by grid + h.Class("border border-gray-200 shadow-sm rounded-md px-4 pb-4 bg-neutral-100"), h.Div( h.Class("flex flex-col gap-1 mt-4"), h.H2( - h.Class("text-lg text-center mb-1"), // Reduced margin at the bottom of the title + h.Class("text-lg text-center mb-1"), h.Text(example.Title), ), - h.If(example.Image != "", h.Div( - h.A( - h.Href(example.Demo), - h.Class("not-prose"), - h.Img( - h.Src(example.Image), - h.Class("w-[75%] rounded-md mx-auto"), + h.If( + example.Image != "", + h.Div( + h.A( + h.Href(example.Demo), + h.Class("not-prose"), + h.Img( + h.Src(example.Image), + h.Class("w-[75%] rounded-md mx-auto"), + ), ), - ), // Ensures image is centered within the card - )), - h.If(example.Description != "", h.Div( - h.Pf(example.Description), - )), + ), + ), + h.If( + example.Description != "", + h.Div( + h.Pf(example.Description), + ), + ), h.Div( h.Div( h.Class("flex gap-2 justify-center mt-2"), diff --git a/htmgo-site/pages/form.go b/htmgo-site/pages/form.go index 94150753..fdd9d24e 100644 --- a/htmgo-site/pages/form.go +++ b/htmgo-site/pages/form.go @@ -9,21 +9,29 @@ import ( ) func Form(ctx *h.RequestContext) *h.Page { - return h.NewPage(base.RootPage(ctx, + return h.NewPage(base.RootPage( + ctx, h.Div( h.Class("flex flex-col items-center justify-center p-4 gap-6"), - h.H2F("Form submission with loading state example", h.Class("text-2xl font-bold")), + h.H2F( + "Form submission with loading state example", + h.Class("text-2xl font-bold"), + ), h.Form( h.TriggerChildren(), h.PostPartial(partials.SubmitForm), h.Class("flex flex-col gap-2"), h.LabelFor("name", "Your Name"), - h.Input("text", + h.Input( + "text", h.Required(), h.Class("p-4 rounded-md border border-slate-200"), h.Name("name"), h.Placeholder("Name"), - h.OnEvent(hx.KeyDownEvent, js.SubmitFormOnEnter()), + h.OnEvent( + hx.KeyDownEvent, + js.SubmitFormOnEnter(), + ), ), SubmitButton(), ), diff --git a/htmgo-site/pages/html-to-go.go b/htmgo-site/pages/html-to-go.go index 0a2d6f23..face609e 100644 --- a/htmgo-site/pages/html-to-go.go +++ b/htmgo-site/pages/html-to-go.go @@ -8,7 +8,8 @@ import ( func HtmlToGoPage(ctx *h.RequestContext) *h.Page { return h.NewPage( - base.PageWithNav(ctx, + base.PageWithNav( + ctx, h.Div( h.Class("flex flex-col h-screen items-center justify-center w-full pt-6"), h.H3( diff --git a/htmgo-site/pages/index.go b/htmgo-site/pages/index.go index 8470795f..1b08002f 100644 --- a/htmgo-site/pages/index.go +++ b/htmgo-site/pages/index.go @@ -7,37 +7,43 @@ import ( func IndexPage(ctx *h.RequestContext) *h.Page { return h.NewPage( - base.PageWithNav(ctx, h.Div( - h.Class("flex items-center justify-center"), + base.PageWithNav( + ctx, h.Div( - h.Class("w-full px-4 flex flex-col prose md:max-w-3xl mt-6 mx-auto"), + h.Class("flex items-center justify-center"), h.Div( - h.Class("flex flex-col mb-6 md:mb-0 md:flex-row justify-between items-center"), + h.Class("w-full px-4 flex flex-col prose md:max-w-3xl mt-6 mx-auto"), h.Div( - h.H1F("htmgo", h.Class("text-center md:text-left")), - h.H3F( - "build simple and scalable systems with %s", - "go + htmx", - h.Class("-mt-4"), + h.Class("flex flex-col mb-6 md:mb-0 md:flex-row justify-between items-center"), + h.Div( + h.H1F( + "htmgo", + h.Class("text-center md:text-left"), + ), + h.H3F( + "build simple and scalable systems with %s", + "go + htmx", + h.Class("-mt-4"), + ), ), - ), - h.Div( - h.Class("mt-2"), - h.A( - h.Href("/docs"), - h.Class("not-prose p-3 bg-slate-900 text-white rounded-md"), - h.Text("Get Started"), + h.Div( + h.Class("mt-2"), + h.A( + h.Href("/docs"), + h.Class("not-prose p-3 bg-slate-900 text-white rounded-md"), + h.Text("Get Started"), + ), ), ), - ), - h.Div( - h.Class("border-b border-b-slate-200 h-1"), h.Div( - h.Class("mt-4"), - MarkdownPage(ctx, "md/index.md", ""), + h.Class("border-b border-b-slate-200 h-1"), + h.Div( + h.Class("mt-4"), + MarkdownPage(ctx, "md/index.md", ""), + ), ), ), - )), + ), ), ) } diff --git a/htmgo-site/pages/markdown.go b/htmgo-site/pages/markdown.go index 6bdc9331..eff2c6af 100644 --- a/htmgo-site/pages/markdown.go +++ b/htmgo-site/pages/markdown.go @@ -20,7 +20,10 @@ func MarkdownContent(ctx *h.RequestContext, path string, id string) *h.Element { embeddedMd := ctx.Get("embeddedMarkdown").(fs.FS) renderer := service.Get[markdown.Renderer](ctx.ServiceLocator()) return h.Div( - h.If(id != "", h.Id(id)), + h.If( + id != "", + h.Id(id), + ), h.Div( h.Class("w-full flex flex-col prose max-w-md md:max-w-xl lg:max-w-3xl prose-code:text-black prose-p:my-1 prose:p-0 prose-li:m-0 prose-ul:m-0 prose-ol:m-0"), h.UnsafeRaw(renderer.RenderFile(path, embeddedMd)), diff --git a/htmgo-site/pages/test.go b/htmgo-site/pages/test.go new file mode 100644 index 00000000..e868461b --- /dev/null +++ b/htmgo-site/pages/test.go @@ -0,0 +1,59 @@ +package pages + +import ( + "fmt" + "github.com/maddalax/htmgo/framework/h" + "htmgo-site/pages/base" +) + +func TestFormatPage(ctx *h.RequestContext) *h.Page { + return h.NewPage( + base.RootPage( + ctx, + h.Div( + h.P( + h.Class("hello"), + h.Details( + h.Summary( + h.Text("Summary"), + ), + h.Text("Details"), + ), + h.Id("hi"), + ), + ), + ), + ) +} + +func notPage() int { + test := 1 + fmt.Printf("test: %d\n", test) + return test +} + +func TestOtherPage(ctx *h.RequestContext) *h.Page { + + return h.NewPage( + base.RootPage( + ctx, + h.Div( + h.Id("test"), + h.Details( + h.Summary( + h.Text("Summary"), + ), + h.Text("Details"), + ), + h.Class("flex flex-col gap-2 bg-white h-full"), + h.Id("test"), + h.Details( + h.Summary( + h.Text("Summary"), + ), + h.Text("Details"), + ), + ), + ), + ) +} diff --git a/htmgo-site/partials/form.go b/htmgo-site/partials/form.go index 4934822e..8ef0b753 100644 --- a/htmgo-site/partials/form.go +++ b/htmgo-site/partials/form.go @@ -8,6 +8,8 @@ import ( func SubmitForm(ctx *h.RequestContext) *h.Partial { time.Sleep(time.Second * 3) return h.NewPartial( - h.Div(h.Text("Form submitted")), + h.Div( + h.Text("Form submitted"), + ), ) } diff --git a/htmgo-site/partials/html-to-go.go b/htmgo-site/partials/html-to-go.go index 358f8686..00945e5d 100644 --- a/htmgo-site/partials/html-to-go.go +++ b/htmgo-site/partials/html-to-go.go @@ -41,24 +41,27 @@ func GoOutput(content string) *h.Element { h.Id("go-output-content"), h.UnsafeRaw(content), ), - h.If(content != "", h.Div( - - h.Class("absolute top-0 right-0 p-2 bg-slate-800 text-white rounded-bl-md cursor-pointer"), - h.Text("Copy"), - // language=JavaScript - h.OnClick(js.EvalJs(` - if(!navigator.clipboard) { - alert("Clipboard API not supported"); - return; - } - let text = self.parentElement.querySelector("#go-output-content").innerText; - navigator.clipboard.writeText(text); - self.innerText = "Copied!"; - setTimeout(() => { - self.innerText = "Copy"; - }, 1000); - `)), - )), + h.If( + content != "", + h.Div( + h.Class("absolute top-0 right-0 p-2 bg-slate-800 text-white rounded-bl-md cursor-pointer"), + h.Text("Copy"), + h.OnClick( + js.EvalJs(` + if(!navigator.clipboard) { + alert("Clipboard API not supported"); + return; + } + let text = self.parentElement.querySelector("#go-output-content").innerText; + navigator.clipboard.writeText(text); + self.innerText = "Copied!"; + setTimeout(() => { + self.innerText = "Copy"; + }, 1000); + `), + ), + ), + ), ), ) } diff --git a/htmgo-site/partials/navbar.go b/htmgo-site/partials/navbar.go index 1b438cfb..75c6b481 100644 --- a/htmgo-site/partials/navbar.go +++ b/htmgo-site/partials/navbar.go @@ -58,29 +58,26 @@ func Star(ctx *h.RequestContext) *h.Element { h.Class("w-4 h-4 -mt-0.5 mr-0.5 stroke-current text-white"), h.Attribute("xmlns", "http://www.w3.org/2000/svg"), h.Attribute("viewBox", "0 0 24 24"), - h.Attribute("fill", "none"), // No fill - h.Attribute("stroke", "currentColor"), // Apply stroke - h.Attribute("stroke-width", "2"), // Stroke width + h.Attribute("fill", "none"), + h.Attribute("stroke", "currentColor"), + h.Attribute("stroke-width", "2"), h.Path( h.D("M12 17.27l5.18 3.05-1.64-5.68 4.46-3.87-5.88-.5L12 3.5l-2.12 6.77-5.88.5 4.46 3.87-1.64 5.68L12 17.27z"), ), ), h.Text("Star"), ), - h.If(count > 0, h.Div( - h.Class("flex items-center px-3 py-1 bg-black text-white text-sm font-semibold"), - h.Pf("%d", count), - )), + h.If( + count > 0, + h.Div( + h.Class("flex items-center px-3 py-1 bg-black text-white text-sm font-semibold"), + h.Pf("%d", count), + ), + ), ) } func NavBar(ctx *h.RequestContext, props NavBarProps) *h.Element { - //prelease := h.If(props.ShowPreRelease, h.A( - // h.Class("bg-blue-200 text-blue-700 text-center p-2 flex items-center justify-center"), - // h.Href("https://github.com/maddalax/htmgo/issues"), - // h.Attribute("target", "_blank"), - // h.Text("htmgo."), - //)) desktopNav := h.Nav( h.Class("hidden sm:block bg-neutral-100 border border-b-slate-300 p-4 md:p-3 max-h-[100vh - 9rem] overflow-y-auto"), @@ -94,7 +91,8 @@ func NavBar(ctx *h.RequestContext, props NavBarProps) *h.Element { h.Class("text-2xl"), h.Href("/"), h.Text("htmgo"), - )), + ), + ), h.Div( h.Id("search-container"), ), @@ -118,7 +116,6 @@ func NavBar(ctx *h.RequestContext, props NavBarProps) *h.Element { return h.Div( h.Id("navbar"), - //prelease, MobileNav(ctx, props.Expanded), desktopNav, ) @@ -139,43 +136,54 @@ func MobileNav(ctx *h.RequestContext, expanded bool) *h.Element { h.Class("text-2xl"), h.Href("/"), h.Text("htmgo"), - )), + ), + ), h.Div( h.Class("flex items-center gap-3"), - h.Div(h.Class("mt-1"), CachedStar(ctx)), + h.Div( + h.Class("mt-1"), + CachedStar(ctx), + ), h.Button( h.Boost(), - h.GetPartialWithQs( ToggleNavbar, - h.NewQs("expanded", h.Ternary(expanded, "false", "true"), "test", "true"), + h.NewQs( + "expanded", + h.Ternary(expanded, "false", "true"), + "test", + "true", + ), "click", ), - h.AttributePairs( - "class", "text-2xl", - "aria-expanded", h.Ternary(expanded, "true", "false"), + "class", + "text-2xl", + "aria-expanded", + h.Ternary(expanded, "true", "false"), ), - h.Class("text-2xl"), h.UnsafeRaw("☰"), ), ), ), ), - h.If(expanded, h.Div( - h.Class("mt-2 ml-2 flex flex-col gap-2"), - h.List(navItems, func(item NavItem, index int) *h.Element { - return h.Div( - h.Class("flex items-center"), - h.A( - h.Boost(), - h.Class(""), - h.Href(item.Url), - h.Text(item.Name), - ), - ) - }), - )), + h.If( + expanded, + h.Div( + h.Class("mt-2 ml-2 flex flex-col gap-2"), + h.List(navItems, func(item NavItem, index int) *h.Element { + return h.Div( + h.Class("flex items-center"), + h.A( + h.Boost(), + h.Class(""), + h.Href(item.Url), + h.Text(item.Name), + ), + ) + }), + ), + ), ) } diff --git a/htmgo-site/partials/sidebar.go b/htmgo-site/partials/sidebar.go index 8387f488..a3447c6b 100644 --- a/htmgo-site/partials/sidebar.go +++ b/htmgo-site/partials/sidebar.go @@ -71,7 +71,10 @@ func DocSidebar(pages []*dirwalk.Page) *h.Element { h.Class("flex flex-col gap-4"), h.List(grouped.Entries(), func(entry datastructures.Entry[string, []*dirwalk.Page], index int) *h.Element { return h.Div( - h.P(h.Text(formatPart(entry.Key)), h.Class("text-slate-800 font-bold")), + h.P( + h.Text(formatPart(entry.Key)), + h.Class("text-slate-800 font-bold"), + ), h.Div( h.Class("pl-4 flex flex-col"), h.List(entry.Value, func(page *dirwalk.Page, index int) *h.Element { diff --git a/templates/starter/pages/index.go b/templates/starter/pages/index.go index 08c6f17b..7c81c8ac 100644 --- a/templates/starter/pages/index.go +++ b/templates/starter/pages/index.go @@ -9,7 +9,11 @@ func IndexPage(ctx *h.RequestContext) *h.Page { return RootPage( h.Div( h.Class("flex flex-col gap-4 items-center pt-24 min-h-screen bg-neutral-100"), - h.H3(h.Id("intro-text"), h.Text("hello htmgo"), h.Class("text-5xl")), + h.H3( + h.Id("intro-text"), + h.Text("hello htmgo"), + h.Class("text-5xl"), + ), h.Div( h.Class("mt-3"), partials.CounterForm(0), diff --git a/templates/starter/pages/root.go b/templates/starter/pages/root.go index d930c81d..323f4363 100644 --- a/templates/starter/pages/root.go +++ b/templates/starter/pages/root.go @@ -7,7 +7,9 @@ import ( func RootPage(children ...h.Ren) *h.Page { return h.NewPage( h.Html( - h.HxExtensions(h.BaseExtensions()), + h.HxExtensions( + h.BaseExtensions(), + ), h.Head( h.Meta("viewport", "width=device-width, initial-scale=1"), h.Link("/public/favicon.ico", "icon"), diff --git a/templates/starter/partials/index.go b/templates/starter/partials/index.go index f5b47e8c..bdedba97 100644 --- a/templates/starter/partials/index.go +++ b/templates/starter/partials/index.go @@ -26,7 +26,8 @@ func CounterForm(count int) *h.Element { h.Class("flex flex-col gap-3 items-center"), h.Id("counter-form"), h.PostPartial(CounterPartial), - h.Input("text", + h.Input( + "text", h.Class("hidden"), h.Value(count), h.Name("count"), diff --git a/tools/html-to-htmgo/htmltogo/indent.go b/tools/html-to-htmgo/htmltogo/indent.go new file mode 100644 index 00000000..519fbe98 --- /dev/null +++ b/tools/html-to-htmgo/htmltogo/indent.go @@ -0,0 +1,139 @@ +package htmltogo + +import ( + "bytes" + "fmt" + "go/ast" + "go/format" + "go/parser" + "go/printer" + "go/token" + "golang.org/x/tools/go/ast/astutil" + "slices" + "strings" +) + +func Indent(input string) string { + fset := token.NewFileSet() + // Parse the code string into an AST + f, err := parser.ParseFile(fset, "", input, parser.ParseComments) + + if err != nil { + return input + } + + htmgoComponentTypes := []string{ + "h.Element", + "h.Page", + "h.Partial", + "h.Ren", + } + + for _, decl := range f.Decls { + switch c := decl.(type) { + case *ast.FuncDecl: + + if c.Type.Results == nil || len(c.Type.Results.List) == 0 { + continue + } + + returnType := c.Type.Results.List[0].Type + + isHtmgoComponent := false + if v, ok := returnType.(*ast.StarExpr); ok { + if x, ok := v.X.(*ast.SelectorExpr); ok { + name := x.X.(*ast.Ident).Name + str := name + "." + x.Sel.Name + isHtmgoComponent = slices.Contains(htmgoComponentTypes, str) + } + } + + if !isHtmgoComponent { + continue + } + + var isHTag = func(n ast.Expr) bool { + switch argc := n.(type) { + // If the first argument is another node, add an indent + case *ast.CallExpr: + if v, ok := argc.Fun.(*ast.SelectorExpr); ok { + if v2, ok := v.X.(*ast.Ident); ok { + if v2.Name == "h" || v2.Name == "js" { + return true + } + } + } + } + return false + } + + var indent = func(children []ast.Expr) []ast.Expr { + children = append(children, ast.NewIdent("INDENTME")) + return children + } + + astutil.Apply(c.Body, nil, func(cursor *astutil.Cursor) bool { + switch n := cursor.Node().(type) { + case *ast.CallExpr: + newChildren := make([]ast.Expr, 0) + + hasAnyHElements := false + + for _, arg := range n.Args { + if isHTag(arg) { + hasAnyHElements = true + break + } + } + + for i, arg := range n.Args { + + if len(n.Args) == 1 && isHTag(arg) { + newChildren = indent(newChildren) + newChildren = append(newChildren, arg) + newChildren = indent(newChildren) + continue + } + + if !hasAnyHElements { + newChildren = append(newChildren, arg) + continue + } + + if len(n.Args) > 1 { + if i == 0 { + newChildren = indent(newChildren) + } + } + newChildren = append(newChildren, arg) + if len(n.Args) > 1 { + newChildren = indent(newChildren) + } + } + n.Args = newChildren + return true + } + return true + }) + } + } + + // Convert the AST node to a string + var buf bytes.Buffer + if err := printer.Fprint(&buf, fset, f); err != nil { + fmt.Println("Error printing AST:", err) + return input + } + + // Output the formatted code + indented := strings.ReplaceAll(buf.String(), "INDENTME,", "\n\t\t") + indented = strings.ReplaceAll(indented, ", INDENTME", ", \n\t\t") + + formatted, err := format.Source([]byte(indented)) + + if err != nil { + return input + } + + return string(formatted) +} diff --git a/tools/html-to-htmgo/htmltogo/main.go b/tools/html-to-htmgo/htmltogo/main.go index ce382c16..d2806fd3 100644 --- a/tools/html-to-htmgo/htmltogo/main.go +++ b/tools/html-to-htmgo/htmltogo/main.go @@ -18,5 +18,5 @@ func Parse(input []byte) []byte { return nil } - return []byte(formatter.Format(parsed)) + return []byte(Indent(formatter.Format(parsed))) } diff --git a/tools/html-to-htmgo/internal/adapters/services/formatter/formatter.go b/tools/html-to-htmgo/internal/adapters/services/formatter/formatter.go index a33d98aa..6bfb3bdb 100644 --- a/tools/html-to-htmgo/internal/adapters/services/formatter/formatter.go +++ b/tools/html-to-htmgo/internal/adapters/services/formatter/formatter.go @@ -16,8 +16,7 @@ import ( func MyComponent() *h.Element { return ` + node.String() + ` }`) - indented := Indent(string(b)) - dist, err := format.Source([]byte(indented)) + dist, err := format.Source(b) if err != nil { return string(b) } diff --git a/tools/html-to-htmgo/internal/adapters/services/formatter/indent.go b/tools/html-to-htmgo/internal/adapters/services/formatter/indent.go deleted file mode 100644 index fdffc5f2..00000000 --- a/tools/html-to-htmgo/internal/adapters/services/formatter/indent.go +++ /dev/null @@ -1,58 +0,0 @@ -package formatter - -import ( - "bytes" - "fmt" - "go/ast" - "go/parser" - "go/printer" - "go/token" - "golang.org/x/tools/go/ast/astutil" - "strings" -) - -func Indent(input string) string { - fset := token.NewFileSet() - // Parse the code string into an AST - f, err := parser.ParseFile(fset, "", input, 0) - - if err != nil { - return input - } - - component := f.Decls[1].(*ast.FuncDecl) - - astutil.Apply(component.Body, nil, func(cursor *astutil.Cursor) bool { - switch n := cursor.Node().(type) { - case *ast.CallExpr: - newChildren := make([]ast.Expr, 0) - for i, arg := range n.Args { - if i == 0 { - switch arg.(type) { - // If the first argument is another node, add an indent - case *ast.CallExpr: - newChildren = append(newChildren, ast.NewIdent("INDENTME")) - } - } - newChildren = append(newChildren, arg) - newChildren = append(newChildren, ast.NewIdent("INDENTME")) - } - n.Args = newChildren - return true - } - return true - }) - - // Convert the AST node to a string - var buf bytes.Buffer - if err := printer.Fprint(&buf, fset, component); err != nil { - fmt.Println("Error printing AST:", err) - return input - } - - // Output the formatted code - indented := strings.ReplaceAll(buf.String(), "INDENTME,", "\n\t\t") - indented = strings.ReplaceAll(indented, ", INDENTME", ", \n\t\t") - - return indented -}