diff --git a/editor.go b/editor.go index d8319ba..1227473 100644 --- a/editor.go +++ b/editor.go @@ -49,7 +49,10 @@ func (a *goBlog) serveEditorPost(w http.ResponseWriter, r *http.Request) { if action == "updatepost" { reqBody["action"] = micropub.ActionUpdate reqBody["url"] = r.FormValue("url") - reqBody["replace"] = map[string][]string{"content": {r.FormValue("content")}} + reqBody["replace"] = map[string][]string{ + "content": {r.FormValue("content")}, + "goblog-editor": r.Form["options"], + } } else { blog, _ := a.getBlog(r) reqBody["type"] = []string{"h-entry"} diff --git a/editorWebsocket.go b/editorWebsocket.go index 2307400..c4214c1 100644 --- a/editorWebsocket.go +++ b/editorWebsocket.go @@ -166,7 +166,7 @@ func (a *goBlog) sendEditorPreview(ctx context.Context, c *ws.Conn, blog string, _, werr := io.WriteString(w, err.Error()) return errors.Join(werr, w.Close()) } - if err := a.checkPost(p, true); err != nil { + if err := a.checkPost(p, true, true); err != nil { _, werr := io.WriteString(w, err.Error()) return errors.Join(werr, w.Close()) } diff --git a/go.mod b/go.mod index 9db9cd5..325af67 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( git.jlel.se/jlelse/go-shutdowner v0.0.0-20210707065515-773db8099c30 git.jlel.se/jlelse/goldmark-mark v0.0.0-20210522162520-9788c89266a4 git.jlel.se/jlelse/template-strings v0.0.0-20220211095702-c012e3b5045b - github.com/PuerkitoBio/goquery v1.10.0 + github.com/PuerkitoBio/goquery v1.10.1 github.com/alecthomas/chroma/v2 v2.14.0 github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500 @@ -18,8 +18,8 @@ require ( github.com/elnormous/contenttype v1.0.4 github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 github.com/emersion/go-smtp v0.21.3 - github.com/go-ap/activitypub v0.0.0-20241223153938-9c1f6077f836 - github.com/go-ap/client v0.0.0-20241212174032-4826270ad6a3 + github.com/go-ap/activitypub v0.0.0-20241228090954-75890bd9cfda + github.com/go-ap/client v0.0.0-20241228091406-581647f214a8 github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73 github.com/go-chi/chi/v5 v5.2.0 github.com/go-fed/httpsig v1.1.0 diff --git a/go.sum b/go.sum index 8eab2a8..59a2a26 100644 --- a/go.sum +++ b/go.sum @@ -16,8 +16,8 @@ git.sr.ht/~mariusor/lw v0.0.0-20241117105956-4b4009e28502 h1:vHrLbbAzwxsIqPiIuYD git.sr.ht/~mariusor/lw v0.0.0-20241117105956-4b4009e28502/go.mod h1:kXJ4JsgGBu7IVBKlrVvGjSLJmpsAGqZwq/JU/kTUaLw= git.sr.ht/~mariusor/mask v0.0.0-20240327084502-ef2a9438457e h1:IrPLQI6txX0tCcdTBNjvF16UZk6SFb9MqyVE0TKVFJ4= git.sr.ht/~mariusor/mask v0.0.0-20240327084502-ef2a9438457e/go.mod h1:Mw0HVQc45uMVOiZNDngXg6zQiO2h/yTsNhI5cm0uk3A= -github.com/PuerkitoBio/goquery v1.10.0 h1:6fiXdLuUvYs2OJSvNRqlNPoBm6YABE226xrbavY5Wv4= -github.com/PuerkitoBio/goquery v1.10.0/go.mod h1:TjZZl68Q3eGHNBA8CWaxAN7rOU1EbDz3CWuolcO5Yu4= +github.com/PuerkitoBio/goquery v1.10.1 h1:Y8JGYUkXWTGRB6Ars3+j3kN0xg1YqqlwvdTV8WTFQcU= +github.com/PuerkitoBio/goquery v1.10.1/go.mod h1:IYiHrOMps66ag56LEH7QYDDupKXyo5A8qrjIx3ZtujY= github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE= github.com/alecthomas/assert/v2 v2.7.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/chroma/v2 v2.14.0 h1:R3+wzpnUArGcQz7fCETQBzO5n9IMNi13iIs46aU4V9E= @@ -66,10 +66,10 @@ github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/ github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/go-ap/activitypub v0.0.0-20241223153938-9c1f6077f836 h1:LrcUX5TUtk5sRkSs7OE0gzXRBy4wU1nu6hC6plcniuc= -github.com/go-ap/activitypub v0.0.0-20241223153938-9c1f6077f836/go.mod h1:5UpNlwO44EF+iKBvyr2djy6vKwsvai9h6KH6N9OL2ZI= -github.com/go-ap/client v0.0.0-20241212174032-4826270ad6a3 h1:nMF0d9OEjUyaxNB1wvAvRILxVXklnc8nF0vegPLoNg8= -github.com/go-ap/client v0.0.0-20241212174032-4826270ad6a3/go.mod h1:aEowIV4wYt3Y6HexHcRA9JfSx08J8VemNR1RTd+TehQ= +github.com/go-ap/activitypub v0.0.0-20241228090954-75890bd9cfda h1:BcuDVID/DpaTdPn5jjuaS9d2U1g2/agTRNEQu+fgU6Q= +github.com/go-ap/activitypub v0.0.0-20241228090954-75890bd9cfda/go.mod h1:5UpNlwO44EF+iKBvyr2djy6vKwsvai9h6KH6N9OL2ZI= +github.com/go-ap/client v0.0.0-20241228091406-581647f214a8 h1:C15a668u8xRLBiWcq1cUjEH4Ykh8VvgJgsSFYBnsAME= +github.com/go-ap/client v0.0.0-20241228091406-581647f214a8/go.mod h1:zO+J2RiLS1yP2mmEnC1EQC9j4YNMrXUKsQXPXu8czSE= github.com/go-ap/errors v0.0.0-20241212155021-5a598b6bf467 h1:sltFaR21xTEWIfw/UCfiNb2d7ET0YIQyTv4v1hVUlTw= github.com/go-ap/errors v0.0.0-20241212155021-5a598b6bf467/go.mod h1:Vkh+Z3f24K8nMsJKXo1FHn5ebPsXvB/WDH5JRtYqdNo= github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73 h1:GMKIYXyXPGIp+hYiWOhfqK4A023HdgisDT4YGgf99mw= diff --git a/micropub.go b/micropub.go index caa762a..ff10467 100644 --- a/micropub.go +++ b/micropub.go @@ -13,6 +13,7 @@ import ( "path/filepath" "reflect" "regexp" + "slices" "strings" "github.com/samber/lo" @@ -187,6 +188,11 @@ func (s *micropubImplementation) Create(req *micropub.Request) (string, error) { } func (s *micropubImplementation) Update(req *micropub.Request) (string, error) { + // Get editor options + editorOptions := req.Updates.Replace["goblog-editor"] + if editorOptions == nil { + editorOptions = []any{} + } // Get post url, err := urlpkg.Parse(req.URL) if err != nil { @@ -219,7 +225,7 @@ func (s *micropubImplementation) Update(req *micropub.Request) (string, error) { if err != nil { return "", fmt.Errorf("%w: %w", micropub.ErrBadRequest, err) } - err = s.a.replacePost(entry, oldPath, oldStatus, oldVisibility) + err = s.a.replacePost(entry, oldPath, oldStatus, oldVisibility, slices.Contains(editorOptions, "noupdated")) if err != nil { return "", fmt.Errorf("%w: %w", micropub.ErrBadRequest, err) } @@ -407,6 +413,7 @@ func micropubVisibility(visibility string) postVisibility { func micropubUpdateMfProperties(properties map[string][]any, req micropub.RequestUpdate) (map[string][]any, error) { if req.Replace != nil { + delete(req.Replace, "goblog-editor") for key, value := range req.Replace { properties[key] = value } diff --git a/postsDb.go b/postsDb.go index d5519e3..0062196 100644 --- a/postsDb.go +++ b/postsDb.go @@ -16,7 +16,7 @@ import ( "go.goblog.app/app/pkgs/builderpool" ) -func (a *goBlog) checkPost(p *post, new bool) (err error) { +func (a *goBlog) checkPost(p *post, new, noUpdated bool) (err error) { if p == nil { return errors.New("no post") } @@ -64,12 +64,14 @@ func (a *goBlog) checkPost(p *post, new bool) (err error) { p.Published = nowString } // Maybe set updated date - if !new && p.Published != "" { + if new || noUpdated { + // Do nothing + } else if p.Published != "" { if published, err := dateparse.ParseLocal(p.Published); err == nil && now.After(published) { // Has published date in the past, so add updated date p.Updated = nowString } - } else if !new && p.Updated != "" { + } else if p.Updated != "" { if updated, err := dateparse.ParseLocal(p.Updated); err == nil && now.After(updated) { // Has updated date in the past, so add new updated date p.Updated = nowString @@ -158,20 +160,26 @@ func (a *goBlog) createPost(p *post) error { return a.createOrReplacePost(p, &postCreationOptions{new: true}) } -func (a *goBlog) replacePost(p *post, oldPath string, oldStatus postStatus, oldVisibility postVisibility) error { - return a.createOrReplacePost(p, &postCreationOptions{new: false, oldPath: oldPath, oldStatus: oldStatus, oldVisibility: oldVisibility}) +func (a *goBlog) replacePost(p *post, oldPath string, oldStatus postStatus, oldVisibility postVisibility, noUpdated bool) error { + return a.createOrReplacePost(p, &postCreationOptions{ + new: false, + oldPath: oldPath, + oldStatus: oldStatus, + oldVisibility: oldVisibility, + noUpdated: noUpdated, + }) } type postCreationOptions struct { - new bool - oldPath string - oldStatus postStatus - oldVisibility postVisibility + new, noUpdated bool + oldPath string + oldStatus postStatus + oldVisibility postVisibility } func (a *goBlog) createOrReplacePost(p *post, o *postCreationOptions) error { // Check post - if err := a.checkPost(p, o.new); err != nil { + if err := a.checkPost(p, o.new, o.noUpdated); err != nil { return err } // Save to db diff --git a/postsDb_test.go b/postsDb_test.go index 2b29642..c0da3d7 100644 --- a/postsDb_test.go +++ b/postsDb_test.go @@ -434,7 +434,7 @@ func Test_checkPost(t *testing.T) { t.Run("New post should get published date", func(t *testing.T) { p := &post{} - app.checkPost(p, true) + app.checkPost(p, true, false) assert.NotEmpty(t, p.Published) }) @@ -443,7 +443,7 @@ func Test_checkPost(t *testing.T) { p := &post{ Path: "/abc", } - app.checkPost(p, true) + app.checkPost(p, true, false) assert.Empty(t, p.Published) }) @@ -452,7 +452,7 @@ func Test_checkPost(t *testing.T) { p := &post{ Published: time.Now().Local().Add(-1 * time.Hour).Format(time.RFC3339), } - app.checkPost(p, false) + app.checkPost(p, false, false) assert.NotEmpty(t, p.Updated) }) @@ -461,7 +461,7 @@ func Test_checkPost(t *testing.T) { p := &post{ Published: time.Now().Local().Add(time.Hour).Format(time.RFC3339), } - app.checkPost(p, false) + app.checkPost(p, false, false) assert.Empty(t, p.Updated) }) @@ -472,7 +472,7 @@ func Test_checkPost(t *testing.T) { Published: time.Now().Local().Add(-2 * time.Hour).Format(time.RFC3339), Updated: oldUpdate, } - app.checkPost(p, false) + app.checkPost(p, false, false) assert.NotEmpty(t, p.Updated) assert.NotEqual(t, oldUpdate, p.Updated) @@ -483,7 +483,7 @@ func Test_checkPost(t *testing.T) { p := &post{ Updated: oldUpdate, } - app.checkPost(p, false) + app.checkPost(p, false, false) assert.Empty(t, p.Published) assert.NotEmpty(t, p.Updated) @@ -494,7 +494,7 @@ func Test_checkPost(t *testing.T) { p := &post{ Status: "unlisted", } - err := app.checkPost(p, true) + err := app.checkPost(p, true, false) assert.ErrorContains(t, err, "invalid post status") }) @@ -503,7 +503,7 @@ func Test_checkPost(t *testing.T) { p := &post{ Visibility: "published", } - err := app.checkPost(p, true) + err := app.checkPost(p, true, false) assert.ErrorContains(t, err, "invalid post visibility") }) diff --git a/postsScheduler.go b/postsScheduler.go index 1b093ef..b60f861 100644 --- a/postsScheduler.go +++ b/postsScheduler.go @@ -35,7 +35,7 @@ func (a *goBlog) checkScheduledPosts() { } for _, post := range postsToPublish { post.Status = statusPublished - err := a.replacePost(post, post.Path, statusScheduled, post.Visibility) + err := a.replacePost(post, post.Path, statusScheduled, post.Visibility, true) if err != nil { a.error("Error publishing scheduled post", "err", err) continue diff --git a/postsScheduler_test.go b/postsScheduler_test.go index f67c2cb..24d3668 100644 --- a/postsScheduler_test.go +++ b/postsScheduler_test.go @@ -62,4 +62,10 @@ func Test_postsScheduler(t *testing.T) { assert.Equal(t, 1, postHook) assert.Equal(t, 0, updateHook) + // Check that the published post has no updated date + p, err := app.db.a.getPost("/test/abc") + require.NoError(t, err) + assert.NotEmpty(t, p.Published) + assert.Empty(t, p.Updated) + } diff --git a/reactions_test.go b/reactions_test.go index d015f4e..1438e27 100644 --- a/reactions_test.go +++ b/reactions_test.go @@ -50,7 +50,7 @@ func Test_reactionsLowLevel(t *testing.T) { Path: "/newpost", Content: "test", Status: statusPublished, - }, "/testpost", statusPublished, visibilityPublic) + }, "/testpost", statusPublished, visibilityPublic, false) require.NoError(t, err) // Check if reaction count is 4 diff --git a/strings/de.yaml b/strings/de.yaml index 28c93f6..8a8117f 100644 --- a/strings/de.yaml +++ b/strings/de.yaml @@ -23,6 +23,7 @@ deletedposts: "Gelöschte Posts" deletedpostsdesc: "Gelöschte Posts, die nach 7 Tagen endgültig gelöscht werden." deletepasskey: "Passkey löschen" docomment: "Kommentieren" +donotsetupdated: "Den Aktualisierungszeitstempel nicht ändern" download: "Herunterladen" drafts: "Entwürfe" draftsdesc: "Posts mit dem Status `draft`." diff --git a/strings/default.yaml b/strings/default.yaml index eef0b19..441a055 100644 --- a/strings/default.yaml +++ b/strings/default.yaml @@ -29,6 +29,7 @@ deletedposts: "Deleted posts" deletedpostsdesc: "Deleted posts that will be permanently deleted after 7 days." deletepasskey: "Delete Passkey" docomment: "Comment" +donotsetupdated: "Do not change the update timestamp" download: "Download" drafts: "Drafts" draftsdesc: "Posts with status `draft`." diff --git a/ui.go b/ui.go index 62d6602..1c6a87f 100644 --- a/ui.go +++ b/ui.go @@ -1446,6 +1446,9 @@ func (a *goBlog) renderEditor(hb *htmlbuilder.HtmlBuilder, rd *renderData) { ) hb.WriteEscaped(edrd.updatePostContent) hb.WriteElementClose("textarea") + hb.WriteElementOpen("input", "type", "checkbox", "name", "options", "value", "noupdated") + hb.WriteEscaped(a.ts.GetTemplateStringVariant(rd.Blog.Lang, "donotsetupdated")) + hb.WriteElementClose("input") hb.WriteElementOpen("div", "id", "update-preview", "class", "hide") hb.WriteElementClose("div") hb.WriteElementOpen("input", "type", "submit", "value", a.ts.GetTemplateStringVariant(rd.Blog.Lang, "update"))