First cut of the link fields

This commit is contained in:
Leon Mika 2025-03-02 10:46:36 +11:00
parent c5925e16e0
commit 38ebb21a34
8 changed files with 145 additions and 35 deletions

View file

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

11
go.mod
View file

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

1
go.sum
View file

@ -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=

View file

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

View file

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

View file

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

View file

@ -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"`
}

View file

@ -3,11 +3,23 @@
{{- $postTarget = printf "/sites/%v/posts/%v" .site.ID .post.ID -}}
{{- end -}}
<form method="post" action="{{$postTarget}}" class="post-form">
<input name="title" placeholder="Title" value="{{.post.Title}}">
<textarea name="body">{{.post.Body}}</textarea>
<div class="bottom-bar">
<div class="main-area">
<input name="title" type="text" placeholder="Title" value="{{.post.Title}}">
<textarea name="body">{{.post.Body}}</textarea>
</div>
<div class="right-area">
<div class="section">
<div class="section-heading">Link</div>
<textarea name="params.link_url" type="text" placeholder="URL"></textarea>
<textarea name="params.link_title" type="text" placeholder="Title"></textarea>
</div>
<div class="section">
<div class="section-heading">Via</div>
<textarea name="params.via_url" type="text" placeholder="URL"></textarea>
<textarea name="params.via_title" type="text" placeholder="Title"></textarea>
</div>
</div>
<div class="bottom-area">
<input type="submit" value="Post">
</div>
</form>