diff --git a/framework/h/app.go b/framework/h/app.go index 274e6fc..6711941 100644 --- a/framework/h/app.go +++ b/framework/h/app.go @@ -105,7 +105,7 @@ func PartialViewWithHeaders(c echo.Context, headers *Headers, partial *Partial) if headers != nil { for s, a := range *headers { - c.Set(s, a) + c.Response().Header().Set(s, a) } } @@ -120,7 +120,7 @@ func PartialView(c echo.Context, partial *Partial) error { c.Set(echo.HeaderContentType, echo.MIMETextHTML) if partial.Headers != nil { for s, a := range *partial.Headers { - c.Set(s, a) + c.Response().Header().Set(s, a) } } diff --git a/framework/h/array.go b/framework/h/array.go new file mode 100644 index 0000000..94287df --- /dev/null +++ b/framework/h/array.go @@ -0,0 +1,32 @@ +package h + +func Unique[T any](slice []T, key func(item T) string) []T { + var result []T + seen := make(map[string]bool) + for _, v := range slice { + k := key(v) + if _, ok := seen[k]; !ok { + seen[k] = true + result = append(result, v) + } + } + return result +} + +func Filter[T any](slice []T, predicate func(item T) bool) []T { + var result []T + for _, v := range slice { + if predicate(v) { + result = append(result, v) + } + } + return result +} + +func Map[T, U any](slice []T, mapper func(item T) U) []U { + var result []U + for _, v := range slice { + result = append(result, mapper(v)) + } + return result +} diff --git a/framework/h/lifecycle.go b/framework/h/lifecycle.go index 8839896..7b4e721 100644 --- a/framework/h/lifecycle.go +++ b/framework/h/lifecycle.go @@ -77,22 +77,27 @@ type JsCommand struct { } func SetText(text string) JsCommand { + // language=JavaScript return JsCommand{Command: fmt.Sprintf("this.innerText = '%s'", text)} } func Increment(amount int) JsCommand { + // language=JavaScript return JsCommand{Command: fmt.Sprintf("this.innerText = parseInt(this.innerText) + %d", amount)} } func SetInnerHtml(r Renderable) JsCommand { + // language=JavaScript return JsCommand{Command: fmt.Sprintf("this.innerHTML = `%s`", Render(r.Render()))} } func SetOuterHtml(r Renderable) JsCommand { + // language=JavaScript return JsCommand{Command: fmt.Sprintf("this.outerHTML = `%s`", Render(r.Render()))} } func AddAttribute(name, value string) JsCommand { + // language=JavaScript return JsCommand{Command: fmt.Sprintf("this.setAttribute('%s', '%s')", name, value)} } @@ -105,18 +110,22 @@ func SetDisabled(disabled bool) JsCommand { } func RemoveAttribute(name string) JsCommand { + // language=JavaScript return JsCommand{Command: fmt.Sprintf("this.removeAttribute('%s')", name)} } func AddClass(class string) JsCommand { + // language=JavaScript return JsCommand{Command: fmt.Sprintf("this.classList.add('%s')", class)} } func RemoveClass(class string) JsCommand { + // language=JavaScript return JsCommand{Command: fmt.Sprintf("this.classList.remove('%s')", class)} } func Alert(text string) JsCommand { + // language=JavaScript return JsCommand{Command: fmt.Sprintf("alert('%s')", text)} } diff --git a/framework/h/tag.go b/framework/h/tag.go index 2b59562..3997879 100644 --- a/framework/h/tag.go +++ b/framework/h/tag.go @@ -54,6 +54,17 @@ func Class(value ...string) Renderable { } } +func ClassX(value string, m map[string]bool) Renderable { + builder := strings.Builder{} + builder.WriteString(value + " ") + for k, v := range m { + if v { + builder.WriteString(k + " ") + } + } + return Class(builder.String()) +} + func MergeClasses(classes ...string) string { builder := "" for _, s := range classes { @@ -151,10 +162,26 @@ func Post(url string) Renderable { return Attribute("hx-post", url) } +func PostOnClick(url string) Renderable { + return AttributeList(Attribute("hx-post", url), Trigger("click")) +} + +func PostPartialOnClick(partial func(ctx *RequestContext) *Partial) Renderable { + return PostOnClick(GetPartialPath(partial)) +} + +func PostPartialOnClickQs(partial func(ctx *RequestContext) *Partial, qs string) Renderable { + return PostOnClick(GetPartialPathWithQs(partial, qs)) +} + func Trigger(trigger string) Renderable { return Attribute("hx-trigger", trigger) } +func TextF(format string, args ...interface{}) Renderable { + return Text(fmt.Sprintf(format, args...)) +} + func Text(text string) Renderable { return &Node{ tag: "text", @@ -542,11 +569,11 @@ func NewSwap(selector string, content *Node) SwapArg { } } -func Swap(ctx *RequestContext, content Renderable) Renderable { - return SwapWithSelector(ctx, "", content) +func OobSwap(ctx *RequestContext, content Renderable) Renderable { + return OobSwapWithSelector(ctx, "", content) } -func SwapWithSelector(ctx *RequestContext, selector string, content Renderable) Renderable { +func OobSwapWithSelector(ctx *RequestContext, selector string, content Renderable) Renderable { if ctx == nil || ctx.Get("HX-Request") == "" { return Empty() } diff --git a/framework/h/util.go b/framework/h/util.go index bba8adb..6d8bd21 100644 --- a/framework/h/util.go +++ b/framework/h/util.go @@ -17,14 +17,6 @@ func Ternary[T any](value bool, a T, b T) T { return b } -func Map[T any, U any](items []T, fn func(T) U) []U { - var result []U - for _, item := range items { - result = append(result, fn(item)) - } - return result -} - func JsonSerialize(data any) string { serialized, err := json.Marshal(data) if err != nil { diff --git a/sandbox/partials/news.go b/sandbox/partials/news.go index 6c03d89..4830141 100644 --- a/sandbox/partials/news.go +++ b/sandbox/partials/news.go @@ -17,8 +17,8 @@ func NewsSheet(ctx echo.Context) *h.Partial { }, SheetWrapper( h.IfElseLazy(open, SheetOpen, SheetClosed), - h.Swap(ctx, OpenSheetButton(open)), - h.Swap(ctx, NewsSheetOpenCount(ctx).Root), + h.OobSwap(ctx, OpenSheetButton(open)), + h.OobSwap(ctx, NewsSheetOpenCount(ctx).Root), ), ) } diff --git a/sandbox/partials/sheet/sheet.go b/sandbox/partials/sheet/sheet.go index fb69a93..56aafcb 100644 --- a/sandbox/partials/sheet/sheet.go +++ b/sandbox/partials/sheet/sheet.go @@ -31,7 +31,7 @@ func Closed() h.Renderable { func Close(ctx echo.Context) *h.Partial { return h.NewPartialWithHeaders( h.Ternary(ctx.Query("path") != "", h.ReplaceUrlHeader(ctx.Query("path")), h.NewHeaders()), - h.Swap(ctx, Closed()), + h.OobSwap(ctx, Closed()), ) } diff --git a/tailwind-lsp-config.json b/tailwind-lsp-config.json new file mode 100644 index 0000000..058edb9 --- /dev/null +++ b/tailwind-lsp-config.json @@ -0,0 +1,46 @@ +{ + "includeLanguages": { + "ftl": "html", + "jinja": "html", + "jinja2": "html", + "smarty": "html", + "tmpl": "gohtml", + "cshtml": "html", + "vbhtml": "html", + "razor": "html", + "go": "html" + }, + "files": { + "exclude": [ + "**/.git/**", + "**/node_modules/**", + "**/.hg/**", + "**/.svn/**" + ] + }, + "emmetCompletions": false, + "classAttributes": ["class", "className", "ngClass"], + "colorDecorators": false, + "showPixelEquivalents": true, + "rootFontSize": 16, + "hovers": true, + "suggestions": true, + "codeActions": true, + "validate": true, + "lint": { + "invalidScreen": "error", + "invalidVariant": "error", + "invalidTailwindDirective": "error", + "invalidApply": "error", + "invalidConfigPath": "error", + "cssConflict": "warning", + "recommendedVariantOrder": "warning" + }, + "experimental": { + "configFile": null, + "classRegex": [[ + "tailwind|Class|h.Class\\(([^)]*)\\)", + "[\"'`]([^\"'`]*).*?[\"'`]" + ]] + } +} \ No newline at end of file