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