First cut of the link fields
This commit is contained in:
parent
c5925e16e0
commit
38ebb21a34
|
@ -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
11
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
|
||||
)
|
||||
|
|
1
go.sum
1
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=
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue