htmgo - custom formatter (#47)

* format htmgo elements on save

* formatter updates

* ensure we maintain comments
This commit is contained in:
maddalax 2024-10-25 10:33:48 -05:00 committed by GitHub
parent 3f8ab7d905
commit 8736c00fd5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
35 changed files with 763 additions and 306 deletions

View file

@ -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 <file>"))
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')

View file

@ -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
}

View file

@ -115,7 +115,7 @@ func OnShutdown() {
}
}
// give it a second
time.Sleep(time.Second * 2)
time.Sleep(time.Second * 1)
// force kill
KillAll()
}

View file

@ -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),
),
),
),
)

View file

@ -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"),
),
)
}

View file

@ -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"),

View file

@ -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("&#9776;"), // The icon for collapsing the sidebar
h.UnsafeRaw("&#9776;"),
// 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"),

View file

@ -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",

View file

@ -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),
)
},
),
),
)
}

View file

@ -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),
),
),
)
}

View file

@ -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"),
),

View file

@ -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"),
),
)
}

View file

@ -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"),

View file

@ -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),

View file

@ -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"),
),
),
),
),

View file

@ -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("&#x203A;"),
h.PostPartialWithQs(CompleteAll, h.NewQs("complete", h.Ternary(notCompletedCount > 0, "true", "false"))),
}),
h.UnsafeRaw("&#x203A;"),
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(`
<svg class="w-6 h-6 text-green-500" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"></path>
</svg>
`)),
`),
),
),
)
}
@ -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),
)

View file

@ -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,

View file

@ -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"),
),
),

View file

@ -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.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.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.Div(
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"),

View file

@ -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(),
),

View file

@ -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(

View file

@ -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.Class("border-b border-b-slate-200 h-1"),
h.Div(
h.Class("mt-4"),
MarkdownPage(ctx, "md/index.md", ""),
),
),
),
h.Div(
h.Class("border-b border-b-slate-200 h-1"),
h.Div(
h.Class("mt-4"),
MarkdownPage(ctx, "md/index.md", ""),
),
),
)),
),
),
)
}

View file

@ -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)),

59
htmgo-site/pages/test.go Normal file
View file

@ -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"),
),
),
),
)
}

View file

@ -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"),
),
)
}

View file

@ -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);
`),
),
),
),
),
)
}

View file

@ -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("&#9776;"),
),
),
),
),
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),
),
)
}),
),
),
)
}

View file

@ -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 {

View file

@ -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),

View file

@ -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"),

View file

@ -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"),

View file

@ -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)
}

View file

@ -18,5 +18,5 @@ func Parse(input []byte) []byte {
return nil
}
return []byte(formatter.Format(parsed))
return []byte(Indent(formatter.Format(parsed)))
}

View file

@ -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)
}

View file

@ -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
}