Compare commits

...

2 Commits

Author SHA1 Message Date
133d8bcfe9 normalize urls, fix website creation 2025-06-27 13:24:03 -07:00
a15bbcee3d redirect to proper location 2025-06-27 13:23:35 -07:00
16 changed files with 484 additions and 382 deletions

View File

@ -257,11 +257,10 @@ func (app *application) postGuestbookCommentCreateRemote(w http.ResponseWriter,
return return
} }
if normalizeUrl(r.Header.Get("Origin")) != normalizeUrl(website.SiteUrl) { if !matchOrigin(r.Header.Get("Origin"), website.Url) {
app.clientError(w, http.StatusForbidden) app.clientError(w, http.StatusForbidden)
return return
} }
if !website.Guestbook.CanComment() { if !website.Guestbook.CanComment() {
app.clientError(w, http.StatusForbidden) app.clientError(w, http.StatusForbidden)
return return
@ -279,10 +278,16 @@ 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.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.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") 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 // if redirect path is filled out, redirect to that path on the website host
// Else redirect to homepage as stored in the website struct // otherwise redirect to the guestbook by default
redirectUrl := r.Header.Get("Referer") redirectUrl := fmt.Sprintf("/websites/%s/guestbook", shortIdToSlug(website.ShortId))
if form.Redirect != "" {
u, err := website.Url.Parse(form.Redirect)
if err == nil {
redirectUrl = u.String()
}
}
if !form.Valid() { if !form.Valid() {
views.GuestbookCommentCreateRemoteErrorView(redirectUrl, "Invalid Input").Render(r.Context(), w) views.GuestbookCommentCreateRemoteErrorView(redirectUrl, "Invalid Input").Render(r.Context(), w)

View File

@ -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)
})
}
}

View File

@ -4,6 +4,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"net/http" "net/http"
"net/url"
"git.32bit.cafe/32bitcafe/guestbook/internal/forms" "git.32bit.cafe/32bitcafe/guestbook/internal/forms"
"git.32bit.cafe/32bitcafe/guestbook/internal/models" "git.32bit.cafe/32bitcafe/guestbook/internal/models"
@ -32,14 +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.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.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.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 err != nil {
form.CheckField(false, "siteurl", "This field must be a valid URL")
}
if !form.Valid() { if !form.Valid() {
data := app.newCommonData(r) data := app.newCommonData(r)
w.WriteHeader(http.StatusUnprocessableEntity) w.WriteHeader(http.StatusUnprocessableEntity)
views.WebsiteCreate("Add a Website", data, form).Render(r.Context(), w) views.WebsiteCreate("Add a Website", data, form).Render(r.Context(), w)
return
} }
websiteShortID := app.createShortId() websiteShortID := app.createShortId()
_, err = app.websites.Insert(websiteShortID, userId, form.Name, form.SiteUrl, form.AuthorName) _, err = app.websites.Insert(websiteShortID, userId, form.Name, u.String(), form.AuthorName)
if err != nil { if err != nil {
app.serverError(w, r, err) app.serverError(w, r, err)
return return
@ -75,10 +82,6 @@ func (app *application) getWebsiteList(w http.ResponseWriter, r *http.Request) {
app.serverError(w, r, err) app.serverError(w, r, err)
return return
} }
if r.Header.Get("HX-Request") == "true" {
views.HxWebsiteList(websites)
return
}
data := app.newCommonData(r) data := app.newCommonData(r)
views.WebsiteList("My Websites", data, websites).Render(r.Context(), w) views.WebsiteList("My Websites", data, websites).Render(r.Context(), w)
} }

View File

@ -5,9 +5,9 @@ import (
"fmt" "fmt"
"math" "math"
"net/http" "net/http"
"net/url"
"runtime/debug" "runtime/debug"
"strconv" "strconv"
"strings"
"time" "time"
"git.32bit.cafe/32bitcafe/guestbook/internal/models" "git.32bit.cafe/32bitcafe/guestbook/internal/models"
@ -130,11 +130,13 @@ func (app *application) durationToTime(duration string) (time.Time, error) {
return result, nil return result, nil
} }
func normalizeUrl(url string) string { func matchOrigin(origin string, u *url.URL) bool {
r, f := strings.CutPrefix(url, "http://") o, err := url.Parse(origin)
if f { if err != nil {
return r return false
} }
r, _ = strings.CutPrefix(url, "https://") if o.Host != u.Host {
return r return false
}
return true
} }

View File

@ -20,6 +20,7 @@ type CommentCreateForm struct {
AuthorEmail string `schema:"authoremail"` AuthorEmail string `schema:"authoremail"`
AuthorSite string `schema:"authorsite"` AuthorSite string `schema:"authorsite"`
Content string `schema:"content"` Content string `schema:"content"`
Redirect string `schema:"redirect"`
validator.Validator `schema:"-"` validator.Validator `schema:"-"`
} }

View File

@ -18,6 +18,12 @@ var mockGuestbookComment = models.GuestbookComment{
IsPublished: true, IsPublished: true,
} }
var mockSerializedGuestbookComment = models.GuestbookCommentSerialized{
AuthorName: "John Test",
CommentText: "Hello, world",
Created: time.Now().Format(time.RFC3339),
}
type GuestbookCommentModel struct{} type GuestbookCommentModel struct{}
func (m *GuestbookCommentModel) Insert(shortId uint64, guestbookId, parentId int64, authorName, 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) { func (m *GuestbookCommentModel) GetDeleted(guestbookId int64) ([]models.GuestbookComment, error) {
switch guestbookId { switch guestbookId {
default: default:

View File

@ -1,6 +1,7 @@
package mocks package mocks
import ( import (
"net/url"
"time" "time"
"git.32bit.cafe/32bitcafe/guestbook/internal/models" "git.32bit.cafe/32bitcafe/guestbook/internal/models"
@ -22,10 +23,14 @@ var mockGuestbook = models.Guestbook{
} }
var mockWebsite = models.Website{ var mockWebsite = models.Website{
ID: 1, ID: 1,
ShortId: 1, ShortId: 1,
Name: "Example", Name: "Example",
SiteUrl: "example.com", // SiteUrl: "example.com",
Url: &url.URL{
Scheme: "http",
Host: "example.com",
},
AuthorName: "John Test", AuthorName: "John Test",
UserId: 1, UserId: 1,
Created: time.Now(), Created: time.Now(),

View File

@ -3,15 +3,17 @@ package models
import ( import (
"database/sql" "database/sql"
"errors" "errors"
"net/url"
"strconv" "strconv"
"time" "time"
) )
type Website struct { type Website struct {
ID int64 ID int64
ShortId uint64 ShortId uint64
Name string Name string
SiteUrl string // SiteUrl string
Url *url.URL
AuthorName string AuthorName string
UserId int64 UserId int64
Created time.Time Created time.Time
@ -179,7 +181,8 @@ func (m *WebsiteModel) Get(shortId uint64) (Website, error) {
} }
row := tx.QueryRow(stmt, shortId) row := tx.QueryRow(stmt, shortId)
var w Website 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 err != nil {
if errors.Is(err, sql.ErrNoRows) { if errors.Is(err, sql.ErrNoRows) {
err = ErrNoRecord err = ErrNoRecord
@ -189,6 +192,10 @@ func (m *WebsiteModel) Get(shortId uint64) (Website, error) {
} }
return Website{}, err 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 stmt = `SELECT Id, ShortId, UserId, WebsiteId, Created, IsActive FROM guestbooks
WHERE WebsiteId = ? AND Deleted IS NULL` WHERE WebsiteId = ? AND Deleted IS NULL`
@ -244,11 +251,16 @@ func (m *WebsiteModel) GetAllUser(userId int64) ([]Website, error) {
var websites []Website var websites []Website
for rows.Next() { for rows.Next() {
var w Website 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) &w.Guestbook.ID, &w.Guestbook.ShortId, &w.Guestbook.Created, &w.Guestbook.IsActive)
if err != nil { if err != nil {
return nil, err return nil, err
} }
w.Url, err = url.Parse(u)
if err != nil {
return nil, err
}
websites = append(websites, w) websites = append(websites, w)
} }
if err = rows.Err(); err != nil { if err = rows.Err(); err != nil {
@ -268,11 +280,16 @@ func (m *WebsiteModel) GetAll() ([]Website, error) {
var websites []Website var websites []Website
for rows.Next() { for rows.Next() {
var w Website 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) &w.Guestbook.ID, &w.Guestbook.ShortId, &w.Guestbook.Created, &w.Guestbook.IsActive)
if err != nil { if err != nil {
return nil, err return nil, err
} }
w.Url, err = url.Parse(u)
if err != nil {
return nil, err
}
websites = append(websites, w) websites = append(websites, w)
} }
if err = rows.Err(); err != nil { if err = rows.Err(); err != nil {

View File

@ -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 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 { type Validator struct {
NonFieldErrors []string NonFieldErrors []string
FieldErrors map[string]string FieldErrors map[string]string
} }
func (v *Validator) Valid() bool { 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) { func (v *Validator) AddFieldError(key, message string) {
if v.FieldErrors == nil { if v.FieldErrors == nil {
v.FieldErrors = make(map[string]string) v.FieldErrors = make(map[string]string)
} }
if _, exists := v.FieldErrors[key]; !exists { if _, exists := v.FieldErrors[key]; !exists {
v.FieldErrors[key] = message v.FieldErrors[key] = message
} }
} }
func (v *Validator) AddNonFieldError(message string) { 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) { func (v *Validator) CheckField(ok bool, key, message string) {
if !ok { if !ok {
v.AddFieldError(key, message) v.AddFieldError(key, message)
} }
} }
func NotBlank(value string) bool { func NotBlank(value string) bool {
return strings.TrimSpace(value) != "" return strings.TrimSpace(value) != ""
} }
func MaxChars(value string, n int) bool { 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 { 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 { 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 { func Matches(value string, rx *regexp.Regexp) bool {
return rx.MatchString(value) return rx.MatchString(value)
} }

View File

@ -0,0 +1 @@
UPDATE websites SET SiteUrl = substr(SiteUrl, 8) WHERE substr(SiteUrl, 1, 4) = 'http';

View File

@ -0,0 +1 @@
UPDATE websites SET SiteUrl = 'http://' || SiteUrl WHERE substr(SiteUrl, 1, 4) <> 'http';

View File

@ -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 = `
<style>
form div {
margin-bottom: 0.75em;
}
label {
display: block;
font-weight: bold;
margin-bottom: 0.25em;
}
input[type="text"], textarea {
width: 100%;
box-sizing: border-box;
}
input[type="submit"] {
font-size: 1em;
padding: 0.5em 1em;
}
</style>
<form action="${this._postUrl}" method="post">
<div>
<label for="authorname">Name</label>
<input type="text" name="authorname" id="authorname"/>
</div>
<div>
<label for="authoremail">Email (Optional)</label>
<input type="text" name="authoremail" id="authoremail"/>
</div>
<div>
<label for="authorsite">Site Url (Optional)</label>
<input type="text" name="authorsite" id="authorsite"/>
</div>
<div>
<label for="content">Comment</label>
<textarea name="content" id="content"></textarea>
</div>
<div>
<input type="hidden" value="${window.location.pathname}" name="redirect" id="redirect" />
</div>
<div>
<input type="submit" value="Submit"/>
</div>
</form>
`;
}
}
class CommentList extends HTMLElement { class CommentList extends HTMLElement {
static get observedAttributes() { static get observedAttributes() {
return ['src']; return ['guestbook'];
} }
constructor() { constructor() {
@ -12,26 +80,28 @@ class CommentList extends HTMLElement {
} }
connectedCallback() { connectedCallback() {
if (this.hasAttribute('src')) { if (this.hasAttribute('guestbook')) {
this.fetchComments(); this.fetchComments();
} }
} }
attributeChangedCallback(name, oldValue, newValue) { attributeChangedCallback(name, oldValue, newValue) {
if (name === 'src' && oldValue !== newValue) { if (name === 'guestbook' && oldValue !== newValue) {
this.fetchComments(); this.fetchComments();
} }
} }
async fetchComments() { async fetchComments() {
const src = this.getAttribute('src'); const guestbook = this.getAttribute('guestbook');
if (!src) return; if (!guestbook) return;
this.loading = true; this.loading = true;
this.error = null; this.error = null;
this.render(); this.render();
const commentsUrl = `${guestbook}/comments`
try { try {
const response = await fetch(src); const response = await fetch(commentsUrl);
if (!response.ok) throw new Error(`HTTP error: ${response.status}`); if (!response.ok) throw new Error(`HTTP error: ${response.status}`);
const data = await response.json(); const data = await response.json();
this.comments = Array.isArray(data) ? data : []; 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);

View File

@ -10,7 +10,7 @@ templ GuestbookDashboardCommentsView(title string, data CommonData, website mode
<div id="dashboard"> <div id="dashboard">
@wSidebar(website) @wSidebar(website)
<div> <div>
<h1>Comments on { website.SiteUrl }</h1> <h1>Comments on { website.Name }</h1>
<hr/> <hr/>
if len(comments) == 0 { if len(comments) == 0 {
<p>No comments yet!</p> <p>No comments yet!</p>

View File

@ -59,9 +59,9 @@ func GuestbookDashboardCommentsView(title string, data CommonData, website model
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var3 string 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 { 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)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {

View File

@ -16,7 +16,7 @@ templ wSidebar(website models.Website) {
<h2>{ website.Name }</h2> <h2>{ website.Name }</h2>
<ul> <ul>
<li><a href={ templ.URL(dashUrl) }>Dashboard</a></li> <li><a href={ templ.URL(dashUrl) }>Dashboard</a></li>
<li><a href={ templ.URL(externalUrl(website.SiteUrl)) } target="_blank">View Website</a></li> <li><a href={ templ.URL(externalUrl(website.Url.String())) } target="_blank">View Website</a></li>
</ul> </ul>
<h3>Guestbook</h3> <h3>Guestbook</h3>
<ul> <ul>
@ -70,7 +70,7 @@ templ websiteCreateForm(csrfToken string, form forms.WebsiteCreateForm) {
if exists { if exists {
<label class="error">{ err }</label> <label class="error">{ err }</label>
} }
<input type="text" name="sitename" id="sitename" required/> <input type="text" name="sitename" id="sitename" value={ form.Name } required/>
</div> </div>
<div> <div>
{{ err, exists = form.FieldErrors["siteurl"] }} {{ err, exists = form.FieldErrors["siteurl"] }}
@ -78,7 +78,7 @@ templ websiteCreateForm(csrfToken string, form forms.WebsiteCreateForm) {
if exists { if exists {
<label class="error">{ err }</label> <label class="error">{ err }</label>
} }
<input type="text" name="siteurl" id="siteurl" required/> <input type="text" name="siteurl" id="siteurl" value={ form.SiteUrl } required/>
</div> </div>
<div> <div>
{{ err, exists = form.FieldErrors["authorname"] }} {{ err, exists = form.FieldErrors["authorname"] }}
@ -86,22 +86,18 @@ templ websiteCreateForm(csrfToken string, form forms.WebsiteCreateForm) {
if exists { if exists {
<label class="error">{ err }</label> <label class="error">{ err }</label>
} }
<input type="text" name="authorname" id="authorname" required/> <input type="text" name="authorname" id="authorname" value={ form.AuthorName } required/>
</div> </div>
<div> <div>
<button type="submit">Submit</button> <button type="submit">Submit</button>
</div> </div>
} }
templ WebsiteCreateButton() {
<button hx-get="/websites/create" hx-target="closest div">Add Website</button>
}
templ WebsiteList(title string, data CommonData, websites []models.Website) { templ WebsiteList(title string, data CommonData, websites []models.Website) {
@base(title, data) { @base(title, data) {
<h1>My Websites</h1> <h1>My Websites</h1>
<div> <div>
@WebsiteCreateButton() <a href="/websites/create">Add Website</a>
</div> </div>
<div> <div>
@displayWebsites(websites) @displayWebsites(websites)
@ -116,15 +112,6 @@ templ WebsiteDashboard(title string, data CommonData, website models.Website) {
<div> <div>
<h1>{ website.Name }</h1> <h1>{ website.Name }</h1>
<h2>Embed your Guestbook</h2> <h2>Embed your Guestbook</h2>
<h3>Comment form</h3>
<p>
Use this form to allow readers of your website to comment on your guestbook!
</p>
<div>
//<button>Copy to Clipboard</button>
@embeddableForm(data.RootUrl, website)
</div>
<h3>Embed your comments</h3>
<p> <p>
Upload <a href="/static/js/guestbook.js" download>this JavaScript WebComponent</a> to your site and include it in your <code>{ `<head>` }</code> tag. Upload <a href="/static/js/guestbook.js" download>this JavaScript WebComponent</a> to your site and include it in your <code>{ `<head>` }</code> tag.
</p> </p>
@ -139,16 +126,26 @@ templ WebsiteDashboard(title string, data CommonData, website models.Website) {
</code> </code>
</pre> </pre>
<p> <p>
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
</p> </p>
{{ 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)) }}
//<button>Copy to Clipboard</button> //<button>Copy to Clipboard</button>
<pre> <pre>
<code> <code>
{ fmt.Sprintf(`<comment-list src="%s"></comment-list>`, getUrl) } { fmt.Sprintf(`<guestbook-form guestbook="%s"></guestbook-form>
<guestbook-comments guestbook="%s"></guestbook-comments>`, gbUrl, gbUrl) }
</code>
</pre>
</div>
<p>
If your web host does not allow CORS requests, use an iframe instead
</p>
<div>
<pre>
<code>
{ fmt.Sprintf(`<iframe src="%s" title="Guestbook"></iframe>`, gbUrl) }
</code> </code>
</pre> </pre>
@embedJavaScriptSnippet(data.RootUrl, website)
</div> </div>
</div> </div>
</div> </div>
@ -170,42 +167,9 @@ templ WebsiteDashboardComingSoon(title string, data CommonData, website models.W
} }
templ WebsiteCreate(title string, data CommonData, form forms.WebsiteCreateForm) { templ WebsiteCreate(title string, data CommonData, form forms.WebsiteCreateForm) {
<form hx-post="/websites/create" hx-target="closest div"> @base(title, data) {
@websiteCreateForm(data.CSRFToken, form) <form action="/websites/create" method="post">
</form> @websiteCreateForm(data.CSRFToken, form)
} </form>
}
templ embeddableForm(root string, website models.Website) {
{{ postUrl := fmt.Sprintf("https://%s/websites/%s/guestbook/comments/create/remote", root, shortIdToSlug(website.ShortId)) }}
{{formStr :=
`<form action="%s" method="post">
<div>
<label for="authorname">Name</label>
<input type="text" name="authorname" id="authorname"/>
</div>
<div>
<label for="authoremail">Email (Optional) </label>
<input type="text" name="authoremail" id="authoremail"/>
</div>
<div>
<label for="authorsite">Site Url (Optional) </label>
<input type="text" name="authorsite" id="authorsite"/>
</div>
<div>
<label for="content">Comment</label>
<textarea name="content" id="content"></textarea>
</div>
<div>
<input type="submit" value="Submit"/>
</div>
</form>`
}}
<pre>
<code>
{ fmt.Sprintf(formStr, postUrl) }
</code>
</pre>
}
templ embedJavaScriptSnippet(root string, website models.Website) {
} }

View File

@ -65,7 +65,7 @@ func wSidebar(website models.Website) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err 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))) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var4)))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
@ -271,92 +271,102 @@ func websiteCreateForm(csrfToken string, form forms.WebsiteCreateForm) templ.Com
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<input type=\"text\" name=\"sitename\" id=\"sitename\" required></div><div>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<input type=\"text\" name=\"sitename\" id=\"sitename\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var18 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(form.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 73, Col: 68}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "\" required></div><div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
err, exists = form.FieldErrors["siteurl"] err, exists = form.FieldErrors["siteurl"]
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "<label for=\"siteurl\">Site URL: </label> ") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<label for=\"siteurl\">Site URL: </label> ")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
if exists { if exists {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<label class=\"error\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "<label class=\"error\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var18 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(err)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 79, Col: 29}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "</label> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "<input type=\"text\" name=\"siteurl\" id=\"siteurl\" required></div><div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
err, exists = form.FieldErrors["authorname"]
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<label for=\"authorname\">Site Author: </label> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if exists {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "<label class=\"error\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var19 string var templ_7745c5c3_Var19 string
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(err) templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(err)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 87, Col: 29} return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 79, Col: 29}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "</label> ") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "</label> ")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "<input type=\"text\" name=\"authorname\" id=\"authorname\" required></div><div><button type=\"submit\">Submit</button></div>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<input type=\"text\" name=\"siteurl\" id=\"siteurl\" value=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
return nil var templ_7745c5c3_Var20 string
}) templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(form.SiteUrl)
} if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 81, Col: 69}
func WebsiteCreateButton() 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) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
if !templ_7745c5c3_IsBuffer { if templ_7745c5c3_Err != nil {
defer func() { return templ_7745c5c3_Err
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, 29, "\" required></div><div>")
templ_7745c5c3_Var20 := templ.GetChildren(ctx) if templ_7745c5c3_Err != nil {
if templ_7745c5c3_Var20 == nil { return templ_7745c5c3_Err
templ_7745c5c3_Var20 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) err, exists = form.FieldErrors["authorname"]
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "<button hx-get=\"/websites/create\" hx-target=\"closest div\">Add Website</button>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "<label for=\"authorname\">Site Author: </label> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if exists {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "<label class=\"error\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var21 string
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(err)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 87, Col: 29}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "</label> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "<input type=\"text\" name=\"authorname\" id=\"authorname\" value=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var22 string
templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(form.AuthorName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 89, Col: 78}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "\" required></div><div><button type=\"submit\">Submit</button></div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@ -365,69 +375,6 @@ func WebsiteCreateButton() templ.Component {
} }
func WebsiteList(title string, data CommonData, websites []models.Website) 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, "<h1>My Websites</h1><div>")
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, "</div><div>")
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, "</div>")
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) { 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 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { 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) ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "<div id=\"dashboard\">") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "<h1>My Websites</h1><div><a href=\"/websites/create\">Add Website</a></div><div>")
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, "</div>")
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, "<div id=\"dashboard\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@ -468,37 +470,29 @@ func WebsiteDashboard(title string, data CommonData, website models.Website) tem
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "<div><h1>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "<div><h1>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var25 string var templ_7745c5c3_Var27 string
templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(website.Name) templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(website.Name)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 117, Col: 22} 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_Var25)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "</h1><h2>Embed your Guestbook</h2><h3>Comment form</h3><p>Use this form to allow readers of your website to comment on your guestbook!</p><div>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "</h1><h2>Embed your Guestbook</h2><p>Upload <a href=\"/static/js/guestbook.js\" download>this JavaScript WebComponent</a> to your site and include it in your <code>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = embeddableForm(data.RootUrl, website).Render(ctx, templ_7745c5c3_Buffer) var templ_7745c5c3_Var28 string
templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(`<head>`)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 116, Col: 140}
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "</div><h3>Embed your comments</h3><p>Upload <a href=\"/static/js/guestbook.js\" download>this JavaScript WebComponent</a> to your site and include it in your <code>") _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var26 string
templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(`<head>`)
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 { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@ -506,51 +500,57 @@ func WebsiteDashboard(title string, data CommonData, website models.Website) tem
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var27 string var templ_7745c5c3_Var29 string
templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs( templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(
`<head> `<head>
<script type="module" src="js/guestbook.js"></script> <script type="module" src="js/guestbook.js"></script>
</head>`) </head>`)
if templ_7745c5c3_Err != nil { 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: 125, Col: 8}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "</code></pre><p>Then add the custom element where you want the comments to show up</p>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "</code></pre><p>Then add the custom elements where you want your form and comments to show up</p>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
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))
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "<pre><code>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "<pre><code>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var28 string var templ_7745c5c3_Var30 string
templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf(`<comment-list src="%s"></comment-list>`, getUrl)) templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf(`<guestbook-form guestbook="%s"></guestbook-form>
<guestbook-comments guestbook="%s"></guestbook-comments>`, gbUrl, gbUrl))
if templ_7745c5c3_Err != nil { 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: 136, Col: 72}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "</code></pre>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "</code></pre></div><p>If your web host does not allow CORS requests, use an iframe instead</p><div><pre><code>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = embedJavaScriptSnippet(data.RootUrl, website).Render(ctx, templ_7745c5c3_Buffer) var templ_7745c5c3_Var31 string
templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf(`<iframe src="%s" title="Guestbook"></iframe>`, 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 { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "</div></div></div>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "</code></pre></div></div></div>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
return nil 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 { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@ -574,12 +574,12 @@ func WebsiteDashboardComingSoon(title string, data CommonData, website models.We
}() }()
} }
ctx = templ.InitializeContext(ctx) ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var29 := templ.GetChildren(ctx) templ_7745c5c3_Var32 := templ.GetChildren(ctx)
if templ_7745c5c3_Var29 == nil { if templ_7745c5c3_Var32 == nil {
templ_7745c5c3_Var29 = templ.NopComponent templ_7745c5c3_Var32 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Var30 := 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_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer { if !templ_7745c5c3_IsBuffer {
@ -603,12 +603,12 @@ func WebsiteDashboardComingSoon(title string, data CommonData, website models.We
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var31 string var templ_7745c5c3_Var34 string
templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(website.Name) templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(website.Name)
if templ_7745c5c3_Err != nil { 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: 160, 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_Var34))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@ -618,7 +618,7 @@ func WebsiteDashboardComingSoon(title string, data CommonData, website models.We
} }
return nil 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_Var33), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@ -627,108 +627,6 @@ func WebsiteDashboardComingSoon(title string, data CommonData, website models.We
} }
func WebsiteCreate(title string, data CommonData, form forms.WebsiteCreateForm) templ.Component { func WebsiteCreate(title string, data CommonData, form forms.WebsiteCreateForm) 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_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, 48, "<form hx-post=\"/websites/create\" hx-target=\"closest div\">")
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, "</form>")
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_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 :=
`<form action="%s" method="post">
<div>
<label for="authorname">Name</label>
<input type="text" name="authorname" id="authorname"/>
</div>
<div>
<label for="authoremail">Email (Optional) </label>
<input type="text" name="authoremail" id="authoremail"/>
</div>
<div>
<label for="authorsite">Site Url (Optional) </label>
<input type="text" name="authorsite" id="authorsite"/>
</div>
<div>
<label for="content">Comment</label>
<textarea name="content" id="content"></textarea>
</div>
<div>
<input type="submit" value="Submit"/>
</div>
</form>`
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "<pre><code>")
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, "</code></pre>")
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) { 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 templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
@ -749,6 +647,36 @@ func embedJavaScriptSnippet(root string, website models.Website) templ.Component
templ_7745c5c3_Var35 = templ.NopComponent templ_7745c5c3_Var35 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
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, "<form action=\"/websites/create\" method=\"post\">")
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, "</form>")
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
}
return nil return nil
}) })
} }