Revert "swap out echo with std lib"

This reverts commit 6ec3dfa790.
This commit is contained in:
maddalax 2024-09-26 14:40:09 -05:00
parent 10de2f216f
commit 01176c845b
14 changed files with 259 additions and 183 deletions

View file

@ -27,8 +27,7 @@ type Partial struct {
} }
const GeneratedDirName = "__htmgo" const GeneratedDirName = "__htmgo"
const HttpModuleName = "net/http" const EchoModuleName = "github.com/labstack/echo/v4"
const ChiModuleName = "github.com/go-chi/chi/v5"
const ModuleName = "github.com/maddalax/htmgo/framework/h" const ModuleName = "github.com/maddalax/htmgo/framework/h"
var PackageName = fmt.Sprintf("package %s", GeneratedDirName) var PackageName = fmt.Sprintf("package %s", GeneratedDirName)
@ -199,7 +198,7 @@ func buildGetPartialFromContext(builder *CodeBuilder, partials []Partial) {
fName := "GetPartialFromContext" fName := "GetPartialFromContext"
body := ` body := `
path := r.URL.Path path := ctx.Request().URL.Path
` `
if len(partials) == 0 { if len(partials) == 0 {
@ -216,7 +215,7 @@ func buildGetPartialFromContext(builder *CodeBuilder, partials []Partial) {
body += fmt.Sprintf(` body += fmt.Sprintf(`
if path == "%s" || path == "%s" { if path == "%s" || path == "%s" {
cc := r.Context().Value(h.RequestContextKey).(*h.RequestContext) cc := ctx.(*h.RequestContext)
return %s(cc) return %s(cc)
} }
`, f.FuncName, path, caller) `, f.FuncName, path, caller)
@ -227,7 +226,7 @@ func buildGetPartialFromContext(builder *CodeBuilder, partials []Partial) {
f := Function{ f := Function{
Name: fName, Name: fName,
Parameters: []NameType{ Parameters: []NameType{
{Name: "r", Type: "*http.Request"}, {Name: "ctx", Type: "echo.Context"},
}, },
Return: []ReturnType{ Return: []ReturnType{
{Type: "*h.Partial"}, {Type: "*h.Partial"},
@ -238,15 +237,14 @@ func buildGetPartialFromContext(builder *CodeBuilder, partials []Partial) {
builder.Append(builder.BuildFunction(f)) builder.Append(builder.BuildFunction(f))
registerFunction := fmt.Sprintf(` registerFunction := fmt.Sprintf(`
func RegisterPartials(router *chi.Mux) { func RegisterPartials(f *echo.Echo) {
router.Handle("/%s/partials*", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { f.Any("%s/partials*", func(ctx echo.Context) error {
partial := GetPartialFromContext(r) partial := GetPartialFromContext(ctx)
if partial == nil { if partial == nil {
w.WriteHeader(404) return ctx.NoContent(404)
return
} }
h.PartialView(w, partial) return h.PartialView(ctx, partial)
})) })
} }
`, moduleName) `, moduleName)
@ -269,8 +267,7 @@ func writePartialsFile() {
builder.AppendLine(GeneratedFileLine) builder.AppendLine(GeneratedFileLine)
builder.AppendLine(PackageName) builder.AppendLine(PackageName)
builder.AddImport(ModuleName) builder.AddImport(ModuleName)
builder.AddImport(HttpModuleName) builder.AddImport(EchoModuleName)
builder.AddImport(ChiModuleName)
moduleName := GetModuleName() moduleName := GetModuleName()
for _, partial := range partials { for _, partial := range partials {
@ -310,8 +307,7 @@ func writePagesFile() {
builder := NewCodeBuilder(nil) builder := NewCodeBuilder(nil)
builder.AppendLine(GeneratedFileLine) builder.AppendLine(GeneratedFileLine)
builder.AppendLine(PackageName) builder.AppendLine(PackageName)
builder.AddImport(HttpModuleName) builder.AddImport(EchoModuleName)
builder.AddImport(ChiModuleName)
pages, _ := findPublicFuncsReturningHPage("pages") pages, _ := findPublicFuncsReturningHPage("pages")
@ -335,20 +331,18 @@ func writePagesFile() {
for _, page := range pages { for _, page := range pages {
call := fmt.Sprintf("%s.%s", page.Package, page.FuncName) call := fmt.Sprintf("%s.%s", page.Package, page.FuncName)
body += fmt.Sprintf( body += fmt.Sprintf(`
` f.GET("%s", func(ctx echo.Context) error {
router.Get("%s", func(writer http.ResponseWriter, request *http.Request) { cc := ctx.(*h.RequestContext)
cc := request.Context().Value(h.RequestContextKey).(*h.RequestContext) return h.HtmlView(ctx, %s(cc))
h.HtmlView(writer, %s(cc))
}) })
`, formatRoute(page.Path), call, `, formatRoute(page.Path), call)
)
} }
f := Function{ f := Function{
Name: fName, Name: fName,
Parameters: []NameType{ Parameters: []NameType{
{Name: "router", Type: "*chi.Mux"}, {Name: "f", Type: "*echo.Echo"},
}, },
Body: body, Body: body,
} }
@ -383,20 +377,19 @@ func GenAst(flags ...process.RunFlag) error {
writePagesFile() writePagesFile()
WriteFile("__htmgo/setup-generated.go", func(content *ast.File) string { WriteFile("__htmgo/setup-generated.go", func(content *ast.File) string {
return `
return fmt.Sprintf(`
// Package __htmgo THIS FILE IS GENERATED. DO NOT EDIT. // Package __htmgo THIS FILE IS GENERATED. DO NOT EDIT.
package __htmgo package __htmgo
import ( import (
"%s" "github.com/labstack/echo/v4"
) )
func Register(r *chi.Mux) { func Register(e *echo.Echo) {
RegisterPartials(r) RegisterPartials(e)
RegisterPages(r) RegisterPages(e)
} }
`, ChiModuleName) `
}) })
return nil return nil

View file

@ -0,0 +1,13 @@
// Package __htmgo THIS FILE IS GENERATED. DO NOT EDIT.
package __htmgo
import "github.com/labstack/echo/v4"
import "github.com/maddalax/htmgo/framework/h"
import "todolist/pages"
func RegisterPages(f *echo.Echo) {
f.GET("/", func(ctx echo.Context) error {
cc := ctx.(*h.RequestContext)
return h.HtmlView(ctx, pages.TaskListPage(cc))
})
}

View file

@ -0,0 +1,49 @@
// Package __htmgo THIS FILE IS GENERATED. DO NOT EDIT.
package __htmgo
import "github.com/maddalax/htmgo/framework/h"
import "github.com/labstack/echo/v4"
import "todolist/partials/task"
func GetPartialFromContext(ctx echo.Context) *h.Partial {
path := ctx.Request().URL.Path
if path == "UpdateName" || path == "/todolist/partials/task.UpdateName" {
cc := ctx.(*h.RequestContext)
return task.UpdateName(cc)
}
if path == "EditNameForm" || path == "/todolist/partials/task.EditNameForm" {
cc := ctx.(*h.RequestContext)
return task.EditNameForm(cc)
}
if path == "ToggleCompleted" || path == "/todolist/partials/task.ToggleCompleted" {
cc := ctx.(*h.RequestContext)
return task.ToggleCompleted(cc)
}
if path == "CompleteAll" || path == "/todolist/partials/task.CompleteAll" {
cc := ctx.(*h.RequestContext)
return task.CompleteAll(cc)
}
if path == "ClearCompleted" || path == "/todolist/partials/task.ClearCompleted" {
cc := ctx.(*h.RequestContext)
return task.ClearCompleted(cc)
}
if path == "Create" || path == "/todolist/partials/task.Create" {
cc := ctx.(*h.RequestContext)
return task.Create(cc)
}
if path == "ChangeTab" || path == "/todolist/partials/task.ChangeTab" {
cc := ctx.(*h.RequestContext)
return task.ChangeTab(cc)
}
return nil
}
func RegisterPartials(f *echo.Echo) {
f.Any("todolist/partials*", func(ctx echo.Context) error {
partial := GetPartialFromContext(ctx)
if partial == nil {
return ctx.NoContent(404)
}
return h.PartialView(ctx, partial)
})
}

View file

@ -0,0 +1,11 @@
// Package __htmgo THIS FILE IS GENERATED. DO NOT EDIT.
package __htmgo
import (
"github.com/labstack/echo/v4"
)
func Register(e *echo.Echo) {
RegisterPartials(e)
RegisterPages(e)
}

File diff suppressed because one or more lines are too long

View file

@ -5,6 +5,7 @@ let lastVersion = "";
htmx.defineExtension("livereload", { htmx.defineExtension("livereload", {
init: function () { init: function () {
const host = window.location.host;
let enabled = false let enabled = false
for (const element of Array.from(htmx.findAll("[hx-ext]"))) { for (const element of Array.from(htmx.findAll("[hx-ext]"))) {
@ -20,12 +21,12 @@ htmx.defineExtension("livereload", {
} }
console.log('livereload extension initialized.'); console.log('livereload extension initialized.');
// Create a new EventSource object and point it to your SSE endpoint
const eventSource = new EventSource('/dev/livereload'); createWebSocketClient({
// Listen for messages from the server url: `${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${host}/dev/livereload`,
eventSource.onmessage = function(event) { onOpen: () => {
const message = event.data },
// Log the message data received from the server onMessage: (message) => {
if(lastVersion === "") { if(lastVersion === "") {
lastVersion = message; lastVersion = message;
} }
@ -33,12 +34,12 @@ htmx.defineExtension("livereload", {
lastVersion = message; lastVersion = message;
reload() reload()
} }
}; },
// Handle errors (e.g., when the connection is closed) onError: (error) => {
eventSource.onerror = function(error) { },
console.error('EventSource error:', error); onClose: () => {
}; }
})
}, },
// @ts-ignore // @ts-ignore
onEvent: function (name, evt) { onEvent: function (name, evt) {
@ -48,4 +49,14 @@ htmx.defineExtension("livereload", {
function reload() { function reload() {
window.location.reload() window.location.reload()
// fetch(window.location.href).then(response => {
// return response.text();
// }).then(html => {
// document.open();
// document.write(html);
// document.close();
// }).catch(err => {
// console.log('failed to fetch live reload', err)
// setTimeout(reload, 100)
// })
} }

View file

@ -3,13 +3,22 @@ module github.com/maddalax/htmgo/framework
go 1.23.0 go 1.23.0
require ( require (
github.com/go-chi/chi/v5 v5.1.0
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/labstack/echo/v4 v4.12.0
github.com/stretchr/testify v1.9.0 github.com/stretchr/testify v1.9.0
golang.org/x/net v0.29.0
) )
require ( require (
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/labstack/gommon v0.4.2 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
golang.org/x/crypto v0.27.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/text v0.18.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

View file

@ -1,13 +1,34 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0=
github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM=
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View file

@ -1,20 +1,18 @@
package h package h
import ( import (
"context"
"fmt" "fmt"
"github.com/go-chi/chi/v5" "github.com/labstack/echo/v4"
"github.com/maddalax/htmgo/framework/hx" "github.com/maddalax/htmgo/framework/hx"
"github.com/maddalax/htmgo/framework/service" "github.com/maddalax/htmgo/framework/service"
"log/slog" "log/slog"
"net/http"
"os/exec" "os/exec"
"runtime" "runtime"
"time" "time"
) )
type RequestContext struct { type RequestContext struct {
*http.Request echo.Context
locator *service.Locator locator *service.Locator
isBoosted bool isBoosted bool
currentBrowserUrl string currentBrowserUrl string
@ -23,21 +21,6 @@ type RequestContext struct {
hxTargetId string hxTargetId string
hxTriggerName string hxTriggerName string
hxTriggerId string hxTriggerId string
kv map[string]interface{}
}
func (c *RequestContext) Set(key string, value interface{}) {
if c.kv == nil {
c.kv = make(map[string]interface{})
}
c.kv[key] = value
}
func (c *RequestContext) Get(key string) interface{} {
if c.kv == nil {
return nil
}
return c.kv[key]
} }
func (c *RequestContext) ServiceLocator() *service.Locator { func (c *RequestContext) ServiceLocator() *service.Locator {
@ -47,72 +30,65 @@ func (c *RequestContext) ServiceLocator() *service.Locator {
type AppOpts struct { type AppOpts struct {
LiveReload bool LiveReload bool
ServiceLocator *service.Locator ServiceLocator *service.Locator
Register func(app *App) Register func(echo *echo.Echo)
} }
type App struct { type App struct {
Opts AppOpts Opts AppOpts
Router *chi.Mux Echo *echo.Echo
}
var instance *App
func GetApp() *App {
if instance == nil {
panic("App instance not initialized")
}
return instance
} }
func Start(opts AppOpts) { func Start(opts AppOpts) {
router := chi.NewRouter() e := echo.New()
instance := App{ instance := App{
Opts: opts, Opts: opts,
Router: router, Echo: e,
} }
instance.start() instance.start()
} }
const RequestContextKey = "htmgo.request.context"
func populateHxFields(cc *RequestContext) { func populateHxFields(cc *RequestContext) {
cc.isBoosted = cc.Request.Header.Get(hx.BoostedHeader) == "true" cc.isBoosted = cc.Request().Header.Get(hx.BoostedHeader) == "true"
cc.isBoosted = cc.Request.Header.Get(hx.BoostedHeader) == "true" cc.currentBrowserUrl = cc.Request().Header.Get(hx.CurrentUrlHeader)
cc.currentBrowserUrl = cc.Request.Header.Get(hx.CurrentUrlHeader) cc.hxPromptResponse = cc.Request().Header.Get(hx.PromptResponseHeader)
cc.hxPromptResponse = cc.Request.Header.Get(hx.PromptResponseHeader) cc.isHxRequest = cc.Request().Header.Get(hx.RequestHeader) == "true"
cc.isHxRequest = cc.Request.Header.Get(hx.RequestHeader) == "true" cc.hxTargetId = cc.Request().Header.Get(hx.TargetIdHeader)
cc.hxTargetId = cc.Request.Header.Get(hx.TargetIdHeader) cc.hxTriggerName = cc.Request().Header.Get(hx.TriggerNameHeader)
cc.hxTriggerName = cc.Request.Header.Get(hx.TriggerNameHeader) cc.hxTriggerId = cc.Request().Header.Get(hx.TriggerIdHeader)
cc.hxTriggerId = cc.Request.Header.Get(hx.TriggerIdHeader)
} }
func (app *App) UseWithContext(h func(w http.ResponseWriter, r *http.Request, context map[string]any)) { func (a App) start() {
app.Router.Use(func(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if a.Opts.Register != nil {
cc := r.Context().Value(RequestContextKey).(*RequestContext) a.Opts.Register(a.Echo)
h(w, r, cc.kv)
handler.ServeHTTP(w, r)
})
})
} }
func (app *App) start() { a.Echo.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
app.Router.Use(func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
cc := &RequestContext{ cc := &RequestContext{
locator: app.Opts.ServiceLocator, Context: c,
Request: r, locator: a.Opts.ServiceLocator,
kv: make(map[string]interface{}),
} }
populateHxFields(cc) populateHxFields(cc)
ctx := context.WithValue(r.Context(), RequestContextKey, cc) return next(cc)
h.ServeHTTP(w, r.WithContext(ctx))
})
})
if app.Opts.Register != nil {
app.Opts.Register(app)
} }
})
if app.Opts.LiveReload && IsDevelopment() { if a.Opts.LiveReload && IsDevelopment() {
app.AddLiveReloadHandler("/dev/livereload") AddLiveReloadHandler("/dev/livereload", a.Echo)
} }
port := ":3000" port := ":3000"
slog.Info(fmt.Sprintf("Server started on port %s", port)) err := a.Echo.Start(port)
err := http.ListenAndServe(port, app.Router)
if err != nil { if err != nil {
// If we are in watch mode, just try to kill any processes holding that port // If we are in watch mode, just try to kill any processes holding that port
@ -127,7 +103,7 @@ func (app *App) start() {
cmd.Run() cmd.Run()
} }
time.Sleep(time.Millisecond * 50) time.Sleep(time.Millisecond * 50)
err = http.ListenAndServe(":3000", app.Router) err = a.Echo.Start(port)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -138,38 +114,46 @@ func (app *App) start() {
} }
} }
func writeHtml(w http.ResponseWriter, element Ren) error { func HtmlView(c echo.Context, page *Page) error {
w.Header().Set("Content-Type", "text/html") root := page.Root
_, err := fmt.Fprint(w, Render(element)) return c.HTML(200,
return err Render(
root,
),
)
} }
func HtmlView(w http.ResponseWriter, page *Page) error { func PartialViewWithHeaders(c echo.Context, headers *Headers, partial *Partial) error {
return writeHtml(w, page.Root)
}
func PartialViewWithHeaders(w http.ResponseWriter, headers *Headers, partial *Partial) error {
if partial.Headers != nil { if partial.Headers != nil {
for s, a := range *partial.Headers { for s, a := range *partial.Headers {
w.Header().Set(s, a) c.Set(s, a)
} }
} }
if headers != nil { if headers != nil {
for s, a := range *headers { for s, a := range *headers {
w.Header().Set(s, a) c.Response().Header().Set(s, a)
} }
} }
return writeHtml(w, partial.Root) return c.HTML(200,
Render(
partial,
),
)
} }
func PartialView(w http.ResponseWriter, partial *Partial) error { func PartialView(c echo.Context, partial *Partial) error {
c.Set(echo.HeaderContentType, echo.MIMETextHTML)
if partial.Headers != nil { if partial.Headers != nil {
for s, a := range *partial.Headers { for s, a := range *partial.Headers {
w.Header().Set(s, a) c.Response().Header().Set(s, a)
} }
} }
return writeHtml(w, partial.Root) return c.HTML(200,
Render(
partial,
),
)
} }

View file

@ -37,7 +37,7 @@ func IfElseLazy[T any](condition bool, cb1 func() T, cb2 func() T) T {
} }
func IfHtmxRequest(ctx *RequestContext, node Ren) Ren { func IfHtmxRequest(ctx *RequestContext, node Ren) Ren {
if ctx.isHxRequest { if ctx.Get("HX-Request") != "" {
return node return node
} }
return Empty() return Empty()

View file

@ -34,7 +34,7 @@ func CombineHeaders(headers ...*Headers) *Headers {
} }
func CurrentPath(ctx *RequestContext) string { func CurrentPath(ctx *RequestContext) string {
current := ctx.Header.Get(hx.CurrentUrlHeader) current := ctx.Request().Header.Get(hx.CurrentUrlHeader)
parsed, err := url.Parse(current) parsed, err := url.Parse(current)
if err != nil { if err != nil {
return "" return ""

View file

@ -1,41 +1,30 @@
package h package h
import ( import (
"fmt"
"github.com/google/uuid" "github.com/google/uuid"
"net/http" "github.com/labstack/echo/v4"
"golang.org/x/net/websocket"
"time" "time"
) )
var Version = uuid.NewString() var Version = uuid.NewString()
func sseHandler(w http.ResponseWriter, r *http.Request) { func handler(c echo.Context) error {
// Set the necessary headers websocket.Handler(func(ws *websocket.Conn) {
w.Header().Set("Content-Type", "text/event-stream") defer ws.Close()
w.Header().Set("Cache-Control", "no-cache") _ = websocket.Message.Send(ws, Version)
w.Header().Set("Connection", "keep-alive") // keep ws alive
w.Header().Set("Access-Control-Allow-Origin", "*") // Optional for CORS for {
err := websocket.Message.Send(ws, Version)
// Flush the headers immediately if err != nil {
flusher, ok := w.(http.Flusher)
if !ok {
http.Error(w, "Streaming unsupported", http.StatusInternalServerError)
return return
} }
for {
// Write an event to the stream
_, err := fmt.Fprintf(w, "data: %s\n\n", Version)
if err != nil {
break
}
// Flush the response to ensure the client gets it immediately
flusher.Flush()
time.Sleep(500 * time.Millisecond) time.Sleep(500 * time.Millisecond)
} }
}).ServeHTTP(c.Response(), c.Request())
return nil
} }
func (app *App) AddLiveReloadHandler(path string) { func AddLiveReloadHandler(path string, app *echo.Echo) {
app.Router.Get(path, sseHandler) app.GET(path, handler)
} }

View file

@ -49,8 +49,8 @@ func (q *Qs) ToString() string {
} }
func GetQueryParam(ctx *RequestContext, key string) string { func GetQueryParam(ctx *RequestContext, key string) string {
value, ok := ctx.URL.Query()[key] value := ctx.QueryParam(key)
if !ok { if value == "" {
current := ctx.currentBrowserUrl current := ctx.currentBrowserUrl
if current != "" { if current != "" {
u, err := url.Parse(current) u, err := url.Parse(current)
@ -59,7 +59,7 @@ func GetQueryParam(ctx *RequestContext, key string) string {
} }
} }
} }
return value[0] return value
} }
func SetQueryParams(href string, qs *Qs) string { func SetQueryParams(href string, qs *Qs) string {

View file

@ -1,12 +1,12 @@
package main package main
import ( import (
"github.com/labstack/echo/v4"
"github.com/maddalax/htmgo/framework/h" "github.com/maddalax/htmgo/framework/h"
"github.com/maddalax/htmgo/framework/service" "github.com/maddalax/htmgo/framework/service"
"htmgo-site/__htmgo" "htmgo-site/__htmgo"
"htmgo-site/internal/markdown" "htmgo-site/internal/markdown"
"io/fs" "io/fs"
"net/http"
) )
func main() { func main() {
@ -19,23 +19,21 @@ func main() {
h.Start(h.AppOpts{ h.Start(h.AppOpts{
ServiceLocator: locator, ServiceLocator: locator,
LiveReload: true, LiveReload: true,
Register: func(app *h.App) { Register: func(e *echo.Echo) {
app.UseWithContext(func(w http.ResponseWriter, r *http.Request, context map[string]any) {
context["embeddedMarkdown"] = markdownAssets
})
sub, err := fs.Sub(staticAssets, "assets/dist") sub, err := fs.Sub(staticAssets, "assets/dist")
if err != nil { if err != nil {
panic(err) panic(err)
} }
e.StaticFS("/public", sub)
http.FileServerFS(sub) e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
c.Set("embeddedMarkdown", markdownAssets)
return next(c)
}
})
app.Router.Handle("/public/*", http.StripPrefix("/public", http.FileServerFS(sub))) __htmgo.Register(e)
__htmgo.Register(app.Router)
}, },
}) })
} }