diff --git a/assets/css/main.css b/assets/css/main.css index 8fd49e2..9970bba 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -2,6 +2,14 @@ html { font-family: -apple-system, BlinkMacSystemFont, "Avenir Next", Avenir, "Nimbus Sans L", Roboto, "Noto Sans", "Segoe UI", Arial, Helvetica, "Helvetica Neue", sans-serif; + font-size: 1.1em; +} + +/** + * Basic styling + */ +input, select, textarea { + padding: 4px; } body.role-site { @@ -44,15 +52,52 @@ div.post { border-bottom: solid thin grey; } - +/** + * Post form + */ form.post-form { - display: flex; + display: grid; + grid-template-columns: auto 23vw; + grid-template-rows: auto 2.5em; + gap: 6px; + flex-direction: column; height: 100%; } +form.post-form input[type='text'] { + margin-bottom: 6px; +} + +form.post-form div.main-area { + grid-column-start: 1; + grid-column-end: 2; + grid-row-start: 1; + grid-row-end: 2; + + display: flex; + flex-direction: column; +} + +div.right-area div.section { + margin-bottom: 24px; +} + +div.right-area div.section input { + width: 100%; +} + +div.right-area div.section div.section-heading { + margin-bottom: 8px; +} + form.post-form textarea { + width: 100%; + min-height: 4vh; +} + +form.post-form textarea[name="body"] { flex-grow: 1; flex-shrink: 1; } \ No newline at end of file diff --git a/go.mod b/go.mod index 9e10103..a21fd7b 100644 --- a/go.mod +++ b/go.mod @@ -3,18 +3,22 @@ module lmika.dev/lmika/hugo-cms go 1.23.3 require ( + github.com/Netflix/go-env v0.1.2 + github.com/davecgh/go-spew v1.1.1 + github.com/gofiber/fiber/v3 v3.0.0-beta.4 github.com/golang-migrate/migrate/v4 v4.18.1 github.com/jackc/pgx/v5 v5.7.2 + golang.org/x/crypto v0.32.0 + gopkg.in/yaml.v3 v3.0.1 + lmika.dev/pkg/modash v0.0.0-20250216001243-c73e50a0913d ) require ( - github.com/Netflix/go-env v0.1.2 // indirect github.com/andybalholm/brotli v1.1.1 // indirect github.com/coreos/go-oidc/v3 v3.12.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-jose/go-jose/v4 v4.0.2 // indirect github.com/gofiber/fiber/v2 v2.52.6 // indirect - github.com/gofiber/fiber/v3 v3.0.0-beta.4 // indirect github.com/gofiber/schema v1.2.0 // indirect github.com/gofiber/template v1.8.3 // indirect github.com/gofiber/template/html/v2 v2.1.3 // indirect @@ -41,12 +45,9 @@ require ( github.com/x448/float16 v0.8.4 // indirect github.com/yuin/goldmark v1.7.8 // indirect go.uber.org/atomic v1.7.0 // indirect - golang.org/x/crypto v0.32.0 // indirect golang.org/x/net v0.34.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/sync v0.10.0 // indirect golang.org/x/sys v0.29.0 // indirect golang.org/x/text v0.21.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect - lmika.dev/pkg/modash v0.0.0-20250216001243-c73e50a0913d // indirect ) diff --git a/go.sum b/go.sum index fcd8439..b26b12f 100644 --- a/go.sum +++ b/go.sum @@ -5,6 +5,7 @@ github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOL github.com/coreos/go-oidc/v3 v3.12.0 h1:sJk+8G2qq94rDI6ehZ71Bol3oUHy63qNYmkiSjrc/Jo= github.com/coreos/go-oidc/v3 v3.12.0/go.mod h1:gE3LgjOgFoHi9a4ce4/tJczr0Ai2/BoDhf0r5lltWI0= 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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= diff --git a/handlers/post.go b/handlers/post.go index 999f2ad..4e98227 100644 --- a/handlers/post.go +++ b/handlers/post.go @@ -3,10 +3,12 @@ package handlers import ( "errors" "fmt" + "github.com/davecgh/go-spew/spew" "github.com/gofiber/fiber/v3" "lmika.dev/lmika/hugo-cms/models" "lmika.dev/lmika/hugo-cms/services/posts" "net/http" + "strings" ) type Post struct { @@ -35,18 +37,12 @@ func (h *Post) New(c fiber.Ctx) error { func (h *Post) Create(c fiber.Ctx) error { site := GetSite(c) - var req struct { - Title string `json:"title" form:"title"` - Body string `json:"body" form:"body"` - } - if err := c.Bind().Body(&req); err != nil { + req, err := h.parseReqToNewPost(c) + if err != nil { return err } - _, err := h.Post.Create(c.Context(), site, posts.NewPost{ - Title: req.Title, - Body: req.Body, - }) + _, err = h.Post.Create(c.Context(), site, req) if err != nil { return err } @@ -82,14 +78,13 @@ func (h *Post) Update(c fiber.Ctx) error { return errors.New("postId is required") } - var req struct { - Title string `json:"title" form:"title"` - Body string `json:"body" form:"body"` - } - if err := c.Bind().Body(&req); err != nil { + req, err := h.parseReqToNewPost(c) + if err != nil { return err } + spew.Dump(req) + post, err := h.Post.GetPost(c.Context(), postID) if err != nil { return err @@ -128,3 +123,26 @@ func (h *Post) Delete(c fiber.Ctx) error { }), ) } + +func (h *Post) parseReqToNewPost(c fiber.Ctx) (req posts.NewPost, err error) { + reqMap := make(map[string]string) + if err := c.Bind().Body(&reqMap); err != nil { + return req, err + } + + for k, v := range reqMap { + switch { + case k == "title": + req.Title = v + case k == "body": + req.Body = v + case strings.HasPrefix(k, "params."): + kk := strings.TrimPrefix(k, "params.") + if req.Params == nil { + req.Params = make(map[string]string) + } + req.Params[kk] = v + } + } + return req, nil +} diff --git a/models/posts.go b/models/posts.go index 83bb663..4d1a24a 100644 --- a/models/posts.go +++ b/models/posts.go @@ -15,6 +15,7 @@ type Post struct { OwnerID int64 Title string Body string + Params map[string]string State PostState PublishDate time.Time CreatedAt time.Time diff --git a/providers/db/posts.go b/providers/db/posts.go index 45b9924..1b0f200 100644 --- a/providers/db/posts.go +++ b/providers/db/posts.go @@ -2,6 +2,7 @@ package db import ( "context" + "encoding/json" "github.com/jackc/pgx/v5/pgtype" "lmika.dev/lmika/hugo-cms/gen/sqlc/dbq" "lmika.dev/lmika/hugo-cms/models" @@ -15,7 +16,7 @@ func (db *DB) ListPostsOfSite(ctx context.Context, siteID int64) ([]models.Post, return nil, err } - return moslice.Map(res, dbPostToPost), nil + return moslice.MapWithError(res, dbPostToPost) } func (db *DB) GetPost(ctx context.Context, postID int64) (models.Post, error) { @@ -24,7 +25,7 @@ func (db *DB) GetPost(ctx context.Context, postID int64) (models.Post, error) { return models.Post{}, err } - return dbPostToPost(res), nil + return dbPostToPost(res) } func (db *DB) DeletePost(ctx context.Context, postID int64) error { @@ -41,16 +42,21 @@ func (db *DB) ListPublishablePosts(ctx context.Context, fromID, siteID int64, no return nil, err } - return moslice.Map(res, dbPostToPost), nil + return moslice.MapWithError(res, dbPostToPost) } func (db *DB) InsertPost(ctx context.Context, p *models.Post) error { + props, err := marshalPostProps(p) + if err != nil { + return err + } + res, err := db.q.InsertPost(ctx, dbq.InsertPostParams{ SiteID: p.SiteID, Title: pgtype.Text{String: p.Title, Valid: p.Title != ""}, Body: p.Body, State: dbq.PostState(p.State), - Props: []byte(`{}`), + Props: props, PublishDate: pgtype.Timestamptz{Time: p.PublishDate, Valid: !p.PublishDate.IsZero()}, CreatedAt: pgtype.Timestamp{Time: p.CreatedAt, Valid: !p.CreatedAt.IsZero()}, UpdatedAt: pgtype.Timestamp{Time: p.UpdatedAt, Valid: !p.UpdatedAt.IsZero()}, @@ -64,19 +70,44 @@ func (db *DB) InsertPost(ctx context.Context, p *models.Post) error { } func (db *DB) UpdatePost(ctx context.Context, p *models.Post) error { + props, err := marshalPostProps(p) + if err != nil { + return err + } + return db.q.UpdatePost(ctx, dbq.UpdatePostParams{ ID: p.ID, SiteID: p.SiteID, Title: pgtype.Text{String: p.Title, Valid: p.Title != ""}, Body: p.Body, State: dbq.PostState(p.State), - Props: []byte(`{}`), + Props: props, PublishDate: pgtype.Timestamptz{Time: p.PublishDate, Valid: !p.PublishDate.IsZero()}, UpdatedAt: pgtype.Timestamp{Time: p.UpdatedAt, Valid: !p.UpdatedAt.IsZero()}, }) } -func dbPostToPost(p dbq.Post) models.Post { +func marshalPostProps(p *models.Post) ([]byte, error) { + var props []byte + if len(p.Params) == 0 { + props = []byte(`{}`) + } else { + var err error + props, err = json.Marshal(p.Params) + if err != nil { + return nil, err + } + } + return props, nil +} +func dbPostToPost(p dbq.Post) (models.Post, error) { + postProps := map[string]string{} + if len(p.Props) != 0 { + if err := json.Unmarshal(p.Props, &postProps); err != nil { + return models.Post{}, err + } + } + return models.Post{ ID: p.ID, SiteID: p.SiteID, @@ -85,5 +116,5 @@ func dbPostToPost(p dbq.Post) models.Post { State: models.PostState(p.State), PublishDate: p.PublishDate.Time, CreatedAt: p.CreatedAt.Time, - } + }, nil } diff --git a/services/posts/services.go b/services/posts/services.go index cecb0b9..c498597 100644 --- a/services/posts/services.go +++ b/services/posts/services.go @@ -89,6 +89,7 @@ func (s *Service) Save(ctx context.Context, site models.Site, post *models.Post) } type NewPost struct { - Title string - Body string + Title string `json:"title" form:"title"` + Body string `json:"body" form:"body"` + Params map[string]string `json:"params" form:"params"` } diff --git a/templates/posts/new.html b/templates/posts/new.html index 350d71e..b20526c 100644 --- a/templates/posts/new.html +++ b/templates/posts/new.html @@ -3,11 +3,23 @@ {{- $postTarget = printf "/sites/%v/posts/%v" .site.ID .post.ID -}} {{- end -}}