UI Redesign #37
							
								
								
									
										4
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,4 @@
 | 
				
			|||||||
 | 
					[submodule "ui/static/fontawesome"]
 | 
				
			||||||
 | 
						path = ui/static/fontawesome
 | 
				
			||||||
 | 
						url = https://github.com/FortAwesome/Font-Awesome.git
 | 
				
			||||||
 | 
						branch = fa-release-7.0.0
 | 
				
			||||||
@ -14,6 +14,10 @@ func (app *application) home(w http.ResponseWriter, r *http.Request) {
 | 
				
			|||||||
	views.Home("Home", app.newCommonData(r)).Render(r.Context(), w)
 | 
						views.Home("Home", app.newCommonData(r)).Render(r.Context(), w)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (app *application) about(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
						views.AboutPage("About Webweav.ing", app.newCommonData(r)).Render(r.Context(), w)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (app *application) notImplemented(w http.ResponseWriter, r *http.Request) {
 | 
					func (app *application) notImplemented(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
	views.ComingSoon("Coming Soon", app.newCommonData(r)).Render(r.Context(), w)
 | 
						views.ComingSoon("Coming Soon", app.newCommonData(r)).Render(r.Context(), w)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -36,7 +36,7 @@ func (app *application) getGuestbook(w http.ResponseWriter, r *http.Request) {
 | 
				
			|||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	comments, err := app.guestbookComments.GetAll(website.Guestbook.ID)
 | 
						comments, err := app.guestbookComments.GetVisible(website.Guestbook.ID)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		app.serverError(w, r, err)
 | 
							app.serverError(w, r, err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
@ -79,7 +79,7 @@ func (app *application) getGuestbookCommentsSerialized(w http.ResponseWriter, r
 | 
				
			|||||||
	if !website.Guestbook.Settings.IsVisible || !website.Guestbook.Settings.AllowRemoteHostAccess {
 | 
						if !website.Guestbook.Settings.IsVisible || !website.Guestbook.Settings.AllowRemoteHostAccess {
 | 
				
			||||||
		app.clientError(w, http.StatusForbidden)
 | 
							app.clientError(w, http.StatusForbidden)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	comments, err := app.guestbookComments.GetAllSerialized(website.Guestbook.ID)
 | 
						comments, err := app.guestbookComments.GetVisibleSerialized(website.Guestbook.ID)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		app.serverError(w, r, err)
 | 
							app.serverError(w, r, err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
@ -151,7 +151,7 @@ func (app *application) postGuestbookCommentCreate(w http.ResponseWriter, r *htt
 | 
				
			|||||||
			views.EmbeddableGuestbookCommentForm(data, website, form).Render(r.Context(), w)
 | 
								views.EmbeddableGuestbookCommentForm(data, website, form).Render(r.Context(), w)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		// TODO: use htmx to avoid getting comments again
 | 
							// TODO: use htmx to avoid getting comments again
 | 
				
			||||||
		comments, err := app.guestbookComments.GetAll(website.Guestbook.ID)
 | 
							comments, err := app.guestbookComments.GetVisible(website.Guestbook.ID)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			app.serverError(w, r, err)
 | 
								app.serverError(w, r, err)
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
@ -243,7 +243,7 @@ func (app *application) getCommentQueue(w http.ResponseWriter, r *http.Request)
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	comments, err := app.guestbookComments.GetUnpublished(website.Guestbook.ID)
 | 
						comments, err := app.guestbookComments.GetAll(website.Guestbook.ID)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		if errors.Is(err, models.ErrNoRecord) {
 | 
							if errors.Is(err, models.ErrNoRecord) {
 | 
				
			||||||
			http.NotFound(w, r)
 | 
								http.NotFound(w, r)
 | 
				
			||||||
@ -315,6 +315,8 @@ func (app *application) putHideGuestbookComment(w http.ResponseWriter, r *http.R
 | 
				
			|||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		app.serverError(w, r, err)
 | 
							app.serverError(w, r, err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						data := app.newCommonData(r)
 | 
				
			||||||
 | 
						views.GuestbookDashboardUpdateButtonPart(data, website, comment).Render(r.Context(), w)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (app *application) deleteGuestbookComment(w http.ResponseWriter, r *http.Request) {
 | 
					func (app *application) deleteGuestbookComment(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
@ -349,6 +351,7 @@ func (app *application) deleteGuestbookComment(w http.ResponseWriter, r *http.Re
 | 
				
			|||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		app.serverError(w, r, err)
 | 
							app.serverError(w, r, err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						views.GuestbookDashboardCommentDeletePart("Comment was successfully deleted").Render(r.Context(), w)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (app *application) getAllGuestbooks(w http.ResponseWriter, r *http.Request) {
 | 
					func (app *application) getAllGuestbooks(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
				
			|||||||
@ -23,7 +23,7 @@ func (app *application) logRequest(next http.Handler) http.Handler {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func commonHeaders(next http.Handler) http.Handler {
 | 
					func commonHeaders(next http.Handler) http.Handler {
 | 
				
			||||||
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 | 
						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("Content-Security-Policy", "default-src 'self'; style-src 'self' fonts.googleapis.com; font-src fonts.gstatic.com 'self'; style-src-elem 'self';")
 | 
				
			||||||
		w.Header().Set("Referrer-Policy", "origin-when-cross-origin")
 | 
							w.Header().Set("Referrer-Policy", "origin-when-cross-origin")
 | 
				
			||||||
		w.Header().Set("X-Content-Type-Options", "nosniff")
 | 
							w.Header().Set("X-Content-Type-Options", "nosniff")
 | 
				
			||||||
		// w.Header().Set("X-Frame-Options", "deny")
 | 
							// w.Header().Set("X-Frame-Options", "deny")
 | 
				
			||||||
 | 
				
			|||||||
@ -35,6 +35,7 @@ func (app *application) routes() http.Handler {
 | 
				
			|||||||
	mux.Handle("/users/login/oidc", dynamic.ThenFunc(app.userLoginOIDC))
 | 
						mux.Handle("/users/login/oidc", dynamic.ThenFunc(app.userLoginOIDC))
 | 
				
			||||||
	mux.Handle("/users/login/oidc/callback", dynamic.ThenFunc(app.userLoginOIDCCallback))
 | 
						mux.Handle("/users/login/oidc/callback", dynamic.ThenFunc(app.userLoginOIDCCallback))
 | 
				
			||||||
	mux.Handle("GET /help", dynamic.ThenFunc(app.notImplemented))
 | 
						mux.Handle("GET /help", dynamic.ThenFunc(app.notImplemented))
 | 
				
			||||||
 | 
						mux.Handle("GET /about", dynamic.ThenFunc(app.about))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	protected := dynamic.Append(app.requireAuthentication)
 | 
						protected := dynamic.Append(app.requireAuthentication)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -43,7 +44,6 @@ func (app *application) routes() http.Handler {
 | 
				
			|||||||
	mux.Handle("POST /users/logout", protected.ThenFunc(app.postUserLogout))
 | 
						mux.Handle("POST /users/logout", protected.ThenFunc(app.postUserLogout))
 | 
				
			||||||
	mux.Handle("GET /users/settings", protected.ThenFunc(app.getUserSettings))
 | 
						mux.Handle("GET /users/settings", protected.ThenFunc(app.getUserSettings))
 | 
				
			||||||
	mux.Handle("PUT /users/settings", protected.ThenFunc(app.putUserSettings))
 | 
						mux.Handle("PUT /users/settings", protected.ThenFunc(app.putUserSettings))
 | 
				
			||||||
	mux.Handle("GET /users/privacy", protected.ThenFunc(app.notImplemented))
 | 
					 | 
				
			||||||
	mux.Handle("GET /guestbooks", protected.ThenFunc(app.getAllGuestbooks))
 | 
						mux.Handle("GET /guestbooks", protected.ThenFunc(app.getAllGuestbooks))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mux.Handle("GET /websites", protected.ThenFunc(app.getWebsiteList))
 | 
						mux.Handle("GET /websites", protected.ThenFunc(app.getWebsiteList))
 | 
				
			||||||
@ -51,13 +51,12 @@ func (app *application) routes() http.Handler {
 | 
				
			|||||||
	mux.Handle("POST /websites/create", protected.ThenFunc(app.postWebsiteCreate))
 | 
						mux.Handle("POST /websites/create", protected.ThenFunc(app.postWebsiteCreate))
 | 
				
			||||||
	mux.Handle("GET /websites/{id}/dashboard", protected.ThenFunc(app.getWebsiteDashboard))
 | 
						mux.Handle("GET /websites/{id}/dashboard", protected.ThenFunc(app.getWebsiteDashboard))
 | 
				
			||||||
	mux.Handle("GET /websites/{id}/dashboard/guestbook/comments", protected.ThenFunc(app.getGuestbookComments))
 | 
						mux.Handle("GET /websites/{id}/dashboard/guestbook/comments", protected.ThenFunc(app.getGuestbookComments))
 | 
				
			||||||
	mux.Handle("GET /websites/{id}/dashboard/guestbook/comments/queue", protected.ThenFunc(app.getCommentQueue))
 | 
						mux.Handle("GET /websites/{id}/dashboard/guestbook/comments/hidden", protected.ThenFunc(app.getCommentQueue))
 | 
				
			||||||
	mux.Handle("DELETE /websites/{id}/dashboard/guestbook/comments/{commentId}", protected.ThenFunc(app.deleteGuestbookComment))
 | 
						mux.Handle("DELETE /websites/{id}/dashboard/guestbook/comments/{commentId}", protected.ThenFunc(app.deleteGuestbookComment))
 | 
				
			||||||
	mux.Handle("PUT /websites/{id}/dashboard/guestbook/comments/{commentId}", protected.ThenFunc(app.putHideGuestbookComment))
 | 
						mux.Handle("PUT /websites/{id}/dashboard/guestbook/comments/{commentId}", protected.ThenFunc(app.putHideGuestbookComment))
 | 
				
			||||||
	mux.Handle("GET /websites/{id}/dashboard/settings", protected.ThenFunc(app.getWebsiteSettings))
 | 
						mux.Handle("GET /websites/{id}/dashboard/settings", protected.ThenFunc(app.getWebsiteSettings))
 | 
				
			||||||
	mux.Handle("PUT /websites/{id}/settings", protected.ThenFunc(app.putWebsiteSettings))
 | 
						mux.Handle("PUT /websites/{id}/settings", protected.ThenFunc(app.putWebsiteSettings))
 | 
				
			||||||
	mux.Handle("PUT /websites/{id}", protected.ThenFunc(app.deleteWebsite))
 | 
						mux.Handle("PUT /websites/{id}", protected.ThenFunc(app.deleteWebsite))
 | 
				
			||||||
	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/themes", protected.ThenFunc(app.getComingSoon))
 | 
				
			||||||
	mux.Handle("GET /websites/{id}/dashboard/guestbook/customize", protected.ThenFunc(app.getComingSoon))
 | 
						mux.Handle("GET /websites/{id}/dashboard/guestbook/customize", protected.ThenFunc(app.getComingSoon))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										8
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								go.mod
									
									
									
									
									
								
							@ -7,6 +7,7 @@ require (
 | 
				
			|||||||
	github.com/alexedwards/scs/sqlite3store v0.0.0-20250212122300-421ef1d8611c
 | 
						github.com/alexedwards/scs/sqlite3store v0.0.0-20250212122300-421ef1d8611c
 | 
				
			||||||
	github.com/alexedwards/scs/v2 v2.8.0
 | 
						github.com/alexedwards/scs/v2 v2.8.0
 | 
				
			||||||
	github.com/coreos/go-oidc/v3 v3.14.1
 | 
						github.com/coreos/go-oidc/v3 v3.14.1
 | 
				
			||||||
 | 
						github.com/golang-migrate/migrate/v4 v4.18.3
 | 
				
			||||||
	github.com/gorilla/schema v1.4.1
 | 
						github.com/gorilla/schema v1.4.1
 | 
				
			||||||
	github.com/joho/godotenv v1.5.1
 | 
						github.com/joho/godotenv v1.5.1
 | 
				
			||||||
	github.com/justinas/alice v1.2.0
 | 
						github.com/justinas/alice v1.2.0
 | 
				
			||||||
@ -16,4 +17,9 @@ require (
 | 
				
			|||||||
	golang.org/x/oauth2 v0.30.0
 | 
						golang.org/x/oauth2 v0.30.0
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require github.com/go-jose/go-jose/v4 v4.0.5 // indirect
 | 
					require (
 | 
				
			||||||
 | 
						github.com/go-jose/go-jose/v4 v4.0.5 // indirect
 | 
				
			||||||
 | 
						github.com/hashicorp/errwrap v1.1.0 // indirect
 | 
				
			||||||
 | 
						github.com/hashicorp/go-multierror v1.1.1 // indirect
 | 
				
			||||||
 | 
						go.uber.org/atomic v1.7.0 // indirect
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										14
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								go.sum
									
									
									
									
									
								
							@ -6,27 +6,41 @@ github.com/alexedwards/scs/v2 v2.8.0 h1:h31yUYoycPuL0zt14c0gd+oqxfRwIj6SOjHdKRZx
 | 
				
			|||||||
github.com/alexedwards/scs/v2 v2.8.0/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gvmdl7h3RRj8=
 | 
					github.com/alexedwards/scs/v2 v2.8.0/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gvmdl7h3RRj8=
 | 
				
			||||||
github.com/coreos/go-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk=
 | 
					github.com/coreos/go-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk=
 | 
				
			||||||
github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
 | 
					github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
 | 
				
			||||||
 | 
					github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
				
			||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 | 
					github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 | 
				
			||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
					github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
				
			||||||
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
 | 
					github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
 | 
				
			||||||
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
 | 
					github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
 | 
				
			||||||
 | 
					github.com/golang-migrate/migrate/v4 v4.18.3 h1:EYGkoOsvgHHfm5U/naS1RP/6PL/Xv3S4B/swMiAmDLs=
 | 
				
			||||||
 | 
					github.com/golang-migrate/migrate/v4 v4.18.3/go.mod h1:99BKpIi6ruaaXRM1A77eqZ+FWPQ3cfRa+ZVy5bmWMaY=
 | 
				
			||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
 | 
					github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
 | 
				
			||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 | 
					github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 | 
				
			||||||
github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E=
 | 
					github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E=
 | 
				
			||||||
github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM=
 | 
					github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM=
 | 
				
			||||||
 | 
					github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
 | 
				
			||||||
 | 
					github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
 | 
				
			||||||
 | 
					github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
 | 
				
			||||||
 | 
					github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
 | 
				
			||||||
 | 
					github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
 | 
				
			||||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
 | 
					github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
 | 
				
			||||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
 | 
					github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
 | 
				
			||||||
github.com/justinas/alice v1.2.0 h1:+MHSA/vccVCF4Uq37S42jwlkvI2Xzl7zTPCN5BnZNVo=
 | 
					github.com/justinas/alice v1.2.0 h1:+MHSA/vccVCF4Uq37S42jwlkvI2Xzl7zTPCN5BnZNVo=
 | 
				
			||||||
github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA=
 | 
					github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA=
 | 
				
			||||||
github.com/justinas/nosurf v1.1.1 h1:92Aw44hjSK4MxJeMSyDa7jwuI9GR2J/JCQiaKvXXSlk=
 | 
					github.com/justinas/nosurf v1.1.1 h1:92Aw44hjSK4MxJeMSyDa7jwuI9GR2J/JCQiaKvXXSlk=
 | 
				
			||||||
github.com/justinas/nosurf v1.1.1/go.mod h1:ALpWdSbuNGy2lZWtyXdjkYv4edL23oSEgfBT1gPJ5BQ=
 | 
					github.com/justinas/nosurf v1.1.1/go.mod h1:ALpWdSbuNGy2lZWtyXdjkYv4edL23oSEgfBT1gPJ5BQ=
 | 
				
			||||||
 | 
					github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
 | 
				
			||||||
 | 
					github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 | 
				
			||||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
 | 
					github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
 | 
				
			||||||
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
 | 
					github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
 | 
				
			||||||
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
 | 
					github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
 | 
				
			||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
					github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
				
			||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
					github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
				
			||||||
 | 
					github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
				
			||||||
 | 
					github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 | 
				
			||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
 | 
					github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
 | 
				
			||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 | 
					github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 | 
				
			||||||
 | 
					go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
 | 
				
			||||||
 | 
					go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
 | 
				
			||||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
 | 
					golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
 | 
				
			||||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
 | 
					golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
 | 
				
			||||||
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
 | 
					golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
 | 
				
			||||||
 | 
				
			|||||||
@ -33,10 +33,10 @@ type GuestbookCommentModel struct {
 | 
				
			|||||||
type GuestbookCommentModelInterface interface {
 | 
					type GuestbookCommentModelInterface interface {
 | 
				
			||||||
	Insert(shortId uint64, guestbookId, parentId int64, authorName, authorEmail, authorSite, commentText, pageUrl string, isPublished bool) (int64, error)
 | 
						Insert(shortId uint64, guestbookId, parentId int64, authorName, authorEmail, authorSite, commentText, pageUrl string, isPublished bool) (int64, error)
 | 
				
			||||||
	Get(shortId uint64) (GuestbookComment, error)
 | 
						Get(shortId uint64) (GuestbookComment, error)
 | 
				
			||||||
	GetAll(guestbookId int64) ([]GuestbookComment, error)
 | 
						GetVisible(guestbookId int64) ([]GuestbookComment, error)
 | 
				
			||||||
	GetAllSerialized(guestbookId int64) ([]GuestbookCommentSerialized, error)
 | 
						GetVisibleSerialized(guestbookId int64) ([]GuestbookCommentSerialized, error)
 | 
				
			||||||
	GetDeleted(guestbookId int64) ([]GuestbookComment, error)
 | 
						GetDeleted(guestbookId int64) ([]GuestbookComment, error)
 | 
				
			||||||
	GetUnpublished(guestbookId int64) ([]GuestbookComment, error)
 | 
						GetAll(guestbookId int64) ([]GuestbookComment, error)
 | 
				
			||||||
	UpdateComment(comment *GuestbookComment) error
 | 
						UpdateComment(comment *GuestbookComment) error
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -74,7 +74,7 @@ func (m *GuestbookCommentModel) Get(shortId uint64) (GuestbookComment, error) {
 | 
				
			|||||||
	return c, nil
 | 
						return c, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (m *GuestbookCommentModel) GetAll(guestbookId int64) ([]GuestbookComment, error) {
 | 
					func (m *GuestbookCommentModel) GetVisible(guestbookId int64) ([]GuestbookComment, error) {
 | 
				
			||||||
	stmt := `SELECT Id, ShortId, GuestbookId, ParentId, AuthorName, AuthorEmail, AuthorSite,
 | 
						stmt := `SELECT Id, ShortId, GuestbookId, ParentId, AuthorName, AuthorEmail, AuthorSite,
 | 
				
			||||||
    CommentText, PageUrl, Created, IsPublished 
 | 
					    CommentText, PageUrl, Created, IsPublished 
 | 
				
			||||||
	    FROM guestbook_comments 
 | 
						    FROM guestbook_comments 
 | 
				
			||||||
@ -100,7 +100,7 @@ func (m *GuestbookCommentModel) GetAll(guestbookId int64) ([]GuestbookComment, e
 | 
				
			|||||||
	return comments, nil
 | 
						return comments, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (m *GuestbookCommentModel) GetAllSerialized(guestbookId int64) ([]GuestbookCommentSerialized, error) {
 | 
					func (m *GuestbookCommentModel) GetVisibleSerialized(guestbookId int64) ([]GuestbookCommentSerialized, error) {
 | 
				
			||||||
	stmt := `SELECT AuthorName, CommentText, Created
 | 
						stmt := `SELECT AuthorName, CommentText, Created
 | 
				
			||||||
	    FROM guestbook_comments 
 | 
						    FROM guestbook_comments 
 | 
				
			||||||
	    WHERE GuestbookId = ? AND IsPublished = TRUE AND DELETED IS NULL
 | 
						    WHERE GuestbookId = ? AND IsPublished = TRUE AND DELETED IS NULL
 | 
				
			||||||
@ -154,11 +154,11 @@ func (m *GuestbookCommentModel) GetDeleted(guestbookId int64) ([]GuestbookCommen
 | 
				
			|||||||
	return comments, nil
 | 
						return comments, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (m *GuestbookCommentModel) GetUnpublished(guestbookId int64) ([]GuestbookComment, error) {
 | 
					func (m *GuestbookCommentModel) GetAll(guestbookId int64) ([]GuestbookComment, error) {
 | 
				
			||||||
	stmt := `SELECT Id, ShortId, GuestbookId, ParentId, AuthorName, AuthorEmail, AuthorSite,
 | 
						stmt := `SELECT Id, ShortId, GuestbookId, ParentId, AuthorName, AuthorEmail, AuthorSite,
 | 
				
			||||||
    CommentText, PageUrl, Created, IsPublished 
 | 
					    CommentText, PageUrl, Created, IsPublished 
 | 
				
			||||||
	    FROM guestbook_comments 
 | 
						    FROM guestbook_comments 
 | 
				
			||||||
	    WHERE GuestbookId = ? AND Deleted IS NULL AND IsPublished = FALSE
 | 
						    WHERE GuestbookId = ? AND Deleted IS NULL
 | 
				
			||||||
	    ORDER BY Created DESC`
 | 
						    ORDER BY Created DESC`
 | 
				
			||||||
	rows, err := m.DB.Query(stmt, guestbookId)
 | 
						rows, err := m.DB.Query(stmt, guestbookId)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
 | 
				
			|||||||
@ -40,7 +40,7 @@ func (m *GuestbookCommentModel) Get(shortId uint64) (models.GuestbookComment, er
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (m *GuestbookCommentModel) GetAll(guestbookId int64) ([]models.GuestbookComment, error) {
 | 
					func (m *GuestbookCommentModel) GetVisible(guestbookId int64) ([]models.GuestbookComment, error) {
 | 
				
			||||||
	switch guestbookId {
 | 
						switch guestbookId {
 | 
				
			||||||
	case 1:
 | 
						case 1:
 | 
				
			||||||
		return []models.GuestbookComment{mockGuestbookComment}, nil
 | 
							return []models.GuestbookComment{mockGuestbookComment}, nil
 | 
				
			||||||
@ -51,7 +51,7 @@ func (m *GuestbookCommentModel) GetAll(guestbookId int64) ([]models.GuestbookCom
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (m *GuestbookCommentModel) GetAllSerialized(guestbookId int64) ([]models.GuestbookCommentSerialized, error) {
 | 
					func (m *GuestbookCommentModel) GetVisibleSerialized(guestbookId int64) ([]models.GuestbookCommentSerialized, error) {
 | 
				
			||||||
	switch guestbookId {
 | 
						switch guestbookId {
 | 
				
			||||||
	case 1:
 | 
						case 1:
 | 
				
			||||||
		return []models.GuestbookCommentSerialized{mockSerializedGuestbookComment}, nil
 | 
							return []models.GuestbookCommentSerialized{mockSerializedGuestbookComment}, nil
 | 
				
			||||||
@ -69,7 +69,7 @@ func (m *GuestbookCommentModel) GetDeleted(guestbookId int64) ([]models.Guestboo
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (m *GuestbookCommentModel) GetUnpublished(guestbookId int64) ([]models.GuestbookComment, error) {
 | 
					func (m *GuestbookCommentModel) GetAll(guestbookId int64) ([]models.GuestbookComment, error) {
 | 
				
			||||||
	switch guestbookId {
 | 
						switch guestbookId {
 | 
				
			||||||
	default:
 | 
						default:
 | 
				
			||||||
		return []models.GuestbookComment{}, models.ErrNoRecord
 | 
							return []models.GuestbookComment{}, models.ErrNoRecord
 | 
				
			||||||
 | 
				
			|||||||
@ -9,10 +9,9 @@ import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Website struct {
 | 
					type Website struct {
 | 
				
			||||||
	ID      int64
 | 
						ID         int64
 | 
				
			||||||
	ShortId uint64
 | 
						ShortId    uint64
 | 
				
			||||||
	Name    string
 | 
						Name       string
 | 
				
			||||||
	// SiteUrl    string
 | 
					 | 
				
			||||||
	Url        *url.URL
 | 
						Url        *url.URL
 | 
				
			||||||
	AuthorName string
 | 
						AuthorName string
 | 
				
			||||||
	UserId     int64
 | 
						UserId     int64
 | 
				
			||||||
@ -317,15 +316,29 @@ func (m *WebsiteModel) Update(w Website) error {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func (m *WebsiteModel) Delete(websiteId int64) error {
 | 
					func (m *WebsiteModel) Delete(websiteId int64) error {
 | 
				
			||||||
	stmt := `UPDATE websites SET Deleted = ? WHERE ID = ?`
 | 
						stmt := `UPDATE websites SET Deleted = ? WHERE ID = ?`
 | 
				
			||||||
	r, err := m.DB.Exec(stmt, time.Now().UTC(), websiteId)
 | 
						tx, err := m.DB.Begin()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						t := time.Now().UTC()
 | 
				
			||||||
 | 
						_, err = tx.Exec(stmt, t, websiteId)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							if rbErr := tx.Rollback(); rbErr != nil {
 | 
				
			||||||
 | 
								return rbErr
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if rows, err := r.RowsAffected(); rows != 1 {
 | 
						stmt = `UPDATE guestbooks SET Deleted = ? WHERE WebsiteId = ?`
 | 
				
			||||||
		if err != nil {
 | 
						_, err = tx.Exec(stmt, t, websiteId)
 | 
				
			||||||
			return err
 | 
						if err != nil {
 | 
				
			||||||
 | 
							if rbErr := tx.Rollback(); rbErr != nil {
 | 
				
			||||||
 | 
								return rbErr
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return errors.New("Failed to update website")
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						err = tx.Commit()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,86 +1,886 @@
 | 
				
			|||||||
/* html {
 | 
					/* CSS Reset and Base Styles */
 | 
				
			||||||
  background: lightgray;
 | 
					*,
 | 
				
			||||||
} */
 | 
					*::before,
 | 
				
			||||||
 | 
					*::after {
 | 
				
			||||||
 | 
					  box-sizing: border-box;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* {
 | 
				
			||||||
 | 
					  margin: 0;
 | 
				
			||||||
 | 
					  padding: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					html {
 | 
				
			||||||
 | 
					  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
 | 
				
			||||||
 | 
					  line-height: 1.6;
 | 
				
			||||||
 | 
					  -webkit-font-smoothing: antialiased;
 | 
				
			||||||
 | 
					  -moz-osx-font-smoothing: grayscale;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* CSS Custom Properties for Theming */
 | 
				
			||||||
 | 
					:root {
 | 
				
			||||||
 | 
					  /* Light mode colors */
 | 
				
			||||||
 | 
					  --color-primary: #2563eb;
 | 
				
			||||||
 | 
					  --color-primary-hover: #1d4ed8;
 | 
				
			||||||
 | 
					  --color-danger: #dc2626;
 | 
				
			||||||
 | 
					  --color-danger-hover: #b91c1c;
 | 
				
			||||||
 | 
					  --color-warning: #d97706;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  --color-text: #1f2937;
 | 
				
			||||||
 | 
					  --color-text-muted: #6b7280;
 | 
				
			||||||
 | 
					  --color-background: #ffffff;
 | 
				
			||||||
 | 
					  --color-surface: #f9fafb;
 | 
				
			||||||
 | 
					  --color-border: #e5e7eb;
 | 
				
			||||||
 | 
					  --color-border-light: #f3f4f6;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /* Spacing scale */
 | 
				
			||||||
 | 
					  --space-xs: 0.25rem;
 | 
				
			||||||
 | 
					  --space-sm: 0.5rem;
 | 
				
			||||||
 | 
					  --space-md: 1rem;
 | 
				
			||||||
 | 
					  --space-lg: 1.5rem;
 | 
				
			||||||
 | 
					  --space-xl: 2rem;
 | 
				
			||||||
 | 
					  --space-2xl: 3rem;
 | 
				
			||||||
 | 
					  --space-3xl: 4rem;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /* Typography scale */
 | 
				
			||||||
 | 
					  --text-xs: 0.75rem;
 | 
				
			||||||
 | 
					  --text-sm: 0.875rem;
 | 
				
			||||||
 | 
					  --text-base: 1rem;
 | 
				
			||||||
 | 
					  --text-lg: 1.125rem;
 | 
				
			||||||
 | 
					  --text-xl: 1.25rem;
 | 
				
			||||||
 | 
					  --text-2xl: 1.5rem;
 | 
				
			||||||
 | 
					  --text-3xl: 1.875rem;
 | 
				
			||||||
 | 
					  --text-4xl: 2.25rem;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /* Border radius */
 | 
				
			||||||
 | 
					  --radius-sm: 0.125rem;
 | 
				
			||||||
 | 
					  --radius-md: 0.375rem;
 | 
				
			||||||
 | 
					  --radius-lg: 0.5rem;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /* Layout */
 | 
				
			||||||
 | 
					  --max-width: 1200px;
 | 
				
			||||||
 | 
					  --header-height: 60px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Dark mode colors */
 | 
				
			||||||
 | 
					@media (prefers-color-scheme: dark) {
 | 
				
			||||||
 | 
					  :root {
 | 
				
			||||||
 | 
					    --color-text: #f9fafb;
 | 
				
			||||||
 | 
					    --color-text-muted: #9ca3af;
 | 
				
			||||||
 | 
					    --color-background: #111827;
 | 
				
			||||||
 | 
					    --color-surface: #1f2937;
 | 
				
			||||||
 | 
					    --color-border: #374151;
 | 
				
			||||||
 | 
					    --color-border-light: #4b5563;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Base Typography */
 | 
				
			||||||
body {
 | 
					body {
 | 
				
			||||||
  max-width: 1024px;
 | 
					  font-size: var(--text-base);
 | 
				
			||||||
  margin: 1rem auto;
 | 
					  color: var(--color-text);
 | 
				
			||||||
  padding: 1rem;
 | 
					  background-color: var(--color-background);
 | 
				
			||||||
  /* background: white; */
 | 
					  min-height: 100vh;
 | 
				
			||||||
  font-size: 1.2rem;
 | 
					 | 
				
			||||||
  line-height: 1.5;
 | 
					 | 
				
			||||||
  font-family: Arial, Helvetica, sans-serif;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
header {
 | 
					 | 
				
			||||||
  text-align: center;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
body > nav {
 | 
					 | 
				
			||||||
  display: flex;
 | 
					  display: flex;
 | 
				
			||||||
  justify-content: space-between;
 | 
					  flex-direction: column;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
body > nav ul {
 | 
					h1, h2, h3, h4, h5, h6 {
 | 
				
			||||||
  list-style: none;
 | 
					  font-weight: 600;
 | 
				
			||||||
  margin: 0 1rem;
 | 
					  line-height: 1.25;
 | 
				
			||||||
  padding: 0;
 | 
					  margin-bottom: var(--space-md);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
body > nav li {
 | 
					h1 { font-size: var(--text-3xl); }
 | 
				
			||||||
  display: inline-block;
 | 
					h2 { font-size: var(--text-2xl); }
 | 
				
			||||||
  padding: 0 0.5rem;
 | 
					h3 { font-size: var(--text-xl); }
 | 
				
			||||||
}
 | 
					h4 { font-size: var(--text-lg); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
nav form {
 | 
					p {
 | 
				
			||||||
  display: inline-block;
 | 
					  margin-bottom: var(--space-md);
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
nav button {
 | 
					 | 
				
			||||||
  border: none;
 | 
					 | 
				
			||||||
  background: none;
 | 
					 | 
				
			||||||
  font-family: unset;
 | 
					 | 
				
			||||||
  font-size: unset;
 | 
					 | 
				
			||||||
  /* color: blue; */
 | 
					 | 
				
			||||||
  /* text-decoration: underline; */
 | 
					 | 
				
			||||||
  cursor: pointer;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
main {
 | 
					 | 
				
			||||||
  padding: 1rem;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
div#dashboard {
 | 
					 | 
				
			||||||
  display: flex;
 | 
					 | 
				
			||||||
  flex-flow: row wrap;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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 {
 | 
					 | 
				
			||||||
  list-style: none;
 | 
					 | 
				
			||||||
  margin: 1rem;
 | 
					 | 
				
			||||||
  padding: 0;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
footer {
 | 
					 | 
				
			||||||
  text-align: center;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
a {
 | 
					a {
 | 
				
			||||||
  /* color: blue; */
 | 
					  color: var(--color-primary);
 | 
				
			||||||
 | 
					  text-decoration: none;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					a:hover {
 | 
				
			||||||
 | 
					  color: var(--color-primary-hover);
 | 
				
			||||||
 | 
					  text-decoration: underline;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Layout Components */
 | 
				
			||||||
 | 
					body > header {
 | 
				
			||||||
 | 
					  background-color: var(--color-surface);
 | 
				
			||||||
 | 
					  border-bottom: 1px solid var(--color-border);
 | 
				
			||||||
 | 
					  height: var(--header-height);
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  align-items: center;
 | 
				
			||||||
 | 
					  padding: 0 var(--space-lg);
 | 
				
			||||||
 | 
					  position: sticky;
 | 
				
			||||||
 | 
					  top: 0;
 | 
				
			||||||
 | 
					  z-index: 100;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					body > header h1 {
 | 
				
			||||||
 | 
					  margin: 0;
 | 
				
			||||||
 | 
					  font-size: var(--text-2xl);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					body > header h1 a {
 | 
				
			||||||
 | 
					  color: var(--color-text);
 | 
				
			||||||
 | 
					  font-weight: 700;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					body > header h1 a:hover {
 | 
				
			||||||
 | 
					  text-decoration: none;
 | 
				
			||||||
 | 
					  color: var(--color-primary);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					nav {
 | 
				
			||||||
 | 
					  background-color: var(--color-surface);
 | 
				
			||||||
 | 
					  border-bottom: 1px solid var(--color-border);
 | 
				
			||||||
 | 
					  padding: var(--space-md) var(--space-lg);
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  justify-content: space-between;
 | 
				
			||||||
 | 
					  align-items: center;
 | 
				
			||||||
 | 
					  flex-wrap: wrap;
 | 
				
			||||||
 | 
					  gap: var(--space-md);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.nav-welcome {
 | 
				
			||||||
 | 
					  font-weight: 500;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.nav-links {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  flex-wrap: wrap;
 | 
				
			||||||
 | 
					  gap: var(--space-md);
 | 
				
			||||||
 | 
					  list-style: none;
 | 
				
			||||||
 | 
					  margin: 0;
 | 
				
			||||||
 | 
					  padding: 0;
 | 
				
			||||||
 | 
					  align-items: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.nav-links li {
 | 
				
			||||||
 | 
					  margin: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.nav-links a {
 | 
				
			||||||
 | 
					  padding: var(--space-sm) var(--space-md);
 | 
				
			||||||
 | 
					  border-radius: var(--radius-md);
 | 
				
			||||||
 | 
					  transition: background-color 0.2s ease;
 | 
				
			||||||
 | 
					  white-space: nowrap;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.nav-links a:hover {
 | 
				
			||||||
 | 
					  background-color: var(--color-border-light);
 | 
				
			||||||
 | 
					  text-decoration: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					main {
 | 
				
			||||||
 | 
					  flex: 1;
 | 
				
			||||||
 | 
					  max-width: var(--max-width);
 | 
				
			||||||
 | 
					  margin: 0 auto;
 | 
				
			||||||
 | 
					  padding: var(--space-2xl) var(--space-lg);
 | 
				
			||||||
 | 
					  width: 100%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					footer {
 | 
				
			||||||
 | 
					  background-color: var(--color-surface);
 | 
				
			||||||
 | 
					  border-top: 1px solid var(--color-border);
 | 
				
			||||||
 | 
					  padding: var(--space-lg);
 | 
				
			||||||
 | 
					  text-align: center;
 | 
				
			||||||
 | 
					  margin-top: auto;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.footer-links {
 | 
				
			||||||
 | 
					  padding: 0;
 | 
				
			||||||
 | 
					  list-style: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.footer-links li {
 | 
				
			||||||
 | 
					    display: inline-block;
 | 
				
			||||||
 | 
					    padding: 0 1rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Dashboard Layout */
 | 
				
			||||||
 | 
					#dashboard {
 | 
				
			||||||
 | 
					  display: grid;
 | 
				
			||||||
 | 
					  grid-template-columns: 280px 1fr;
 | 
				
			||||||
 | 
					  gap: var(--space-2xl);
 | 
				
			||||||
 | 
					  margin-top: var(--space-xl);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#dashboard nav {
 | 
				
			||||||
 | 
					  background: none;
 | 
				
			||||||
 | 
					  border: none;
 | 
				
			||||||
 | 
					  padding: 0;
 | 
				
			||||||
 | 
					  display: block;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#dashboard nav > div {
 | 
				
			||||||
 | 
					  margin-bottom: var(--space-xl);
 | 
				
			||||||
 | 
					  padding: var(--space-lg);
 | 
				
			||||||
 | 
					  background-color: var(--color-surface);
 | 
				
			||||||
 | 
					  border: 1px solid var(--color-border);
 | 
				
			||||||
 | 
					  border-radius: var(--radius-lg);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#dashboard nav h3 {
 | 
				
			||||||
 | 
					  font-size: var(--text-lg);
 | 
				
			||||||
 | 
					  margin-bottom: var(--space-md);
 | 
				
			||||||
 | 
					  padding-bottom: var(--space-sm);
 | 
				
			||||||
 | 
					  border-bottom: 1px solid var(--color-border);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#dashboard nav ul {
 | 
				
			||||||
 | 
					  list-style: none;
 | 
				
			||||||
 | 
					  margin-bottom: var(--space-md);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#dashboard nav ul:last-child {
 | 
				
			||||||
 | 
					  margin-bottom: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#dashboard nav li {
 | 
				
			||||||
 | 
					  margin-bottom: var(--space-xs);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#dashboard nav a {
 | 
				
			||||||
 | 
					  display: block;
 | 
				
			||||||
 | 
					  padding: var(--space-sm) var(--space-md);
 | 
				
			||||||
 | 
					  border-radius: var(--radius-md);
 | 
				
			||||||
 | 
					  transition: background-color 0.2s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#dashboard nav a:hover {
 | 
				
			||||||
 | 
					  background-color: var(--color-border-light);
 | 
				
			||||||
 | 
					  text-decoration: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Forms */
 | 
				
			||||||
 | 
					form {
 | 
				
			||||||
 | 
					  background-color: var(--color-surface);
 | 
				
			||||||
 | 
					  padding: var(--space-xl);
 | 
				
			||||||
 | 
					  border-radius: var(--radius-lg);
 | 
				
			||||||
 | 
					  border: 1px solid var(--color-border);
 | 
				
			||||||
 | 
					  margin-bottom: var(--space-xl);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.form-group {
 | 
				
			||||||
 | 
					  margin-bottom: var(--space-lg);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.form-group small {
 | 
				
			||||||
 | 
					  display: block;
 | 
				
			||||||
 | 
					  margin-top: var(--space-xs);
 | 
				
			||||||
 | 
					  font-size: var(--text-sm);
 | 
				
			||||||
 | 
					  color: var(--color-text-muted);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fieldset {
 | 
				
			||||||
 | 
					  border: 1px solid var(--color-border);
 | 
				
			||||||
 | 
					  border-radius: var(--radius-md);
 | 
				
			||||||
 | 
					  padding: var(--space-lg);
 | 
				
			||||||
 | 
					  margin-bottom: var(--space-lg);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fieldset.radio-group {
 | 
				
			||||||
 | 
					  border: none;
 | 
				
			||||||
 | 
					  padding: 0;
 | 
				
			||||||
 | 
					  margin: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fieldset.danger-zone {
 | 
				
			||||||
 | 
					  border-color: var(--color-danger);
 | 
				
			||||||
 | 
					  background-color: rgba(220, 38, 38, 0.05);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					legend {
 | 
				
			||||||
 | 
					  padding: 0 var(--space-sm);
 | 
				
			||||||
 | 
					  font-weight: 600;
 | 
				
			||||||
 | 
					  color: var(--color-text);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					label {
 | 
				
			||||||
 | 
					  display: block;
 | 
				
			||||||
 | 
					  font-weight: 500;
 | 
				
			||||||
 | 
					  margin-bottom: var(--space-sm);
 | 
				
			||||||
 | 
					  color: var(--color-text);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					input[type="text"],
 | 
				
			||||||
 | 
					input[type="email"],
 | 
				
			||||||
 | 
					input[type="url"],
 | 
				
			||||||
 | 
					textarea,
 | 
				
			||||||
 | 
					select {
 | 
				
			||||||
 | 
					  width: 100%;
 | 
				
			||||||
 | 
					  padding: var(--space-md);
 | 
				
			||||||
 | 
					  border: 1px solid var(--color-border);
 | 
				
			||||||
 | 
					  border-radius: var(--radius-md);
 | 
				
			||||||
 | 
					  background-color: var(--color-background);
 | 
				
			||||||
 | 
					  color: var(--color-text);
 | 
				
			||||||
 | 
					  font-size: var(--text-base);
 | 
				
			||||||
 | 
					  transition: border-color 0.2s ease, box-shadow 0.2s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					input[type="text"]:focus,
 | 
				
			||||||
 | 
					input[type="email"]:focus,
 | 
				
			||||||
 | 
					input[type="url"]:focus,
 | 
				
			||||||
 | 
					textarea:focus,
 | 
				
			||||||
 | 
					select:focus {
 | 
				
			||||||
 | 
					  outline: none;
 | 
				
			||||||
 | 
					  border-color: var(--color-primary);
 | 
				
			||||||
 | 
					  box-shadow: 0 0 0 3px rgb(37 99 235 / 0.1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					textarea {
 | 
				
			||||||
 | 
					  min-height: 120px;
 | 
				
			||||||
 | 
					  resize: vertical;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Radio buttons */
 | 
				
			||||||
 | 
					input[type="radio"] {
 | 
				
			||||||
 | 
					  margin-right: var(--space-sm);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					label:has(input[type="radio"]) {
 | 
				
			||||||
 | 
					  display: inline-flex;
 | 
				
			||||||
 | 
					  align-items: center;
 | 
				
			||||||
 | 
					  margin-right: var(--space-lg);
 | 
				
			||||||
 | 
					  margin-bottom: var(--space-sm);
 | 
				
			||||||
 | 
					  font-weight: normal;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Buttons */
 | 
				
			||||||
 | 
					button,
 | 
				
			||||||
 | 
					input[type="submit"] {
 | 
				
			||||||
 | 
					  display: inline-flex;
 | 
				
			||||||
 | 
					  align-items: center;
 | 
				
			||||||
 | 
					  justify-content: center;
 | 
				
			||||||
 | 
					  padding: var(--space-md) var(--space-lg);
 | 
				
			||||||
 | 
					  border: none;
 | 
				
			||||||
 | 
					  border-radius: var(--radius-md);
 | 
				
			||||||
 | 
					  background-color: var(--color-primary);
 | 
				
			||||||
 | 
					  color: white;
 | 
				
			||||||
 | 
					  font-size: var(--text-base);
 | 
				
			||||||
 | 
					  font-weight: 500;
 | 
				
			||||||
 | 
					  cursor: pointer;
 | 
				
			||||||
 | 
					  transition: background-color 0.2s ease, transform 0.1s ease;
 | 
				
			||||||
 | 
					  min-height: 44px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					button:hover,
 | 
				
			||||||
 | 
					input[type="submit"]:hover {
 | 
				
			||||||
 | 
					  background-color: var(--color-primary-hover);
 | 
				
			||||||
 | 
					  transform: translateY(-1px);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					button:active,
 | 
				
			||||||
 | 
					input[type="submit"]:active {
 | 
				
			||||||
 | 
					  transform: translateY(0);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					button.danger {
 | 
				
			||||||
 | 
					  background-color: var(--color-danger);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					button.danger:hover {
 | 
				
			||||||
 | 
					  background-color: var(--color-danger-hover);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					button.outline {
 | 
				
			||||||
 | 
					  background-color: transparent;
 | 
				
			||||||
 | 
					  color: var(--color-text);
 | 
				
			||||||
 | 
					  border: 1px solid var(--color-border);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					button.outline:hover {
 | 
				
			||||||
 | 
					  background-color: var(--color-surface);
 | 
				
			||||||
 | 
					  border-color: var(--color-text-muted);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Button variants */
 | 
				
			||||||
 | 
					.btn {
 | 
				
			||||||
 | 
					  display: inline-flex;
 | 
				
			||||||
 | 
					  align-items: center;
 | 
				
			||||||
 | 
					  justify-content: center;
 | 
				
			||||||
 | 
					  padding: var(--space-md) var(--space-lg);
 | 
				
			||||||
 | 
					  border: none;
 | 
				
			||||||
 | 
					  border-radius: var(--radius-md);
 | 
				
			||||||
 | 
					  font-size: var(--text-base);
 | 
				
			||||||
 | 
					  font-weight: 500;
 | 
				
			||||||
 | 
					  cursor: pointer;
 | 
				
			||||||
 | 
					  text-decoration: none;
 | 
				
			||||||
 | 
					  transition: all 0.2s ease;
 | 
				
			||||||
 | 
					  min-height: 44px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-primary {
 | 
				
			||||||
 | 
					  background-color: var(--color-primary);
 | 
				
			||||||
 | 
					  color: white;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-primary:hover {
 | 
				
			||||||
 | 
					  background-color: var(--color-primary-hover);
 | 
				
			||||||
 | 
					  color: white;
 | 
				
			||||||
 | 
					  text-decoration: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-outline {
 | 
				
			||||||
 | 
					  background-color: transparent;
 | 
				
			||||||
 | 
					  color: var(--color-text);
 | 
				
			||||||
 | 
					  border: 1px solid var(--color-border);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-outline:hover {
 | 
				
			||||||
 | 
					  background-color: var(--color-surface);
 | 
				
			||||||
 | 
					  border-color: var(--color-text-muted);
 | 
				
			||||||
 | 
					  text-decoration: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Comments */
 | 
				
			||||||
 | 
					#comments {
 | 
				
			||||||
 | 
					  margin-top: var(--space-2xl);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.comment {
 | 
				
			||||||
 | 
					  background-color: var(--color-surface);
 | 
				
			||||||
 | 
					  border: 1px solid var(--color-border);
 | 
				
			||||||
 | 
					  border-radius: var(--radius-lg);
 | 
				
			||||||
 | 
					  padding: var(--space-lg);
 | 
				
			||||||
 | 
					  margin-bottom: var(--space-lg);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.comment-header {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  justify-content: space-between;
 | 
				
			||||||
 | 
					  align-items: flex-start;
 | 
				
			||||||
 | 
					  margin-bottom: var(--space-md);
 | 
				
			||||||
 | 
					  gap: var(--space-md);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.comment-meta {
 | 
				
			||||||
 | 
					  flex: 1;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.comment-author {
 | 
				
			||||||
 | 
					  margin: 0 0 var(--space-xs) 0;
 | 
				
			||||||
 | 
					  font-size: var(--text-lg);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.comment time {
 | 
				
			||||||
 | 
					  font-size: var(--text-sm);
 | 
				
			||||||
 | 
					  color: var(--color-text-muted);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.comment-content p:last-of-type {
 | 
				
			||||||
 | 
					  margin-bottom: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.comment-actions {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  gap: var(--space-sm);
 | 
				
			||||||
 | 
					  flex-shrink: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.comments-list {
 | 
				
			||||||
 | 
					  margin-top: var(--space-lg);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Code blocks */
 | 
				
			||||||
 | 
					pre {
 | 
				
			||||||
 | 
					  background-color: var(--color-surface);
 | 
				
			||||||
 | 
					  border: 1px solid var(--color-border);
 | 
				
			||||||
 | 
					  border-radius: var(--radius-md);
 | 
				
			||||||
 | 
					  padding: var(--space-lg);
 | 
				
			||||||
 | 
					  overflow-x: auto;
 | 
				
			||||||
 | 
					  margin-bottom: var(--space-lg);
 | 
				
			||||||
 | 
					  white-space: pre-wrap;
 | 
				
			||||||
 | 
					  word-break: break-all;
 | 
				
			||||||
 | 
					  line-height: 1.4;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					code {
 | 
				
			||||||
 | 
					  font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
 | 
				
			||||||
 | 
					  font-size: var(--text-sm);
 | 
				
			||||||
 | 
					  background-color: var(--color-border-light);
 | 
				
			||||||
 | 
					  padding: var(--space-xs) var(--space-sm);
 | 
				
			||||||
 | 
					  border-radius: var(--radius-sm);
 | 
				
			||||||
 | 
					  word-break: break-all;
 | 
				
			||||||
 | 
					  white-space: pre-wrap;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pre code {
 | 
				
			||||||
 | 
					  background: none;
 | 
				
			||||||
 | 
					  padding: 0;
 | 
				
			||||||
 | 
					  white-space: pre-wrap;
 | 
				
			||||||
 | 
					  word-break: break-all;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.code-example {
 | 
				
			||||||
 | 
					  margin: var(--space-lg) 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.code-example figcaption {
 | 
				
			||||||
 | 
					  font-size: var(--text-sm);
 | 
				
			||||||
 | 
					  color: var(--color-text-muted);
 | 
				
			||||||
 | 
					  margin-bottom: var(--space-sm);
 | 
				
			||||||
 | 
					  font-weight: 500;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Lists */
 | 
				
			||||||
 | 
					ul, ol {
 | 
				
			||||||
 | 
					  padding-left: var(--space-xl);
 | 
				
			||||||
 | 
					  margin-bottom: var(--space-lg);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					li {
 | 
				
			||||||
 | 
					  margin-bottom: var(--space-sm);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ul#websites {
 | 
				
			||||||
 | 
					  list-style: none;
 | 
				
			||||||
 | 
					  padding: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ul#websites li {
 | 
				
			||||||
 | 
					  margin-bottom: var(--space-md);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Website cards */
 | 
				
			||||||
 | 
					.website-card {
 | 
				
			||||||
 | 
					  background-color: var(--color-surface);
 | 
				
			||||||
 | 
					  border: 1px solid var(--color-border);
 | 
				
			||||||
 | 
					  border-radius: var(--radius-lg);
 | 
				
			||||||
 | 
					  padding: var(--space-xl);
 | 
				
			||||||
 | 
					  transition: box-shadow 0.2s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.website-card:hover {
 | 
				
			||||||
 | 
					  box-shadow: var(--shadow-md);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.website-header {
 | 
				
			||||||
 | 
					  margin-bottom: var(--space-lg);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.website-name {
 | 
				
			||||||
 | 
					  margin: 0 0 var(--space-sm) 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.website-name a {
 | 
				
			||||||
 | 
					  color: var(--color-text);
 | 
				
			||||||
 | 
					  font-weight: 600;
 | 
				
			||||||
 | 
					  font-size: var(--text-xl);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.website-url {
 | 
				
			||||||
 | 
					  font-size: var(--text-sm);
 | 
				
			||||||
 | 
					  color: var(--color-text-muted);
 | 
				
			||||||
 | 
					  font-family: monospace;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.website-actions {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  gap: var(--space-sm);
 | 
				
			||||||
 | 
					  flex-wrap: wrap;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Screen reader only content */
 | 
				
			||||||
 | 
					.sr-only {
 | 
				
			||||||
 | 
					  position: absolute;
 | 
				
			||||||
 | 
					  width: 1px;
 | 
				
			||||||
 | 
					  height: 1px;
 | 
				
			||||||
 | 
					  padding: 0;
 | 
				
			||||||
 | 
					  margin: -1px;
 | 
				
			||||||
 | 
					  overflow: hidden;
 | 
				
			||||||
 | 
					  clip: rect(0, 0, 0, 0);
 | 
				
			||||||
 | 
					  white-space: nowrap;
 | 
				
			||||||
 | 
					  border: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Notices and alerts */
 | 
				
			||||||
 | 
					.notice,
 | 
				
			||||||
 | 
					.warning-notice {
 | 
				
			||||||
 | 
					  padding: var(--space-md);
 | 
				
			||||||
 | 
					  border-radius: var(--radius-md);
 | 
				
			||||||
 | 
					  margin-bottom: var(--space-lg);
 | 
				
			||||||
 | 
					  border-left: 4px solid var(--color-warning);
 | 
				
			||||||
 | 
					  background-color: rgba(217, 119, 6, 0.1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.warning-notice {
 | 
				
			||||||
 | 
					  border-left-color: var(--color-danger);
 | 
				
			||||||
 | 
					  background-color: rgba(220, 38, 38, 0.1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Section headers */
 | 
				
			||||||
 | 
					.section-header {
 | 
				
			||||||
 | 
					  margin-bottom: var(--space-xl);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.section-header:has(.btn) {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  justify-content: space-between;
 | 
				
			||||||
 | 
					  align-items: flex-start;
 | 
				
			||||||
 | 
					  flex-wrap: wrap;
 | 
				
			||||||
 | 
					  gap: var(--space-md);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.section-header h1 {
 | 
				
			||||||
 | 
					  margin-bottom: var(--space-sm);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.section-description {
 | 
				
			||||||
 | 
					  color: var(--color-text-muted);
 | 
				
			||||||
 | 
					  margin: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Hero section (index.html) */
 | 
				
			||||||
 | 
					.hero {
 | 
				
			||||||
 | 
					  text-align: center;
 | 
				
			||||||
 | 
					  padding: var(--space-3xl) 0;
 | 
				
			||||||
 | 
					  background: linear-gradient(135deg, var(--color-surface) 0%, var(--color-background) 100%);
 | 
				
			||||||
 | 
					  border-radius: var(--radius-lg);
 | 
				
			||||||
 | 
					  margin-bottom: var(--space-2xl);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.hero-header h1 {
 | 
				
			||||||
 | 
					  font-size: var(--text-4xl);
 | 
				
			||||||
 | 
					  margin-bottom: var(--space-md);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.hero-subtitle {
 | 
				
			||||||
 | 
					  font-size: var(--text-xl);
 | 
				
			||||||
 | 
					  color: var(--color-text-muted);
 | 
				
			||||||
 | 
					  margin: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Instruction sections (dashboard_main.html) */
 | 
				
			||||||
 | 
					.instruction-overview {
 | 
				
			||||||
 | 
					  background-color: var(--color-surface);
 | 
				
			||||||
 | 
					  border: 1px solid var(--color-border);
 | 
				
			||||||
 | 
					  border-radius: var(--radius-md);
 | 
				
			||||||
 | 
					  padding: var(--space-lg);
 | 
				
			||||||
 | 
					  margin-bottom: var(--space-2xl);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.instruction-method {
 | 
				
			||||||
 | 
					  margin-bottom: var(--space-2xl);
 | 
				
			||||||
 | 
					  padding-bottom: var(--space-2xl);
 | 
				
			||||||
 | 
					  border-bottom: 1px solid var(--color-border);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.instruction-method:last-child {
 | 
				
			||||||
 | 
					  border-bottom: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.instruction-step {
 | 
				
			||||||
 | 
					  margin-bottom: var(--space-xl);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.instruction-step h3 {
 | 
				
			||||||
 | 
					  color: var(--color-primary);
 | 
				
			||||||
 | 
					  margin-bottom: var(--space-md);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.method-note {
 | 
				
			||||||
 | 
					  background-color: rgba(59, 130, 246, 0.1);
 | 
				
			||||||
 | 
					  border: 1px solid rgba(59, 130, 246, 0.2);
 | 
				
			||||||
 | 
					  border-radius: var(--radius-md);
 | 
				
			||||||
 | 
					  padding: var(--space-md);
 | 
				
			||||||
 | 
					  margin-top: var(--space-lg);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.help-section {
 | 
				
			||||||
 | 
					  background-color: var(--color-surface);
 | 
				
			||||||
 | 
					  border: 1px solid var(--color-border);
 | 
				
			||||||
 | 
					  border-radius: var(--radius-lg);
 | 
				
			||||||
 | 
					  padding: var(--space-xl);
 | 
				
			||||||
 | 
					  margin-top: var(--space-2xl);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Utilities */
 | 
				
			||||||
 | 
					hr {
 | 
				
			||||||
 | 
					  border: none;
 | 
				
			||||||
 | 
					  height: 1px;
 | 
				
			||||||
 | 
					  background-color: var(--color-border);
 | 
				
			||||||
 | 
					  margin: var(--space-lg) 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.htmx-indicator{opacity:0}
 | 
				
			||||||
 | 
					.htmx-request .htmx-indicator{opacity:1; transition: opacity 200ms ease-in;}
 | 
				
			||||||
 | 
					.htmx-request.htmx-indicator{opacity:1; transition: opacity 200ms ease-in;}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Responsive Design */
 | 
				
			||||||
 | 
					@media (max-width: 768px) {
 | 
				
			||||||
 | 
					  :root {
 | 
				
			||||||
 | 
					    --space-lg: 1rem;
 | 
				
			||||||
 | 
					    --space-xl: 1.5rem;
 | 
				
			||||||
 | 
					    --space-2xl: 2rem;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  body > header {
 | 
				
			||||||
 | 
					    padding: 0 var(--space-md);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  nav {
 | 
				
			||||||
 | 
					    padding: var(--space-md);
 | 
				
			||||||
 | 
					    flex-direction: column;
 | 
				
			||||||
 | 
					    align-items: stretch;
 | 
				
			||||||
 | 
					    gap: var(--space-sm);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .nav-welcome {
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					    padding-bottom: var(--space-sm);
 | 
				
			||||||
 | 
					    border-bottom: 1px solid var(--color-border);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .nav-links {
 | 
				
			||||||
 | 
					    justify-content: center;
 | 
				
			||||||
 | 
					    gap: var(--space-sm);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .nav-links a {
 | 
				
			||||||
 | 
					    padding: var(--space-sm);
 | 
				
			||||||
 | 
					    font-size: var(--text-sm);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  main {
 | 
				
			||||||
 | 
					    padding: var(--space-lg) var(--space-md);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  #dashboard {
 | 
				
			||||||
 | 
					    grid-template-columns: 1fr;
 | 
				
			||||||
 | 
					    gap: var(--space-lg);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  #dashboard nav > div {
 | 
				
			||||||
 | 
					    padding: var(--space-md);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  form {
 | 
				
			||||||
 | 
					    padding: var(--space-lg);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  h1 { font-size: var(--text-2xl); }
 | 
				
			||||||
 | 
					  h2 { font-size: var(--text-xl); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  label:has(input[type="radio"]) {
 | 
				
			||||||
 | 
					    display: block;
 | 
				
			||||||
 | 
					    margin-bottom: var(--space-sm);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .hero {
 | 
				
			||||||
 | 
					    padding: var(--space-2xl) 0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .hero-header h1 {
 | 
				
			||||||
 | 
					    font-size: var(--text-3xl);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .hero-subtitle {
 | 
				
			||||||
 | 
					    font-size: var(--text-lg);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .section-header:has(.btn) {
 | 
				
			||||||
 | 
					    flex-direction: column;
 | 
				
			||||||
 | 
					    align-items: stretch;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .section-header .btn {
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .section-description {
 | 
				
			||||||
 | 
					    font-size: var(--text-sm);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .website-actions {
 | 
				
			||||||
 | 
					    flex-direction: column;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .website-actions .btn {
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .comment-header {
 | 
				
			||||||
 | 
					    flex-direction: column;
 | 
				
			||||||
 | 
					    align-items: stretch;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .comment-actions {
 | 
				
			||||||
 | 
					    margin-top: var(--space-sm);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  pre {
 | 
				
			||||||
 | 
					    font-size: var(--text-xs);
 | 
				
			||||||
 | 
					    padding: var(--space-md);
 | 
				
			||||||
 | 
					    white-space: pre-wrap;
 | 
				
			||||||
 | 
					    word-break: break-all;
 | 
				
			||||||
 | 
					    overflow-x: hidden;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  code {
 | 
				
			||||||
 | 
					    font-size: var(--text-xs);
 | 
				
			||||||
 | 
					    word-break: break-all;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@media (max-width: 480px) {
 | 
				
			||||||
 | 
					  body > header h1 {
 | 
				
			||||||
 | 
					    font-size: var(--text-xl);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .nav-links {
 | 
				
			||||||
 | 
					    flex-direction: column;
 | 
				
			||||||
 | 
					    gap: var(--space-xs);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .nav-links a {
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  button,
 | 
				
			||||||
 | 
					  input[type="submit"] {
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    margin-bottom: var(--space-sm);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Focus styles for better accessibility */
 | 
				
			||||||
 | 
					button:focus-visible,
 | 
				
			||||||
 | 
					input:focus-visible,
 | 
				
			||||||
 | 
					textarea:focus-visible,
 | 
				
			||||||
 | 
					select:focus-visible {
 | 
				
			||||||
 | 
					  outline: 2px solid var(--color-primary);
 | 
				
			||||||
 | 
					  outline-offset: 2px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Print styles */
 | 
				
			||||||
 | 
					@media print {
 | 
				
			||||||
 | 
					  body > header,
 | 
				
			||||||
 | 
					  nav,
 | 
				
			||||||
 | 
					  footer,
 | 
				
			||||||
 | 
					  button,
 | 
				
			||||||
 | 
					  input[type="submit"] {
 | 
				
			||||||
 | 
					    display: none;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  main {
 | 
				
			||||||
 | 
					    max-width: none;
 | 
				
			||||||
 | 
					    padding: 0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  #dashboard {
 | 
				
			||||||
 | 
					    grid-template-columns: 1fr;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										1
									
								
								ui/static/fontawesome
									
									
									
									
									
										Submodule
									
								
							
							
								
								
								
								
								
								
									
									
								
							
						
						
									
										1
									
								
								ui/static/fontawesome
									
									
									
									
									
										Submodule
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					Subproject commit 5a85d8a93237e08d9d1f861aa5630f292424cfc0
 | 
				
			||||||
@ -41,31 +41,37 @@ templ commonHeader() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
templ topNav(data CommonData) {
 | 
					templ topNav(data CommonData) {
 | 
				
			||||||
	{{ hxHeaders := fmt.Sprintf("{\"X-CSRF-Token\": \"%s\"}", data.CSRFToken) }}
 | 
						{{ hxHeaders := fmt.Sprintf("{\"X-CSRF-Token\": \"%s\"}", data.CSRFToken) }}
 | 
				
			||||||
	<nav>
 | 
						<nav aria-label="Site navigation">
 | 
				
			||||||
		<div>
 | 
							<div class="nav-welcome">
 | 
				
			||||||
			if data.IsAuthenticated {
 | 
								<span>
 | 
				
			||||||
				Welcome, { data.CurrentUser.Username }
 | 
									if data.IsAuthenticated {
 | 
				
			||||||
			}
 | 
										Welcome, { data.CurrentUser.Username }
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								</span>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
		<div>
 | 
							<ul class="nav-links">
 | 
				
			||||||
			if data.IsAuthenticated {
 | 
								if data.IsAuthenticated {
 | 
				
			||||||
				<a href="/guestbooks">All Guestbooks</a> |
 | 
									<li><a href="/guestbooks">All Guestbooks</a></li>
 | 
				
			||||||
				<a href="/websites">My Websites</a> | 
 | 
									<li><a href="/websites">My Websites</a></li>
 | 
				
			||||||
				<a href="/users/settings">Settings</a> | 
 | 
									<li><a href="/users/settings">Settings</a></li>
 | 
				
			||||||
				<a href="#" hx-post="/users/logout" hx-headers={ hxHeaders }>Logout</a>
 | 
									<li><a href="#" hx-post="/users/logout" hx-headers={ hxHeaders }>Logout</a></li>
 | 
				
			||||||
			} else {
 | 
								} else {
 | 
				
			||||||
				if data.LocalAuthEnabled {
 | 
									if data.LocalAuthEnabled {
 | 
				
			||||||
					<a href="/users/register">Create an Account</a> | 
 | 
										<li><a href="/users/register">Create an Account</a></li> | 
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				<a href="/users/login">Login</a>
 | 
									<li><a href="/users/login">Login</a></li>
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		</div>
 | 
							</ul>
 | 
				
			||||||
	</nav>
 | 
						</nav>
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
templ commonFooter() {
 | 
					templ commonFooter() {
 | 
				
			||||||
	<footer>
 | 
						<footer>
 | 
				
			||||||
		<p>A <a href="https://32bit.cafe">32bit.cafe</a> Project</p>
 | 
							<p>A <a href="https://32bit.cafe" rel="noopener">32bit.cafe</a> Project</p>
 | 
				
			||||||
 | 
							<ul class="footer-links">
 | 
				
			||||||
 | 
								<li><a href="/about">About</a></li>
 | 
				
			||||||
 | 
								<li><a href="/help">Help</a></li>
 | 
				
			||||||
 | 
							</ul>
 | 
				
			||||||
	</footer>
 | 
						</footer>
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -76,18 +82,19 @@ templ base(title string, data CommonData) {
 | 
				
			|||||||
			<title>{ title } - webweav.ing</title>
 | 
								<title>{ title } - webweav.ing</title>
 | 
				
			||||||
			<meta charset="UTF-8"/>
 | 
								<meta charset="UTF-8"/>
 | 
				
			||||||
			<meta name="viewport" content="width=device-width, initial-scale=1"/>
 | 
								<meta name="viewport" content="width=device-width, initial-scale=1"/>
 | 
				
			||||||
			<link href="/static/css/classless.min.css" rel="stylesheet"/>
 | 
								<meta name="htmx-config" content={ `{"includeIndicatorStyles":false}` }/>
 | 
				
			||||||
			<link href="/static/css/style.css" rel="stylesheet"/>
 | 
								<link href="/static/css/style.css" rel="stylesheet"/>
 | 
				
			||||||
 | 
								<link href="/static/fontawesome/css/fontawesome.css" rel="stylesheet"/>
 | 
				
			||||||
 | 
								<link href="/static/fontawesome/css/solid.css" rel="stylesheet"/>
 | 
				
			||||||
			<script src="/static/js/htmx.min.js"></script>
 | 
								<script src="/static/js/htmx.min.js"></script>
 | 
				
			||||||
		</head>
 | 
							</head>
 | 
				
			||||||
		<body>
 | 
							<body>
 | 
				
			||||||
			@commonHeader()
 | 
								@commonHeader()
 | 
				
			||||||
			@topNav(data)
 | 
								@topNav(data)
 | 
				
			||||||
			<main>
 | 
								<main role="main">
 | 
				
			||||||
				if data.Flash != "" {
 | 
									if data.Flash != "" {
 | 
				
			||||||
					<div class="flash">{ data.Flash }</div>
 | 
										<div class="notice flash">{ data.Flash }</div>
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				<h1>{ title }</h1>
 | 
					 | 
				
			||||||
				{ children... }
 | 
									{ children... }
 | 
				
			||||||
			</main>
 | 
								</main>
 | 
				
			||||||
			@commonFooter()
 | 
								@commonFooter()
 | 
				
			||||||
 | 
				
			|||||||
@ -92,7 +92,7 @@ func topNav(data CommonData) templ.Component {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
		ctx = templ.ClearChildren(ctx)
 | 
							ctx = templ.ClearChildren(ctx)
 | 
				
			||||||
		hxHeaders := fmt.Sprintf("{\"X-CSRF-Token\": \"%s\"}", data.CSRFToken)
 | 
							hxHeaders := fmt.Sprintf("{\"X-CSRF-Token\": \"%s\"}", data.CSRFToken)
 | 
				
			||||||
		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<nav><div>")
 | 
							templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<nav aria-label=\"Site navigation\"><div class=\"nav-welcome\"><span>")
 | 
				
			||||||
		if templ_7745c5c3_Err != nil {
 | 
							if templ_7745c5c3_Err != nil {
 | 
				
			||||||
			return templ_7745c5c3_Err
 | 
								return templ_7745c5c3_Err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@ -104,48 +104,48 @@ func topNav(data CommonData) templ.Component {
 | 
				
			|||||||
			var templ_7745c5c3_Var3 string
 | 
								var templ_7745c5c3_Var3 string
 | 
				
			||||||
			templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(data.CurrentUser.Username)
 | 
								templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(data.CurrentUser.Username)
 | 
				
			||||||
			if templ_7745c5c3_Err != nil {
 | 
								if templ_7745c5c3_Err != nil {
 | 
				
			||||||
				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 47, Col: 40}
 | 
									return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 48, Col: 41}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			_, 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 {
 | 
				
			||||||
				return templ_7745c5c3_Err
 | 
									return templ_7745c5c3_Err
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</div><div>")
 | 
							templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</span></div><ul class=\"nav-links\">")
 | 
				
			||||||
		if templ_7745c5c3_Err != nil {
 | 
							if templ_7745c5c3_Err != nil {
 | 
				
			||||||
			return templ_7745c5c3_Err
 | 
								return templ_7745c5c3_Err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if data.IsAuthenticated {
 | 
							if data.IsAuthenticated {
 | 
				
			||||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<a href=\"/guestbooks\">All Guestbooks</a> | <a href=\"/websites\">My Websites</a> |  <a href=\"/users/settings\">Settings</a> |  <a href=\"#\" hx-post=\"/users/logout\" hx-headers=\"")
 | 
								templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<li><a href=\"/guestbooks\">All Guestbooks</a></li><li><a href=\"/websites\">My Websites</a></li><li><a href=\"/users/settings\">Settings</a></li><li><a href=\"#\" hx-post=\"/users/logout\" hx-headers=\"")
 | 
				
			||||||
			if templ_7745c5c3_Err != nil {
 | 
								if templ_7745c5c3_Err != nil {
 | 
				
			||||||
				return templ_7745c5c3_Err
 | 
									return templ_7745c5c3_Err
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			var templ_7745c5c3_Var4 string
 | 
								var templ_7745c5c3_Var4 string
 | 
				
			||||||
			templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(hxHeaders)
 | 
								templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(hxHeaders)
 | 
				
			||||||
			if templ_7745c5c3_Err != nil {
 | 
								if templ_7745c5c3_Err != nil {
 | 
				
			||||||
				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 55, Col: 62}
 | 
									return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 57, Col: 66}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
 | 
								_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
 | 
				
			||||||
			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, 6, "\">Logout</a>")
 | 
								templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\">Logout</a></li>")
 | 
				
			||||||
			if templ_7745c5c3_Err != nil {
 | 
								if templ_7745c5c3_Err != nil {
 | 
				
			||||||
				return templ_7745c5c3_Err
 | 
									return templ_7745c5c3_Err
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			if data.LocalAuthEnabled {
 | 
								if data.LocalAuthEnabled {
 | 
				
			||||||
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<a href=\"/users/register\">Create an Account</a> | ")
 | 
									templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<li><a href=\"/users/register\">Create an Account</a></li>| ")
 | 
				
			||||||
				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, 8, " <a href=\"/users/login\">Login</a>")
 | 
								templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, " <li><a href=\"/users/login\">Login</a></li>")
 | 
				
			||||||
			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, 9, "</div></nav>")
 | 
							templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</ul></nav>")
 | 
				
			||||||
		if templ_7745c5c3_Err != nil {
 | 
							if templ_7745c5c3_Err != nil {
 | 
				
			||||||
			return templ_7745c5c3_Err
 | 
								return templ_7745c5c3_Err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@ -174,7 +174,7 @@ func commonFooter() templ.Component {
 | 
				
			|||||||
			templ_7745c5c3_Var5 = templ.NopComponent
 | 
								templ_7745c5c3_Var5 = templ.NopComponent
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		ctx = templ.ClearChildren(ctx)
 | 
							ctx = templ.ClearChildren(ctx)
 | 
				
			||||||
		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<footer><p>A <a href=\"https://32bit.cafe\">32bit.cafe</a> Project</p></footer>")
 | 
							templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<footer><p>A <a href=\"https://32bit.cafe\" rel=\"noopener\">32bit.cafe</a> Project</p><ul class=\"footer-links\"><li><a href=\"/about\">About</a></li><li><a href=\"/help\">Help</a></li></ul></footer>")
 | 
				
			||||||
		if templ_7745c5c3_Err != nil {
 | 
							if templ_7745c5c3_Err != nil {
 | 
				
			||||||
			return templ_7745c5c3_Err
 | 
								return templ_7745c5c3_Err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@ -210,13 +210,26 @@ func base(title string, data CommonData) templ.Component {
 | 
				
			|||||||
		var templ_7745c5c3_Var7 string
 | 
							var templ_7745c5c3_Var7 string
 | 
				
			||||||
		templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(title)
 | 
							templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(title)
 | 
				
			||||||
		if templ_7745c5c3_Err != nil {
 | 
							if templ_7745c5c3_Err != nil {
 | 
				
			||||||
			return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 76, Col: 17}
 | 
								return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 82, Col: 17}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
 | 
							_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
 | 
				
			||||||
		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, 12, " - webweav.ing</title><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><link href=\"/static/css/classless.min.css\" rel=\"stylesheet\"><link href=\"/static/css/style.css\" rel=\"stylesheet\"><script src=\"/static/js/htmx.min.js\"></script></head><body>")
 | 
							templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, " - webweav.ing</title><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><meta name=\"htmx-config\" content=\"")
 | 
				
			||||||
 | 
							if templ_7745c5c3_Err != nil {
 | 
				
			||||||
 | 
								return templ_7745c5c3_Err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							var templ_7745c5c3_Var8 string
 | 
				
			||||||
 | 
							templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(`{"includeIndicatorStyles":false}`)
 | 
				
			||||||
 | 
							if templ_7745c5c3_Err != nil {
 | 
				
			||||||
 | 
								return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 85, Col: 72}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
 | 
				
			||||||
 | 
							if templ_7745c5c3_Err != nil {
 | 
				
			||||||
 | 
								return templ_7745c5c3_Err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "\"><link href=\"/static/css/style.css\" rel=\"stylesheet\"><link href=\"/static/fontawesome/css/fontawesome.css\" rel=\"stylesheet\"><link href=\"/static/fontawesome/css/solid.css\" rel=\"stylesheet\"><script src=\"/static/js/htmx.min.js\"></script></head><body>")
 | 
				
			||||||
		if templ_7745c5c3_Err != nil {
 | 
							if templ_7745c5c3_Err != nil {
 | 
				
			||||||
			return templ_7745c5c3_Err
 | 
								return templ_7745c5c3_Err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@ -228,51 +241,34 @@ func base(title string, data CommonData) templ.Component {
 | 
				
			|||||||
		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, 13, "<main>")
 | 
							templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<main role=\"main\">")
 | 
				
			||||||
		if templ_7745c5c3_Err != nil {
 | 
							if templ_7745c5c3_Err != nil {
 | 
				
			||||||
			return templ_7745c5c3_Err
 | 
								return templ_7745c5c3_Err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if data.Flash != "" {
 | 
							if data.Flash != "" {
 | 
				
			||||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<div class=\"flash\">")
 | 
								templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<div class=\"notice flash\">")
 | 
				
			||||||
			if templ_7745c5c3_Err != nil {
 | 
								if templ_7745c5c3_Err != nil {
 | 
				
			||||||
				return templ_7745c5c3_Err
 | 
									return templ_7745c5c3_Err
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			var templ_7745c5c3_Var8 string
 | 
								var templ_7745c5c3_Var9 string
 | 
				
			||||||
			templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(data.Flash)
 | 
								templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(data.Flash)
 | 
				
			||||||
			if templ_7745c5c3_Err != nil {
 | 
								if templ_7745c5c3_Err != nil {
 | 
				
			||||||
				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 88, Col: 36}
 | 
									return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 96, Col: 43}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
 | 
								_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
 | 
				
			||||||
			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, 15, "</div>")
 | 
								templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</div>")
 | 
				
			||||||
			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, 16, "<h1>")
 | 
					 | 
				
			||||||
		if templ_7745c5c3_Err != nil {
 | 
					 | 
				
			||||||
			return templ_7745c5c3_Err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		var templ_7745c5c3_Var9 string
 | 
					 | 
				
			||||||
		templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(title)
 | 
					 | 
				
			||||||
		if templ_7745c5c3_Err != nil {
 | 
					 | 
				
			||||||
			return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/common.templ`, Line: 90, Col: 15}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
 | 
					 | 
				
			||||||
		if templ_7745c5c3_Err != nil {
 | 
					 | 
				
			||||||
			return templ_7745c5c3_Err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</h1>")
 | 
					 | 
				
			||||||
		if templ_7745c5c3_Err != nil {
 | 
					 | 
				
			||||||
			return templ_7745c5c3_Err
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		templ_7745c5c3_Err = templ_7745c5c3_Var6.Render(ctx, templ_7745c5c3_Buffer)
 | 
							templ_7745c5c3_Err = templ_7745c5c3_Var6.Render(ctx, templ_7745c5c3_Buffer)
 | 
				
			||||||
		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, 18, "</main>")
 | 
							templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</main>")
 | 
				
			||||||
		if templ_7745c5c3_Err != nil {
 | 
							if templ_7745c5c3_Err != nil {
 | 
				
			||||||
			return templ_7745c5c3_Err
 | 
								return templ_7745c5c3_Err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@ -280,7 +276,7 @@ func base(title string, data CommonData) templ.Component {
 | 
				
			|||||||
		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, 19, "</body></html>")
 | 
							templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</body></html>")
 | 
				
			||||||
		if templ_7745c5c3_Err != nil {
 | 
							if templ_7745c5c3_Err != nil {
 | 
				
			||||||
			return templ_7745c5c3_Err
 | 
								return templ_7745c5c3_Err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
				
			|||||||
@ -10,91 +10,140 @@ templ GuestbookDashboardCommentsView(title string, data CommonData, website mode
 | 
				
			|||||||
		<div id="dashboard">
 | 
							<div id="dashboard">
 | 
				
			||||||
			@wSidebar(website)
 | 
								@wSidebar(website)
 | 
				
			||||||
			<div>
 | 
								<div>
 | 
				
			||||||
				<h1>Comments on { website.Name }</h1>
 | 
									<section aria-labelledby="comments-management-heading">
 | 
				
			||||||
				<hr/>
 | 
										<header class="section-header">
 | 
				
			||||||
				if len(comments) == 0 {
 | 
											<h1 id="comments-management-heading">Comments on { website.Name }</h1>
 | 
				
			||||||
					<p>No comments yet!</p>
 | 
											<p class="section-description">Manage, moderate, and organize comments on your guestbook</p>
 | 
				
			||||||
				}
 | 
										</header>
 | 
				
			||||||
				for  _, c := range comments {
 | 
										<hr role="separator"/>
 | 
				
			||||||
					@GuestbookDashboardCommentView(data, website, c)
 | 
										if len(comments) == 0 {
 | 
				
			||||||
				}
 | 
											<p>No comments yet!</p>
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										for  i, c := range comments {
 | 
				
			||||||
 | 
											@GuestbookDashboardCommentView(data, website, c, i)
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									</section>
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
templ GuestbookDashboardCommentView(data CommonData, w models.Website, c models.GuestbookComment) {
 | 
					templ GuestbookDashboardCommentDeletePart(text string) {
 | 
				
			||||||
	{{ commentUrl := fmt.Sprintf("%s/dashboard/guestbook/comments/%s", wUrl(w), shortIdToSlug(c.ShortId)) }}
 | 
						<div class="comment-update-msg">
 | 
				
			||||||
	{{ hxHeaders := fmt.Sprintf("{\"X-CSRF-Token\": \"%s\"}", data.CSRFToken) }}
 | 
							<p>{ text }</p>
 | 
				
			||||||
	<div class="comment">
 | 
					 | 
				
			||||||
		<div>
 | 
					 | 
				
			||||||
			if c.Deleted.IsZero() {
 | 
					 | 
				
			||||||
				<button class="danger" hx-delete={ commentUrl } hx-target="closest div.comment" hx-headers={ hxHeaders }>Delete</button>
 | 
					 | 
				
			||||||
				<button class="outline" hx-put={ commentUrl } hx-target="closest div.comment" hx-headers={ hxHeaders }>
 | 
					 | 
				
			||||||
					if !c.IsPublished {
 | 
					 | 
				
			||||||
						Publish
 | 
					 | 
				
			||||||
					} else {
 | 
					 | 
				
			||||||
						Hide
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
				</button>
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		</div>
 | 
					 | 
				
			||||||
		<div>
 | 
					 | 
				
			||||||
			<strong>{ c.AuthorName }</strong>
 | 
					 | 
				
			||||||
			if len(c.AuthorEmail) > 0 {
 | 
					 | 
				
			||||||
				{{ email := "mailto:" + c.AuthorEmail }}
 | 
					 | 
				
			||||||
				| <a href={ templ.URL(email) } target="_blank">{ c.AuthorEmail }</a>
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			if len(c.AuthorSite) > 0 {
 | 
					 | 
				
			||||||
				| <a href={ templ.URL(externalUrl(c.AuthorSite)) } target="_blank">{ c.AuthorSite }</a>
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
			<p>
 | 
					 | 
				
			||||||
				{ c.Created.In(data.CurrentUser.Settings.LocalTimezone).Format("01-02-2006 03:04PM") }
 | 
					 | 
				
			||||||
			</p>
 | 
					 | 
				
			||||||
		</div>
 | 
					 | 
				
			||||||
		<p>
 | 
					 | 
				
			||||||
			{ c.CommentText }
 | 
					 | 
				
			||||||
		</p>
 | 
					 | 
				
			||||||
		<hr/>
 | 
					 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					templ GuestbookDashboardUpdateButtonPart(data CommonData, w models.Website, c models.GuestbookComment) {
 | 
				
			||||||
 | 
						{{ hxHeaders := fmt.Sprintf("{\"X-CSRF-Token\": \"%s\"}", data.CSRFToken) }}
 | 
				
			||||||
 | 
						{{ commentUrl := fmt.Sprintf("%s/dashboard/guestbook/comments/%s", wUrl(w), shortIdToSlug(c.ShortId)) }}
 | 
				
			||||||
 | 
						<button
 | 
				
			||||||
 | 
							type="button"
 | 
				
			||||||
 | 
							class="outline"
 | 
				
			||||||
 | 
							hx-put={ commentUrl }
 | 
				
			||||||
 | 
							hx-headers={ hxHeaders }
 | 
				
			||||||
 | 
							hx-swap="outerHTML"
 | 
				
			||||||
 | 
						>
 | 
				
			||||||
 | 
							if !c.IsPublished {
 | 
				
			||||||
 | 
								Publish
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								Hide
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						</button>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					templ GuestbookDashboardCommentView(data CommonData, w models.Website, c models.GuestbookComment, i int) {
 | 
				
			||||||
 | 
						{{ commentUrl := fmt.Sprintf("%s/dashboard/guestbook/comments/%s", wUrl(w), shortIdToSlug(c.ShortId)) }}
 | 
				
			||||||
 | 
						{{ hxHeaders := fmt.Sprintf("{\"X-CSRF-Token\": \"%s\"}", data.CSRFToken) }}
 | 
				
			||||||
 | 
						{{ authorClass := fmt.Sprintf("comment-author-%d", i) }}
 | 
				
			||||||
 | 
						<article class="comment" role="article" aria-labelledby={ authorClass }>
 | 
				
			||||||
 | 
							<div class="comment-update-msg"></div>
 | 
				
			||||||
 | 
							<header class="comment-header">
 | 
				
			||||||
 | 
								<div class="comment-meta">
 | 
				
			||||||
 | 
									<h3 id={ authorClass } class="comment-author">{ c.AuthorName }</h3>
 | 
				
			||||||
 | 
									<time datetime={ c.Created.In(data.CurrentUser.Settings.LocalTimezone).Format("01-02-2006 03:04PM") }>{ c.Created.In(data.CurrentUser.Settings.LocalTimezone).Format("01-02-2006 03:04PM") }</time>
 | 
				
			||||||
 | 
									if len(c.AuthorEmail) > 0 {
 | 
				
			||||||
 | 
										<div class="comment-contact">
 | 
				
			||||||
 | 
											{{ email := "mailto:" + c.AuthorEmail }}
 | 
				
			||||||
 | 
											<i class="fa-solid fa-envelope"></i>
 | 
				
			||||||
 | 
											<a href={ templ.URL(email) } target="_blank">{ c.AuthorEmail }</a>
 | 
				
			||||||
 | 
										</div>
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if len(c.AuthorSite) > 0 {
 | 
				
			||||||
 | 
										<div class="comment-contact">
 | 
				
			||||||
 | 
											<i class="fa-solid fa-house"></i>
 | 
				
			||||||
 | 
											<a href={ templ.URL(externalUrl(c.AuthorSite)) } target="_blank">{ c.AuthorSite }</a>
 | 
				
			||||||
 | 
										</div>
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								</div>
 | 
				
			||||||
 | 
								<div class="comment-actions" role="group" aria-label={ fmt.Sprintf("Actions for comment by %s", c.AuthorName) }>
 | 
				
			||||||
 | 
									if c.Deleted.IsZero() {
 | 
				
			||||||
 | 
										<button
 | 
				
			||||||
 | 
											type="button"
 | 
				
			||||||
 | 
											class="danger"
 | 
				
			||||||
 | 
											hx-delete={ commentUrl }
 | 
				
			||||||
 | 
											hx-target="closest article.comment"
 | 
				
			||||||
 | 
											hx-confirm="Are you sure you want to delete this comment? This action cannot be undone."
 | 
				
			||||||
 | 
											hx-headers={ hxHeaders }
 | 
				
			||||||
 | 
										>
 | 
				
			||||||
 | 
											Delete
 | 
				
			||||||
 | 
										</button>
 | 
				
			||||||
 | 
										@GuestbookDashboardUpdateButtonPart(data, w, c)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								</div>
 | 
				
			||||||
 | 
							</header>
 | 
				
			||||||
 | 
							<div class="comment-content">
 | 
				
			||||||
 | 
								<p>
 | 
				
			||||||
 | 
									{ c.CommentText }
 | 
				
			||||||
 | 
								</p>
 | 
				
			||||||
 | 
							</div>
 | 
				
			||||||
 | 
							<hr role="separator"/>
 | 
				
			||||||
 | 
						</article>
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
templ commentForm(form forms.CommentCreateForm) {
 | 
					templ commentForm(form forms.CommentCreateForm) {
 | 
				
			||||||
	<div>
 | 
						<fieldset>
 | 
				
			||||||
		<label for="authorname">Name</label>
 | 
							<legend>Leave a comment</legend>
 | 
				
			||||||
		{{ error, exists := form.FieldErrors["authorName"] }}
 | 
							<div class="form-group">
 | 
				
			||||||
		if exists {
 | 
								<label for="authorname">Name <span aria-label="required">*</span></label>
 | 
				
			||||||
			<label class="error">{ error }</label>
 | 
								{{ error, exists := form.FieldErrors["authorName"] }}
 | 
				
			||||||
		}
 | 
								if exists {
 | 
				
			||||||
		<input type="text" name="authorname" id="authorname"/>
 | 
									<label class="error">{ error }</label>
 | 
				
			||||||
	</div>
 | 
								}
 | 
				
			||||||
	<div>
 | 
								<input type="text" name="authorname" id="authorname" required aria-describedby="authorname-help"/>
 | 
				
			||||||
		<label for="authoremail">Email (Optional) </label>
 | 
								<small id="authorname-help">Your name or handle</small>
 | 
				
			||||||
		{{ error, exists = form.FieldErrors["authorEmail"] }}
 | 
							</div>
 | 
				
			||||||
		if exists {
 | 
							<div class="form-group">
 | 
				
			||||||
			<label class="error">{ error }</label>
 | 
								<label for="authoremail">Email (Optional)</label>
 | 
				
			||||||
		}
 | 
								{{ error, exists = form.FieldErrors["authorEmail"] }}
 | 
				
			||||||
		<input type="text" name="authoremail" id="authoremail"/>
 | 
								if exists {
 | 
				
			||||||
	</div>
 | 
									<label class="error">{ error }</label>
 | 
				
			||||||
	<div>
 | 
								}
 | 
				
			||||||
		<label for="authorsite">Site Url (Optional) </label>
 | 
								<input type="email" name="authoremail" id="authoremail" aria-describedby="authoremail-help"/>
 | 
				
			||||||
		{{ error, exists = form.FieldErrors["authorSite"] }}
 | 
								<small id="authoremail-help">Your email address will only be shared with the guestbook's owner</small>
 | 
				
			||||||
		if exists {
 | 
							</div>
 | 
				
			||||||
			<label class="error">{ error }</label>
 | 
							<div class="form-group">
 | 
				
			||||||
		}
 | 
								<label for="authorsite">Website URL (Optional)</label>
 | 
				
			||||||
		<input type="text" name="authorsite" id="authorsite"/>
 | 
								{{ error, exists = form.FieldErrors["authorSite"] }}
 | 
				
			||||||
	</div>
 | 
								if exists {
 | 
				
			||||||
	<div>
 | 
									<label class="error">{ error }</label>
 | 
				
			||||||
		<label for="content">Comment</label>
 | 
								}
 | 
				
			||||||
		{{ error, exists = form.FieldErrors["content"] }}
 | 
								<input type="url" name="authorsite" id="authorsite" aria-describedby="authorsite-help"/>
 | 
				
			||||||
		if exists {
 | 
								<small id="authorsite-help">Link to your website or social profile</small>
 | 
				
			||||||
			<label class="error">{ error }</label>
 | 
							</div>
 | 
				
			||||||
		}
 | 
							<div class="form-group">
 | 
				
			||||||
		<textarea name="content" id="content"></textarea>
 | 
								<label for="content">Comment <span aria-label="required">*</span></label>
 | 
				
			||||||
	</div>
 | 
								{{ error, exists = form.FieldErrors["content"] }}
 | 
				
			||||||
	<div>
 | 
								if exists {
 | 
				
			||||||
		<input type="submit" value="Submit"/>
 | 
									<label class="error">{ error }</label>
 | 
				
			||||||
	</div>
 | 
								}
 | 
				
			||||||
 | 
								<textarea name="content" id="content" required aria-describedby="content-help"></textarea>
 | 
				
			||||||
 | 
								<small id="content-help">Share your thoughts, feedback, or just say hello!</small>
 | 
				
			||||||
 | 
							</div>
 | 
				
			||||||
 | 
							<div class="form-group">
 | 
				
			||||||
 | 
								<button type="submit">Submit Comment</button>
 | 
				
			||||||
 | 
							</div>
 | 
				
			||||||
 | 
						</fieldset>
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
templ GuestbookView(title string, data CommonData, website models.Website, guestbook models.Guestbook, comments []models.GuestbookComment, form forms.CommentCreateForm) {
 | 
					templ GuestbookView(title string, data CommonData, website models.Website, guestbook models.Guestbook, comments []models.GuestbookComment, form forms.CommentCreateForm) {
 | 
				
			||||||
@ -105,39 +154,46 @@ templ GuestbookView(title string, data CommonData, website models.Website, guest
 | 
				
			|||||||
		<html>
 | 
							<html>
 | 
				
			||||||
			<head>
 | 
								<head>
 | 
				
			||||||
				<title>{ title }</title>
 | 
									<title>{ title }</title>
 | 
				
			||||||
				<link href="/static/css/classless.min.css" rel="stylesheet"/>
 | 
									<meta name="viewport" content="width=device-width, initial-scale=1"/>
 | 
				
			||||||
 | 
									<link href="/static/css/style.css" rel="stylesheet"/>
 | 
				
			||||||
				<script src="/static/js/main.js" defer></script>
 | 
									<script src="/static/js/main.js" defer></script>
 | 
				
			||||||
			</head>
 | 
								</head>
 | 
				
			||||||
			<body>
 | 
								<body>
 | 
				
			||||||
				<main>
 | 
									<main role="main">
 | 
				
			||||||
					<div>
 | 
										<section aria-labelledby="guestbook-heading">
 | 
				
			||||||
						<h1>{ website.Name } Guestbook</h1>
 | 
											<h1>{ website.Name } Guestbook</h1>
 | 
				
			||||||
						{ data.Flash }
 | 
											{ data.Flash }
 | 
				
			||||||
						<form action={ templ.URL(postUrl) } method="post">
 | 
											<form action={ templ.URL(postUrl) } method="post">
 | 
				
			||||||
							<input type="hidden" name="csrf_token" value={ data.CSRFToken }/>
 | 
												<input type="hidden" name="csrf_token" value={ data.CSRFToken }/>
 | 
				
			||||||
							@commentForm(form)
 | 
												@commentForm(form)
 | 
				
			||||||
						</form>
 | 
											</form>
 | 
				
			||||||
					</div>
 | 
										</section>
 | 
				
			||||||
					<div id="comments">
 | 
										<section id="comments" aria-labelledby="comments-heading">
 | 
				
			||||||
 | 
											<h2 id="comments-heading">Comments</h2>
 | 
				
			||||||
						if len(comments) == 0 {
 | 
											if len(comments) == 0 {
 | 
				
			||||||
							<p>No comments yet!</p>
 | 
												<p>No comments yet!</p>
 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
						for _, c := range comments {
 | 
											for i, c := range comments {
 | 
				
			||||||
							<div>
 | 
												{{ commentAuthorRole := fmt.Sprintf("comment-author-%d", i+1) }}
 | 
				
			||||||
								<h3>
 | 
												<article class="comment" role="article" aria-labelledby={ commentAuthorRole }>
 | 
				
			||||||
									if c.AuthorSite != "" {
 | 
													<header class="comment-header">
 | 
				
			||||||
										<a href={ templ.URL(externalUrl(c.AuthorSite)) } target="_blank">{ c.AuthorName }</a>
 | 
														<h3 id={ commentAuthorRole } class="comment-author">
 | 
				
			||||||
									} else {
 | 
															if c.AuthorSite != "" {
 | 
				
			||||||
										{ c.AuthorName }
 | 
																<a href={ templ.URL(externalUrl(c.AuthorSite)) } target="_blank">{ c.AuthorName }</a>
 | 
				
			||||||
									}
 | 
															} else {
 | 
				
			||||||
								</h3>
 | 
																{ c.AuthorName }
 | 
				
			||||||
								<time datetime={ c.Created.Format(time.RFC3339) }>{ c.Created.Format("01-02-2006 03:04PM") }</time>
 | 
															}
 | 
				
			||||||
								<p>
 | 
														</h3>
 | 
				
			||||||
									{ c.CommentText }
 | 
														<time datetime={ c.Created.Format(time.RFC3339) }>{ c.Created.Format("01-02-2006 03:04PM") }</time>
 | 
				
			||||||
								</p>
 | 
													</header>
 | 
				
			||||||
							</div>
 | 
													<div class="comment-content">
 | 
				
			||||||
 | 
														<p>
 | 
				
			||||||
 | 
															{ c.CommentText }
 | 
				
			||||||
 | 
														</p>
 | 
				
			||||||
 | 
													</div>
 | 
				
			||||||
 | 
												</article>
 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
					</div>
 | 
										</section>
 | 
				
			||||||
				</main>
 | 
									</main>
 | 
				
			||||||
			</body>
 | 
								</body>
 | 
				
			||||||
		</html>
 | 
							</html>
 | 
				
			||||||
 | 
				
			|||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -2,13 +2,14 @@ package views
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
templ Home(title string, data CommonData) {
 | 
					templ Home(title string, data CommonData) {
 | 
				
			||||||
	@base(title, data) {
 | 
						@base(title, data) {
 | 
				
			||||||
		<h2>Welcome</h2>
 | 
							<section aria-labelledby="welcome-heading">
 | 
				
			||||||
		<p>
 | 
								<h2 id="welcome-heading">Welcome</h2>
 | 
				
			||||||
			Welcome to webweav.ing, a collection of webmastery tools created by the <a href="https://32bit.cafe">32-Bit Cafe</a>.
 | 
								<p>Welcome to webweav.ing, a collection of webmastery tools created by the <a href="https://32bit.cafe" rel="noopener">32-Bit Cafe</a>.</p>
 | 
				
			||||||
		</p>
 | 
								<aside class="notice" role="alert" aria-labelledby="service-status">
 | 
				
			||||||
		<p>
 | 
									<h3 id="service-status" class="sr-only">Service Status Notice</h3>
 | 
				
			||||||
			Note this service is in a pre-alpha state. Your account and data can disappear at any time. 
 | 
									<p><strong>Important:</strong> This service is in a pre-alpha state. Your account and data can disappear at any time.</p>
 | 
				
			||||||
		</p>
 | 
								</aside>
 | 
				
			||||||
 | 
							</section>
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -17,3 +18,11 @@ templ ComingSoon(title string, data CommonData) {
 | 
				
			|||||||
		<h2>Coming Soon</h2>
 | 
							<h2>Coming Soon</h2>
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					templ AboutPage(title string, data CommonData) {
 | 
				
			||||||
 | 
						@base(title, data) {
 | 
				
			||||||
 | 
							<section aria-labelledby="about-heading">
 | 
				
			||||||
 | 
								<h2 id="about-heading">About Webweav.ing</h2>
 | 
				
			||||||
 | 
							</section>
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -41,7 +41,7 @@ func Home(title string, data CommonData) templ.Component {
 | 
				
			|||||||
				}()
 | 
									}()
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			ctx = templ.InitializeContext(ctx)
 | 
								ctx = templ.InitializeContext(ctx)
 | 
				
			||||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<h2>Welcome</h2><p>Welcome to webweav.ing, a collection of webmastery tools created by the <a href=\"https://32bit.cafe\">32-Bit Cafe</a>.</p><p>Note this service is in a pre-alpha state. Your account and data can disappear at any time. </p>")
 | 
								templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<section aria-labelledby=\"welcome-heading\"><h2 id=\"welcome-heading\">Welcome</h2><p>Welcome to webweav.ing, a collection of webmastery tools created by the <a href=\"https://32bit.cafe\" rel=\"noopener\">32-Bit Cafe</a>.</p><aside class=\"notice\" role=\"alert\" aria-labelledby=\"service-status\"><h3 id=\"service-status\" class=\"sr-only\">Service Status Notice</h3><p><strong>Important:</strong> This service is in a pre-alpha state. Your account and data can disappear at any time.</p></aside></section>")
 | 
				
			||||||
			if templ_7745c5c3_Err != nil {
 | 
								if templ_7745c5c3_Err != nil {
 | 
				
			||||||
				return templ_7745c5c3_Err
 | 
									return templ_7745c5c3_Err
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
@ -102,4 +102,51 @@ func ComingSoon(title string, data CommonData) templ.Component {
 | 
				
			|||||||
	})
 | 
						})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func AboutPage(title string, data CommonData) 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_Var5 := templ.GetChildren(ctx)
 | 
				
			||||||
 | 
							if templ_7745c5c3_Var5 == nil {
 | 
				
			||||||
 | 
								templ_7745c5c3_Var5 = templ.NopComponent
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							ctx = templ.ClearChildren(ctx)
 | 
				
			||||||
 | 
							templ_7745c5c3_Var6 := 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, 3, "<section aria-labelledby=\"about-heading\"><h2 id=\"about-heading\">About Webweav.ing</h2></section>")
 | 
				
			||||||
 | 
								if templ_7745c5c3_Err != nil {
 | 
				
			||||||
 | 
									return templ_7745c5c3_Err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							templ_7745c5c3_Err = base(title, data).Render(templ.WithChildren(ctx, templ_7745c5c3_Var6), templ_7745c5c3_Buffer)
 | 
				
			||||||
 | 
							if templ_7745c5c3_Err != nil {
 | 
				
			||||||
 | 
								return templ_7745c5c3_Err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var _ = templruntime.GeneratedTemplate
 | 
					var _ = templruntime.GeneratedTemplate
 | 
				
			||||||
 | 
				
			|||||||
@ -86,20 +86,27 @@ templ UserSettingsView(data CommonData, timezones []string) {
 | 
				
			|||||||
	{{ user := data.CurrentUser }}
 | 
						{{ user := data.CurrentUser }}
 | 
				
			||||||
	@base("User Settings", data) {
 | 
						@base("User Settings", data) {
 | 
				
			||||||
		<div>
 | 
							<div>
 | 
				
			||||||
			<form hx-put="/users/settings">
 | 
								<section aria-labelledby="user-settings-heading">
 | 
				
			||||||
				<input type="hidden" name="csrf_token" value={ data.CSRFToken }/>
 | 
									<form hx-put="/users/settings">
 | 
				
			||||||
				<label>Local Timezone</label>
 | 
										<input type="hidden" name="csrf_token" value={ data.CSRFToken }/>
 | 
				
			||||||
				<select name="timezones" id="timezone-select">
 | 
										<fieldset>
 | 
				
			||||||
					for _, tz := range timezones {
 | 
											<legend id="user-settings-heading">User Settings</legend>
 | 
				
			||||||
						if tz == user.Settings.LocalTimezone.String() {
 | 
											<div class="form-group">
 | 
				
			||||||
							<option value={ tz } selected="true">{ tz }</option>
 | 
												<label>Local Timezone</label>
 | 
				
			||||||
						} else {
 | 
												<select name="timezones" id="timezone-select">
 | 
				
			||||||
							<option value={ tz }>{ tz }</option>
 | 
													for _, tz := range timezones {
 | 
				
			||||||
						}
 | 
														if tz == user.Settings.LocalTimezone.String() {
 | 
				
			||||||
					}
 | 
															<option value={ tz } selected="true">{ tz }</option>
 | 
				
			||||||
				</select>
 | 
														} else {
 | 
				
			||||||
				<input type="submit" value="Submit"/>
 | 
															<option value={ tz }>{ tz }</option>
 | 
				
			||||||
			</form>
 | 
														}
 | 
				
			||||||
 | 
													}
 | 
				
			||||||
 | 
												</select>
 | 
				
			||||||
 | 
											</div>
 | 
				
			||||||
 | 
										</fieldset>
 | 
				
			||||||
 | 
										<button type="submit">Save Settings</button>
 | 
				
			||||||
 | 
									</form>
 | 
				
			||||||
 | 
								</section>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -444,20 +444,20 @@ func UserSettingsView(data CommonData, timezones []string) templ.Component {
 | 
				
			|||||||
				}()
 | 
									}()
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			ctx = templ.InitializeContext(ctx)
 | 
								ctx = templ.InitializeContext(ctx)
 | 
				
			||||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "<div><form hx-put=\"/users/settings\"><input type=\"hidden\" name=\"csrf_token\" value=\"")
 | 
								templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "<div><section aria-labelledby=\"user-settings-heading\"><form hx-put=\"/users/settings\"><input type=\"hidden\" name=\"csrf_token\" value=\"")
 | 
				
			||||||
			if templ_7745c5c3_Err != nil {
 | 
								if templ_7745c5c3_Err != nil {
 | 
				
			||||||
				return templ_7745c5c3_Err
 | 
									return templ_7745c5c3_Err
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			var templ_7745c5c3_Var22 string
 | 
								var templ_7745c5c3_Var22 string
 | 
				
			||||||
			templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(data.CSRFToken)
 | 
								templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(data.CSRFToken)
 | 
				
			||||||
			if templ_7745c5c3_Err != nil {
 | 
								if templ_7745c5c3_Err != nil {
 | 
				
			||||||
				return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 90, Col: 65}
 | 
									return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 91, Col: 66}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
 | 
								_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
 | 
				
			||||||
			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, 35, "\"> <label>Local Timezone</label> <select name=\"timezones\" id=\"timezone-select\">")
 | 
								templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "\"><fieldset><legend id=\"user-settings-heading\">User Settings</legend><div class=\"form-group\"><label>Local Timezone</label> <select name=\"timezones\" id=\"timezone-select\">")
 | 
				
			||||||
			if templ_7745c5c3_Err != nil {
 | 
								if templ_7745c5c3_Err != nil {
 | 
				
			||||||
				return templ_7745c5c3_Err
 | 
									return templ_7745c5c3_Err
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
@ -470,7 +470,7 @@ func UserSettingsView(data CommonData, timezones []string) templ.Component {
 | 
				
			|||||||
					var templ_7745c5c3_Var23 string
 | 
										var templ_7745c5c3_Var23 string
 | 
				
			||||||
					templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(tz)
 | 
										templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(tz)
 | 
				
			||||||
					if templ_7745c5c3_Err != nil {
 | 
										if templ_7745c5c3_Err != nil {
 | 
				
			||||||
						return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 95, Col: 25}
 | 
											return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 99, Col: 28}
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
 | 
										_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
 | 
				
			||||||
					if templ_7745c5c3_Err != nil {
 | 
										if templ_7745c5c3_Err != nil {
 | 
				
			||||||
@ -483,7 +483,7 @@ func UserSettingsView(data CommonData, timezones []string) templ.Component {
 | 
				
			|||||||
					var templ_7745c5c3_Var24 string
 | 
										var templ_7745c5c3_Var24 string
 | 
				
			||||||
					templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(tz)
 | 
										templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(tz)
 | 
				
			||||||
					if templ_7745c5c3_Err != nil {
 | 
										if templ_7745c5c3_Err != nil {
 | 
				
			||||||
						return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 95, Col: 48}
 | 
											return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 99, Col: 51}
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
 | 
										_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
 | 
				
			||||||
					if templ_7745c5c3_Err != nil {
 | 
										if templ_7745c5c3_Err != nil {
 | 
				
			||||||
@ -501,7 +501,7 @@ func UserSettingsView(data CommonData, timezones []string) templ.Component {
 | 
				
			|||||||
					var templ_7745c5c3_Var25 string
 | 
										var templ_7745c5c3_Var25 string
 | 
				
			||||||
					templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(tz)
 | 
										templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(tz)
 | 
				
			||||||
					if templ_7745c5c3_Err != nil {
 | 
										if templ_7745c5c3_Err != nil {
 | 
				
			||||||
						return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 97, Col: 25}
 | 
											return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 101, Col: 28}
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))
 | 
										_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))
 | 
				
			||||||
					if templ_7745c5c3_Err != nil {
 | 
										if templ_7745c5c3_Err != nil {
 | 
				
			||||||
@ -514,7 +514,7 @@ func UserSettingsView(data CommonData, timezones []string) templ.Component {
 | 
				
			|||||||
					var templ_7745c5c3_Var26 string
 | 
										var templ_7745c5c3_Var26 string
 | 
				
			||||||
					templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(tz)
 | 
										templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(tz)
 | 
				
			||||||
					if templ_7745c5c3_Err != nil {
 | 
										if templ_7745c5c3_Err != nil {
 | 
				
			||||||
						return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 97, Col: 32}
 | 
											return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/users.templ`, Line: 101, Col: 35}
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26))
 | 
										_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26))
 | 
				
			||||||
					if templ_7745c5c3_Err != nil {
 | 
										if templ_7745c5c3_Err != nil {
 | 
				
			||||||
@ -526,7 +526,7 @@ func UserSettingsView(data CommonData, timezones []string) templ.Component {
 | 
				
			|||||||
					}
 | 
										}
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "</select> <input type=\"submit\" value=\"Submit\"></form></div>")
 | 
								templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "</select></div></fieldset><button type=\"submit\">Save Settings</button></form></section></div>")
 | 
				
			||||||
			if templ_7745c5c3_Err != nil {
 | 
								if templ_7745c5c3_Err != nil {
 | 
				
			||||||
				return templ_7745c5c3_Err
 | 
									return templ_7745c5c3_Err
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
				
			|||||||
@ -14,91 +14,126 @@ func wUrl(w models.Website) string {
 | 
				
			|||||||
templ wSidebar(website models.Website) {
 | 
					templ wSidebar(website models.Website) {
 | 
				
			||||||
	{{ dashUrl := wUrl(website) + "/dashboard" }}
 | 
						{{ dashUrl := wUrl(website) + "/dashboard" }}
 | 
				
			||||||
	{{ gbUrl := wUrl(website) + "/guestbook" }}
 | 
						{{ gbUrl := wUrl(website) + "/guestbook" }}
 | 
				
			||||||
	<nav>
 | 
						<nav aria-label="Dashboard navigation">
 | 
				
			||||||
		<div>
 | 
							<div>
 | 
				
			||||||
			<ul>
 | 
								<section aria-labelledby="main-nav-heading">
 | 
				
			||||||
				<li><a href={ templ.URL(dashUrl) }>Dashboard</a></li>
 | 
									<h3 id="main-nav-heading">Website</h3>
 | 
				
			||||||
				<li><a href={ templ.URL(dashUrl + "/settings") }>Settings</a></li>
 | 
									<ul role="list">
 | 
				
			||||||
				<li><a href={ templ.URL(externalUrl(website.Url.String())) } target="_blank">View Website</a></li>
 | 
										<li><a href={ templ.URL(dashUrl) } aria-current="page">Dashboard</a></li>
 | 
				
			||||||
			</ul>
 | 
										<li><a href={ templ.URL(dashUrl + "/settings") }>Settings</a></li>
 | 
				
			||||||
			<h3>Guestbook</h3>
 | 
										<li><a href={ templ.URL(externalUrl(website.Url.String())) } target="_blank">View Website</a></li>
 | 
				
			||||||
			<ul>
 | 
									</ul>
 | 
				
			||||||
				<li><a href={ templ.URL(gbUrl) } target="_blank">View Guestbook</a></li>
 | 
								</section>
 | 
				
			||||||
			</ul>
 | 
					 | 
				
			||||||
			<ul>
 | 
					 | 
				
			||||||
				<li><a href={ templ.URL(dashUrl + "/guestbook/comments") }>Manage messages</a></li>
 | 
					 | 
				
			||||||
				<li><a href={ templ.URL(dashUrl + "/guestbook/comments/queue") }>Review message queue</a></li>
 | 
					 | 
				
			||||||
				<li><a href={ templ.URL(dashUrl + "/guestbook/comments/trash") }>Trash</a></li>
 | 
					 | 
				
			||||||
			</ul>
 | 
					 | 
				
			||||||
			<ul>
 | 
					 | 
				
			||||||
				//<li><a href={ templ.URL(dashUrl + "/guestbook/themes") }>Themes</a></li>
 | 
					 | 
				
			||||||
				//<li><a href={ templ.URL(dashUrl + "/guestbook/customize") }>Custom CSS</a></li>
 | 
					 | 
				
			||||||
			</ul>
 | 
					 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
		<div>
 | 
							<div>
 | 
				
			||||||
			<h3>Feeds</h3>
 | 
								<section aria-labelledby="guestbook-nav-heading">
 | 
				
			||||||
			<p>Coming Soon</p>
 | 
									<h3 id="guestbook-nav-heading">Guestbook</h3>
 | 
				
			||||||
 | 
									<ul role="list">
 | 
				
			||||||
 | 
										<li><a href={ templ.URL(gbUrl) } target="_blank">View Guestbook</a></li>
 | 
				
			||||||
 | 
										<li><a href={ templ.URL(dashUrl + "/guestbook/comments") }>Manage Comments</a></li>
 | 
				
			||||||
 | 
									</ul>
 | 
				
			||||||
 | 
								</section>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
		<div>
 | 
							<div>
 | 
				
			||||||
			<h3>Account</h3>
 | 
								<section aria-labelledby="feeds-nav-heading">
 | 
				
			||||||
			<ul>
 | 
									<h3 id="feeds-nav-heading">Feeds</h3>
 | 
				
			||||||
				<li><a href="/users/settings">Settings</a></li>
 | 
									<p>Coming Soon</p>
 | 
				
			||||||
				<li><a href="/users/privacy">Privacy</a></li>
 | 
								</section>
 | 
				
			||||||
				<li><a href="/help">Help</a></li>
 | 
							</div>
 | 
				
			||||||
			</ul>
 | 
							<div>
 | 
				
			||||||
 | 
								<section aria-labelledby="account-nav-heading">
 | 
				
			||||||
 | 
									<h3 id="account-nav-heading">Account</h3>
 | 
				
			||||||
 | 
									<ul role="list">
 | 
				
			||||||
 | 
										<li><a href="/users/settings">Settings</a></li>
 | 
				
			||||||
 | 
										<li><a href="/users/privacy">Privacy</a></li>
 | 
				
			||||||
 | 
										<li><a href="/help">Help</a></li>
 | 
				
			||||||
 | 
									</ul>
 | 
				
			||||||
 | 
								</section>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	</nav>
 | 
						</nav>
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
templ websiteCreateForm(csrfToken string, form forms.WebsiteCreateForm) {
 | 
					templ websiteCreateForm(csrfToken string, form forms.WebsiteCreateForm) {
 | 
				
			||||||
	<input type="hidden" name="csrf_token" value={ csrfToken }/>
 | 
						<input type="hidden" name="csrf_token" value={ csrfToken }/>
 | 
				
			||||||
 | 
						<fieldset>
 | 
				
			||||||
 | 
							<legend id="website-create-heading">Website Settings</legend>
 | 
				
			||||||
 | 
							<div class="form-group">
 | 
				
			||||||
 | 
								{{ err, exists := form.FieldErrors["ws_name"] }}
 | 
				
			||||||
 | 
								<label for="ws_name">Site Name <span aria-label="required">*</span></label>
 | 
				
			||||||
 | 
								if exists {
 | 
				
			||||||
 | 
									<label class="error">{ err }</label>
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								<input type="text" name="ws_name" id="sitename" value={ form.Name } required aria-describedby="sitename-help"/>
 | 
				
			||||||
 | 
								<small id="sitename-help">The display name for your website</small>
 | 
				
			||||||
 | 
							</div>
 | 
				
			||||||
 | 
							<div class="form-group">
 | 
				
			||||||
 | 
								{{ err, exists = form.FieldErrors["ws_url"] }}
 | 
				
			||||||
 | 
								<label for="ws_url">Site URL <span aria-label="required">*</span></label>
 | 
				
			||||||
 | 
								if exists {
 | 
				
			||||||
 | 
									<label class="error">{ err }</label>
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								<input type="url" name="ws_url" id="ws_url" value={ form.SiteUrl } required aria-describedby="siteurl-help"/>
 | 
				
			||||||
 | 
								<small id="siteurl-help">The full URL where your website can be accessed</small>
 | 
				
			||||||
 | 
							</div>
 | 
				
			||||||
 | 
							<div>
 | 
				
			||||||
 | 
								{{ err, exists = form.FieldErrors["ws_author"] }}
 | 
				
			||||||
 | 
								<label for="ws_author">Site Author <span aria-label="required">*</span></label>
 | 
				
			||||||
 | 
								if exists {
 | 
				
			||||||
 | 
									<label class="error">{ err }</label>
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								<input type="text" name="ws_author" id="authorname" value={ form.AuthorName } required aria-describedby="authorname-help"/>
 | 
				
			||||||
 | 
								<small id="authorname-help">Your name or the website owner's name</small>
 | 
				
			||||||
 | 
							</div>
 | 
				
			||||||
 | 
						</fieldset>
 | 
				
			||||||
	<div>
 | 
						<div>
 | 
				
			||||||
		{{ err, exists := form.FieldErrors["sitename"] }}
 | 
							<button type="submit">Add Website</button>
 | 
				
			||||||
		<label for="sitename">Site Name: </label>
 | 
					 | 
				
			||||||
		if exists {
 | 
					 | 
				
			||||||
			<label class="error">{ err }</label>
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		<input type="text" name="sitename" id="sitename" value={ form.Name } required/>
 | 
					 | 
				
			||||||
	</div>
 | 
					 | 
				
			||||||
	<div>
 | 
					 | 
				
			||||||
		{{ err, exists = form.FieldErrors["siteurl"] }}
 | 
					 | 
				
			||||||
		<label for="siteurl">Site URL: </label>
 | 
					 | 
				
			||||||
		if exists {
 | 
					 | 
				
			||||||
			<label class="error">{ err }</label>
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		<input type="text" name="siteurl" id="siteurl" value={ form.SiteUrl } required/>
 | 
					 | 
				
			||||||
	</div>
 | 
					 | 
				
			||||||
	<div>
 | 
					 | 
				
			||||||
		{{ err, exists = form.FieldErrors["authorname"] }}
 | 
					 | 
				
			||||||
		<label for="authorname">Site Author: </label>
 | 
					 | 
				
			||||||
		if exists {
 | 
					 | 
				
			||||||
			<label class="error">{ err }</label>
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		<input type="text" name="authorname" id="authorname" value={ form.AuthorName } required/>
 | 
					 | 
				
			||||||
	</div>
 | 
					 | 
				
			||||||
	<div>
 | 
					 | 
				
			||||||
		<button type="submit">Submit</button>
 | 
					 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
templ WebsiteList(title string, data CommonData, websites []models.Website) {
 | 
					templ WebsiteList(title string, data CommonData, websites []models.Website) {
 | 
				
			||||||
	@base(title, data) {
 | 
						@base(title, data) {
 | 
				
			||||||
		<div>
 | 
							<section aria-labelledby="websites-heading">
 | 
				
			||||||
			<a href="/websites/create">Add Website</a>
 | 
								<header class="section-header">
 | 
				
			||||||
		</div>
 | 
									<h1 id="websites-heading">My Websites</h1>
 | 
				
			||||||
		<div>
 | 
									<a href="/websites/create" class="btn btn-primary" role="button" aria-label="Create a new website">Add Website</a>
 | 
				
			||||||
			if len(websites) == 0 {
 | 
								</header>
 | 
				
			||||||
				<p>No Websites yet. <a href="">Register a website.</a></p>
 | 
								<div class="websites-container">
 | 
				
			||||||
			} else {
 | 
									if len(websites) == 0 {
 | 
				
			||||||
				<ul id="websites" hx-get="/websites" hx-trigger="newWebsite from:body" hx-swap="outerHTML">
 | 
										<h2>No Websites yet.</h2>
 | 
				
			||||||
					for _, w := range websites {
 | 
										<p>Create your first website to get started with webweav.ing tools.</p>
 | 
				
			||||||
						<li>
 | 
										<a href="/websites/create" class="btn btn-primary">Create Your First Website</a>
 | 
				
			||||||
							<a href={ templ.URL(wUrl(w) + "/dashboard") }>{ w.Name }</a>
 | 
									} else {
 | 
				
			||||||
						</li>
 | 
										<ul
 | 
				
			||||||
					}
 | 
											id="websites"
 | 
				
			||||||
				</ul>
 | 
											role="list"
 | 
				
			||||||
			}
 | 
											aria-label="Your websites"
 | 
				
			||||||
		</div>
 | 
											hx-get="/websites"
 | 
				
			||||||
 | 
											hx-trigger="newWebsite from:body"
 | 
				
			||||||
 | 
											hx-swap="outerHTML"
 | 
				
			||||||
 | 
										>
 | 
				
			||||||
 | 
											for _, w := range websites {
 | 
				
			||||||
 | 
												<li role="listitem">
 | 
				
			||||||
 | 
													<article class="website-card">
 | 
				
			||||||
 | 
														<header class="website-header">
 | 
				
			||||||
 | 
															<h2 class="website-name">
 | 
				
			||||||
 | 
																<a href={ templ.URL(wUrl(w) + "/dashboard") } aria-label={ fmt.Sprintf("Manage %s website dashboard", w.Name) }>
 | 
				
			||||||
 | 
																	{ w.Name }
 | 
				
			||||||
 | 
																</a>
 | 
				
			||||||
 | 
															</h2>
 | 
				
			||||||
 | 
															<span class="website-url" aria-label="Website URL">{ w.Url.String() }</span>
 | 
				
			||||||
 | 
														</header>
 | 
				
			||||||
 | 
														<div class="website-actions">
 | 
				
			||||||
 | 
															<a href={ templ.URL(wUrl(w) + "/dashboard") } class="btn btn-outline" aria-label={ fmt.Sprintf("Open %s dashboard", w.Name) }>Dashboard</a>
 | 
				
			||||||
 | 
															<a href={ templ.URL(wUrl(w) + "/guestbook") } target="_blank" rel="noopener" class="btn btn-outline" aria-label={ fmt.Sprintf("View %s website guestbook", w.Name) }>View Guestbook</a>
 | 
				
			||||||
 | 
															<a href={ templ.URL(externalUrl(w.Url.String())) } target="_blank" rel="noopener" class="btn btn-outline" aria-label={ fmt.Sprintf("View %s website", w.Name) }>Visit Site</a>
 | 
				
			||||||
 | 
														</div>
 | 
				
			||||||
 | 
													</article>
 | 
				
			||||||
 | 
												</li>
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										</ul>
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								</div>
 | 
				
			||||||
 | 
							</section>
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -107,103 +142,142 @@ templ WebsiteDashboard(title string, data CommonData, website models.Website) {
 | 
				
			|||||||
		<div id="dashboard">
 | 
							<div id="dashboard">
 | 
				
			||||||
			@wSidebar(website)
 | 
								@wSidebar(website)
 | 
				
			||||||
			<div>
 | 
								<div>
 | 
				
			||||||
				<h1>Embed your Guestbook</h1>
 | 
									{{ gbUrl := fmt.Sprintf("https://%s/websites/%s/guestbook", data.RootUrl, shortIdToSlug(website.ShortId)) }}
 | 
				
			||||||
				<p>
 | 
									<section aria-labelledby="embed-instructions">
 | 
				
			||||||
					Upload <a href="/static/js/guestbook.js" download>this JavaScript WebComponent</a> to your site and include it in your <code>{ `<head>` }</code> tag.
 | 
										<h1 id="embed-instructions">Embed your Guestbook</h1>
 | 
				
			||||||
				</p>
 | 
										<div class="instruction-overview">
 | 
				
			||||||
				<div>
 | 
											<p>There are two ways to add your guestbook to your website: using our JavaScript component (recommended) or with an iframe.</p>
 | 
				
			||||||
					//<button>Copy to Clipboard</button>
 | 
										</div>
 | 
				
			||||||
					<pre>
 | 
										<article class="instruction-method">
 | 
				
			||||||
						<code id="guestbookSnippet">
 | 
											<h2>Method 1: JavaScript Component (Recommended)</h2>
 | 
				
			||||||
							{ 
 | 
											<div class="instruction-step">
 | 
				
			||||||
`<head>
 | 
												<h3>Step 1: Download and upload the component</h3>
 | 
				
			||||||
    <script type="module" src="js/guestbook.js"></script>
 | 
												<p>Download <a href="/static/js/guestbook.js" download>this JavaScript WebComponent</a> and upload it to your website's JavaScript folder.</p>
 | 
				
			||||||
</head>` }
 | 
											</div>
 | 
				
			||||||
						</code>
 | 
											<div class="instruction-step">
 | 
				
			||||||
					</pre>
 | 
												<h3>Step 2: Include in your HTML head</h3>
 | 
				
			||||||
					<p>
 | 
												<figure class="code-example">
 | 
				
			||||||
						Then add the custom elements where you want your form and comments to show up
 | 
													<figcaption>Add this script tag to your <head> section:</figcaption>
 | 
				
			||||||
					</p>
 | 
													<pre>
 | 
				
			||||||
					{{ gbUrl := fmt.Sprintf("https://%s/websites/%s/guestbook", data.RootUrl, shortIdToSlug(website.ShortId)) }}
 | 
														<code id="guestbookSnippet" aria-label="HTML head script tag">
 | 
				
			||||||
					//<button>Copy to Clipboard</button>
 | 
															<head>
 | 
				
			||||||
					<pre>
 | 
															<script type="module" src="js/guestbook.js"></script>
 | 
				
			||||||
						<code>
 | 
															</head>
 | 
				
			||||||
							{ fmt.Sprintf(`<guestbook-form guestbook="%s"></guestbook-form>
 | 
														</code>
 | 
				
			||||||
 | 
													</pre>
 | 
				
			||||||
 | 
												</figure>
 | 
				
			||||||
 | 
											</div>
 | 
				
			||||||
 | 
											<div class="instruction-step">
 | 
				
			||||||
 | 
												<h3>Step 3: Add the custom elements</h3>
 | 
				
			||||||
 | 
												<figure class="code-example">
 | 
				
			||||||
 | 
													<figcaption>Place these elements where you want your guestbook to appear:</figcaption>
 | 
				
			||||||
 | 
													<pre>
 | 
				
			||||||
 | 
														<code aria-label="Custom guestbook elements">
 | 
				
			||||||
 | 
															{ fmt.Sprintf(`<guestbook-form guestbook="%s"></guestbook-form>
 | 
				
			||||||
<guestbook-comments guestbook="%s"></guestbook-comments>`, gbUrl, gbUrl) }
 | 
					<guestbook-comments guestbook="%s"></guestbook-comments>`, gbUrl, gbUrl) }
 | 
				
			||||||
						</code>
 | 
														</code>
 | 
				
			||||||
					</pre>
 | 
													</pre>
 | 
				
			||||||
				</div>
 | 
												</figure>
 | 
				
			||||||
				<p>
 | 
											</div>
 | 
				
			||||||
					If your web host does not allow CORS requests, use an iframe instead
 | 
										</article>
 | 
				
			||||||
				</p>
 | 
										<article class="instruction-method">
 | 
				
			||||||
				<div>
 | 
											<h2>Method 2: iframe (Alternative)</h2>
 | 
				
			||||||
					<pre>
 | 
											<details>
 | 
				
			||||||
						<code>
 | 
												<summary>Use this method if your web host doesn't allow CORS requests</summary>
 | 
				
			||||||
							{ fmt.Sprintf(`<iframe src="%s" title="Guestbook"></iframe>`, gbUrl) }
 | 
												<div class="instruction-step">
 | 
				
			||||||
						</code>
 | 
													<p>If your hosting provider blocks cross-origin requests, you can embed the guestbook using an iframe instead:</p>
 | 
				
			||||||
					</pre>
 | 
													<figure class="code-example">
 | 
				
			||||||
				</div>
 | 
														<figcaption>iframe embedding code:</figcaption>
 | 
				
			||||||
 | 
														<pre>
 | 
				
			||||||
 | 
															<code aria-label="iframe embedding code">
 | 
				
			||||||
 | 
																{ fmt.Sprintf(`<iframe src="%s" 
 | 
				
			||||||
 | 
					    title="Guestbook"
 | 
				
			||||||
 | 
					    width="100%%"
 | 
				
			||||||
 | 
					    height="600"
 | 
				
			||||||
 | 
					    style="border: 1px solid #ccc; border-radius: 8px;"
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					        <p>Your browser does not support iframes. <a href="%s">View the guestbook directly.</a></p>
 | 
				
			||||||
 | 
					</iframe>`, gbUrl, gbUrl) }
 | 
				
			||||||
 | 
															</code>
 | 
				
			||||||
 | 
														</pre>
 | 
				
			||||||
 | 
													</figure>
 | 
				
			||||||
 | 
													<aside role="note" class="method-note">
 | 
				
			||||||
 | 
														<p><strong>Note:</strong> The iframe method may have styling limitations and won't integrate as seamlessly with your site's design.</p>
 | 
				
			||||||
 | 
													</aside>
 | 
				
			||||||
 | 
												</div>
 | 
				
			||||||
 | 
											</details>
 | 
				
			||||||
 | 
										</article>
 | 
				
			||||||
 | 
										<aside role="complementary" class="help-section">
 | 
				
			||||||
 | 
											<h2>Need Help?</h2>
 | 
				
			||||||
 | 
											<p>If you're having trouble embedding your guestbook, check out our <a href="/help">help documentation</a> or contact support.</p>
 | 
				
			||||||
 | 
										</aside>
 | 
				
			||||||
 | 
									</section>
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
templ websiteSettingsForm(data CommonData, website models.Website, form forms.WebsiteSettingsForm) {
 | 
					templ websiteSettingsForm(data CommonData, website models.Website, form forms.WebsiteSettingsForm) {
 | 
				
			||||||
	<h3>Website Settings</h3>
 | 
						<legend id="website-settings-heading">Website Settings</legend>
 | 
				
			||||||
	<div>
 | 
						<div class="form-group">
 | 
				
			||||||
		{{ err, exists := form.FieldErrors["ws_name"] }}
 | 
							{{ err, exists := form.FieldErrors["ws_name"] }}
 | 
				
			||||||
		<label for="ws_name">Site Name: </label>
 | 
							<label for="ws_name">Site Name <span aria-label="required">*</span></label>
 | 
				
			||||||
		if exists {
 | 
							if exists {
 | 
				
			||||||
			<label class="error">{ err }</label>
 | 
								<label class="error">{ err }</label>
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if form.SiteName != "" {
 | 
							if form.SiteName != "" {
 | 
				
			||||||
			<input type="text" name="ws_name" id="sitename" value={ form.SiteName } required/>
 | 
								<input type="text" name="ws_name" id="sitename" value={ form.SiteName } required aria-describedby="sitename-help"/>
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			<input type="text" name="ws_name" id="sitename" value={ website.Name } required/>
 | 
								<input type="text" name="ws_name" id="sitename" value={ website.Name } required aria-describedby="sitename-help"/>
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							<small id="sitename-help">The display name for your website</small>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
	<div>
 | 
						<div class="form-group">
 | 
				
			||||||
		{{ err, exists = form.FieldErrors["ws_url"] }}
 | 
							{{ err, exists = form.FieldErrors["ws_url"] }}
 | 
				
			||||||
		<label for="ws_url">Site URL: </label>
 | 
							<label for="ws_url">Site URL <span aria-label="required">*</span></label>
 | 
				
			||||||
		if exists {
 | 
							if exists {
 | 
				
			||||||
			<label class="error">{ err }</label>
 | 
								<label class="error">{ err }</label>
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if form.SiteUrl != "" {
 | 
							if form.SiteUrl != "" {
 | 
				
			||||||
			<input type="text" name="ws_url" id="ws_url" value={ form.SiteUrl } required/>
 | 
								<input type="url" name="ws_url" id="ws_url" value={ form.SiteUrl } required aria-describedby="siteurl-help"/>
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			<input type="text" name="ws_url" id="ws_url" value={ website.Url.String() } required/>
 | 
								<input type="url" name="ws_url" id="ws_url" value={ website.Url.String() } required aria-describedby="siteurl-help"/>
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							<small id="siteurl-help">The full URL where your website can be accessed</small>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
	<div>
 | 
						<div>
 | 
				
			||||||
		{{ err, exists = form.FieldErrors["ws_author"] }}
 | 
							{{ err, exists = form.FieldErrors["ws_author"] }}
 | 
				
			||||||
		<label for="ws_author">Site Author: </label>
 | 
							<label for="ws_author">Site Author <span aria-label="required">*</span></label>
 | 
				
			||||||
		if exists {
 | 
							if exists {
 | 
				
			||||||
			<label class="error">{ err }</label>
 | 
								<label class="error">{ err }</label>
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if form.AuthorName != "" {
 | 
							if form.AuthorName != "" {
 | 
				
			||||||
			<input type="text" name="ws_author" id="authorname" value={ form.AuthorName } required/>
 | 
								<input type="text" name="ws_author" id="authorname" value={ form.AuthorName } required aria-describedby="authorname-help"/>
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			<input type="text" name="ws_author" id="authorname" value={ website.AuthorName } required/>
 | 
								<input type="text" name="ws_author" id="authorname" value={ website.AuthorName } required aria-describedby="authorname-help"/>
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							<small id="authorname-help">Your name or the website owner's name</small>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
templ guestbookSettingsForm(data CommonData, website models.Website, gb models.Guestbook, form forms.WebsiteSettingsForm) {
 | 
					templ guestbookSettingsForm(data CommonData, website models.Website, gb models.Guestbook, form forms.WebsiteSettingsForm) {
 | 
				
			||||||
	<h3>Guestbook Settings</h3>
 | 
						<legend>Guestbook Settings</legend>
 | 
				
			||||||
	<div>
 | 
						<div class="form-group">
 | 
				
			||||||
		<label>Guestbook Visibility</label>
 | 
							<fieldset class="radio-group">
 | 
				
			||||||
		<label for="gb_visible_true">
 | 
								<legend>Guestbook Visibility</legend>
 | 
				
			||||||
			<input type="radio" name="gb_visible" id="gb_visible_true" value="true" checked?={ gb.Settings.IsVisible }/>
 | 
								<label>
 | 
				
			||||||
			Public
 | 
									<input type="radio" name="gb_visible" id="gb_visible_true" value="true" checked?={ gb.Settings.IsVisible }/>
 | 
				
			||||||
		</label>
 | 
									Public
 | 
				
			||||||
		<label for="gb_visible_false">
 | 
								</label>
 | 
				
			||||||
			<input type="radio" name="gb_visible" id="gb_visible_false" value="false" checked?={ !gb.Settings.IsVisible }/>
 | 
								<label>
 | 
				
			||||||
			Private
 | 
									<input type="radio" name="gb_visible" id="gb_visible_false" value="false" checked?={ !gb.Settings.IsVisible }/>
 | 
				
			||||||
		</label>
 | 
									Private
 | 
				
			||||||
 | 
								</label>
 | 
				
			||||||
 | 
							</fieldset>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
	<div>
 | 
						<div class="form-group">
 | 
				
			||||||
		<label>Guestbook Commenting</label>
 | 
							<label for="gb-commenting">Guestbook Commenting</label>
 | 
				
			||||||
		<select name="gb_commenting" id="gb-commenting">
 | 
							<select name="gb_commenting" id="gb-commenting" aria-describedby="commenting-help">
 | 
				
			||||||
			<option value="true" selected?={ gb.Settings.IsCommentingEnabled }>Enabled</option>
 | 
								<option value="true" selected?={ gb.Settings.IsCommentingEnabled }>Enabled</option>
 | 
				
			||||||
			<option value="1h">Disabled for 1 Hour</option>
 | 
								<option value="1h">Disabled for 1 Hour</option>
 | 
				
			||||||
			<option value="4h">Disabled for 4 Hours</option>
 | 
								<option value="4h">Disabled for 4 Hours</option>
 | 
				
			||||||
@ -217,17 +291,21 @@ templ guestbookSettingsForm(data CommonData, website models.Website, gb models.G
 | 
				
			|||||||
			{{ localtime := gb.Settings.ReenableCommenting.In(data.CurrentUser.Settings.LocalTimezone) }}
 | 
								{{ localtime := gb.Settings.ReenableCommenting.In(data.CurrentUser.Settings.LocalTimezone) }}
 | 
				
			||||||
			<label>Commenting re-enabled on <time value={ localtime.Format(time.RFC3339) }>{ localtime.Format("2 January 2006") } at { localtime.Format("3:04PM MST") }</time></label>
 | 
								<label>Commenting re-enabled on <time value={ localtime.Format(time.RFC3339) }>{ localtime.Format("2 January 2006") } at { localtime.Format("3:04PM MST") }</time></label>
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							<small id="commenting-help">Control when users can post new comments</small>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
	<div>
 | 
						<div class="form-group">
 | 
				
			||||||
		<label>Enable Widgets</label>
 | 
							<fieldset class="radio-group">
 | 
				
			||||||
		<label for="gb_remote_true">
 | 
								<legend>Enable Widgets</legend>
 | 
				
			||||||
			<input type="radio" name="gb_remote" id="gb_remote_true" value="true" checked?={ gb.Settings.AllowRemoteHostAccess }/>
 | 
								<label for="gb_remote_true">
 | 
				
			||||||
			Yes
 | 
									<input type="radio" name="gb_remote" id="gb_remote_true" value="true" checked?={ gb.Settings.AllowRemoteHostAccess }/>
 | 
				
			||||||
		</label>
 | 
									Yes
 | 
				
			||||||
		<label for="gb_remote_false">
 | 
								</label>
 | 
				
			||||||
			<input type="radio" name="gb_remote" id="gb_remote_false" value="false" checked?={ !gb.Settings.AllowRemoteHostAccess }/>
 | 
								<label for="gb_remote_false">
 | 
				
			||||||
			No
 | 
									<input type="radio" name="gb_remote" id="gb_remote_false" value="false" checked?={ !gb.Settings.AllowRemoteHostAccess }/>
 | 
				
			||||||
		</label>
 | 
									No
 | 
				
			||||||
 | 
								</label>
 | 
				
			||||||
 | 
							</fieldset>
 | 
				
			||||||
 | 
							<small>Allow embedding guestbook on external websites</small>
 | 
				
			||||||
	</div>
 | 
						</div>
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -239,27 +317,36 @@ templ SettingsForm(data CommonData, website models.Website, form forms.WebsiteSe
 | 
				
			|||||||
			{ msg }
 | 
								{ msg }
 | 
				
			||||||
		</p>
 | 
							</p>
 | 
				
			||||||
		<input type="hidden" name="csrf_token" value={ data.CSRFToken }/>
 | 
							<input type="hidden" name="csrf_token" value={ data.CSRFToken }/>
 | 
				
			||||||
		<div id="settings_form">
 | 
							<fieldset>
 | 
				
			||||||
			@websiteSettingsForm(data, website, form)
 | 
								@websiteSettingsForm(data, website, form)
 | 
				
			||||||
 | 
							</fieldset>
 | 
				
			||||||
 | 
							<fieldset>
 | 
				
			||||||
			@guestbookSettingsForm(data, website, gb, form)
 | 
								@guestbookSettingsForm(data, website, gb, form)
 | 
				
			||||||
		</div>
 | 
							</fieldset>
 | 
				
			||||||
		<input type="submit" value="Submit"/>
 | 
							<button type="submit">Save Settings</button>
 | 
				
			||||||
	</form>
 | 
						</form>
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
templ DeleteForm(data CommonData, website models.Website, form forms.WebsiteDeleteForm) {
 | 
					templ DeleteForm(data CommonData, website models.Website, form forms.WebsiteDeleteForm) {
 | 
				
			||||||
	{{ putUrl := fmt.Sprintf("/websites/%s", shortIdToSlug(website.ShortId)) }}
 | 
						{{ putUrl := fmt.Sprintf("/websites/%s", shortIdToSlug(website.ShortId)) }}
 | 
				
			||||||
	<form hx-put={ putUrl } hx-swap="outerHTML">
 | 
						<form hx-put={ putUrl } hx-swap="outerHTML" aria-label="Delete website">
 | 
				
			||||||
		<input type="hidden" name="csrf_token" value={ data.CSRFToken }/>
 | 
							<input type="hidden" name="csrf_token" value={ data.CSRFToken }/>
 | 
				
			||||||
		<h3>Delete Website</h3>
 | 
							<fieldset class="danger-zone">
 | 
				
			||||||
		<p>Deleting a website is permanent. Be absolutely sure before proceeding.</p>
 | 
								<legend id="danger-zone-heading">Delete Website</legend>
 | 
				
			||||||
		{{ err, exists := form.FieldErrors["delete"] }}
 | 
								<aside role="alert" class="warning-notice">
 | 
				
			||||||
		<label for="delete">Type your site name in the form.</label>
 | 
									<p><strong>Warning:</strong> Deleting a website is permanent. Be absolutely sure before proceeding.</p>
 | 
				
			||||||
		if exists {
 | 
								</aside>
 | 
				
			||||||
			<label class="error">{ err }</label>
 | 
								{{ err, exists := form.FieldErrors["delete"] }}
 | 
				
			||||||
		}
 | 
								<div class="form-group">
 | 
				
			||||||
		<input type="text" name="delete" id="delete" required/>
 | 
									if exists {
 | 
				
			||||||
		<input type="submit" value="Delete" class="danger"/>
 | 
										<label class="error">{ err }</label>
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									<label for="delete">Type your site name to confirm deletion</label>
 | 
				
			||||||
 | 
									<input type="text" name="delete" id="delete" required aria-describedby="delete-help" placeholder={ website.Name }/>
 | 
				
			||||||
 | 
									<small id="delete-help">Type { website.Name } exactly as shown to confirm deletion</small>
 | 
				
			||||||
 | 
								</div>
 | 
				
			||||||
 | 
								<button type="submit" class="danger">Delete Website</button>
 | 
				
			||||||
 | 
							</fieldset>
 | 
				
			||||||
	</form>
 | 
						</form>
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -269,8 +356,12 @@ templ WebsiteDashboardSettings(data CommonData, website models.Website, form for
 | 
				
			|||||||
		<div id="dashboard">
 | 
							<div id="dashboard">
 | 
				
			||||||
			@wSidebar(website)
 | 
								@wSidebar(website)
 | 
				
			||||||
			<div>
 | 
								<div>
 | 
				
			||||||
				@SettingsForm(data, website, form, "")
 | 
									<section aria-labelledby="website-settings-heading">
 | 
				
			||||||
				@DeleteForm(data, website, forms.WebsiteDeleteForm{})
 | 
										@SettingsForm(data, website, form, "")
 | 
				
			||||||
 | 
									</section>
 | 
				
			||||||
 | 
									<section aria-labelledby="danger-zone-heading">
 | 
				
			||||||
 | 
										@DeleteForm(data, website, forms.WebsiteDeleteForm{})
 | 
				
			||||||
 | 
									</section>
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -291,8 +382,10 @@ 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) {
 | 
				
			||||||
	@base(title, data) {
 | 
						@base(title, data) {
 | 
				
			||||||
		<form action="/websites/create" method="post">
 | 
							<section aria-labelledby="website-create-heading">
 | 
				
			||||||
			@websiteCreateForm(data.CSRFToken, form)
 | 
								<form action="/websites/create" method="post">
 | 
				
			||||||
		</form>
 | 
									@websiteCreateForm(data.CSRFToken, form)
 | 
				
			||||||
 | 
								</form>
 | 
				
			||||||
 | 
							</section>
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user