Skip to content

Make TaskCheckBox render correctly #11214

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 40 additions & 1 deletion modules/markup/markdown/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@

package markdown

import "github.com/yuin/goldmark/ast"
import (
"strconv"

"github.com/yuin/goldmark/ast"
)

// Details is a block that contains Summary and details
type Details struct {
Expand Down Expand Up @@ -70,6 +74,41 @@ func IsSummary(node ast.Node) bool {
return ok
}

// TaskCheckBoxListItem is a block that repressents a list item of a markdown block with a checkbox
type TaskCheckBoxListItem struct {
*ast.ListItem
IsChecked bool
}

// KindTaskCheckBoxListItem is the NodeKind for TaskCheckBoxListItem
var KindTaskCheckBoxListItem = ast.NewNodeKind("TaskCheckBoxListItem")

// Dump implements Node.Dump .
func (n *TaskCheckBoxListItem) Dump(source []byte, level int) {
m := map[string]string{}
m["IsChecked"] = strconv.FormatBool(n.IsChecked)
ast.DumpHelper(n, source, level, m, nil)
}

// Kind implements Node.Kind.
func (n *TaskCheckBoxListItem) Kind() ast.NodeKind {
return KindTaskCheckBoxListItem
}

// NewTaskCheckBoxListItem returns a new TaskCheckBoxListItem node.
func NewTaskCheckBoxListItem(listItem *ast.ListItem) *TaskCheckBoxListItem {
return &TaskCheckBoxListItem{
ListItem: listItem,
}
}

// IsTaskCheckBoxListItem returns true if the given node implements the TaskCheckBoxListItem interface,
// otherwise false.
func IsTaskCheckBoxListItem(node ast.Node) bool {
_, ok := node.(*TaskCheckBoxListItem)
return ok
}

// Icon is an inline for a fomantic icon
type Icon struct {
ast.BaseInline
Expand Down
70 changes: 51 additions & 19 deletions modules/markup/markdown/goldmark.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"regexp"
"strings"

"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/markup/common"
"code.gitea.io/gitea/modules/setting"
Expand Down Expand Up @@ -129,6 +128,21 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
if v.HasChildren() && v.FirstChild().HasChildren() && v.FirstChild().FirstChild().HasChildren() {
if _, ok := v.FirstChild().FirstChild().FirstChild().(*east.TaskCheckBox); ok {
v.SetAttributeString("class", []byte("task-list"))
children := make([]ast.Node, 0, v.ChildCount())
child := v.FirstChild()
for child != nil {
children = append(children, child)
child = child.NextSibling()
}
v.RemoveChildren(v)

for _, child := range children {
listItem := child.(*ast.ListItem)
newChild := NewTaskCheckBoxListItem(listItem)
taskCheckBox := child.FirstChild().FirstChild().(*east.TaskCheckBox)
newChild.IsChecked = taskCheckBox.IsChecked
v.AppendChild(v, newChild)
}
}
}
}
Expand Down Expand Up @@ -221,11 +235,11 @@ func (r *HTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
reg.Register(KindDetails, r.renderDetails)
reg.Register(KindSummary, r.renderSummary)
reg.Register(KindIcon, r.renderIcon)
reg.Register(KindTaskCheckBoxListItem, r.renderTaskCheckBoxListItem)
reg.Register(east.KindTaskCheckBox, r.renderTaskCheckBox)
}

func (r *HTMLRenderer) renderDocument(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
log.Info("renderDocument %v", node)
n := node.(*ast.Document)

if val, has := n.AttributeString("lang"); has {
Expand Down Expand Up @@ -311,24 +325,42 @@ func (r *HTMLRenderer) renderIcon(w util.BufWriter, source []byte, node ast.Node
return ast.WalkContinue, nil
}

func (r *HTMLRenderer) renderTaskCheckBox(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
if !entering {
return ast.WalkContinue, nil
}
n := node.(*east.TaskCheckBox)

end := ">"
if r.XHTML {
end = " />"
}
var err error
if n.IsChecked {
_, err = w.WriteString(`<span class="ui fitted disabled checkbox"><input type="checkbox" disabled="disabled"` + end + `<label` + end + `</span>`)
func (r *HTMLRenderer) renderTaskCheckBoxListItem(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
n := node.(*TaskCheckBoxListItem)
if entering {
n.Dump(source, 0)
if n.Attributes() != nil {
_, _ = w.WriteString("<li")
html.RenderAttributes(w, n, html.ListItemAttributeFilter)
_ = w.WriteByte('>')
} else {
_, _ = w.WriteString("<li>")
}
end := ">"
if r.XHTML {
end = " />"
}
var err error
if n.IsChecked {
_, err = w.WriteString(`<span class="ui checked checkbox"><input type="checkbox" checked="" readonly="readonly"` + end + `<label>`)
} else {
_, err = w.WriteString(`<span class="ui checkbox"><input type="checkbox" readonly="readonly"` + end + `<label>`)
}
if err != nil {
return ast.WalkStop, err
}
fc := n.FirstChild()
if fc != nil {
if _, ok := fc.(*ast.TextBlock); !ok {
_ = w.WriteByte('\n')
}
}
} else {
_, err = w.WriteString(`<span class="ui checked fitted disabled checkbox"><input type="checkbox" checked="" disabled="disabled"` + end + `<label` + end + `</span>`)
}
if err != nil {
return ast.WalkStop, err
_, _ = w.WriteString("</label></span></li>\n")
}
return ast.WalkContinue, nil
}

func (r *HTMLRenderer) renderTaskCheckBox(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
return ast.WalkContinue, nil
}
8 changes: 6 additions & 2 deletions modules/markup/sanitizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func ReplaceSanitizer() {

// Checkboxes
sanitizer.policy.AllowAttrs("type").Matching(regexp.MustCompile(`^checkbox$`)).OnElements("input")
sanitizer.policy.AllowAttrs("checked", "disabled").OnElements("input")
sanitizer.policy.AllowAttrs("checked", "disabled", "readonly").OnElements("input")

// Custom URL-Schemes
sanitizer.policy.AllowURLSchemes(setting.Markdown.CustomURLSchemes...)
Expand All @@ -57,7 +57,11 @@ func ReplaceSanitizer() {
sanitizer.policy.AllowAttrs("class").Matching(regexp.MustCompile(`task-list`)).OnElements("ul")

// Allow icons
sanitizer.policy.AllowAttrs("class").Matching(regexp.MustCompile(`^icon(\s+[\p{L}\p{N}_-]+)+$`)).OnElements("i", "span")
sanitizer.policy.AllowAttrs("class").Matching(regexp.MustCompile(`^icon(\s+[\p{L}\p{N}_-]+)+$`)).OnElements("i")
sanitizer.policy.AllowAttrs("class").Matching(regexp.MustCompile(`^((icon(\s+[\p{L}\p{N}_-]+)+)|(ui checkbox)|(ui checked checkbox))$`)).OnElements("span")

// Allow unlabelled labels
sanitizer.policy.AllowNoAttrs().OnElements("label")

// Allow generally safe attributes
generalSafeAttrs := []string{"abbr", "accept", "accept-charset",
Expand Down