Add clickable Markdown checkboxes (#182)

This commit is contained in:
Thomas Miceli
2023-12-26 03:23:47 +01:00
parent 0753c5cb54
commit 85e2da054b
11 changed files with 177 additions and 9 deletions

View File

@ -365,7 +365,7 @@ func (gist *Gist) NbCommits() (string, error) {
}
func (gist *Gist) AddAndCommitFiles(files *[]FileDTO) error {
if err := git.CloneTmp(gist.User.Username, gist.Uuid, gist.Uuid, gist.User.Email); err != nil {
if err := git.CloneTmp(gist.User.Username, gist.Uuid, gist.Uuid, gist.User.Email, true); err != nil {
return err
}
@ -386,6 +386,26 @@ func (gist *Gist) AddAndCommitFiles(files *[]FileDTO) error {
return git.Push(gist.Uuid)
}
func (gist *Gist) AddAndCommitFile(file *FileDTO) error {
if err := git.CloneTmp(gist.User.Username, gist.Uuid, gist.Uuid, gist.User.Email, false); err != nil {
return err
}
if err := git.SetFileContent(gist.Uuid, file.Filename, file.Content); err != nil {
return err
}
if err := git.AddAll(gist.Uuid); err != nil {
return err
}
if err := git.CommitRepository(gist.Uuid, gist.User.Username, gist.User.Email); err != nil {
return err
}
return git.Push(gist.Uuid)
}
func (gist *Gist) ForkClone(username string, uuid string) error {
return git.ForkClone(gist.User.Username, gist.Uuid, username, uuid)
}

View File

@ -201,7 +201,7 @@ func GetLog(user string, gist string, skip int) ([]*Commit, error) {
return parseLog(stdout, truncateLimit), err
}
func CloneTmp(user string, gist string, gistTmpId string, email string) error {
func CloneTmp(user string, gist string, gistTmpId string, email string, remove bool) error {
repositoryPath := RepositoryPath(user, gist)
tmpPath := TmpRepositoriesPath()
@ -219,11 +219,13 @@ func CloneTmp(user string, gist string, gistTmpId string, email string) error {
return err
}
// remove every file (and not the .git directory!)
if err = removeFilesExceptGit(tmpRepositoryPath); err != nil {
return err
// remove every file (keep the .git directory)
// useful when user wants to edit multiple files from an existing gist
if remove {
if err = removeFilesExceptGit(tmpRepositoryPath); err != nil {
return err
}
}
cmd = exec.Command("git", "config", "--local", "user.name", user)
cmd.Dir = tmpRepositoryPath
if err = cmd.Run(); err != nil {

View File

@ -272,7 +272,7 @@ func TestInitViaGitInit(t *testing.T) {
}
func commitToBare(t *testing.T, user string, gist string, files map[string]string) {
err := CloneTmp(user, gist, gist, "thomas@mail.com")
err := CloneTmp(user, gist, gist, "thomas@mail.com", true)
require.NoError(t, err, "Could not commit to repository")
if len(files) > 0 {

View File

@ -1,15 +1,24 @@
package render
import (
"bufio"
"bytes"
"github.com/Kunde21/markdownfmt/v3"
"github.com/alecthomas/chroma/v2/formatters/html"
"github.com/rs/zerolog/log"
"github.com/thomiceli/opengist/internal/db"
"github.com/thomiceli/opengist/internal/git"
"github.com/yuin/goldmark"
emoji "github.com/yuin/goldmark-emoji"
highlighting "github.com/yuin/goldmark-highlighting/v2"
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/extension"
astex "github.com/yuin/goldmark/extension/ast"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/text"
"github.com/yuin/goldmark/util"
"go.abhg.dev/goldmark/mermaid"
"strconv"
)
func MarkdownGistPreview(gist *db.Gist) (RenderedGist, error) {
@ -43,5 +52,62 @@ func newMarkdown() goldmark.Markdown {
emoji.Emoji,
&mermaid.Extender{},
),
goldmark.WithParserOptions(
parser.WithASTTransformers(
util.Prioritized(&CheckboxTransformer{}, 10000),
),
),
)
}
type CheckboxTransformer struct{}
func (t *CheckboxTransformer) Transform(node *ast.Document, _ text.Reader, _ parser.Context) {
i := 1
err := ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
if entering {
if _, ok := n.(*astex.TaskCheckBox); ok {
listitem := n.Parent().Parent()
listitem.SetAttribute([]byte("data-checkbox-nb"), []byte(strconv.Itoa(i)))
i += 1
}
}
return ast.WalkContinue, nil
})
if err != nil {
log.Err(err)
}
}
func Checkbox(content string, checkboxNb int) (string, error) {
buf := bytes.Buffer{}
w := bufio.NewWriter(&buf)
source := []byte(content)
markdown := markdownfmt.NewGoldmark()
reader := text.NewReader(source)
document := markdown.Parser().Parse(reader)
i := 1
err := ast.Walk(document, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
if entering {
if listItem, ok := n.(*astex.TaskCheckBox); ok {
if i == checkboxNb {
listItem.IsChecked = !listItem.IsChecked
}
i += 1
}
}
return ast.WalkContinue, nil
})
if err != nil {
return "", err
}
if err = markdown.Renderer().Render(w, source, document); err != nil {
return "", err
}
_ = w.Flush()
return buf.String(), nil
}

View File

@ -787,3 +787,39 @@ func forks(ctx echo.Context) error {
setData(ctx, "revision", "HEAD")
return html(ctx, "forks.html")
}
func checkbox(ctx echo.Context) error {
filename := ctx.FormValue("file")
checkboxNb := ctx.FormValue("checkbox")
i, err := strconv.Atoi(checkboxNb)
if err != nil {
return errorRes(400, "Invalid number", nil)
}
gist := getData(ctx, "gist").(*db.Gist)
file, err := gist.File("HEAD", filename, false)
if err != nil {
return errorRes(500, "Error getting file content", err)
} else if file == nil {
return notFound("File not found")
}
markdown, err := render.Checkbox(file.Content, i)
if err != nil {
return errorRes(500, "Error checking checkbox", err)
}
if err = gist.AddAndCommitFile(&db.FileDTO{
Filename: filename,
Content: markdown,
}); err != nil {
return errorRes(500, "Error adding and committing files", err)
}
if err = gist.UpdatePreviewAndCount(); err != nil {
return errorRes(500, "Error updating the gist", err)
}
return plainText(ctx, 200, "ok")
}

View File

@ -265,6 +265,7 @@ func NewServer(isDev bool) *Server {
g3.GET("/likes", likes)
g3.POST("/fork", fork, logged)
g3.GET("/forks", forks)
g3.PUT("/checkbox", checkbox, logged, writePermission)
}
}