diff --git a/internal/glance/admin-edit.go b/internal/glance/admin-edit.go index 9645ebc..4a0e77a 100644 --- a/internal/glance/admin-edit.go +++ b/internal/glance/admin-edit.go @@ -966,6 +966,135 @@ func (a *application) handleAdminWidgetSchemas(w http.ResponseWriter, r *http.Re } } +// ---------- page settings + reorder ---------- + +// pageMetadataKeys is the set of page-level keys the settings form handles. +// Anything else (head-widgets, columns) stays untouched on save. +var pageMetadataKeys = map[string]bool{ + "name": true, + "slug": true, + "width": true, + "desktop-navigation-width": true, + "show-mobile-header": true, + "hide-desktop-navigation": true, + "center-vertically": true, +} + +func (a *application) handleAdminUpdatePageFields(w http.ResponseWriter, r *http.Request) { + if !a.adminAccessAllowed(w, r) { + return + } + if err := r.ParseForm(); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + slug := r.PathValue("page") + pageIdx, ok := a.freshPageIndexBySlug(slug) + if !ok { + a.handleNotFound(w, r) + return + } + + editor, err := loadConfigEditor(a.ConfigPath) + if err != nil { + adminError(w, http.StatusInternalServerError, err.Error()) + return + } + pageNode, err := editor.pageNodeAt(pageIdx) + if err != nil { + adminError(w, http.StatusInternalServerError, err.Error()) + return + } + + fields := make(map[string]interface{}) + for key := range pageMetadataKeys { + if !r.Form.Has(key) { + continue + } + raw := strings.TrimSpace(r.FormValue(key)) + switch key { + case "show-mobile-header", "hide-desktop-navigation", "center-vertically": + fields[key] = raw == "on" || raw == "true" || raw == "1" + default: + if raw == "" { + fields[key] = nil // remove the key from yaml + } else { + fields[key] = raw + } + } + } + // Browsers don't post unchecked checkboxes; explicitly set them false so + // turning a flag off persists. + for _, key := range []string{"show-mobile-header", "hide-desktop-navigation", "center-vertically"} { + if _, set := fields[key]; !set { + fields[key] = false + } + } + + if name, ok := fields["name"].(string); !ok || name == "" { + adminError(w, http.StatusBadRequest, "name is required") + return + } + if err := applyFieldsToWidget(pageNode, fields); err != nil { + adminError(w, http.StatusBadRequest, err.Error()) + return + } + + if err := editor.save(); err != nil { + adminError(w, http.StatusBadRequest, err.Error()) + return + } + + // The slug may have changed if the user edited it (or the title without an + // explicit slug); redirect to the page list rather than a possibly-stale URL. + http.Redirect(w, r, a.Config.Server.BaseURL+"/edit", http.StatusSeeOther) +} + +func (a *application) handleAdminMovePage(w http.ResponseWriter, r *http.Request) { + if !a.adminAccessAllowed(w, r) { + return + } + slug := r.PathValue("page") + idx, ok := a.freshPageIndexBySlug(slug) + if !ok { + a.handleNotFound(w, r) + return + } + delta := 0 + switch r.URL.Query().Get("dir") { + case "up": + delta = -1 + case "down": + delta = 1 + default: + adminError(w, http.StatusBadRequest, "dir must be up|down") + return + } + + editor, err := loadConfigEditor(a.ConfigPath) + if err != nil { + adminError(w, http.StatusInternalServerError, err.Error()) + return + } + pages, err := editor.pagesNode(false) + if err != nil { + adminError(w, http.StatusInternalServerError, err.Error()) + return + } + target := idx + delta + if idx < 0 || idx >= len(pages.Content) || target < 0 || target >= len(pages.Content) { + adminError(w, http.StatusBadRequest, "cannot move further in that direction") + return + } + pages.Content[idx], pages.Content[target] = pages.Content[target], pages.Content[idx] + + if err := editor.save(); err != nil { + adminError(w, http.StatusBadRequest, err.Error()) + return + } + http.Redirect(w, r, a.Config.Server.BaseURL+"/edit", http.StatusSeeOther) +} + // ---------- column endpoints ---------- func (a *application) editColumns(slug string) (*configEditor, *yaml.Node, error) { diff --git a/internal/glance/admin.go b/internal/glance/admin.go index 7676f07..3a0d010 100644 --- a/internal/glance/admin.go +++ b/internal/glance/admin.go @@ -10,9 +10,10 @@ import ( ) var ( - adminIndexTemplate = mustParseTemplate("admin.html", "document.html", "footer.html") - adminPageTemplate = mustParseTemplate("admin-page.html", "document.html", "footer.html") - adminWidgetTemplate = mustParseTemplate("admin-widget.html", "document.html", "footer.html") + adminIndexTemplate = mustParseTemplate("admin.html", "document.html", "footer.html") + adminPageTemplate = mustParseTemplate("admin-page.html", "document.html", "footer.html") + adminWidgetTemplate = mustParseTemplate("admin-widget.html", "document.html", "footer.html") + adminPageSettingsTemplate = mustParseTemplate("admin-page-settings.html", "document.html", "footer.html") ) // allWidgetTypes mirrors the switch in newWidget(). Aliases ("stocks") omitted. @@ -208,6 +209,8 @@ type adminPageSummary struct { Title string Slug string WidgetCount int + IsFirst bool + IsLast bool } type adminPageDetail struct { @@ -217,6 +220,16 @@ type adminPageDetail struct { Columns []adminColumnView } +type adminPageSettings struct { + Name string + Slug string + Width string + DesktopNavigationWidth string + ShowMobileHeader bool + HideDesktopNavigation bool + CenterVertically bool +} + type adminTemplateData struct { App *application Request templateRequestData @@ -232,6 +245,10 @@ type adminTemplateData struct { DocsURL string IsNew bool ErrorMessage string + + PageSettings *adminPageSettings + IsFirstPage bool + IsLastPage bool } func (a *application) handleAdminIndex(w http.ResponseWriter, r *http.Request) { @@ -240,6 +257,7 @@ func (a *application) handleAdminIndex(w http.ResponseWriter, r *http.Request) { } pages := a.freshPagesFromDisk() + last := len(pages) - 1 summaries := make([]adminPageSummary, 0, len(pages)) for p := range pages { page := &pages[p] @@ -251,6 +269,8 @@ func (a *application) handleAdminIndex(w http.ResponseWriter, r *http.Request) { Title: page.Title, Slug: page.Slug, WidgetCount: count, + IsFirst: p == 0, + IsLast: p == last, }) } @@ -363,6 +383,36 @@ func (a *application) handleAdminWidget(w http.ResponseWriter, r *http.Request) }) } +func (a *application) handleAdminPageSettings(w http.ResponseWriter, r *http.Request) { + if !a.adminAccessAllowed(w, r) { + return + } + pages := a.freshPagesFromDisk() + page, idx, exists := findPageBySlug(pages, r.PathValue("page")) + if !exists { + a.handleNotFound(w, r) + return + } + settings := &adminPageSettings{ + Name: page.Title, + Slug: page.Slug, + Width: page.Width, + DesktopNavigationWidth: page.DesktopNavigationWidth, + ShowMobileHeader: page.ShowMobileHeader, + HideDesktopNavigation: page.HideDesktopNavigation, + CenterVertically: page.CenterVertically, + } + data := adminTemplateData{ + App: a, + Page: &adminPageDetail{Title: page.Title, Slug: page.Slug}, + PageSettings: settings, + IsFirstPage: idx == 0, + IsLastPage: idx == len(pages)-1, + } + a.populateTemplateRequestData(&data.Request, r) + renderAdminTemplate(w, adminPageSettingsTemplate, data) +} + func (a *application) handleAdminNewWidget(w http.ResponseWriter, r *http.Request) { if !a.adminAccessAllowed(w, r) { return diff --git a/internal/glance/glance.go b/internal/glance/glance.go index 0a22afd..5620d5a 100644 --- a/internal/glance/glance.go +++ b/internal/glance/glance.go @@ -454,6 +454,7 @@ func (a *application) server() (func() error, func() error) { mux.HandleFunc("GET /edit", a.handleAdminIndex) mux.HandleFunc("GET /edit/pages/{page}", a.handleAdminPage) + mux.HandleFunc("GET /edit/pages/{page}/settings", a.handleAdminPageSettings) mux.HandleFunc("GET /edit/pages/{page}/widgets/{col}/{idx}", a.handleAdminWidget) mux.HandleFunc("POST /edit/api/pages", a.handleAdminAddPage) @@ -474,6 +475,8 @@ func (a *application) server() (func() error, func() error) { mux.HandleFunc("POST /edit/api/pages/{page}/columns/{col}/delete", a.handleAdminDeleteColumn) mux.HandleFunc("POST /edit/api/pages/{page}/columns/{col}/move", a.handleAdminMoveColumn) mux.HandleFunc("POST /edit/api/pages/{page}/columns/{col}/size", a.handleAdminColumnSize) + mux.HandleFunc("POST /edit/api/pages/{page}/fields", a.handleAdminUpdatePageFields) + mux.HandleFunc("POST /edit/api/pages/{page}/move", a.handleAdminMovePage) if a.RequiresAuth { mux.HandleFunc("GET /login", a.handleLoginPageRequest) diff --git a/internal/glance/templates/admin-page-settings.html b/internal/glance/templates/admin-page-settings.html new file mode 100644 index 0000000..a114d78 --- /dev/null +++ b/internal/glance/templates/admin-page-settings.html @@ -0,0 +1,70 @@ +{{- template "document.html" . }} + +{{- define "document-title" }}Settings: {{ .Page.Title }} - {{ .App.Config.Branding.AppName }}{{ end }} + +{{- define "document-body" }} +
+
+ +

Page settings

+ +
+ + + + + + + + + + + + + + + +
+ + Cancel +
+
+
+ {{ template "footer.html" . }} +
+{{- end }} diff --git a/internal/glance/templates/admin-page.html b/internal/glance/templates/admin-page.html index 7c74365..03e4c5e 100644 --- a/internal/glance/templates/admin-page.html +++ b/internal/glance/templates/admin-page.html @@ -8,7 +8,12 @@
← Edit
-

{{ .Page.Title }}

+
+

{{ .Page.Title }}

+ + + +

/{{ .Page.Slug }}

{{- if .Page.HeadWidgets }} diff --git a/internal/glance/templates/admin.html b/internal/glance/templates/admin.html index c54073d..a91d407 100644 --- a/internal/glance/templates/admin.html +++ b/internal/glance/templates/admin.html @@ -21,6 +21,15 @@ — {{ .WidgetCount }} widget{{ if ne .WidgetCount 1 }}s{{ end }}
/{{ .Slug }}
+
+ +
+
+ +
+ + +