From 4aa09ce50267b9814f84675afd641eb0e8c69498 Mon Sep 17 00:00:00 2001 From: Maximilian Friedersdorff Date: Fri, 25 Jul 2025 13:44:21 +0100 Subject: [PATCH] Attempt to implement checkbox shitter --- Makefile | 6 ++ go.mod | 1 + go.sum | 6 ++ internal/conf/templates/view.tmpl.html | 19 +++++- internal/notes/notes.go | 81 +++++++++++++++++++++++++- internal/notes/views/views.go | 43 ++++++++++---- 6 files changed, 143 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index 5f145c6..c3f15ea 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,11 @@ +# vim: noexpandtab ai sw=4 ts=4 all: build build: go run cmd/fetch-static/main.go go build -o gonotes cmd/server/main.go + +run: + ./gonotes -c ./conf.toml + +dev: build run diff --git a/go.mod b/go.mod index 3993305..b383f6e 100644 --- a/go.mod +++ b/go.mod @@ -7,4 +7,5 @@ require github.com/yuin/goldmark v1.7.8 require ( forgejo.gwairfelin.com/max/gispatcho v0.1.2 github.com/pelletier/go-toml/v2 v2.2.3 + github.com/teekennedy/goldmark-markdown v0.5.1 ) diff --git a/go.sum b/go.sum index ea380da..42e8514 100644 --- a/go.sum +++ b/go.sum @@ -6,9 +6,15 @@ github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNH github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rhysd/go-fakeio v1.0.0 h1:+TjiKCOs32dONY7DaoVz/VPOdvRkPfBkEyUDIpM8FQY= +github.com/rhysd/go-fakeio v1.0.0/go.mod h1:joYxF906trVwp2JLrE4jlN7A0z6wrz8O6o1UjarbFzE= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/teekennedy/goldmark-markdown v0.5.1 h1:2lIlJ3AcIwaD1wFl4dflJSJFMhRTKEsEj+asVsu6M/0= +github.com/teekennedy/goldmark-markdown v0.5.1/go.mod h1:so260mNSPELuRyynZY18719dRYlD+OSnAovqsyrOMOM= github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= +go.abhg.dev/goldmark/toc v0.11.0 h1:IRixVy3/yVPKvFBc37EeBPi8XLTXrtH6BYaonSjkF8o= +go.abhg.dev/goldmark/toc v0.11.0/go.mod h1:XMFIoI1Sm6dwF9vKzVDOYE/g1o5BmKXghLG8q/wJNww= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/conf/templates/view.tmpl.html b/internal/conf/templates/view.tmpl.html index 2176027..48de3fd 100644 --- a/internal/conf/templates/view.tmpl.html +++ b/internal/conf/templates/view.tmpl.html @@ -7,5 +7,22 @@ Edit Delete -{{end}} + +{{end}} diff --git a/internal/notes/notes.go b/internal/notes/notes.go index 5791ce1..81804eb 100644 --- a/internal/notes/notes.go +++ b/internal/notes/notes.go @@ -12,8 +12,14 @@ import ( "path/filepath" "forgejo.gwairfelin.com/max/gonotes/internal/conf" + markdown "github.com/teekennedy/goldmark-markdown" "github.com/yuin/goldmark" + "github.com/yuin/goldmark/ast" "github.com/yuin/goldmark/extension" + astext "github.com/yuin/goldmark/extension/ast" + "github.com/yuin/goldmark/parser" + "github.com/yuin/goldmark/text" + "github.com/yuin/goldmark/util" ) // Note is the central data structure. It can be Saved, Rendered and Loaded @@ -50,7 +56,7 @@ func (n *Note) Render() { n.BodyRendered = template.HTML(buf.String()) } -// Load a note from the disk. The path is derived from the title +// LoadNote from the disk. The path is derived from the title func LoadNote(encodedTitle string) (*Note, error) { filename := filepath.Join(conf.Conf.NotesDir, fmtPath(encodedTitle)) body, err := os.ReadFile(filename) @@ -70,6 +76,79 @@ func (n *Note) EncodedTitle() string { return base64.StdEncoding.EncodeToString([]byte(n.Title)) } +type toggleCheckboxTransformer struct{ nthBox int } + +func (t *toggleCheckboxTransformer) Transform(node *ast.Document, reader text.Reader, pc parser.Context) { + boxesFound := 0 + + // Walk to find checkbox, toggle checked status + ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) { + if !entering { + return ast.WalkContinue, nil + } + + box, ok := n.(*astext.TaskCheckBox) + if !ok { + return ast.WalkContinue, nil + } + + if boxesFound < t.nthBox { + boxesFound += 1 + return ast.WalkContinue, nil + } + + box.IsChecked = !box.IsChecked + return ast.WalkStop, nil + }) +} + +func (r *markdown.Renderer) renderTaskCheckBox(node ast.Node, entering bool) ast.WalkStatus { + if entering { + var itemPrefix []byte + l := r.rc.lists[len(r.rc.lists)-1] + + if l.list.IsOrdered() { + itemPrefix = append(itemPrefix, []byte(fmt.Sprint(l.num))...) + r.rc.lists[len(r.rc.lists)-1].num += 1 + } + itemPrefix = append(itemPrefix, l.list.Marker, ' ') + // Prefix the current line with the item prefix + r.rc.writer.PushPrefix(itemPrefix, 0, 0) + // Prefix subsequent lines with padding the same length as the item prefix + indentLen := int(max(r.config.NestedListLength, NestedListLengthMinimum)) + indent := bytes.Repeat([]byte{' '}, indentLen) + r.rc.writer.PushPrefix(bytes.Repeat(indent, len(itemPrefix)), 1) + } else { + r.rc.writer.PopPrefix() + r.rc.writer.PopPrefix() + } + return ast.WalkContinue +} + +func (n *Note) ToggleBox(nthBox int) { + log.Printf("Toggling %dth box", nthBox) + + checkboxTransformer := toggleCheckboxTransformer{nthBox: nthBox} + transformer := util.Prioritized(&checkboxTransformer, 0) + + renderer := markdown.NewRenderer() + renderer.Register(astext.KindTaskCheckBox, func(writer util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) { + }) + + var buf bytes.Buffer + md := goldmark.New( + goldmark.WithExtensions( + extension.TaskList, + ), + goldmark.WithRenderer(markdown.NewRenderer()), + goldmark.WithParserOptions(parser.WithASTTransformers(transformer)), + ) + + md.Convert(n.Body, &buf) + n.Body = buf.Bytes() + n.Save() +} + func DecodeTitle(encodedTitle string) string { title, err := base64.StdEncoding.DecodeString(encodedTitle) if err != nil { diff --git a/internal/notes/views/views.go b/internal/notes/views/views.go index c5f3ea6..60e7f62 100644 --- a/internal/notes/views/views.go +++ b/internal/notes/views/views.go @@ -5,6 +5,7 @@ import ( "net/http" "os" "path/filepath" + "strconv" "strings" urls "forgejo.gwairfelin.com/max/gispatcho" @@ -19,13 +20,14 @@ func GetRoutes(prefix string) *http.ServeMux { myurls = urls.URLs{ Prefix: prefix, URLs: map[string]urls.URL{ - "list": {Path: "/", Protocol: "GET", Handler: list}, - "new": {Path: "/new", Protocol: "GET", Handler: new}, - "view_": {Path: "/{note}", Protocol: "GET", Handler: view}, - "view": {Path: "/{note}/", Protocol: "GET", Handler: view}, - "delete": {Path: "/{note}/delete/", Protocol: "GET", Handler: delete}, - "edit": {Path: "/{note}/edit/", Protocol: "GET", Handler: edit}, - "save": {Path: "/{note}/edit/save/", Protocol: "POST", Handler: save}, + "list": {Path: "/", Protocol: "GET", Handler: list}, + "new": {Path: "/new", Protocol: "GET", Handler: new}, + "view_": {Path: "/{note}", Protocol: "GET", Handler: view}, + "view": {Path: "/{note}/", Protocol: "GET", Handler: view}, + "delete": {Path: "/{note}/delete/", Protocol: "GET", Handler: delete}, + "edit": {Path: "/{note}/edit/", Protocol: "GET", Handler: edit}, + "save": {Path: "/{note}/edit/save/", Protocol: "POST", Handler: save}, + "togglebox": {Path: "/{note}/togglebox/", Protocol: "POST", Handler: togglebox}, }, } return myurls.GetRouter() @@ -109,9 +111,28 @@ func save(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, myurls.Reverse("view", urls.Repl{"note": note.EncodedTitle()}), http.StatusFound) } -type titleAndUrl struct { +func togglebox(w http.ResponseWriter, r *http.Request) { + title := r.PathValue("note") + nthBox, err := strconv.Atoi(r.FormValue("box")) + if err != nil { + log.Fatal("You fucked up boy") + return + } + + note, err := notes.LoadNote(title) + if err != nil { + http.Redirect(w, r, myurls.Reverse("view", urls.Repl{"note": title}), http.StatusFound) + return + } + + note.ToggleBox(nthBox) + + http.Redirect(w, r, myurls.Reverse("view", urls.Repl{"note": note.EncodedTitle()}), http.StatusFound) +} + +type titleAndURL struct { Title string - Url string + URL string } func list(w http.ResponseWriter, r *http.Request) { @@ -122,7 +143,7 @@ func list(w http.ResponseWriter, r *http.Request) { return } - titlesAndUrls := make([]titleAndUrl, 0) + titlesAndUrls := make([]titleAndURL, 0) for _, f := range files { if !f.IsDir() { @@ -133,7 +154,7 @@ func list(w http.ResponseWriter, r *http.Request) { titlesAndUrls = append( titlesAndUrls, - titleAndUrl{Title: title, Url: myurls.Reverse("view", urls.Repl{"note": encodedTitle})}, + titleAndURL{Title: title, URL: myurls.Reverse("view", urls.Repl{"note": encodedTitle})}, ) log.Print(titlesAndUrls) }