From 66cf07a0241d5899ab9e2c0f8a7a7eff3566036c Mon Sep 17 00:00:00 2001 From: yequari Date: Wed, 11 Jun 2025 23:09:12 -0700 Subject: [PATCH 1/7] add route for json data, standalone form, timezone conversion --- cmd/web/handlers_guestbook.go | 44 ++- cmd/web/routes.go | 5 +- ui/static/js/main.js | 13 + ui/views/guestbooks.templ | 43 +-- ui/views/guestbooks_templ.go | 496 +++++++++++++++++----------------- 5 files changed, 331 insertions(+), 270 deletions(-) create mode 100644 ui/static/js/main.js diff --git a/cmd/web/handlers_guestbook.go b/cmd/web/handlers_guestbook.go index 92b285a..46668bc 100644 --- a/cmd/web/handlers_guestbook.go +++ b/cmd/web/handlers_guestbook.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "errors" "fmt" "net/http" @@ -136,6 +137,29 @@ func (app *application) getGuestbookComments(w http.ResponseWriter, r *http.Requ views.GuestbookDashboardCommentsView("Comments", data, website, website.Guestbook, comments).Render(r.Context(), w) } +func (app *application) getGuestbookCommentsSerialized(w http.ResponseWriter, r *http.Request) { + slug := r.PathValue("id") + website, err := app.websites.Get(slugToShortId(slug)) + if err != nil { + if errors.Is(err, models.ErrNoRecord) { + http.NotFound(w, r) + } else { + app.serverError(w, r, err) + } + return + } + if !website.Guestbook.Settings.IsVisible || !website.Guestbook.Settings.AllowRemoteHostAccess { + app.clientError(w, http.StatusForbidden) + } + comments, err := app.guestbookComments.GetAll(website.Guestbook.ID) + if err != nil { + app.serverError(w, r, err) + return + } + b, err := json.Marshal(comments) + w.Write(b) +} + func (app *application) getGuestbookCommentCreate(w http.ResponseWriter, r *http.Request) { // TODO: This will be the embeddable form slug := r.PathValue("id") @@ -148,13 +172,21 @@ func (app *application) getGuestbookCommentCreate(w http.ResponseWriter, r *http } return } + s := website.Guestbook.Settings + if !s.IsVisible || !s.AllowRemoteHostAccess || !website.Guestbook.CanComment() { + app.clientError(w, http.StatusForbidden) + } data := app.newCommonData(r) form := forms.CommentCreateForm{} - views.CreateGuestbookComment("New Comment", data, website, website.Guestbook, form).Render(r.Context(), w) + views.EmbeddableGuestbookCommentForm(data, website, form).Render(r.Context(), w) } func (app *application) postGuestbookCommentCreate(w http.ResponseWriter, r *http.Request) { slug := r.PathValue("id") + headless, err := strconv.ParseBool(r.URL.Query().Get("headless")) + if err != nil { + headless = false + } website, err := app.websites.Get(slugToShortId(slug)) if err != nil { if errors.Is(err, models.ErrNoRecord) { @@ -184,14 +216,17 @@ func (app *application) postGuestbookCommentCreate(w http.ResponseWriter, r *htt form.CheckField(validator.NotBlank(form.Content), "content", "This field cannot be blank") if !form.Valid() { + data := app.newCommonData(r) + w.WriteHeader(http.StatusUnprocessableEntity) + if headless { + views.EmbeddableGuestbookCommentForm(data, website, form).Render(r.Context(), w) + } // TODO: use htmx to avoid getting comments again comments, err := app.guestbookComments.GetAll(website.Guestbook.ID) if err != nil { app.serverError(w, r, err) return } - data := app.newCommonData(r) - w.WriteHeader(http.StatusUnprocessableEntity) views.GuestbookView("Guestbook", data, website, website.Guestbook, comments, form).Render(r.Context(), w) return } @@ -203,6 +238,9 @@ func (app *application) postGuestbookCommentCreate(w http.ResponseWriter, r *htt return } app.sessionManager.Put(r.Context(), "flash", "Comment successfully posted!") + if headless { + http.Redirect(w, r, fmt.Sprintf("/websites/%s/guestbook/comments/create", slug), http.StatusSeeOther) + } http.Redirect(w, r, fmt.Sprintf("/websites/%s/guestbook", slug), http.StatusSeeOther) } diff --git a/cmd/web/routes.go b/cmd/web/routes.go index 0b72dda..6faea18 100644 --- a/cmd/web/routes.go +++ b/cmd/web/routes.go @@ -17,8 +17,10 @@ func (app *application) routes() http.Handler { standard := alice.New(app.recoverPanic, app.logRequest, commonHeaders) mux.Handle("/{$}", dynamic.ThenFunc(app.home)) - mux.Handle("POST /websites/{id}/guestbook/comments/create", dynamic.ThenFunc(app.postGuestbookCommentCreate)) mux.Handle("GET /websites/{id}/guestbook", dynamic.ThenFunc(app.getGuestbook)) + mux.Handle("GET /websites/{id}/guestbook/comments", standard.ThenFunc(app.getGuestbookCommentsSerialized)) + mux.Handle("GET /websites/{id}/guestbook/comments/create", dynamic.ThenFunc(app.getGuestbookCommentCreate)) + mux.Handle("POST /websites/{id}/guestbook/comments/create", dynamic.ThenFunc(app.postGuestbookCommentCreate)) mux.Handle("GET /users/register", dynamic.ThenFunc(app.getUserRegister)) mux.Handle("POST /users/register", dynamic.ThenFunc(app.postUserRegister)) mux.Handle("GET /users/login", dynamic.ThenFunc(app.getUserLogin)) @@ -48,7 +50,6 @@ func (app *application) routes() http.Handler { mux.Handle("GET /websites/{id}/dashboard/guestbook/comments/trash", protected.ThenFunc(app.getCommentTrash)) mux.Handle("GET /websites/{id}/dashboard/guestbook/themes", protected.ThenFunc(app.getComingSoon)) mux.Handle("GET /websites/{id}/dashboard/guestbook/customize", protected.ThenFunc(app.getComingSoon)) - mux.Handle("GET /websites/{id}/guestbook/comments/create", protected.ThenFunc(app.getGuestbookCommentCreate)) return standard.Then(mux) } diff --git a/ui/static/js/main.js b/ui/static/js/main.js new file mode 100644 index 0000000..3943564 --- /dev/null +++ b/ui/static/js/main.js @@ -0,0 +1,13 @@ + +const convertDates = () => { + const dates = document.getElementsByTagName("time") + for (let i = 0; i < dates.length; i++) { + const e = dates.item(i) + const d = e.attributes.getNamedItem("datetime").value + const dt = new Date(Date.parse(d)) + const localtime = dt.toLocaleString("en-US", { "year": "numeric", "month": "short", "day": "numeric", "hour": "numeric", "minute": "numeric", "timeZoneName": "short"}) + e.innerText = localtime + } +} + +convertDates() diff --git a/ui/views/guestbooks.templ b/ui/views/guestbooks.templ index 5ad5583..c4835d8 100644 --- a/ui/views/guestbooks.templ +++ b/ui/views/guestbooks.templ @@ -61,7 +61,7 @@ templ GuestbookDashboardCommentView(data CommonData, w models.Website, c models. templ commentForm(form forms.CommentCreateForm) {
- + {{ error, exists := form.FieldErrors["authorName"] }} if exists { @@ -69,7 +69,7 @@ templ commentForm(form forms.CommentCreateForm) {
- + {{ error, exists = form.FieldErrors["authorEmail"] }} if exists { @@ -77,7 +77,7 @@ templ commentForm(form forms.CommentCreateForm) {
- + {{ error, exists = form.FieldErrors["authorSite"] }} if exists { @@ -85,7 +85,7 @@ templ commentForm(form forms.CommentCreateForm) {
- + {{ error, exists = form.FieldErrors["content"] }} if exists { @@ -106,11 +106,13 @@ templ GuestbookView(title string, data CommonData, website models.Website, guest { title } +

Guestbook for ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var23 string templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(website.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 113, Col: 38} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 114, Col: 38} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var24 templ.SafeURL = templ.URL(postUrl) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var24))) + var templ_7745c5c3_Var24 string + templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(data.Flash) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 115, Col: 18} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "\" method=\"post\">") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "\" method=\"post\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -514,155 +527,75 @@ func GuestbookView(title string, data CommonData, website models.Website, guestb if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if len(comments) == 0 { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "

No comments yet!

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "

No comments yet!

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } for _, c := range comments { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var26 string - templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(c.AuthorName) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 125, Col: 26} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var27 string - templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(c.Created.Format("01-02-2006 03:04PM")) + templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(c.AuthorName) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 126, Col: 48} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 127, Col: 26} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "\">") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var29 string + templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(c.Created.Format("01-02-2006 03:04PM")) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 128, Col: 98} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var30 string + templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(c.CommentText) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 130, Col: 24} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - return nil - }) -} - -func CreateGuestbookComment(title string, data CommonData, website models.Website, guestbook models.Guestbook, form forms.CommentCreateForm) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var29 := templ.GetChildren(ctx) - if templ_7745c5c3_Var29 == nil { - templ_7745c5c3_Var29 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - postUrl := fmt.Sprintf("/websites/%s/guestbook/comments/create", shortIdToSlug(website.ShortId)) - if data.IsHtmx { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = commentForm(form).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } else { - templ_7745c5c3_Var31 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = commentForm(form).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) - templ_7745c5c3_Err = base(title, data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var31), templ_7745c5c3_Buffer) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -687,61 +620,61 @@ func settingRadio(selected bool, name, id, value string) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var33 := templ.GetChildren(ctx) - if templ_7745c5c3_Var33 == nil { - templ_7745c5c3_Var33 = templ.NopComponent + templ_7745c5c3_Var31 := templ.GetChildren(ctx) + if templ_7745c5c3_Var31 == nil { + templ_7745c5c3_Var31 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, ">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -765,14 +698,14 @@ func GuestbookSettingsView(data CommonData, website models.Website) templ.Compon }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var37 := templ.GetChildren(ctx) - if templ_7745c5c3_Var37 == nil { - templ_7745c5c3_Var37 = templ.NopComponent + templ_7745c5c3_Var35 := templ.GetChildren(ctx) + if templ_7745c5c3_Var35 == nil { + templ_7745c5c3_Var35 = templ.NopComponent } ctx = templ.ClearChildren(ctx) putUrl := fmt.Sprintf("/websites/%s/dashboard/guestbook/settings", shortIdToSlug(website.ShortId)) gb := website.Guestbook - templ_7745c5c3_Var38 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_Var36 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) if !templ_7745c5c3_IsBuffer { @@ -784,7 +717,7 @@ func GuestbookSettingsView(data CommonData, website models.Website) templ.Compon }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -792,149 +725,222 @@ func GuestbookSettingsView(data CommonData, website models.Website) templ.Compon if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "

Guestbook Settings

Guestbook Settings

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 79, "> No
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } return nil }) - templ_7745c5c3_Err = base("Guestbook Settings", data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var38), templ_7745c5c3_Buffer) + templ_7745c5c3_Err = base("Guestbook Settings", data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var36), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func EmbeddableGuestbookCommentForm(data CommonData, w models.Website, f forms.CommentCreateForm) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var42 := templ.GetChildren(ctx) + if templ_7745c5c3_Var42 == nil { + templ_7745c5c3_Var42 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + postUrl := fmt.Sprintf("/websites/%s/guestbook/comments/create?headless=true", shortIdToSlug(w.ShortId)) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 80, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var43 string + templ_7745c5c3_Var43, templ_7745c5c3_Err = templ.JoinStringErrs(data.Flash) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 208, Col: 15} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var43)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = commentForm(f).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -958,12 +964,12 @@ func AllGuestbooksView(data CommonData, websites []models.Website) templ.Compone }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var44 := templ.GetChildren(ctx) - if templ_7745c5c3_Var44 == nil { - templ_7745c5c3_Var44 = templ.NopComponent + templ_7745c5c3_Var46 := templ.GetChildren(ctx) + if templ_7745c5c3_Var46 == nil { + templ_7745c5c3_Var46 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Var45 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_Var47 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) if !templ_7745c5c3_IsBuffer { @@ -975,50 +981,50 @@ func AllGuestbooksView(data CommonData, websites []models.Website) templ.Compone }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, "

All Guestbooks

This page exists only for testing the service.

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } return nil }) - templ_7745c5c3_Err = base("All Guestbooks", data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var45), templ_7745c5c3_Buffer) + templ_7745c5c3_Err = base("All Guestbooks", data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var47), templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } -- 2.30.2 From 89be6fa34dc0b37a5ed91c7669b047ac895aa570 Mon Sep 17 00:00:00 2001 From: yequari Date: Thu, 12 Jun 2025 09:36:45 -0700 Subject: [PATCH 2/7] cors middleware --- cmd/web/middleware.go | 141 ++++++++++++++++++++++-------------------- cmd/web/routes.go | 3 +- 2 files changed, 76 insertions(+), 68 deletions(-) diff --git a/cmd/web/middleware.go b/cmd/web/middleware.go index cb43934..c9d83ec 100644 --- a/cmd/web/middleware.go +++ b/cmd/web/middleware.go @@ -8,87 +8,94 @@ import ( "github.com/justinas/nosurf" ) -func (app *application) logRequest (next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - var ( - ip = r.RemoteAddr - proto = r.Proto - method = r.Method - uri = r.URL.RequestURI() - ) - app.logger.Info("received request", "ip", ip, "proto", proto, "method", method, "uri", uri) - next.ServeHTTP(w, r) - }) +func (app *application) logRequest(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var ( + ip = r.RemoteAddr + proto = r.Proto + method = r.Method + uri = r.URL.RequestURI() + ) + app.logger.Info("received request", "ip", ip, "proto", proto, "method", method, "uri", uri) + next.ServeHTTP(w, r) + }) } -func commonHeaders (next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Security-Policy", "default-src 'self'; style-src 'self' fonts.googleapis.com; font-src fonts.gstatic.com") - w.Header().Set("Referrer-Policy", "origin-when-cross-origin") - w.Header().Set("X-Content-Type-Options", "nosniff") - // w.Header().Set("X-Frame-Options", "deny") - w.Header().Set("X-XSS-Protection", "0") - next.ServeHTTP(w, r) - }) +func commonHeaders(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Security-Policy", "default-src 'self'; style-src 'self' fonts.googleapis.com; font-src fonts.gstatic.com") + w.Header().Set("Referrer-Policy", "origin-when-cross-origin") + w.Header().Set("X-Content-Type-Options", "nosniff") + // w.Header().Set("X-Frame-Options", "deny") + w.Header().Set("X-XSS-Protection", "0") + next.ServeHTTP(w, r) + }) } func (app *application) recoverPanic(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer func() { - if err := recover(); err != nil { - w.Header().Set("Connection", "close") - app.serverError(w, r, fmt.Errorf("%s", err)) - } - }() + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + defer func() { + if err := recover(); err != nil { + w.Header().Set("Connection", "close") + app.serverError(w, r, fmt.Errorf("%s", err)) + } + }() - next.ServeHTTP(w, r) - }) + next.ServeHTTP(w, r) + }) } func (app *application) requireAuthentication(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if !app.isAuthenticated(r) { - http.Redirect(w, r, "/users/login", http.StatusSeeOther) - return - } - w.Header().Add("Cache-Control", "no-store") - next.ServeHTTP(w, r) - }) + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if !app.isAuthenticated(r) { + http.Redirect(w, r, "/users/login", http.StatusSeeOther) + return + } + w.Header().Add("Cache-Control", "no-store") + next.ServeHTTP(w, r) + }) } func noSurf(next http.Handler) http.Handler { - csrfHandler := nosurf.New(next) - csrfHandler.SetBaseCookie(http.Cookie{ - HttpOnly: true, - Path: "/", - Secure: true, - }) + csrfHandler := nosurf.New(next) + csrfHandler.SetBaseCookie(http.Cookie{ + HttpOnly: true, + Path: "/", + Secure: true, + }) - return csrfHandler + return csrfHandler } func (app *application) authenticate(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - id := app.sessionManager.GetInt64(r.Context(), "authenticatedUserId") - if id == 0 { - next.ServeHTTP(w, r) - return - } - exists, err := app.users.Exists(id) - if err != nil { - app.serverError(w, r, err) - return - } - user, err := app.users.GetById(id) - if err != nil { - app.serverError(w, r, err) - return - } - if exists { - ctx := context.WithValue(r.Context(), isAuthenticatedContextKey, true) - ctx = context.WithValue(ctx, userNameContextKey, user) - r = r.WithContext(ctx) - } - next.ServeHTTP(w, r) - }) + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + id := app.sessionManager.GetInt64(r.Context(), "authenticatedUserId") + if id == 0 { + next.ServeHTTP(w, r) + return + } + exists, err := app.users.Exists(id) + if err != nil { + app.serverError(w, r, err) + return + } + user, err := app.users.GetById(id) + if err != nil { + app.serverError(w, r, err) + return + } + if exists { + ctx := context.WithValue(r.Context(), isAuthenticatedContextKey, true) + ctx = context.WithValue(ctx, userNameContextKey, user) + r = r.WithContext(ctx) + } + next.ServeHTTP(w, r) + }) +} + +func (app *application) enableCors(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Access-Control-Allow-Origin", "*") + next.ServeHTTP(w, r) + }) } diff --git a/cmd/web/routes.go b/cmd/web/routes.go index 6faea18..f6b6375 100644 --- a/cmd/web/routes.go +++ b/cmd/web/routes.go @@ -15,10 +15,11 @@ func (app *application) routes() http.Handler { dynamic := alice.New(app.sessionManager.LoadAndSave, noSurf, app.authenticate) standard := alice.New(app.recoverPanic, app.logRequest, commonHeaders) + withCors := standard.Append(app.enableCors) mux.Handle("/{$}", dynamic.ThenFunc(app.home)) mux.Handle("GET /websites/{id}/guestbook", dynamic.ThenFunc(app.getGuestbook)) - mux.Handle("GET /websites/{id}/guestbook/comments", standard.ThenFunc(app.getGuestbookCommentsSerialized)) + mux.Handle("GET /websites/{id}/guestbook/comments", withCors.ThenFunc(app.getGuestbookCommentsSerialized)) mux.Handle("GET /websites/{id}/guestbook/comments/create", dynamic.ThenFunc(app.getGuestbookCommentCreate)) mux.Handle("POST /websites/{id}/guestbook/comments/create", dynamic.ThenFunc(app.postGuestbookCommentCreate)) mux.Handle("GET /users/register", dynamic.ThenFunc(app.getUserRegister)) -- 2.30.2 From a72d32850b784002395a5099082d7ae6fb6b9d7a Mon Sep 17 00:00:00 2001 From: yequari Date: Wed, 25 Jun 2025 00:02:27 -0700 Subject: [PATCH 3/7] setup remote commenting, serialize comments in json --- cmd/web/handlers_guestbook.go | 51 ++- cmd/web/routes.go | 1 + internal/models/guestbookcomment.go | 31 ++ ui/views/common.templ | 122 ++++--- ui/views/common_templ.go | 8 +- ui/views/guestbooks.templ | 42 ++- ui/views/guestbooks_templ.go | 506 ++++++++++++++++++---------- ui/views/home.templ | 26 +- 8 files changed, 524 insertions(+), 263 deletions(-) diff --git a/cmd/web/handlers_guestbook.go b/cmd/web/handlers_guestbook.go index 46668bc..1295114 100644 --- a/cmd/web/handlers_guestbook.go +++ b/cmd/web/handlers_guestbook.go @@ -151,11 +151,12 @@ func (app *application) getGuestbookCommentsSerialized(w http.ResponseWriter, r if !website.Guestbook.Settings.IsVisible || !website.Guestbook.Settings.AllowRemoteHostAccess { app.clientError(w, http.StatusForbidden) } - comments, err := app.guestbookComments.GetAll(website.Guestbook.ID) + comments, err := app.guestbookComments.GetAllSerialized(website.Guestbook.ID) if err != nil { app.serverError(w, r, err) return } + b, err := json.Marshal(comments) w.Write(b) } @@ -244,6 +245,54 @@ func (app *application) postGuestbookCommentCreate(w http.ResponseWriter, r *htt http.Redirect(w, r, fmt.Sprintf("/websites/%s/guestbook", slug), http.StatusSeeOther) } +func (app *application) postGuestbookCommentCreateRemote(w http.ResponseWriter, r *http.Request) { + slug := r.PathValue("id") + website, err := app.websites.Get(slugToShortId(slug)) + if err != nil { + if errors.Is(err, models.ErrNoRecord) { + http.NotFound(w, r) + } else { + app.serverError(w, r, err) + } + return + } + + if !website.Guestbook.CanComment() { + app.clientError(w, http.StatusForbidden) + return + } + + var form forms.CommentCreateForm + err = app.decodePostForm(r, &form) + if err != nil { + app.clientError(w, http.StatusBadRequest) + return + } + + form.CheckField(validator.NotBlank(form.AuthorName), "authorName", "This field cannot be blank") + form.CheckField(validator.MaxChars(form.AuthorName, 256), "authorName", "This field cannot be more than 256 characters long") + form.CheckField(validator.MaxChars(form.AuthorEmail, 256), "authorEmail", "This field cannot be more than 256 characters long") + form.CheckField(validator.MaxChars(form.AuthorSite, 256), "authorSite", "This field cannot be more than 256 characters long") + form.CheckField(validator.NotBlank(form.Content), "content", "This field cannot be blank") + // TODO: Add optional field filled with window.location.href + // If it is populated, use as redirect URL + // Else redirect to homepage as stored in the website struct + redirectUrl := r.Header.Get("Referer") + + if !form.Valid() { + views.GuestbookCommentCreateRemoteErrorView(redirectUrl, "Invalid Input").Render(r.Context(), w) + return + } + + shortId := app.createShortId() + _, err = app.guestbookComments.Insert(shortId, website.Guestbook.ID, 0, form.AuthorName, form.AuthorEmail, form.AuthorSite, form.Content, "", true) + if err != nil { + app.serverError(w, r, err) + return + } + views.GuestbookCommentCreateRemoteSuccessView(redirectUrl).Render(r.Context(), w) +} + func (app *application) getCommentQueue(w http.ResponseWriter, r *http.Request) { slug := r.PathValue("id") website, err := app.websites.Get(slugToShortId(slug)) diff --git a/cmd/web/routes.go b/cmd/web/routes.go index f6b6375..f9e768e 100644 --- a/cmd/web/routes.go +++ b/cmd/web/routes.go @@ -20,6 +20,7 @@ func (app *application) routes() http.Handler { mux.Handle("/{$}", dynamic.ThenFunc(app.home)) mux.Handle("GET /websites/{id}/guestbook", dynamic.ThenFunc(app.getGuestbook)) mux.Handle("GET /websites/{id}/guestbook/comments", withCors.ThenFunc(app.getGuestbookCommentsSerialized)) + mux.Handle("POST /websites/{id}/guestbook/comments/create/remote", standard.ThenFunc(app.postGuestbookCommentCreateRemote)) mux.Handle("GET /websites/{id}/guestbook/comments/create", dynamic.ThenFunc(app.getGuestbookCommentCreate)) mux.Handle("POST /websites/{id}/guestbook/comments/create", dynamic.ThenFunc(app.postGuestbookCommentCreate)) mux.Handle("GET /users/register", dynamic.ThenFunc(app.getUserRegister)) diff --git a/internal/models/guestbookcomment.go b/internal/models/guestbookcomment.go index 4a7203b..d44de26 100644 --- a/internal/models/guestbookcomment.go +++ b/internal/models/guestbookcomment.go @@ -20,6 +20,12 @@ type GuestbookComment struct { IsPublished bool } +type GuestbookCommentSerialized struct { + AuthorName string + CommentText string + Created string +} + type GuestbookCommentModel struct { DB *sql.DB } @@ -28,6 +34,7 @@ type GuestbookCommentModelInterface interface { Insert(shortId uint64, guestbookId, parentId int64, authorName, authorEmail, authorSite, commentText, pageUrl string, isPublished bool) (int64, error) Get(shortId uint64) (GuestbookComment, error) GetAll(guestbookId int64) ([]GuestbookComment, error) + GetAllSerialized(guestbookId int64) ([]GuestbookCommentSerialized, error) GetDeleted(guestbookId int64) ([]GuestbookComment, error) GetUnpublished(guestbookId int64) ([]GuestbookComment, error) UpdateComment(comment *GuestbookComment) error @@ -93,6 +100,30 @@ func (m *GuestbookCommentModel) GetAll(guestbookId int64) ([]GuestbookComment, e return comments, nil } +func (m *GuestbookCommentModel) GetAllSerialized(guestbookId int64) ([]GuestbookCommentSerialized, error) { + stmt := `SELECT AuthorName, CommentText, Created + FROM guestbook_comments + WHERE GuestbookId = ? AND IsPublished = TRUE AND DELETED IS NULL + ORDER BY Created DESC` + rows, err := m.DB.Query(stmt, guestbookId) + if err != nil { + return nil, err + } + var comments []GuestbookCommentSerialized + for rows.Next() { + var c GuestbookCommentSerialized + err = rows.Scan(&c.AuthorName, &c.CommentText, &c.Created) + if err != nil { + return nil, err + } + comments = append(comments, c) + } + if err = rows.Err(); err != nil { + return nil, err + } + return comments, nil +} + func (m *GuestbookCommentModel) GetDeleted(guestbookId int64) ([]GuestbookComment, error) { stmt := `SELECT Id, ShortId, GuestbookId, ParentId, AuthorName, AuthorEmail, AuthorSite, CommentText, PageUrl, Created, IsPublished, Deleted diff --git a/ui/views/common.templ b/ui/views/common.templ index 13b5dd4..a9111f1 100644 --- a/ui/views/common.templ +++ b/ui/views/common.templ @@ -6,87 +6,85 @@ import "fmt" import "strings" type CommonData struct { - CurrentYear int - Flash string - IsAuthenticated bool - CSRFToken string - CurrentUser *models.User - IsHtmx bool + CurrentYear int + Flash string + IsAuthenticated bool + CSRFToken string + CurrentUser *models.User + IsHtmx bool } func shortIdToSlug(shortId uint64) string { - return strconv.FormatUint(shortId, 36) + return strconv.FormatUint(shortId, 36) } func slugToShortId(slug string) uint64 { - id, _ := strconv.ParseUint(slug, 36, 64) - return id + id, _ := strconv.ParseUint(slug, 36, 64) + return id } func externalUrl(url string) string { - if !strings.HasPrefix(url, "http") { - return "http://" + url - } - return url + if !strings.HasPrefix(url, "http") { + return "http://" + url + } + return url } templ commonHeader() { -
-

webweav.ing

-
+
+

webweav.ing

+
} templ topNav(data CommonData) { - {{ hxHeaders := fmt.Sprintf("{\"X-CSRF-Token\": \"%s\"}", data.CSRFToken) }} - + {{ hxHeaders := fmt.Sprintf("{\"X-CSRF-Token\": \"%s\"}", data.CSRFToken) }} + } - templ commonFooter() { - + } templ base(title string, data CommonData) { - - - - { title } - webweav.ing - - - - - - - - @commonHeader() - @topNav(data) -
- if data.Flash != "" { -
{ data.Flash }
- } - { children... } -
- @commonFooter() - - + + + + { title } - webweav.ing + + + + + + + + @commonHeader() + @topNav(data) +
+ if data.Flash != "" { +
{ data.Flash }
+ } + { children... } +
+ @commonFooter() + + } - diff --git a/ui/views/common_templ.go b/ui/views/common_templ.go index f707c81..8d07a52 100644 --- a/ui/views/common_templ.go +++ b/ui/views/common_templ.go @@ -101,7 +101,7 @@ func topNav(data CommonData) templ.Component { var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(data.CurrentUser.Username) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 44, Col: 52} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 44, Col: 40} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -120,7 +120,7 @@ func topNav(data CommonData) templ.Component { var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(hxHeaders) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 52, Col: 74} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 52, Col: 62} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { @@ -201,7 +201,7 @@ func base(title string, data CommonData) templ.Component { var templ_7745c5c3_Var7 string templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 72, Col: 26} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 71, Col: 17} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { @@ -231,7 +231,7 @@ func base(title string, data CommonData) templ.Component { var templ_7745c5c3_Var8 string templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(data.Flash) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 84, Col: 51} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 83, Col: 36} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) if templ_7745c5c3_Err != nil { diff --git a/ui/views/guestbooks.templ b/ui/views/guestbooks.templ index c4835d8..8004437 100644 --- a/ui/views/guestbooks.templ +++ b/ui/views/guestbooks.templ @@ -106,7 +106,7 @@ templ GuestbookView(title string, data CommonData, website models.Website, guest { title } -
@@ -124,7 +124,13 @@ templ GuestbookView(title string, data CommonData, website models.Website, guest } for _, c := range comments {
-

{ c.AuthorName }

+

+ if c.AuthorSite != "" { + { c.AuthorName } + } else { + { c.AuthorName } + } +

{ c.CommentText } @@ -232,3 +238,35 @@ templ AllGuestbooksView(data CommonData, websites []models.Website) {

} } + +templ GuestbookCommentCreateRemoteErrorView(url, err string) { + + + + + +

+ An error occurred while posting comment. { err }. Redirecting. +

+

+ Redirect +

+ + +} + +templ GuestbookCommentCreateRemoteSuccessView(url string) { + + + + + +

+ Comment successfully posted. Redirecting. +

+

+ Redirect +

+ + +} diff --git a/ui/views/guestbooks_templ.go b/ui/views/guestbooks_templ.go index 3c55d7c..5b12c21 100644 --- a/ui/views/guestbooks_templ.go +++ b/ui/views/guestbooks_templ.go @@ -542,60 +542,89 @@ func GuestbookView(title string, data CommonData, website models.Website, guestb if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var27 string - templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(c.AuthorName) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 127, Col: 26} + if c.AuthorSite != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var28 string + templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(c.AuthorName) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 129, Col: 89} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + var templ_7745c5c3_Var29 string + templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(c.AuthorName) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 131, Col: 24} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var32 string + templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(c.CommentText) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 136, Col: 24} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -620,61 +649,61 @@ func settingRadio(selected bool, name, id, value string) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var31 := templ.GetChildren(ctx) - if templ_7745c5c3_Var31 == nil { - templ_7745c5c3_Var31 = templ.NopComponent + templ_7745c5c3_Var33 := templ.GetChildren(ctx) + if templ_7745c5c3_Var33 == nil { + templ_7745c5c3_Var33 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, ">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -698,14 +727,14 @@ func GuestbookSettingsView(data CommonData, website models.Website) templ.Compon }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var35 := templ.GetChildren(ctx) - if templ_7745c5c3_Var35 == nil { - templ_7745c5c3_Var35 = templ.NopComponent + templ_7745c5c3_Var37 := templ.GetChildren(ctx) + if templ_7745c5c3_Var37 == nil { + templ_7745c5c3_Var37 = templ.NopComponent } ctx = templ.ClearChildren(ctx) putUrl := fmt.Sprintf("/websites/%s/dashboard/guestbook/settings", shortIdToSlug(website.ShortId)) gb := website.Guestbook - templ_7745c5c3_Var36 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_Var38 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) if !templ_7745c5c3_IsBuffer { @@ -717,7 +746,7 @@ func GuestbookSettingsView(data CommonData, website models.Website) templ.Compon }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -725,149 +754,149 @@ func GuestbookSettingsView(data CommonData, website models.Website) templ.Compon if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "

Guestbook Settings

Guestbook Settings

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 82, "> No
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } return nil }) - templ_7745c5c3_Err = base("Guestbook Settings", data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var36), templ_7745c5c3_Buffer) + templ_7745c5c3_Err = base("Guestbook Settings", data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var38), templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -891,48 +920,48 @@ func EmbeddableGuestbookCommentForm(data CommonData, w models.Website, f forms.C }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var42 := templ.GetChildren(ctx) - if templ_7745c5c3_Var42 == nil { - templ_7745c5c3_Var42 = templ.NopComponent + templ_7745c5c3_Var44 := templ.GetChildren(ctx) + if templ_7745c5c3_Var44 == nil { + templ_7745c5c3_Var44 = templ.NopComponent } ctx = templ.ClearChildren(ctx) postUrl := fmt.Sprintf("/websites/%s/guestbook/comments/create?headless=true", shortIdToSlug(w.ShortId)) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 80, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var43 string - templ_7745c5c3_Var43, templ_7745c5c3_Err = templ.JoinStringErrs(data.Flash) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 208, Col: 15} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var43)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var45 string - templ_7745c5c3_Var45, templ_7745c5c3_Err = templ.JoinStringErrs(data.CSRFToken) + templ_7745c5c3_Var45, templ_7745c5c3_Err = templ.JoinStringErrs(data.Flash) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 210, Col: 65} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 214, Col: 15} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var45)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 83, "\">") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -940,7 +969,7 @@ func EmbeddableGuestbookCommentForm(data CommonData, w models.Website, f forms.C if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 87, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -964,12 +993,12 @@ func AllGuestbooksView(data CommonData, websites []models.Website) templ.Compone }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var46 := templ.GetChildren(ctx) - if templ_7745c5c3_Var46 == nil { - templ_7745c5c3_Var46 = templ.NopComponent + templ_7745c5c3_Var48 := templ.GetChildren(ctx) + if templ_7745c5c3_Var48 == nil { + templ_7745c5c3_Var48 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Var47 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_Var49 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) if !templ_7745c5c3_IsBuffer { @@ -981,50 +1010,165 @@ func AllGuestbooksView(data CommonData, websites []models.Website) templ.Compone }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 85, "

All Guestbooks

This page exists only for testing the service.

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } return nil }) - templ_7745c5c3_Err = base("All Guestbooks", data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var47), templ_7745c5c3_Buffer) + templ_7745c5c3_Err = base("All Guestbooks", data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var49), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func GuestbookCommentCreateRemoteErrorView(url, err string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var52 := templ.GetChildren(ctx) + if templ_7745c5c3_Var52 == nil { + templ_7745c5c3_Var52 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 94, "

An error occurred while posting comment. ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var54 string + templ_7745c5c3_Var54, templ_7745c5c3_Err = templ.JoinStringErrs(err) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 249, Col: 50} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var54)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 96, ". Redirecting.

Redirect

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func GuestbookCommentCreateRemoteSuccessView(url string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var56 := templ.GetChildren(ctx) + if templ_7745c5c3_Var56 == nil { + templ_7745c5c3_Var56 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 98, "

Comment successfully posted. Redirecting.

Redirect

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/ui/views/home.templ b/ui/views/home.templ index 9276a04..e4fa8c5 100644 --- a/ui/views/home.templ +++ b/ui/views/home.templ @@ -1,19 +1,19 @@ package views templ Home(title string, data CommonData) { - @base(title, data) { -

Welcome

-

- Welcome to webweav.ing, a collection of webmastery tools created by the 32-Bit Cafe. -

-

- Note this service is in a pre-alpha state. Your account and data can disappear at any time. -

- } + @base(title, data) { +

Welcome

+

+ Welcome to webweav.ing, a collection of webmastery tools created by the 32-Bit Cafe. +

+

+ Note this service is in a pre-alpha state. Your account and data can disappear at any time. +

+ } } templ ComingSoon(title string, data CommonData) { - @base(title, data) { -

Coming Soon

- } -} \ No newline at end of file + @base(title, data) { +

Coming Soon

+ } +} -- 2.30.2 From 306053b1e36e9812a499d5414eb9bd7aa8531c98 Mon Sep 17 00:00:00 2001 From: yequari Date: Wed, 25 Jun 2025 20:05:37 -0700 Subject: [PATCH 4/7] allow remote comments only from expected url --- cmd/web/handlers_guestbook.go | 5 +++++ cmd/web/helpers.go | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/cmd/web/handlers_guestbook.go b/cmd/web/handlers_guestbook.go index 1295114..ce531b2 100644 --- a/cmd/web/handlers_guestbook.go +++ b/cmd/web/handlers_guestbook.go @@ -257,6 +257,11 @@ func (app *application) postGuestbookCommentCreateRemote(w http.ResponseWriter, return } + if normalizeUrl(r.Header.Get("Origin")) != normalizeUrl(website.SiteUrl) { + app.clientError(w, http.StatusForbidden) + return + } + if !website.Guestbook.CanComment() { app.clientError(w, http.StatusForbidden) return diff --git a/cmd/web/helpers.go b/cmd/web/helpers.go index 7d927d1..3060325 100644 --- a/cmd/web/helpers.go +++ b/cmd/web/helpers.go @@ -7,6 +7,7 @@ import ( "net/http" "runtime/debug" "strconv" + "strings" "time" "git.32bit.cafe/32bitcafe/guestbook/internal/models" @@ -127,3 +128,12 @@ func (app *application) durationToTime(duration string) (time.Time, error) { result = time.Now().UTC().Add(offset) return result, nil } + +func normalizeUrl(url string) string { + r, f := strings.CutPrefix(url, "http://") + if f { + return r + } + r, _ = strings.CutPrefix(url, "https://") + return r +} -- 2.30.2 From c3b10ae2390114196557cc57ac9e44efbdb53366 Mon Sep 17 00:00:00 2001 From: yequari Date: Thu, 26 Jun 2025 20:12:24 -0700 Subject: [PATCH 5/7] add remote client examples and webcomponent script --- cmd/web/helpers.go | 1 + cmd/web/main.go | 3 + ui/static/css/style.css | 7 ++ ui/static/js/guestbook.js | 105 +++++++++++++++++++++ ui/views/common_templ.go | 1 + ui/views/websites.templ | 69 +++++++++++++- ui/views/websites_templ.go | 189 +++++++++++++++++++++++++++++++++---- 7 files changed, 356 insertions(+), 19 deletions(-) create mode 100644 ui/static/js/guestbook.js diff --git a/cmd/web/helpers.go b/cmd/web/helpers.go index 3060325..e1321e0 100644 --- a/cmd/web/helpers.go +++ b/cmd/web/helpers.go @@ -110,6 +110,7 @@ func (app *application) newCommonData(r *http.Request) views.CommonData { CSRFToken: nosurf.Token(r), CurrentUser: app.getCurrentUser(r), IsHtmx: r.Header.Get("Hx-Request") == "true", + RootUrl: app.rootUrl, } } diff --git a/cmd/web/main.go b/cmd/web/main.go index 7f22d07..5fe6dac 100644 --- a/cmd/web/main.go +++ b/cmd/web/main.go @@ -28,12 +28,14 @@ type application struct { formDecoder *schema.Decoder debug bool timezones []string + rootUrl string } func main() { addr := flag.String("addr", ":3000", "HTTP network address") dsn := flag.String("dsn", "guestbook.db", "data source name") debug := flag.Bool("debug", false, "enable debug mode") + root := flag.String("root", "localhost:3000", "root URL of application") flag.Parse() logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug})) @@ -62,6 +64,7 @@ func main() { formDecoder: formDecoder, debug: *debug, timezones: getAvailableTimezones(), + rootUrl: *root, } err = app.users.InitializeSettingsMap() diff --git a/ui/static/css/style.css b/ui/static/css/style.css index 0b1a576..1f291a6 100644 --- a/ui/static/css/style.css +++ b/ui/static/css/style.css @@ -58,10 +58,17 @@ div#dashboard { div#dashboard nav { flex: 1 1 25%; margin-top: 2rem; + min-width: 0; } div#dashboard > div { flex: 10 1 40%; + min-width: 0; +} + +div > pre { + max-width: 100%; + overflow: auto; } main nav ul { diff --git a/ui/static/js/guestbook.js b/ui/static/js/guestbook.js new file mode 100644 index 0000000..9af432e --- /dev/null +++ b/ui/static/js/guestbook.js @@ -0,0 +1,105 @@ +class CommentList extends HTMLElement { + static get observedAttributes() { + return ['src']; + } + + constructor() { + super(); + this.attachShadow({ mode: 'open' }); + this.comments = []; + this.loading = false; + this.error = null; + } + + connectedCallback() { + if (this.hasAttribute('src')) { + this.fetchComments(); + } + } + + attributeChangedCallback(name, oldValue, newValue) { + if (name === 'src' && oldValue !== newValue) { + this.fetchComments(); + } + } + + async fetchComments() { + const src = this.getAttribute('src'); + if (!src) return; + this.loading = true; + this.error = null; + this.render(); + + try { + const response = await fetch(src); + if (!response.ok) throw new Error(`HTTP error: ${response.status}`); + const data = await response.json(); + this.comments = Array.isArray(data) ? data : []; + this.loading = false; + this.render(); + } catch (err) { + this.error = err.message; + this.loading = false; + this.render(); + } + } + + formatDate(isoString) { + if (!isoString) return ''; + const date = new Date(isoString); + return date.toLocaleString(); + } + + render() { + this.shadowRoot.innerHTML = ` + +
+ ${this.loading + ? `
Loading comments...
` + : this.error + ? `
Error: ${this.error}
` + : this.comments.length === 0 + ? `
No comments found.
` + : this.comments.map(comment => ` +
+ ${comment.AuthorName || 'Unknown Author'} + ${this.formatDate(comment.Created)} +
${comment.CommentText || ''}
+
+ `).join('') + } +
+ `; + } +} + +customElements.define('comment-list', CommentList); diff --git a/ui/views/common_templ.go b/ui/views/common_templ.go index 8d07a52..e79039c 100644 --- a/ui/views/common_templ.go +++ b/ui/views/common_templ.go @@ -20,6 +20,7 @@ type CommonData struct { CSRFToken string CurrentUser *models.User IsHtmx bool + RootUrl string } func shortIdToSlug(shortId uint64) string { diff --git a/ui/views/websites.templ b/ui/views/websites.templ index d778676..1c7f1be 100644 --- a/ui/views/websites.templ +++ b/ui/views/websites.templ @@ -115,9 +115,41 @@ templ WebsiteDashboard(title string, data CommonData, website models.Website) { @wSidebar(website)

{ website.Name }

+

Embed your Guestbook

+

Comment form

- Stats and stuff will go here. + Use this form to allow readers of your website to comment on your guestbook!

+
+ // + @embeddableForm(data.RootUrl, website) +
+

Embed your comments

+

+ Upload this JavaScript WebComponent to your site and include it in your { `` } tag. +

+
+ // +
+						
+							{ 
+`
+    
+` }
+						
+					
+

+ Then add the custom element where you want the comments to show up +

+ {{ getUrl := fmt.Sprintf("https://%s/websites/%s/guestbook/comments", data.RootUrl, shortIdToSlug(website.ShortId)) }} + // +
+						
+							{ fmt.Sprintf(``, getUrl) }
+						
+					
+ @embedJavaScriptSnippet(data.RootUrl, website) +
} @@ -142,3 +174,38 @@ templ WebsiteCreate(title string, data CommonData, form forms.WebsiteCreateForm) @websiteCreateForm(data.CSRFToken, form) } + +templ embeddableForm(root string, website models.Website) { + {{ postUrl := fmt.Sprintf("https://%s/websites/%s/guestbook/comments/create/remote", root, shortIdToSlug(website.ShortId)) }} + {{formStr := + `
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
` + }} +
+		
+			{ fmt.Sprintf(formStr, postUrl) }
+		
+	
+} + +templ embedJavaScriptSnippet(root string, website models.Website) { +} diff --git a/ui/views/websites_templ.go b/ui/views/websites_templ.go index 2d6d6a6..a19e987 100644 --- a/ui/views/websites_templ.go +++ b/ui/views/websites_templ.go @@ -481,7 +481,70 @@ func WebsiteDashboard(title string, data CommonData, website models.Website) tem if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "

Stats and stuff will go here.

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "

Embed your Guestbook

Comment form

Use this form to allow readers of your website to comment on your guestbook!

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = embeddableForm(data.RootUrl, website).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "

Embed your comments

Upload this JavaScript WebComponent to your site and include it in your ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var26 string + templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(``) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 129, Col: 140} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, " tag.

")
+			if templ_7745c5c3_Err != nil {
+				return templ_7745c5c3_Err
+			}
+			var templ_7745c5c3_Var27 string
+			templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(
+				`
+    
+`)
+			if templ_7745c5c3_Err != nil {
+				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 138, Col: 8}
+			}
+			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27))
+			if templ_7745c5c3_Err != nil {
+				return templ_7745c5c3_Err
+			}
+			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "

Then add the custom element where you want the comments to show up

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + getUrl := fmt.Sprintf("https://%s/websites/%s/guestbook/comments", data.RootUrl, shortIdToSlug(website.ShortId)) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "
")
+			if templ_7745c5c3_Err != nil {
+				return templ_7745c5c3_Err
+			}
+			var templ_7745c5c3_Var28 string
+			templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf(``, getUrl))
+			if templ_7745c5c3_Err != nil {
+				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 148, Col: 70}
+			}
+			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28))
+			if templ_7745c5c3_Err != nil {
+				return templ_7745c5c3_Err
+			}
+			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = embedJavaScriptSnippet(data.RootUrl, website).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -511,12 +574,12 @@ func WebsiteDashboardComingSoon(title string, data CommonData, website models.We }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var26 := templ.GetChildren(ctx) - if templ_7745c5c3_Var26 == nil { - templ_7745c5c3_Var26 = templ.NopComponent + templ_7745c5c3_Var29 := templ.GetChildren(ctx) + if templ_7745c5c3_Var29 == nil { + templ_7745c5c3_Var29 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Var27 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_Var30 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) if !templ_7745c5c3_IsBuffer { @@ -528,7 +591,7 @@ func WebsiteDashboardComingSoon(title string, data CommonData, website models.We }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -536,26 +599,26 @@ func WebsiteDashboardComingSoon(title string, data CommonData, website models.We if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var28 string - templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(website.Name) + var templ_7745c5c3_Var31 string + templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(website.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 131, Col: 22} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 163, Col: 22} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "

Coming Soon

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "

Coming Soon

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } return nil }) - templ_7745c5c3_Err = base(title, data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var27), templ_7745c5c3_Buffer) + templ_7745c5c3_Err = base(title, data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var30), templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -579,12 +642,12 @@ func WebsiteCreate(title string, data CommonData, form forms.WebsiteCreateForm) }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var29 := templ.GetChildren(ctx) - if templ_7745c5c3_Var29 == nil { - templ_7745c5c3_Var29 = templ.NopComponent + templ_7745c5c3_Var32 := templ.GetChildren(ctx) + if templ_7745c5c3_Var32 == nil { + templ_7745c5c3_Var32 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -592,7 +655,7 @@ func WebsiteCreate(title string, data CommonData, form forms.WebsiteCreateForm) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -600,4 +663,94 @@ func WebsiteCreate(title string, data CommonData, form forms.WebsiteCreateForm) }) } +func embeddableForm(root string, website models.Website) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var33 := templ.GetChildren(ctx) + if templ_7745c5c3_Var33 == nil { + templ_7745c5c3_Var33 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + postUrl := fmt.Sprintf("https://%s/websites/%s/guestbook/comments/create/remote", root, shortIdToSlug(website.ShortId)) + formStr := + `
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
` + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "
")
+		if templ_7745c5c3_Err != nil {
+			return templ_7745c5c3_Err
+		}
+		var templ_7745c5c3_Var34 string
+		templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf(formStr, postUrl))
+		if templ_7745c5c3_Err != nil {
+			return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 205, Col: 34}
+		}
+		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34))
+		if templ_7745c5c3_Err != nil {
+			return templ_7745c5c3_Err
+		}
+		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func embedJavaScriptSnippet(root string, website models.Website) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var35 := templ.GetChildren(ctx) + if templ_7745c5c3_Var35 == nil { + templ_7745c5c3_Var35 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + return nil + }) +} + var _ = templruntime.GeneratedTemplate -- 2.30.2 From a15bbcee3d761c4b6eb0ed193ed6ae8281420fe6 Mon Sep 17 00:00:00 2001 From: yequari Date: Fri, 27 Jun 2025 13:23:35 -0700 Subject: [PATCH 6/7] redirect to proper location --- cmd/web/handlers_guestbook.go | 17 +++++-- cmd/web/handlers_website.go | 7 ++- internal/forms/forms.go | 1 + ui/static/js/guestbook.js | 86 ++++++++++++++++++++++++++++--- ui/views/websites.templ | 27 +++++----- ui/views/websites_templ.go | 96 +++++++++++++++++------------------ 6 files changed, 159 insertions(+), 75 deletions(-) diff --git a/cmd/web/handlers_guestbook.go b/cmd/web/handlers_guestbook.go index ce531b2..1a6e6b3 100644 --- a/cmd/web/handlers_guestbook.go +++ b/cmd/web/handlers_guestbook.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "net/http" + "net/url" "strconv" "time" @@ -279,10 +280,18 @@ func (app *application) postGuestbookCommentCreateRemote(w http.ResponseWriter, form.CheckField(validator.MaxChars(form.AuthorEmail, 256), "authorEmail", "This field cannot be more than 256 characters long") form.CheckField(validator.MaxChars(form.AuthorSite, 256), "authorSite", "This field cannot be more than 256 characters long") form.CheckField(validator.NotBlank(form.Content), "content", "This field cannot be blank") - // TODO: Add optional field filled with window.location.href - // If it is populated, use as redirect URL - // Else redirect to homepage as stored in the website struct - redirectUrl := r.Header.Get("Referer") + + // if redirect path is filled out, redirect to that path on the website host + // otherwise redirect to the guestbook by default + redirectUrl := fmt.Sprintf("/websites/%s/guestbook", shortIdToSlug(website.ShortId)) + if form.Redirect != "" { + u := url.URL{ + Scheme: "http", + Host: website.SiteUrl, + Path: form.Redirect, + } + redirectUrl = u.String() + } if !form.Valid() { views.GuestbookCommentCreateRemoteErrorView(redirectUrl, "Invalid Input").Render(r.Context(), w) diff --git a/cmd/web/handlers_website.go b/cmd/web/handlers_website.go index cb94c72..9e6d2d5 100644 --- a/cmd/web/handlers_website.go +++ b/cmd/web/handlers_website.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "net/http" + "net/url" "git.32bit.cafe/32bitcafe/guestbook/internal/forms" "git.32bit.cafe/32bitcafe/guestbook/internal/models" @@ -33,13 +34,15 @@ func (app *application) postWebsiteCreate(w http.ResponseWriter, r *http.Request form.CheckField(validator.NotBlank(form.SiteUrl), "siteurl", "This field cannot be blank") form.CheckField(validator.MaxChars(form.SiteUrl, 512), "siteurl", "This field cannot exceed 512 characters") - if !form.Valid() { + u, err := url.Parse(form.SiteUrl) + + if !form.Valid() || err != nil { data := app.newCommonData(r) w.WriteHeader(http.StatusUnprocessableEntity) views.WebsiteCreate("Add a Website", data, form).Render(r.Context(), w) } websiteShortID := app.createShortId() - _, err = app.websites.Insert(websiteShortID, userId, form.Name, form.SiteUrl, form.AuthorName) + _, err = app.websites.Insert(websiteShortID, userId, form.Name, u.Host, form.AuthorName) if err != nil { app.serverError(w, r, err) return diff --git a/internal/forms/forms.go b/internal/forms/forms.go index b5edd31..30abaa3 100644 --- a/internal/forms/forms.go +++ b/internal/forms/forms.go @@ -20,6 +20,7 @@ type CommentCreateForm struct { AuthorEmail string `schema:"authoremail"` AuthorSite string `schema:"authorsite"` Content string `schema:"content"` + Redirect string `schema:"redirect"` validator.Validator `schema:"-"` } diff --git a/ui/static/js/guestbook.js b/ui/static/js/guestbook.js index 9af432e..8a6dcac 100644 --- a/ui/static/js/guestbook.js +++ b/ui/static/js/guestbook.js @@ -1,6 +1,74 @@ +class GuestbookForm extends HTMLElement { + static get observedAttributes() { + return ['guestbook']; + } + + constructor() { + super(); + this.attachShadow({ mode: 'open' }); + this._guestbook = this.getAttribute('guestbook') || ''; + this._postUrl = `${this._guestbook}/comments/create/remote` + this.render(); + } + + attributeChangedCallback(name, oldValue, newValue) { + if (name === 'guestbook') { + this._guestbook = newValue; + this.render(); + } + } + + render() { + this.shadowRoot.innerHTML = ` + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ +
+
+ `; + } +} + class CommentList extends HTMLElement { static get observedAttributes() { - return ['src']; + return ['guestbook']; } constructor() { @@ -12,26 +80,28 @@ class CommentList extends HTMLElement { } connectedCallback() { - if (this.hasAttribute('src')) { + if (this.hasAttribute('guestbook')) { this.fetchComments(); } } attributeChangedCallback(name, oldValue, newValue) { - if (name === 'src' && oldValue !== newValue) { + if (name === 'guestbook' && oldValue !== newValue) { this.fetchComments(); } } async fetchComments() { - const src = this.getAttribute('src'); - if (!src) return; + const guestbook = this.getAttribute('guestbook'); + if (!guestbook) return; this.loading = true; this.error = null; this.render(); + const commentsUrl = `${guestbook}/comments` + try { - const response = await fetch(src); + const response = await fetch(commentsUrl); if (!response.ok) throw new Error(`HTTP error: ${response.status}`); const data = await response.json(); this.comments = Array.isArray(data) ? data : []; @@ -102,4 +172,6 @@ class CommentList extends HTMLElement { } } -customElements.define('comment-list', CommentList); +customElements.define('guestbook-form', GuestbookForm); +customElements.define('guestbook-comments', CommentList); + diff --git a/ui/views/websites.templ b/ui/views/websites.templ index 1c7f1be..db383dd 100644 --- a/ui/views/websites.templ +++ b/ui/views/websites.templ @@ -116,15 +116,6 @@ templ WebsiteDashboard(title string, data CommonData, website models.Website) {

{ website.Name }

Embed your Guestbook

-

Comment form

-

- Use this form to allow readers of your website to comment on your guestbook! -

-
- // - @embeddableForm(data.RootUrl, website) -
-

Embed your comments

Upload this JavaScript WebComponent to your site and include it in your { `` } tag.

@@ -139,16 +130,26 @@ templ WebsiteDashboard(title string, data CommonData, website models.Website) {

- Then add the custom element where you want the comments to show up + Then add the custom elements where you want your form and comments to show up

- {{ getUrl := fmt.Sprintf("https://%s/websites/%s/guestbook/comments", data.RootUrl, shortIdToSlug(website.ShortId)) }} + {{ gbUrl := fmt.Sprintf("https://%s/websites/%s/guestbook", data.RootUrl, shortIdToSlug(website.ShortId)) }} //
 						
-							{ fmt.Sprintf(``, getUrl) }
+							{ fmt.Sprintf(`
+`, gbUrl, gbUrl) }
+						
+					
+
+

+ If your web host does not allow CORS requests, use an iframe instead +

+
+
+						
+							{ fmt.Sprintf(``, gbUrl) }
 						
 					
- @embedJavaScriptSnippet(data.RootUrl, website)
diff --git a/ui/views/websites_templ.go b/ui/views/websites_templ.go index a19e987..e8aee69 100644 --- a/ui/views/websites_templ.go +++ b/ui/views/websites_templ.go @@ -481,28 +481,20 @@ func WebsiteDashboard(title string, data CommonData, website models.Website) tem if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "

Embed your Guestbook

Comment form

Use this form to allow readers of your website to comment on your guestbook!

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = embeddableForm(data.RootUrl, website).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "

Embed your comments

Upload this JavaScript WebComponent to your site and include it in your ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "

Embed your Guestbook

Upload this JavaScript WebComponent to your site and include it in your ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var26 string templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(``) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 129, Col: 140} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 120, Col: 140} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, " tag.

")
+			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, " tag.

")
 			if templ_7745c5c3_Err != nil {
 				return templ_7745c5c3_Err
 			}
@@ -512,39 +504,45 @@ func WebsiteDashboard(title string, data CommonData, website models.Website) tem
     
 `)
 			if templ_7745c5c3_Err != nil {
-				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 138, Col: 8}
+				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 129, Col: 8}
 			}
 			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27))
 			if templ_7745c5c3_Err != nil {
 				return templ_7745c5c3_Err
 			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "

Then add the custom element where you want the comments to show up

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "

Then add the custom elements where you want your form and comments to show up

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - getUrl := fmt.Sprintf("https://%s/websites/%s/guestbook/comments", data.RootUrl, shortIdToSlug(website.ShortId)) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "
")
+			gbUrl := fmt.Sprintf("https://%s/websites/%s/guestbook", data.RootUrl, shortIdToSlug(website.ShortId))
+			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "
")
 			if templ_7745c5c3_Err != nil {
 				return templ_7745c5c3_Err
 			}
 			var templ_7745c5c3_Var28 string
-			templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf(``, getUrl))
+			templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf(`
+`, gbUrl, gbUrl))
 			if templ_7745c5c3_Err != nil {
-				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 148, Col: 70}
+				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 140, Col: 72}
 			}
 			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28))
 			if templ_7745c5c3_Err != nil {
 				return templ_7745c5c3_Err
 			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "

If your web host does not allow CORS requests, use an iframe instead

")
 			if templ_7745c5c3_Err != nil {
 				return templ_7745c5c3_Err
 			}
-			templ_7745c5c3_Err = embedJavaScriptSnippet(data.RootUrl, website).Render(ctx, templ_7745c5c3_Buffer)
+			var templ_7745c5c3_Var29 string
+			templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf(``, gbUrl))
+			if templ_7745c5c3_Err != nil {
+				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 150, Col: 75}
+			}
+			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))
 			if templ_7745c5c3_Err != nil {
 				return templ_7745c5c3_Err
 			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -574,12 +572,12 @@ func WebsiteDashboardComingSoon(title string, data CommonData, website models.We }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var29 := templ.GetChildren(ctx) - if templ_7745c5c3_Var29 == nil { - templ_7745c5c3_Var29 = templ.NopComponent + templ_7745c5c3_Var30 := templ.GetChildren(ctx) + if templ_7745c5c3_Var30 == nil { + templ_7745c5c3_Var30 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Var30 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_Var31 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) if !templ_7745c5c3_IsBuffer { @@ -591,7 +589,7 @@ func WebsiteDashboardComingSoon(title string, data CommonData, website models.We }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -599,26 +597,26 @@ func WebsiteDashboardComingSoon(title string, data CommonData, website models.We if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var31 string - templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(website.Name) + var templ_7745c5c3_Var32 string + templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(website.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 163, Col: 22} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 164, Col: 22} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "

Coming Soon

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "

Coming Soon

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } return nil }) - templ_7745c5c3_Err = base(title, data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var30), templ_7745c5c3_Buffer) + templ_7745c5c3_Err = base(title, data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var31), templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -642,12 +640,12 @@ func WebsiteCreate(title string, data CommonData, form forms.WebsiteCreateForm) }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var32 := templ.GetChildren(ctx) - if templ_7745c5c3_Var32 == nil { - templ_7745c5c3_Var32 = templ.NopComponent + templ_7745c5c3_Var33 := templ.GetChildren(ctx) + if templ_7745c5c3_Var33 == nil { + templ_7745c5c3_Var33 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -655,7 +653,7 @@ func WebsiteCreate(title string, data CommonData, form forms.WebsiteCreateForm) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -679,9 +677,9 @@ func embeddableForm(root string, website models.Website) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var33 := templ.GetChildren(ctx) - if templ_7745c5c3_Var33 == nil { - templ_7745c5c3_Var33 = templ.NopComponent + templ_7745c5c3_Var34 := templ.GetChildren(ctx) + if templ_7745c5c3_Var34 == nil { + templ_7745c5c3_Var34 = templ.NopComponent } ctx = templ.ClearChildren(ctx) postUrl := fmt.Sprintf("https://%s/websites/%s/guestbook/comments/create/remote", root, shortIdToSlug(website.ShortId)) @@ -707,20 +705,20 @@ func embeddableForm(root string, website models.Website) templ.Component { ` - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "
")
+		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "
")
 		if templ_7745c5c3_Err != nil {
 			return templ_7745c5c3_Err
 		}
-		var templ_7745c5c3_Var34 string
-		templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf(formStr, postUrl))
+		var templ_7745c5c3_Var35 string
+		templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf(formStr, postUrl))
 		if templ_7745c5c3_Err != nil {
-			return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 205, Col: 34}
+			return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 206, Col: 34}
 		}
-		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34))
+		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35))
 		if templ_7745c5c3_Err != nil {
 			return templ_7745c5c3_Err
 		}
-		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -744,9 +742,9 @@ func embedJavaScriptSnippet(root string, website models.Website) templ.Component }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var35 := templ.GetChildren(ctx) - if templ_7745c5c3_Var35 == nil { - templ_7745c5c3_Var35 = templ.NopComponent + templ_7745c5c3_Var36 := templ.GetChildren(ctx) + if templ_7745c5c3_Var36 == nil { + templ_7745c5c3_Var36 = templ.NopComponent } ctx = templ.ClearChildren(ctx) return nil -- 2.30.2 From 133d8bcfe9033283ade32fc15261f2a3b5e3ccc4 Mon Sep 17 00:00:00 2001 From: yequari Date: Fri, 27 Jun 2025 13:24:03 -0700 Subject: [PATCH 7/7] normalize urls, fix website creation --- cmd/web/handlers_guestbook.go | 12 +- cmd/web/handlers_guestbook_test.go | 85 +++ cmd/web/handlers_website.go | 14 +- cmd/web/helpers.go | 16 +- internal/models/mocks/guestbookcomment.go | 17 + internal/models/mocks/website.go | 13 +- internal/models/website.go | 31 +- internal/validator/validator.go | 37 +- .../000005_normalize_site_urls.down.sql | 1 + migrations/000005_normalize_site_urls.up.sql | 1 + ui/views/guestbooks.templ | 2 +- ui/views/guestbooks_templ.go | 4 +- ui/views/websites.templ | 57 +- ui/views/websites_templ.go | 494 ++++++++---------- 14 files changed, 401 insertions(+), 383 deletions(-) create mode 100644 migrations/000005_normalize_site_urls.down.sql create mode 100644 migrations/000005_normalize_site_urls.up.sql diff --git a/cmd/web/handlers_guestbook.go b/cmd/web/handlers_guestbook.go index 1a6e6b3..5234eb4 100644 --- a/cmd/web/handlers_guestbook.go +++ b/cmd/web/handlers_guestbook.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "net/http" - "net/url" "strconv" "time" @@ -258,11 +257,10 @@ func (app *application) postGuestbookCommentCreateRemote(w http.ResponseWriter, return } - if normalizeUrl(r.Header.Get("Origin")) != normalizeUrl(website.SiteUrl) { + if !matchOrigin(r.Header.Get("Origin"), website.Url) { app.clientError(w, http.StatusForbidden) return } - if !website.Guestbook.CanComment() { app.clientError(w, http.StatusForbidden) return @@ -285,12 +283,10 @@ func (app *application) postGuestbookCommentCreateRemote(w http.ResponseWriter, // otherwise redirect to the guestbook by default redirectUrl := fmt.Sprintf("/websites/%s/guestbook", shortIdToSlug(website.ShortId)) if form.Redirect != "" { - u := url.URL{ - Scheme: "http", - Host: website.SiteUrl, - Path: form.Redirect, + u, err := website.Url.Parse(form.Redirect) + if err == nil { + redirectUrl = u.String() } - redirectUrl = u.String() } if !form.Valid() { diff --git a/cmd/web/handlers_guestbook_test.go b/cmd/web/handlers_guestbook_test.go index c96693b..182f081 100644 --- a/cmd/web/handlers_guestbook_test.go +++ b/cmd/web/handlers_guestbook_test.go @@ -144,3 +144,88 @@ func TestPostGuestbookCommentCreate(t *testing.T) { }) } } + +func TestPostGuestbookCommentCreateRemote(t *testing.T) { + app := newTestApplication(t) + ts := newTestServer(t, app.routes()) + defer ts.Close() + + _, _, body := ts.get(t, fmt.Sprintf("/websites/%s/guestbook", shortIdToSlug(1))) + validCSRFToken := extractCSRFToken(t, body) + + const ( + validAuthorName = "John Test" + validAuthorEmail = "test@example.com" + validAuthorSite = "example.com" + validContent = "This is a comment" + ) + + tests := []struct { + name string + authorName string + authorEmail string + authorSite string + content string + csrfToken string + wantCode int + }{ + { + name: "Valid input", + authorName: validAuthorName, + authorEmail: validAuthorEmail, + authorSite: validAuthorSite, + content: validContent, + csrfToken: validCSRFToken, + wantCode: http.StatusSeeOther, + }, + { + name: "Blank name", + authorName: "", + authorEmail: validAuthorEmail, + authorSite: validAuthorSite, + content: validContent, + csrfToken: validCSRFToken, + wantCode: http.StatusUnprocessableEntity, + }, + { + name: "Blank email", + authorName: validAuthorName, + authorEmail: "", + authorSite: validAuthorSite, + content: validContent, + csrfToken: validCSRFToken, + wantCode: http.StatusSeeOther, + }, + { + name: "Blank site", + authorName: validAuthorName, + authorEmail: validAuthorEmail, + authorSite: "", + content: validContent, + csrfToken: validCSRFToken, + wantCode: http.StatusSeeOther, + }, + { + name: "Blank content", + authorName: validAuthorName, + authorEmail: validAuthorEmail, + authorSite: validAuthorSite, + content: "", + csrfToken: validCSRFToken, + wantCode: http.StatusUnprocessableEntity, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + form := url.Values{} + form.Add("authorname", tt.authorName) + form.Add("authoremail", tt.authorEmail) + form.Add("authorsite", tt.authorSite) + form.Add("content", tt.content) + form.Add("csrf_token", tt.csrfToken) + code, _, body := ts.postForm(t, fmt.Sprintf("/websites/%s/guestbook/comments/create/remote", shortIdToSlug(1)), form) + assert.Equal(t, code, tt.wantCode) + assert.Equal(t, body, body) + }) + } +} diff --git a/cmd/web/handlers_website.go b/cmd/web/handlers_website.go index 9e6d2d5..a2b8e9e 100644 --- a/cmd/web/handlers_website.go +++ b/cmd/web/handlers_website.go @@ -33,16 +33,20 @@ func (app *application) postWebsiteCreate(w http.ResponseWriter, r *http.Request form.CheckField(validator.MaxChars(form.Name, 256), "sitename", "This field cannot exceed 256 characters") form.CheckField(validator.NotBlank(form.SiteUrl), "siteurl", "This field cannot be blank") form.CheckField(validator.MaxChars(form.SiteUrl, 512), "siteurl", "This field cannot exceed 512 characters") + form.CheckField(validator.Matches(form.SiteUrl, validator.WebRX), "siteurl", "This field must be a valid URL (including http:// or https://)") u, err := url.Parse(form.SiteUrl) - - if !form.Valid() || err != nil { + if err != nil { + form.CheckField(false, "siteurl", "This field must be a valid URL") + } + if !form.Valid() { data := app.newCommonData(r) w.WriteHeader(http.StatusUnprocessableEntity) views.WebsiteCreate("Add a Website", data, form).Render(r.Context(), w) + return } websiteShortID := app.createShortId() - _, err = app.websites.Insert(websiteShortID, userId, form.Name, u.Host, form.AuthorName) + _, err = app.websites.Insert(websiteShortID, userId, form.Name, u.String(), form.AuthorName) if err != nil { app.serverError(w, r, err) return @@ -78,10 +82,6 @@ func (app *application) getWebsiteList(w http.ResponseWriter, r *http.Request) { app.serverError(w, r, err) return } - if r.Header.Get("HX-Request") == "true" { - views.HxWebsiteList(websites) - return - } data := app.newCommonData(r) views.WebsiteList("My Websites", data, websites).Render(r.Context(), w) } diff --git a/cmd/web/helpers.go b/cmd/web/helpers.go index e1321e0..f86b71b 100644 --- a/cmd/web/helpers.go +++ b/cmd/web/helpers.go @@ -5,9 +5,9 @@ import ( "fmt" "math" "net/http" + "net/url" "runtime/debug" "strconv" - "strings" "time" "git.32bit.cafe/32bitcafe/guestbook/internal/models" @@ -130,11 +130,13 @@ func (app *application) durationToTime(duration string) (time.Time, error) { return result, nil } -func normalizeUrl(url string) string { - r, f := strings.CutPrefix(url, "http://") - if f { - return r +func matchOrigin(origin string, u *url.URL) bool { + o, err := url.Parse(origin) + if err != nil { + return false } - r, _ = strings.CutPrefix(url, "https://") - return r + if o.Host != u.Host { + return false + } + return true } diff --git a/internal/models/mocks/guestbookcomment.go b/internal/models/mocks/guestbookcomment.go index 6a0c972..f8520de 100644 --- a/internal/models/mocks/guestbookcomment.go +++ b/internal/models/mocks/guestbookcomment.go @@ -18,6 +18,12 @@ var mockGuestbookComment = models.GuestbookComment{ IsPublished: true, } +var mockSerializedGuestbookComment = models.GuestbookCommentSerialized{ + AuthorName: "John Test", + CommentText: "Hello, world", + Created: time.Now().Format(time.RFC3339), +} + type GuestbookCommentModel struct{} func (m *GuestbookCommentModel) Insert(shortId uint64, guestbookId, parentId int64, authorName, @@ -45,6 +51,17 @@ func (m *GuestbookCommentModel) GetAll(guestbookId int64) ([]models.GuestbookCom } } +func (m *GuestbookCommentModel) GetAllSerialized(guestbookId int64) ([]models.GuestbookCommentSerialized, error) { + switch guestbookId { + case 1: + return []models.GuestbookCommentSerialized{mockSerializedGuestbookComment}, nil + case 2: + return []models.GuestbookCommentSerialized{}, nil + default: + return []models.GuestbookCommentSerialized{}, models.ErrNoRecord + } +} + func (m *GuestbookCommentModel) GetDeleted(guestbookId int64) ([]models.GuestbookComment, error) { switch guestbookId { default: diff --git a/internal/models/mocks/website.go b/internal/models/mocks/website.go index 83cc69e..1e14872 100644 --- a/internal/models/mocks/website.go +++ b/internal/models/mocks/website.go @@ -1,6 +1,7 @@ package mocks import ( + "net/url" "time" "git.32bit.cafe/32bitcafe/guestbook/internal/models" @@ -22,10 +23,14 @@ var mockGuestbook = models.Guestbook{ } var mockWebsite = models.Website{ - ID: 1, - ShortId: 1, - Name: "Example", - SiteUrl: "example.com", + ID: 1, + ShortId: 1, + Name: "Example", + // SiteUrl: "example.com", + Url: &url.URL{ + Scheme: "http", + Host: "example.com", + }, AuthorName: "John Test", UserId: 1, Created: time.Now(), diff --git a/internal/models/website.go b/internal/models/website.go index 53eb35d..811bedf 100644 --- a/internal/models/website.go +++ b/internal/models/website.go @@ -3,15 +3,17 @@ package models import ( "database/sql" "errors" + "net/url" "strconv" "time" ) type Website struct { - ID int64 - ShortId uint64 - Name string - SiteUrl string + ID int64 + ShortId uint64 + Name string + // SiteUrl string + Url *url.URL AuthorName string UserId int64 Created time.Time @@ -179,7 +181,8 @@ func (m *WebsiteModel) Get(shortId uint64) (Website, error) { } row := tx.QueryRow(stmt, shortId) var w Website - err = row.Scan(&w.ID, &w.ShortId, &w.Name, &w.SiteUrl, &w.AuthorName, &w.UserId, &w.Created) + var u string + err = row.Scan(&w.ID, &w.ShortId, &w.Name, &u, &w.AuthorName, &w.UserId, &w.Created) if err != nil { if errors.Is(err, sql.ErrNoRows) { err = ErrNoRecord @@ -189,6 +192,10 @@ func (m *WebsiteModel) Get(shortId uint64) (Website, error) { } return Website{}, err } + w.Url, err = url.Parse(u) + if err != nil { + return Website{}, err + } stmt = `SELECT Id, ShortId, UserId, WebsiteId, Created, IsActive FROM guestbooks WHERE WebsiteId = ? AND Deleted IS NULL` @@ -244,11 +251,16 @@ func (m *WebsiteModel) GetAllUser(userId int64) ([]Website, error) { var websites []Website for rows.Next() { var w Website - err := rows.Scan(&w.ID, &w.ShortId, &w.Name, &w.SiteUrl, &w.AuthorName, &w.UserId, &w.Created, + var u string + err := rows.Scan(&w.ID, &w.ShortId, &w.Name, &u, &w.AuthorName, &w.UserId, &w.Created, &w.Guestbook.ID, &w.Guestbook.ShortId, &w.Guestbook.Created, &w.Guestbook.IsActive) if err != nil { return nil, err } + w.Url, err = url.Parse(u) + if err != nil { + return nil, err + } websites = append(websites, w) } if err = rows.Err(); err != nil { @@ -268,11 +280,16 @@ func (m *WebsiteModel) GetAll() ([]Website, error) { var websites []Website for rows.Next() { var w Website - err := rows.Scan(&w.ID, &w.ShortId, &w.Name, &w.SiteUrl, &w.AuthorName, &w.UserId, &w.Created, + var u string + err := rows.Scan(&w.ID, &w.ShortId, &w.Name, &u, &w.AuthorName, &w.UserId, &w.Created, &w.Guestbook.ID, &w.Guestbook.ShortId, &w.Guestbook.Created, &w.Guestbook.IsActive) if err != nil { return nil, err } + w.Url, err = url.Parse(u) + if err != nil { + return nil, err + } websites = append(websites, w) } if err = rows.Err(); err != nil { diff --git a/internal/validator/validator.go b/internal/validator/validator.go index 0f57692..62037b2 100644 --- a/internal/validator/validator.go +++ b/internal/validator/validator.go @@ -8,51 +8,52 @@ import ( ) var EmailRX = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$") +var WebRX = regexp.MustCompile("^https?:\\/\\/") type Validator struct { - NonFieldErrors []string - FieldErrors map[string]string + NonFieldErrors []string + FieldErrors map[string]string } func (v *Validator) Valid() bool { - return len(v.FieldErrors) == 0 && len(v.NonFieldErrors) == 0 + return len(v.FieldErrors) == 0 && len(v.NonFieldErrors) == 0 } func (v *Validator) AddFieldError(key, message string) { - if v.FieldErrors == nil { - v.FieldErrors = make(map[string]string) - } - if _, exists := v.FieldErrors[key]; !exists { - v.FieldErrors[key] = message - } + if v.FieldErrors == nil { + v.FieldErrors = make(map[string]string) + } + if _, exists := v.FieldErrors[key]; !exists { + v.FieldErrors[key] = message + } } func (v *Validator) AddNonFieldError(message string) { - v.NonFieldErrors = append(v.NonFieldErrors, message) + v.NonFieldErrors = append(v.NonFieldErrors, message) } func (v *Validator) CheckField(ok bool, key, message string) { - if !ok { - v.AddFieldError(key, message) - } + if !ok { + v.AddFieldError(key, message) + } } func NotBlank(value string) bool { - return strings.TrimSpace(value) != "" + return strings.TrimSpace(value) != "" } func MaxChars(value string, n int) bool { - return utf8.RuneCountInString(value) <= n + return utf8.RuneCountInString(value) <= n } func PermittedValue[T comparable](value T, permittedValues ...T) bool { - return slices.Contains(permittedValues, value) + return slices.Contains(permittedValues, value) } func MinChars(value string, n int) bool { - return utf8.RuneCountInString(value) >= n + return utf8.RuneCountInString(value) >= n } func Matches(value string, rx *regexp.Regexp) bool { - return rx.MatchString(value) + return rx.MatchString(value) } diff --git a/migrations/000005_normalize_site_urls.down.sql b/migrations/000005_normalize_site_urls.down.sql new file mode 100644 index 0000000..1d42f9e --- /dev/null +++ b/migrations/000005_normalize_site_urls.down.sql @@ -0,0 +1 @@ +UPDATE websites SET SiteUrl = substr(SiteUrl, 8) WHERE substr(SiteUrl, 1, 4) = 'http'; diff --git a/migrations/000005_normalize_site_urls.up.sql b/migrations/000005_normalize_site_urls.up.sql new file mode 100644 index 0000000..a9ea2b3 --- /dev/null +++ b/migrations/000005_normalize_site_urls.up.sql @@ -0,0 +1 @@ +UPDATE websites SET SiteUrl = 'http://' || SiteUrl WHERE substr(SiteUrl, 1, 4) <> 'http'; diff --git a/ui/views/guestbooks.templ b/ui/views/guestbooks.templ index 8004437..080d947 100644 --- a/ui/views/guestbooks.templ +++ b/ui/views/guestbooks.templ @@ -10,7 +10,7 @@ templ GuestbookDashboardCommentsView(title string, data CommonData, website mode
@wSidebar(website)
-

Comments on { website.SiteUrl }

+

Comments on { website.Name }


if len(comments) == 0 {

No comments yet!

diff --git a/ui/views/guestbooks_templ.go b/ui/views/guestbooks_templ.go index 5b12c21..7d167f9 100644 --- a/ui/views/guestbooks_templ.go +++ b/ui/views/guestbooks_templ.go @@ -59,9 +59,9 @@ func GuestbookDashboardCommentsView(title string, data CommonData, website model return templ_7745c5c3_Err } var templ_7745c5c3_Var3 string - templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(website.SiteUrl) + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(website.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 13, Col: 37} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/guestbooks.templ`, Line: 13, Col: 34} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { diff --git a/ui/views/websites.templ b/ui/views/websites.templ index db383dd..e8c29d6 100644 --- a/ui/views/websites.templ +++ b/ui/views/websites.templ @@ -16,7 +16,7 @@ templ wSidebar(website models.Website) {

{ website.Name }

Guestbook

    @@ -70,7 +70,7 @@ templ websiteCreateForm(csrfToken string, form forms.WebsiteCreateForm) { if exists { } - +
{{ err, exists = form.FieldErrors["siteurl"] }} @@ -78,7 +78,7 @@ templ websiteCreateForm(csrfToken string, form forms.WebsiteCreateForm) { if exists { } - +
{{ err, exists = form.FieldErrors["authorname"] }} @@ -86,22 +86,18 @@ templ websiteCreateForm(csrfToken string, form forms.WebsiteCreateForm) { if exists { } - +
} -templ WebsiteCreateButton() { - -} - templ WebsiteList(title string, data CommonData, websites []models.Website) { @base(title, data) {

My Websites

- @WebsiteCreateButton() + Add Website
@displayWebsites(websites) @@ -171,42 +167,9 @@ templ WebsiteDashboardComingSoon(title string, data CommonData, website models.W } templ WebsiteCreate(title string, data CommonData, form forms.WebsiteCreateForm) { -
- @websiteCreateForm(data.CSRFToken, form) -
-} - -templ embeddableForm(root string, website models.Website) { - {{ postUrl := fmt.Sprintf("https://%s/websites/%s/guestbook/comments/create/remote", root, shortIdToSlug(website.ShortId)) }} - {{formStr := - `
-
- - -
-
- - -
-
- - -
-
- - -
-
- -
-
` - }} -
-		
-			{ fmt.Sprintf(formStr, postUrl) }
-		
-	
-} - -templ embedJavaScriptSnippet(root string, website models.Website) { + @base(title, data) { +
+ @websiteCreateForm(data.CSRFToken, form) +
+ } } diff --git a/ui/views/websites_templ.go b/ui/views/websites_templ.go index e8aee69..83e490a 100644 --- a/ui/views/websites_templ.go +++ b/ui/views/websites_templ.go @@ -65,7 +65,7 @@ func wSidebar(website models.Website) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var4 templ.SafeURL = templ.URL(externalUrl(website.SiteUrl)) + var templ_7745c5c3_Var4 templ.SafeURL = templ.URL(externalUrl(website.Url.String())) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var4))) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err @@ -271,92 +271,102 @@ func websiteCreateForm(csrfToken string, form forms.WebsiteCreateForm) templ.Com return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } err, exists = form.FieldErrors["siteurl"] - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, " ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, " ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if exists { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, " ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - err, exists = form.FieldErrors["authorname"] - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, " ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - if exists { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, " ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err } - ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "") + err, exists = form.FieldErrors["authorname"] + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if exists { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -365,69 +375,6 @@ func WebsiteCreateButton() templ.Component { } func WebsiteList(title string, data CommonData, websites []models.Website) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var21 := templ.GetChildren(ctx) - if templ_7745c5c3_Var21 == nil { - templ_7745c5c3_Var21 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Var22 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "

My Websites

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = WebsiteCreateButton().Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = displayWebsites(websites).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) - templ_7745c5c3_Err = base(title, data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var22), templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -func WebsiteDashboard(title string, data CommonData, website models.Website) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { @@ -460,7 +407,62 @@ func WebsiteDashboard(title string, data CommonData, website models.Website) tem }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "

My Websites

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = displayWebsites(websites).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = base(title, data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var24), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func WebsiteDashboard(title string, data CommonData, website models.Website) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var25 := templ.GetChildren(ctx) + if templ_7745c5c3_Var25 == nil { + templ_7745c5c3_Var25 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var26 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -468,87 +470,87 @@ func WebsiteDashboard(title string, data CommonData, website models.Website) tem if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var25 string - templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(website.Name) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 117, Col: 22} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "

Embed your Guestbook

Upload this JavaScript WebComponent to your site and include it in your ") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - var templ_7745c5c3_Var26 string - templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(``) - if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 120, Col: 140} - } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, " tag.

")
+			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var27 string - templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs( - ` - -`) + templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(website.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 129, Col: 8} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 113, Col: 22} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "

Then add the custom elements where you want your form and comments to show up

") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - gbUrl := fmt.Sprintf("https://%s/websites/%s/guestbook", data.RootUrl, shortIdToSlug(website.ShortId)) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "
")
+			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "

Embed your Guestbook

Upload this JavaScript WebComponent to your site and include it in your ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var28 string - templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf(` -`, gbUrl, gbUrl)) + templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(``) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 140, Col: 72} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 116, Col: 140} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "

If your web host does not allow CORS requests, use an iframe instead

")
+			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, " tag.

")
 			if templ_7745c5c3_Err != nil {
 				return templ_7745c5c3_Err
 			}
 			var templ_7745c5c3_Var29 string
-			templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf(``, gbUrl))
+			templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(
+				`
+    
+`)
 			if templ_7745c5c3_Err != nil {
-				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 150, Col: 75}
+				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 125, Col: 8}
 			}
 			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))
 			if templ_7745c5c3_Err != nil {
 				return templ_7745c5c3_Err
 			}
-			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "

Then add the custom elements where you want your form and comments to show up

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + gbUrl := fmt.Sprintf("https://%s/websites/%s/guestbook", data.RootUrl, shortIdToSlug(website.ShortId)) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "
")
+			if templ_7745c5c3_Err != nil {
+				return templ_7745c5c3_Err
+			}
+			var templ_7745c5c3_Var30 string
+			templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf(`
+`, gbUrl, gbUrl))
+			if templ_7745c5c3_Err != nil {
+				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 136, Col: 72}
+			}
+			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))
+			if templ_7745c5c3_Err != nil {
+				return templ_7745c5c3_Err
+			}
+			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "

If your web host does not allow CORS requests, use an iframe instead

")
+			if templ_7745c5c3_Err != nil {
+				return templ_7745c5c3_Err
+			}
+			var templ_7745c5c3_Var31 string
+			templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf(``, gbUrl))
+			if templ_7745c5c3_Err != nil {
+				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 146, Col: 75}
+			}
+			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31))
+			if templ_7745c5c3_Err != nil {
+				return templ_7745c5c3_Err
+			}
+			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } return nil }) - templ_7745c5c3_Err = base(title, data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var24), templ_7745c5c3_Buffer) + templ_7745c5c3_Err = base(title, data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var26), templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -572,12 +574,12 @@ func WebsiteDashboardComingSoon(title string, data CommonData, website models.We }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var30 := templ.GetChildren(ctx) - if templ_7745c5c3_Var30 == nil { - templ_7745c5c3_Var30 = templ.NopComponent + templ_7745c5c3_Var32 := templ.GetChildren(ctx) + if templ_7745c5c3_Var32 == nil { + templ_7745c5c3_Var32 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Var31 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_Var33 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) if !templ_7745c5c3_IsBuffer { @@ -589,7 +591,7 @@ func WebsiteDashboardComingSoon(title string, data CommonData, website models.We }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -597,26 +599,26 @@ func WebsiteDashboardComingSoon(title string, data CommonData, website models.We if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var32 string - templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(website.Name) + var templ_7745c5c3_Var34 string + templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(website.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 164, Col: 22} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 160, Col: 22} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "

Coming Soon

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "

Coming Soon

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } return nil }) - templ_7745c5c3_Err = base(title, data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var31), templ_7745c5c3_Buffer) + templ_7745c5c3_Err = base(title, data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var33), templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -640,113 +642,41 @@ func WebsiteCreate(title string, data CommonData, form forms.WebsiteCreateForm) }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var33 := templ.GetChildren(ctx) - if templ_7745c5c3_Var33 == nil { - templ_7745c5c3_Var33 = templ.NopComponent + templ_7745c5c3_Var35 := templ.GetChildren(ctx) + if templ_7745c5c3_Var35 == nil { + templ_7745c5c3_Var35 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "
") + templ_7745c5c3_Var36 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = websiteCreateForm(data.CSRFToken, form).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = base(title, data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var36), templ_7745c5c3_Buffer) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = websiteCreateForm(data.CSRFToken, form).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -func embeddableForm(root string, website models.Website) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var34 := templ.GetChildren(ctx) - if templ_7745c5c3_Var34 == nil { - templ_7745c5c3_Var34 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - postUrl := fmt.Sprintf("https://%s/websites/%s/guestbook/comments/create/remote", root, shortIdToSlug(website.ShortId)) - formStr := - `
-
- - -
-
- - -
-
- - -
-
- - -
-
- -
-
` - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "
")
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		var templ_7745c5c3_Var35 string
-		templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf(formStr, postUrl))
-		if templ_7745c5c3_Err != nil {
-			return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 206, Col: 34}
-		}
-		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35))
-		if templ_7745c5c3_Err != nil {
-			return templ_7745c5c3_Err
-		}
-		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -func embedJavaScriptSnippet(root string, website models.Website) templ.Component { - return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { - templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context - if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { - return templ_7745c5c3_CtxErr - } - templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) - if !templ_7745c5c3_IsBuffer { - defer func() { - templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) - if templ_7745c5c3_Err == nil { - templ_7745c5c3_Err = templ_7745c5c3_BufErr - } - }() - } - ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var36 := templ.GetChildren(ctx) - if templ_7745c5c3_Var36 == nil { - templ_7745c5c3_Var36 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) return nil }) } -- 2.30.2