mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 16:40:24 +08:00 
			
		
		
		
	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:
		@@ -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",
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user