Attempt to implement checkbox shitter

This commit is contained in:
Maximilian Friedersdorff 2025-07-25 13:44:21 +01:00
parent 0f2400435f
commit 4aa09ce502
6 changed files with 143 additions and 13 deletions

View file

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

View file

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