diff --git a/cmd/web/helpers.go b/cmd/web/helpers.go index 3060325..e1321e0 100644 --- a/cmd/web/helpers.go +++ b/cmd/web/helpers.go @@ -110,6 +110,7 @@ func (app *application) newCommonData(r *http.Request) views.CommonData { CSRFToken: nosurf.Token(r), CurrentUser: app.getCurrentUser(r), IsHtmx: r.Header.Get("Hx-Request") == "true", + RootUrl: app.rootUrl, } } diff --git a/cmd/web/main.go b/cmd/web/main.go index 7f22d07..5fe6dac 100644 --- a/cmd/web/main.go +++ b/cmd/web/main.go @@ -28,12 +28,14 @@ type application struct { formDecoder *schema.Decoder debug bool timezones []string + rootUrl string } func main() { addr := flag.String("addr", ":3000", "HTTP network address") dsn := flag.String("dsn", "guestbook.db", "data source name") debug := flag.Bool("debug", false, "enable debug mode") + root := flag.String("root", "localhost:3000", "root URL of application") flag.Parse() logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug})) @@ -62,6 +64,7 @@ func main() { formDecoder: formDecoder, debug: *debug, timezones: getAvailableTimezones(), + rootUrl: *root, } err = app.users.InitializeSettingsMap() diff --git a/ui/static/css/style.css b/ui/static/css/style.css index 0b1a576..1f291a6 100644 --- a/ui/static/css/style.css +++ b/ui/static/css/style.css @@ -58,10 +58,17 @@ div#dashboard { div#dashboard nav { flex: 1 1 25%; margin-top: 2rem; + min-width: 0; } div#dashboard > div { flex: 10 1 40%; + min-width: 0; +} + +div > pre { + max-width: 100%; + overflow: auto; } main nav ul { diff --git a/ui/static/js/guestbook.js b/ui/static/js/guestbook.js new file mode 100644 index 0000000..9af432e --- /dev/null +++ b/ui/static/js/guestbook.js @@ -0,0 +1,105 @@ +class CommentList extends HTMLElement { + static get observedAttributes() { + return ['src']; + } + + constructor() { + super(); + this.attachShadow({ mode: 'open' }); + this.comments = []; + this.loading = false; + this.error = null; + } + + connectedCallback() { + if (this.hasAttribute('src')) { + this.fetchComments(); + } + } + + attributeChangedCallback(name, oldValue, newValue) { + if (name === 'src' && oldValue !== newValue) { + this.fetchComments(); + } + } + + async fetchComments() { + const src = this.getAttribute('src'); + if (!src) return; + this.loading = true; + this.error = null; + this.render(); + + try { + const response = await fetch(src); + if (!response.ok) throw new Error(`HTTP error: ${response.status}`); + const data = await response.json(); + this.comments = Array.isArray(data) ? data : []; + this.loading = false; + this.render(); + } catch (err) { + this.error = err.message; + this.loading = false; + this.render(); + } + } + + formatDate(isoString) { + if (!isoString) return ''; + const date = new Date(isoString); + return date.toLocaleString(); + } + + render() { + this.shadowRoot.innerHTML = ` + +
- Stats and stuff will go here. + Use this form to allow readers of your website to comment on your guestbook!
+
+ Upload this JavaScript WebComponent to your site and include it in your { `` }
tag.
+
+
+ {
+`
+
+` }
+
+
+ + Then add the custom element where you want the comments to show up +
+ {{ getUrl := fmt.Sprintf("https://%s/websites/%s/guestbook/comments", data.RootUrl, shortIdToSlug(website.ShortId)) }} + // +
+
+ { fmt.Sprintf(` `, getUrl) }
+
+
+ @embedJavaScriptSnippet(data.RootUrl, website)
+
+
+ { fmt.Sprintf(formStr, postUrl) }
+
+
+}
+
+templ embedJavaScriptSnippet(root string, website models.Website) {
+}
diff --git a/ui/views/websites_templ.go b/ui/views/websites_templ.go
index 2d6d6a6..a19e987 100644
--- a/ui/views/websites_templ.go
+++ b/ui/views/websites_templ.go
@@ -481,7 +481,70 @@ func WebsiteDashboard(title string, data CommonData, website models.Website) tem
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "Stats and stuff will go here.
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "Use this form to allow readers of your website to comment on your guestbook!
Upload this JavaScript WebComponent to your site and include it in your ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var26 string
+ templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(``)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 129, Col: 140}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "
tag.
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var27 string
+ templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(
+ `
+
+`)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 138, Col: 8}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "
Then add the custom element where you want the comments to show up
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + getUrl := fmt.Sprintf("https://%s/websites/%s/guestbook/comments", data.RootUrl, shortIdToSlug(website.ShortId)) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var28 string
+ templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf(` `, getUrl))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 148, Col: 70}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = embedJavaScriptSnippet(data.RootUrl, website).Render(ctx, templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "Coming Soon
Coming Soon
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var34 string
+ templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf(formStr, postUrl))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `ui/views/websites.templ`, Line: 205, Col: 34}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+func embedJavaScriptSnippet(root string, website models.Website) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var35 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var35 == nil {
+ templ_7745c5c3_Var35 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ return nil
+ })
+}
+
var _ = templruntime.GeneratedTemplate