Skip to content

Commit 9f959ac

Browse files
zeripathlafriks
andauthored
Make TaskCheckBox render correctly (#11214)
* Fix checkbox rendering Signed-off-by: Andrew Thornton <art27@cantab.net> * Normalize checkbox rendering Signed-off-by: Andrew Thornton <art27@cantab.net> * set the checkboxes to readonly instead of disabled Signed-off-by: Andrew Thornton <art27@cantab.net> Co-authored-by: Lauris BH <lauris@nix.lv>
1 parent f1f56da commit 9f959ac

File tree

3 files changed

+97
-22
lines changed

3 files changed

+97
-22
lines changed

modules/markup/markdown/ast.go

+40-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@
44

55
package markdown
66

7-
import "github.com/yuin/goldmark/ast"
7+
import (
8+
"strconv"
9+
10+
"github.com/yuin/goldmark/ast"
11+
)
812

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

77+
// TaskCheckBoxListItem is a block that repressents a list item of a markdown block with a checkbox
78+
type TaskCheckBoxListItem struct {
79+
*ast.ListItem
80+
IsChecked bool
81+
}
82+
83+
// KindTaskCheckBoxListItem is the NodeKind for TaskCheckBoxListItem
84+
var KindTaskCheckBoxListItem = ast.NewNodeKind("TaskCheckBoxListItem")
85+
86+
// Dump implements Node.Dump .
87+
func (n *TaskCheckBoxListItem) Dump(source []byte, level int) {
88+
m := map[string]string{}
89+
m["IsChecked"] = strconv.FormatBool(n.IsChecked)
90+
ast.DumpHelper(n, source, level, m, nil)
91+
}
92+
93+
// Kind implements Node.Kind.
94+
func (n *TaskCheckBoxListItem) Kind() ast.NodeKind {
95+
return KindTaskCheckBoxListItem
96+
}
97+
98+
// NewTaskCheckBoxListItem returns a new TaskCheckBoxListItem node.
99+
func NewTaskCheckBoxListItem(listItem *ast.ListItem) *TaskCheckBoxListItem {
100+
return &TaskCheckBoxListItem{
101+
ListItem: listItem,
102+
}
103+
}
104+
105+
// IsTaskCheckBoxListItem returns true if the given node implements the TaskCheckBoxListItem interface,
106+
// otherwise false.
107+
func IsTaskCheckBoxListItem(node ast.Node) bool {
108+
_, ok := node.(*TaskCheckBoxListItem)
109+
return ok
110+
}
111+
73112
// Icon is an inline for a fomantic icon
74113
type Icon struct {
75114
ast.BaseInline

modules/markup/markdown/goldmark.go

+51-19
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"regexp"
1111
"strings"
1212

13-
"code.gitea.io/gitea/modules/log"
1413
"code.gitea.io/gitea/modules/markup"
1514
"code.gitea.io/gitea/modules/markup/common"
1615
"code.gitea.io/gitea/modules/setting"
@@ -129,6 +128,21 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
129128
if v.HasChildren() && v.FirstChild().HasChildren() && v.FirstChild().FirstChild().HasChildren() {
130129
if _, ok := v.FirstChild().FirstChild().FirstChild().(*east.TaskCheckBox); ok {
131130
v.SetAttributeString("class", []byte("task-list"))
131+
children := make([]ast.Node, 0, v.ChildCount())
132+
child := v.FirstChild()
133+
for child != nil {
134+
children = append(children, child)
135+
child = child.NextSibling()
136+
}
137+
v.RemoveChildren(v)
138+
139+
for _, child := range children {
140+
listItem := child.(*ast.ListItem)
141+
newChild := NewTaskCheckBoxListItem(listItem)
142+
taskCheckBox := child.FirstChild().FirstChild().(*east.TaskCheckBox)
143+
newChild.IsChecked = taskCheckBox.IsChecked
144+
v.AppendChild(v, newChild)
145+
}
132146
}
133147
}
134148
}
@@ -221,11 +235,11 @@ func (r *HTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
221235
reg.Register(KindDetails, r.renderDetails)
222236
reg.Register(KindSummary, r.renderSummary)
223237
reg.Register(KindIcon, r.renderIcon)
238+
reg.Register(KindTaskCheckBoxListItem, r.renderTaskCheckBoxListItem)
224239
reg.Register(east.KindTaskCheckBox, r.renderTaskCheckBox)
225240
}
226241

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

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

314-
func (r *HTMLRenderer) renderTaskCheckBox(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
315-
if !entering {
316-
return ast.WalkContinue, nil
317-
}
318-
n := node.(*east.TaskCheckBox)
319-
320-
end := ">"
321-
if r.XHTML {
322-
end = " />"
323-
}
324-
var err error
325-
if n.IsChecked {
326-
_, err = w.WriteString(`<span class="ui fitted disabled checkbox"><input type="checkbox" disabled="disabled"` + end + `<label` + end + `</span>`)
328+
func (r *HTMLRenderer) renderTaskCheckBoxListItem(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
329+
n := node.(*TaskCheckBoxListItem)
330+
if entering {
331+
n.Dump(source, 0)
332+
if n.Attributes() != nil {
333+
_, _ = w.WriteString("<li")
334+
html.RenderAttributes(w, n, html.ListItemAttributeFilter)
335+
_ = w.WriteByte('>')
336+
} else {
337+
_, _ = w.WriteString("<li>")
338+
}
339+
end := ">"
340+
if r.XHTML {
341+
end = " />"
342+
}
343+
var err error
344+
if n.IsChecked {
345+
_, err = w.WriteString(`<span class="ui checked checkbox"><input type="checkbox" checked="" readonly="readonly"` + end + `<label>`)
346+
} else {
347+
_, err = w.WriteString(`<span class="ui checkbox"><input type="checkbox" readonly="readonly"` + end + `<label>`)
348+
}
349+
if err != nil {
350+
return ast.WalkStop, err
351+
}
352+
fc := n.FirstChild()
353+
if fc != nil {
354+
if _, ok := fc.(*ast.TextBlock); !ok {
355+
_ = w.WriteByte('\n')
356+
}
357+
}
327358
} else {
328-
_, err = w.WriteString(`<span class="ui checked fitted disabled checkbox"><input type="checkbox" checked="" disabled="disabled"` + end + `<label` + end + `</span>`)
329-
}
330-
if err != nil {
331-
return ast.WalkStop, err
359+
_, _ = w.WriteString("</label></span></li>\n")
332360
}
333361
return ast.WalkContinue, nil
334362
}
363+
364+
func (r *HTMLRenderer) renderTaskCheckBox(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
365+
return ast.WalkContinue, nil
366+
}

modules/markup/sanitizer.go

+6-2
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ func ReplaceSanitizer() {
4242

4343
// Checkboxes
4444
sanitizer.policy.AllowAttrs("type").Matching(regexp.MustCompile(`^checkbox$`)).OnElements("input")
45-
sanitizer.policy.AllowAttrs("checked", "disabled").OnElements("input")
45+
sanitizer.policy.AllowAttrs("checked", "disabled", "readonly").OnElements("input")
4646

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

5959
// Allow icons
60-
sanitizer.policy.AllowAttrs("class").Matching(regexp.MustCompile(`^icon(\s+[\p{L}\p{N}_-]+)+$`)).OnElements("i", "span")
60+
sanitizer.policy.AllowAttrs("class").Matching(regexp.MustCompile(`^icon(\s+[\p{L}\p{N}_-]+)+$`)).OnElements("i")
61+
sanitizer.policy.AllowAttrs("class").Matching(regexp.MustCompile(`^((icon(\s+[\p{L}\p{N}_-]+)+)|(ui checkbox)|(ui checked checkbox))$`)).OnElements("span")
62+
63+
// Allow unlabelled labels
64+
sanitizer.policy.AllowNoAttrs().OnElements("label")
6165

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

0 commit comments

Comments
 (0)