forked from gitea/gitea
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>
This commit is contained in:
parent
f1f56da4d1
commit
9f959ac064
|
@ -4,7 +4,11 @@
|
||||||
|
|
||||||
package markdown
|
package markdown
|
||||||
|
|
||||||
import "github.com/yuin/goldmark/ast"
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/yuin/goldmark/ast"
|
||||||
|
)
|
||||||
|
|
||||||
// Details is a block that contains Summary and details
|
// Details is a block that contains Summary and details
|
||||||
type Details struct {
|
type Details struct {
|
||||||
|
@ -70,6 +74,41 @@ func IsSummary(node ast.Node) bool {
|
||||||
return ok
|
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
|
// Icon is an inline for a fomantic icon
|
||||||
type Icon struct {
|
type Icon struct {
|
||||||
ast.BaseInline
|
ast.BaseInline
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
|
||||||
"code.gitea.io/gitea/modules/markup"
|
"code.gitea.io/gitea/modules/markup"
|
||||||
"code.gitea.io/gitea/modules/markup/common"
|
"code.gitea.io/gitea/modules/markup/common"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
@ -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 v.HasChildren() && v.FirstChild().HasChildren() && v.FirstChild().FirstChild().HasChildren() {
|
||||||
if _, ok := v.FirstChild().FirstChild().FirstChild().(*east.TaskCheckBox); ok {
|
if _, ok := v.FirstChild().FirstChild().FirstChild().(*east.TaskCheckBox); ok {
|
||||||
v.SetAttributeString("class", []byte("task-list"))
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -221,11 +235,11 @@ func (r *HTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
|
||||||
reg.Register(KindDetails, r.renderDetails)
|
reg.Register(KindDetails, r.renderDetails)
|
||||||
reg.Register(KindSummary, r.renderSummary)
|
reg.Register(KindSummary, r.renderSummary)
|
||||||
reg.Register(KindIcon, r.renderIcon)
|
reg.Register(KindIcon, r.renderIcon)
|
||||||
|
reg.Register(KindTaskCheckBoxListItem, r.renderTaskCheckBoxListItem)
|
||||||
reg.Register(east.KindTaskCheckBox, r.renderTaskCheckBox)
|
reg.Register(east.KindTaskCheckBox, r.renderTaskCheckBox)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *HTMLRenderer) renderDocument(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
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)
|
n := node.(*ast.Document)
|
||||||
|
|
||||||
if val, has := n.AttributeString("lang"); has {
|
if val, has := n.AttributeString("lang"); has {
|
||||||
|
@ -311,24 +325,42 @@ func (r *HTMLRenderer) renderIcon(w util.BufWriter, source []byte, node ast.Node
|
||||||
return ast.WalkContinue, nil
|
return ast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *HTMLRenderer) renderTaskCheckBox(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
func (r *HTMLRenderer) renderTaskCheckBoxListItem(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||||
if !entering {
|
n := node.(*TaskCheckBoxListItem)
|
||||||
return ast.WalkContinue, nil
|
if entering {
|
||||||
}
|
n.Dump(source, 0)
|
||||||
n := node.(*east.TaskCheckBox)
|
if n.Attributes() != nil {
|
||||||
|
_, _ = w.WriteString("<li")
|
||||||
end := ">"
|
html.RenderAttributes(w, n, html.ListItemAttributeFilter)
|
||||||
if r.XHTML {
|
_ = w.WriteByte('>')
|
||||||
end = " />"
|
} else {
|
||||||
}
|
_, _ = w.WriteString("<li>")
|
||||||
var err error
|
}
|
||||||
if n.IsChecked {
|
end := ">"
|
||||||
_, err = w.WriteString(`<span class="ui fitted disabled checkbox"><input type="checkbox" disabled="disabled"` + end + `<label` + end + `</span>`)
|
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 {
|
} else {
|
||||||
_, err = w.WriteString(`<span class="ui checked fitted disabled checkbox"><input type="checkbox" checked="" disabled="disabled"` + end + `<label` + end + `</span>`)
|
_, _ = w.WriteString("</label></span></li>\n")
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return ast.WalkStop, err
|
|
||||||
}
|
}
|
||||||
return ast.WalkContinue, nil
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ func ReplaceSanitizer() {
|
||||||
|
|
||||||
// Checkboxes
|
// Checkboxes
|
||||||
sanitizer.policy.AllowAttrs("type").Matching(regexp.MustCompile(`^checkbox$`)).OnElements("input")
|
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
|
// Custom URL-Schemes
|
||||||
sanitizer.policy.AllowURLSchemes(setting.Markdown.CustomURLSchemes...)
|
sanitizer.policy.AllowURLSchemes(setting.Markdown.CustomURLSchemes...)
|
||||||
|
@ -57,7 +57,11 @@ func ReplaceSanitizer() {
|
||||||
sanitizer.policy.AllowAttrs("class").Matching(regexp.MustCompile(`task-list`)).OnElements("ul")
|
sanitizer.policy.AllowAttrs("class").Matching(regexp.MustCompile(`task-list`)).OnElements("ul")
|
||||||
|
|
||||||
// Allow icons
|
// 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
|
// Allow generally safe attributes
|
||||||
generalSafeAttrs := []string{"abbr", "accept", "accept-charset",
|
generalSafeAttrs := []string{"abbr", "accept", "accept-charset",
|
||||||
|
|
Loading…
Reference in New Issue