htmgo - custom formatter (#47)
* format htmgo elements on save * formatter updates * ensure we maintain comments
This commit is contained in:
parent
3f8ab7d905
commit
8736c00fd5
35 changed files with 763 additions and 306 deletions
|
|
@ -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')
|
||||
|
|
|
|||
50
cli/htmgo/tasks/formatter/formatter.go
Normal file
50
cli/htmgo/tasks/formatter/formatter.go
Normal 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
|
||||
}
|
||||
|
|
@ -115,7 +115,7 @@ func OnShutdown() {
|
|||
}
|
||||
}
|
||||
// give it a second
|
||||
time.Sleep(time.Second * 2)
|
||||
time.Sleep(time.Second * 1)
|
||||
// force kill
|
||||
KillAll()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
)
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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(`
|
||||
<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),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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", ""),
|
||||
),
|
||||
),
|
||||
)),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
59
htmgo-site/pages/test.go
Normal 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"),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -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"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
`),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
),
|
||||
)
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
139
tools/html-to-htmgo/htmltogo/indent.go
Normal file
139
tools/html-to-htmgo/htmltogo/indent.go
Normal 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)
|
||||
}
|
||||
|
|
@ -18,5 +18,5 @@ func Parse(input []byte) []byte {
|
|||
return nil
|
||||
}
|
||||
|
||||
return []byte(formatter.Format(parsed))
|
||||
return []byte(Indent(formatter.Format(parsed)))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
Loading…
Reference in a new issue