Only split the file into small ones (and rename AttentionTypes to attentionTypes)tags/v1.22.0-rc0
@@ -4,19 +4,14 @@ | |||
package markdown | |||
import ( | |||
"bytes" | |||
"fmt" | |||
"regexp" | |||
"strings" | |||
"code.gitea.io/gitea/modules/container" | |||
"code.gitea.io/gitea/modules/markup" | |||
"code.gitea.io/gitea/modules/markup/common" | |||
"code.gitea.io/gitea/modules/setting" | |||
"code.gitea.io/gitea/modules/svg" | |||
giteautil "code.gitea.io/gitea/modules/util" | |||
"github.com/microcosm-cc/bluemonday/css" | |||
"github.com/yuin/goldmark/ast" | |||
east "github.com/yuin/goldmark/extension/ast" | |||
"github.com/yuin/goldmark/parser" | |||
@@ -28,12 +23,12 @@ import ( | |||
// ASTTransformer is a default transformer of the goldmark tree. | |||
type ASTTransformer struct { | |||
AttentionTypes container.Set[string] | |||
attentionTypes container.Set[string] | |||
} | |||
func NewASTTransformer() *ASTTransformer { | |||
return &ASTTransformer{ | |||
AttentionTypes: container.SetOf("note", "tip", "important", "warning", "caution"), | |||
attentionTypes: container.SetOf("note", "tip", "important", "warning", "caution"), | |||
} | |||
} | |||
@@ -66,123 +61,15 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa | |||
switch v := n.(type) { | |||
case *ast.Heading: | |||
for _, attr := range v.Attributes() { | |||
if _, ok := attr.Value.([]byte); !ok { | |||
v.SetAttribute(attr.Name, []byte(fmt.Sprintf("%v", attr.Value))) | |||
} | |||
} | |||
txt := n.Text(reader.Source()) | |||
header := markup.Header{ | |||
Text: util.BytesToReadOnlyString(txt), | |||
Level: v.Level, | |||
} | |||
if id, found := v.AttributeString("id"); found { | |||
header.ID = util.BytesToReadOnlyString(id.([]byte)) | |||
} | |||
tocList = append(tocList, header) | |||
g.applyElementDir(v) | |||
g.transformHeading(ctx, v, reader, &tocList) | |||
case *ast.Paragraph: | |||
g.applyElementDir(v) | |||
case *ast.Image: | |||
// Images need two things: | |||
// | |||
// 1. Their src needs to munged to be a real value | |||
// 2. If they're not wrapped with a link they need a link wrapper | |||
// Check if the destination is a real link | |||
if len(v.Destination) > 0 && !markup.IsFullURLBytes(v.Destination) { | |||
v.Destination = []byte(giteautil.URLJoin( | |||
ctx.Links.ResolveMediaLink(ctx.IsWiki), | |||
strings.TrimLeft(string(v.Destination), "/"), | |||
)) | |||
} | |||
parent := n.Parent() | |||
// Create a link around image only if parent is not already a link | |||
if _, ok := parent.(*ast.Link); !ok && parent != nil { | |||
next := n.NextSibling() | |||
// Create a link wrapper | |||
wrap := ast.NewLink() | |||
wrap.Destination = v.Destination | |||
wrap.Title = v.Title | |||
wrap.SetAttributeString("target", []byte("_blank")) | |||
// Duplicate the current image node | |||
image := ast.NewImage(ast.NewLink()) | |||
image.Destination = v.Destination | |||
image.Title = v.Title | |||
for _, attr := range v.Attributes() { | |||
image.SetAttribute(attr.Name, attr.Value) | |||
} | |||
for child := v.FirstChild(); child != nil; { | |||
next := child.NextSibling() | |||
image.AppendChild(image, child) | |||
child = next | |||
} | |||
// Append our duplicate image to the wrapper link | |||
wrap.AppendChild(wrap, image) | |||
// Wire in the next sibling | |||
wrap.SetNextSibling(next) | |||
// Replace the current node with the wrapper link | |||
parent.ReplaceChild(parent, n, wrap) | |||
// But most importantly ensure the next sibling is still on the old image too | |||
v.SetNextSibling(next) | |||
} | |||
g.transformImage(ctx, v, reader) | |||
case *ast.Link: | |||
// Links need their href to munged to be a real value | |||
link := v.Destination | |||
isAnchorFragment := len(link) > 0 && link[0] == '#' | |||
if !isAnchorFragment && !markup.IsFullURLBytes(link) { | |||
base := ctx.Links.Base | |||
if ctx.IsWiki { | |||
base = ctx.Links.WikiLink() | |||
} else if ctx.Links.HasBranchInfo() { | |||
base = ctx.Links.SrcLink() | |||
} | |||
link = []byte(giteautil.URLJoin(base, string(link))) | |||
} | |||
if isAnchorFragment { | |||
link = []byte("#user-content-" + string(link)[1:]) | |||
} | |||
v.Destination = link | |||
g.transformLink(ctx, v, reader) | |||
case *ast.List: | |||
if v.HasChildren() { | |||
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) | |||
if !child.HasChildren() || !child.FirstChild().HasChildren() { | |||
v.AppendChild(v, child) | |||
continue | |||
} | |||
taskCheckBox, ok := child.FirstChild().FirstChild().(*east.TaskCheckBox) | |||
if !ok { | |||
v.AppendChild(v, child) | |||
continue | |||
} | |||
newChild := NewTaskCheckBoxListItem(listItem) | |||
newChild.IsChecked = taskCheckBox.IsChecked | |||
newChild.SetAttributeString("class", []byte("task-list-item")) | |||
segments := newChild.FirstChild().Lines() | |||
if segments.Len() > 0 { | |||
segment := segments.At(0) | |||
newChild.SourcePosition = rc.metaLength + segment.Start | |||
} | |||
v.AppendChild(v, newChild) | |||
} | |||
} | |||
g.applyElementDir(v) | |||
g.transformList(ctx, v, reader, rc) | |||
case *ast.Text: | |||
if v.SoftLineBreak() && !v.HardLineBreak() { | |||
if ctx.Metas["mode"] != "document" { | |||
@@ -192,10 +79,7 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa | |||
} | |||
} | |||
case *ast.CodeSpan: | |||
colorContent := n.Text(reader.Source()) | |||
if css.ColorHandler(strings.ToLower(string(colorContent))) { | |||
v.AppendChild(v, NewColorPreview(colorContent)) | |||
} | |||
g.transformCodeSpan(ctx, v, reader) | |||
case *ast.Blockquote: | |||
return g.transformBlockquote(v, reader) | |||
} | |||
@@ -219,50 +103,6 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa | |||
} | |||
} | |||
type prefixedIDs struct { | |||
values container.Set[string] | |||
} | |||
// Generate generates a new element id. | |||
func (p *prefixedIDs) Generate(value []byte, kind ast.NodeKind) []byte { | |||
dft := []byte("id") | |||
if kind == ast.KindHeading { | |||
dft = []byte("heading") | |||
} | |||
return p.GenerateWithDefault(value, dft) | |||
} | |||
// GenerateWithDefault generates a new element id. | |||
func (p *prefixedIDs) GenerateWithDefault(value, dft []byte) []byte { | |||
result := common.CleanValue(value) | |||
if len(result) == 0 { | |||
result = dft | |||
} | |||
if !bytes.HasPrefix(result, []byte("user-content-")) { | |||
result = append([]byte("user-content-"), result...) | |||
} | |||
if p.values.Add(util.BytesToReadOnlyString(result)) { | |||
return result | |||
} | |||
for i := 1; ; i++ { | |||
newResult := fmt.Sprintf("%s-%d", result, i) | |||
if p.values.Add(newResult) { | |||
return []byte(newResult) | |||
} | |||
} | |||
} | |||
// Put puts a given element id to the used ids table. | |||
func (p *prefixedIDs) Put(value []byte) { | |||
p.values.Add(util.BytesToReadOnlyString(value)) | |||
} | |||
func newPrefixedIDs() *prefixedIDs { | |||
return &prefixedIDs{ | |||
values: make(container.Set[string]), | |||
} | |||
} | |||
// NewHTMLRenderer creates a HTMLRenderer to render | |||
// in the gitea form. | |||
func NewHTMLRenderer(opts ...html.Option) renderer.NodeRenderer { | |||
@@ -295,60 +135,6 @@ func (r *HTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) { | |||
reg.Register(east.KindTaskCheckBox, r.renderTaskCheckBox) | |||
} | |||
// renderCodeSpan renders CodeSpan elements (like goldmark upstream does) but also renders ColorPreview elements. | |||
// See #21474 for reference | |||
func (r *HTMLRenderer) renderCodeSpan(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) { | |||
if entering { | |||
if n.Attributes() != nil { | |||
_, _ = w.WriteString("<code") | |||
html.RenderAttributes(w, n, html.CodeAttributeFilter) | |||
_ = w.WriteByte('>') | |||
} else { | |||
_, _ = w.WriteString("<code>") | |||
} | |||
for c := n.FirstChild(); c != nil; c = c.NextSibling() { | |||
switch v := c.(type) { | |||
case *ast.Text: | |||
segment := v.Segment | |||
value := segment.Value(source) | |||
if bytes.HasSuffix(value, []byte("\n")) { | |||
r.Writer.RawWrite(w, value[:len(value)-1]) | |||
r.Writer.RawWrite(w, []byte(" ")) | |||
} else { | |||
r.Writer.RawWrite(w, value) | |||
} | |||
case *ColorPreview: | |||
_, _ = w.WriteString(fmt.Sprintf(`<span class="color-preview" style="background-color: %v"></span>`, string(v.Color))) | |||
} | |||
} | |||
return ast.WalkSkipChildren, nil | |||
} | |||
_, _ = w.WriteString("</code>") | |||
return ast.WalkContinue, nil | |||
} | |||
// renderAttention renders a quote marked with i.e. "> **Note**" or "> **Warning**" with a corresponding svg | |||
func (r *HTMLRenderer) renderAttention(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { | |||
if entering { | |||
n := node.(*Attention) | |||
var octiconName string | |||
switch n.AttentionType { | |||
case "tip": | |||
octiconName = "light-bulb" | |||
case "important": | |||
octiconName = "report" | |||
case "warning": | |||
octiconName = "alert" | |||
case "caution": | |||
octiconName = "stop" | |||
default: // including "note" | |||
octiconName = "info" | |||
} | |||
_, _ = w.WriteString(string(svg.RenderHTML("octicon-"+octiconName, 16, "attention-icon attention-"+n.AttentionType))) | |||
} | |||
return ast.WalkContinue, nil | |||
} | |||
func (r *HTMLRenderer) renderDocument(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { | |||
n := node.(*ast.Document) | |||
@@ -435,38 +221,3 @@ func (r *HTMLRenderer) renderIcon(w util.BufWriter, source []byte, node ast.Node | |||
return ast.WalkContinue, nil | |||
} | |||
func (r *HTMLRenderer) renderTaskCheckBoxListItem(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { | |||
n := node.(*TaskCheckBoxListItem) | |||
if entering { | |||
if n.Attributes() != nil { | |||
_, _ = w.WriteString("<li") | |||
html.RenderAttributes(w, n, html.ListItemAttributeFilter) | |||
_ = w.WriteByte('>') | |||
} else { | |||
_, _ = w.WriteString("<li>") | |||
} | |||
fmt.Fprintf(w, `<input type="checkbox" disabled="" data-source-position="%d"`, n.SourcePosition) | |||
if n.IsChecked { | |||
_, _ = w.WriteString(` checked=""`) | |||
} | |||
if r.XHTML { | |||
_, _ = w.WriteString(` />`) | |||
} else { | |||
_ = w.WriteByte('>') | |||
} | |||
fc := n.FirstChild() | |||
if fc != nil { | |||
if _, ok := fc.(*ast.TextBlock); !ok { | |||
_ = w.WriteByte('\n') | |||
} | |||
} | |||
} else { | |||
_, _ = w.WriteString("</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 | |||
} |
@@ -0,0 +1,59 @@ | |||
// Copyright 2024 The Gitea Authors. All rights reserved. | |||
// SPDX-License-Identifier: MIT | |||
package markdown | |||
import ( | |||
"bytes" | |||
"fmt" | |||
"code.gitea.io/gitea/modules/container" | |||
"code.gitea.io/gitea/modules/markup/common" | |||
"github.com/yuin/goldmark/ast" | |||
"github.com/yuin/goldmark/util" | |||
) | |||
type prefixedIDs struct { | |||
values container.Set[string] | |||
} | |||
// Generate generates a new element id. | |||
func (p *prefixedIDs) Generate(value []byte, kind ast.NodeKind) []byte { | |||
dft := []byte("id") | |||
if kind == ast.KindHeading { | |||
dft = []byte("heading") | |||
} | |||
return p.GenerateWithDefault(value, dft) | |||
} | |||
// GenerateWithDefault generates a new element id. | |||
func (p *prefixedIDs) GenerateWithDefault(value, dft []byte) []byte { | |||
result := common.CleanValue(value) | |||
if len(result) == 0 { | |||
result = dft | |||
} | |||
if !bytes.HasPrefix(result, []byte("user-content-")) { | |||
result = append([]byte("user-content-"), result...) | |||
} | |||
if p.values.Add(util.BytesToReadOnlyString(result)) { | |||
return result | |||
} | |||
for i := 1; ; i++ { | |||
newResult := fmt.Sprintf("%s-%d", result, i) | |||
if p.values.Add(newResult) { | |||
return []byte(newResult) | |||
} | |||
} | |||
} | |||
// Put puts a given element id to the used ids table. | |||
func (p *prefixedIDs) Put(value []byte) { | |||
p.values.Add(util.BytesToReadOnlyString(value)) | |||
} | |||
func newPrefixedIDs() *prefixedIDs { | |||
return &prefixedIDs{ | |||
values: make(container.Set[string]), | |||
} | |||
} |
@@ -6,12 +6,37 @@ package markdown | |||
import ( | |||
"strings" | |||
"code.gitea.io/gitea/modules/svg" | |||
"github.com/yuin/goldmark/ast" | |||
"github.com/yuin/goldmark/text" | |||
"github.com/yuin/goldmark/util" | |||
"golang.org/x/text/cases" | |||
"golang.org/x/text/language" | |||
) | |||
// renderAttention renders a quote marked with i.e. "> **Note**" or "> **Warning**" with a corresponding svg | |||
func (r *HTMLRenderer) renderAttention(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { | |||
if entering { | |||
n := node.(*Attention) | |||
var octiconName string | |||
switch n.AttentionType { | |||
case "tip": | |||
octiconName = "light-bulb" | |||
case "important": | |||
octiconName = "report" | |||
case "warning": | |||
octiconName = "alert" | |||
case "caution": | |||
octiconName = "stop" | |||
default: // including "note" | |||
octiconName = "info" | |||
} | |||
_, _ = w.WriteString(string(svg.RenderHTML("octicon-"+octiconName, 16, "attention-icon attention-"+n.AttentionType))) | |||
} | |||
return ast.WalkContinue, nil | |||
} | |||
func (g *ASTTransformer) transformBlockquote(v *ast.Blockquote, reader text.Reader) (ast.WalkStatus, error) { | |||
// We only want attention blockquotes when the AST looks like: | |||
// > Text("[") Text("!TYPE") Text("]") | |||
@@ -43,7 +68,7 @@ func (g *ASTTransformer) transformBlockquote(v *ast.Blockquote, reader text.Read | |||
// grab attention type from markdown source | |||
attentionType := strings.ToLower(val2[1:]) | |||
if !g.AttentionTypes.Contains(attentionType) { | |||
if !g.attentionTypes.Contains(attentionType) { | |||
return ast.WalkContinue, nil | |||
} | |||
@@ -0,0 +1,57 @@ | |||
// Copyright 2024 The Gitea Authors. All rights reserved. | |||
// SPDX-License-Identifier: MIT | |||
package markdown | |||
import ( | |||
"bytes" | |||
"fmt" | |||
"strings" | |||
"code.gitea.io/gitea/modules/markup" | |||
"github.com/microcosm-cc/bluemonday/css" | |||
"github.com/yuin/goldmark/ast" | |||
"github.com/yuin/goldmark/renderer/html" | |||
"github.com/yuin/goldmark/text" | |||
"github.com/yuin/goldmark/util" | |||
) | |||
// renderCodeSpan renders CodeSpan elements (like goldmark upstream does) but also renders ColorPreview elements. | |||
// See #21474 for reference | |||
func (r *HTMLRenderer) renderCodeSpan(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) { | |||
if entering { | |||
if n.Attributes() != nil { | |||
_, _ = w.WriteString("<code") | |||
html.RenderAttributes(w, n, html.CodeAttributeFilter) | |||
_ = w.WriteByte('>') | |||
} else { | |||
_, _ = w.WriteString("<code>") | |||
} | |||
for c := n.FirstChild(); c != nil; c = c.NextSibling() { | |||
switch v := c.(type) { | |||
case *ast.Text: | |||
segment := v.Segment | |||
value := segment.Value(source) | |||
if bytes.HasSuffix(value, []byte("\n")) { | |||
r.Writer.RawWrite(w, value[:len(value)-1]) | |||
r.Writer.RawWrite(w, []byte(" ")) | |||
} else { | |||
r.Writer.RawWrite(w, value) | |||
} | |||
case *ColorPreview: | |||
_, _ = w.WriteString(fmt.Sprintf(`<span class="color-preview" style="background-color: %v"></span>`, string(v.Color))) | |||
} | |||
} | |||
return ast.WalkSkipChildren, nil | |||
} | |||
_, _ = w.WriteString("</code>") | |||
return ast.WalkContinue, nil | |||
} | |||
func (g *ASTTransformer) transformCodeSpan(ctx *markup.RenderContext, v *ast.CodeSpan, reader text.Reader) { | |||
colorContent := v.Text(reader.Source()) | |||
if css.ColorHandler(strings.ToLower(string(colorContent))) { | |||
v.AppendChild(v, NewColorPreview(colorContent)) | |||
} | |||
} |
@@ -0,0 +1,32 @@ | |||
// Copyright 2024 The Gitea Authors. All rights reserved. | |||
// SPDX-License-Identifier: MIT | |||
package markdown | |||
import ( | |||
"fmt" | |||
"code.gitea.io/gitea/modules/markup" | |||
"github.com/yuin/goldmark/ast" | |||
"github.com/yuin/goldmark/text" | |||
"github.com/yuin/goldmark/util" | |||
) | |||
func (g *ASTTransformer) transformHeading(ctx *markup.RenderContext, v *ast.Heading, reader text.Reader, tocList *[]markup.Header) { | |||
for _, attr := range v.Attributes() { | |||
if _, ok := attr.Value.([]byte); !ok { | |||
v.SetAttribute(attr.Name, []byte(fmt.Sprintf("%v", attr.Value))) | |||
} | |||
} | |||
txt := v.Text(reader.Source()) | |||
header := markup.Header{ | |||
Text: util.BytesToReadOnlyString(txt), | |||
Level: v.Level, | |||
} | |||
if id, found := v.AttributeString("id"); found { | |||
header.ID = util.BytesToReadOnlyString(id.([]byte)) | |||
} | |||
*tocList = append(*tocList, header) | |||
g.applyElementDir(v) | |||
} |
@@ -0,0 +1,66 @@ | |||
// Copyright 2024 The Gitea Authors. All rights reserved. | |||
// SPDX-License-Identifier: MIT | |||
package markdown | |||
import ( | |||
"strings" | |||
"code.gitea.io/gitea/modules/markup" | |||
giteautil "code.gitea.io/gitea/modules/util" | |||
"github.com/yuin/goldmark/ast" | |||
"github.com/yuin/goldmark/text" | |||
) | |||
func (g *ASTTransformer) transformImage(ctx *markup.RenderContext, v *ast.Image, reader text.Reader) { | |||
// Images need two things: | |||
// | |||
// 1. Their src needs to munged to be a real value | |||
// 2. If they're not wrapped with a link they need a link wrapper | |||
// Check if the destination is a real link | |||
if len(v.Destination) > 0 && !markup.IsFullURLBytes(v.Destination) { | |||
v.Destination = []byte(giteautil.URLJoin( | |||
ctx.Links.ResolveMediaLink(ctx.IsWiki), | |||
strings.TrimLeft(string(v.Destination), "/"), | |||
)) | |||
} | |||
parent := v.Parent() | |||
// Create a link around image only if parent is not already a link | |||
if _, ok := parent.(*ast.Link); !ok && parent != nil { | |||
next := v.NextSibling() | |||
// Create a link wrapper | |||
wrap := ast.NewLink() | |||
wrap.Destination = v.Destination | |||
wrap.Title = v.Title | |||
wrap.SetAttributeString("target", []byte("_blank")) | |||
// Duplicate the current image node | |||
image := ast.NewImage(ast.NewLink()) | |||
image.Destination = v.Destination | |||
image.Title = v.Title | |||
for _, attr := range v.Attributes() { | |||
image.SetAttribute(attr.Name, attr.Value) | |||
} | |||
for child := v.FirstChild(); child != nil; { | |||
next := child.NextSibling() | |||
image.AppendChild(image, child) | |||
child = next | |||
} | |||
// Append our duplicate image to the wrapper link | |||
wrap.AppendChild(wrap, image) | |||
// Wire in the next sibling | |||
wrap.SetNextSibling(next) | |||
// Replace the current node with the wrapper link | |||
parent.ReplaceChild(parent, v, wrap) | |||
// But most importantly ensure the next sibling is still on the old image too | |||
v.SetNextSibling(next) | |||
} | |||
} |
@@ -0,0 +1,31 @@ | |||
// Copyright 2024 The Gitea Authors. All rights reserved. | |||
// SPDX-License-Identifier: MIT | |||
package markdown | |||
import ( | |||
"code.gitea.io/gitea/modules/markup" | |||
giteautil "code.gitea.io/gitea/modules/util" | |||
"github.com/yuin/goldmark/ast" | |||
"github.com/yuin/goldmark/text" | |||
) | |||
func (g *ASTTransformer) transformLink(ctx *markup.RenderContext, v *ast.Link, reader text.Reader) { | |||
// Links need their href to munged to be a real value | |||
link := v.Destination | |||
isAnchorFragment := len(link) > 0 && link[0] == '#' | |||
if !isAnchorFragment && !markup.IsFullURLBytes(link) { | |||
base := ctx.Links.Base | |||
if ctx.IsWiki { | |||
base = ctx.Links.WikiLink() | |||
} else if ctx.Links.HasBranchInfo() { | |||
base = ctx.Links.SrcLink() | |||
} | |||
link = []byte(giteautil.URLJoin(base, string(link))) | |||
} | |||
if isAnchorFragment { | |||
link = []byte("#user-content-" + string(link)[1:]) | |||
} | |||
v.Destination = link | |||
} |
@@ -0,0 +1,86 @@ | |||
// Copyright 2024 The Gitea Authors. All rights reserved. | |||
// SPDX-License-Identifier: MIT | |||
package markdown | |||
import ( | |||
"fmt" | |||
"code.gitea.io/gitea/modules/markup" | |||
"github.com/yuin/goldmark/ast" | |||
east "github.com/yuin/goldmark/extension/ast" | |||
"github.com/yuin/goldmark/renderer/html" | |||
"github.com/yuin/goldmark/text" | |||
"github.com/yuin/goldmark/util" | |||
) | |||
func (r *HTMLRenderer) renderTaskCheckBoxListItem(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { | |||
n := node.(*TaskCheckBoxListItem) | |||
if entering { | |||
if n.Attributes() != nil { | |||
_, _ = w.WriteString("<li") | |||
html.RenderAttributes(w, n, html.ListItemAttributeFilter) | |||
_ = w.WriteByte('>') | |||
} else { | |||
_, _ = w.WriteString("<li>") | |||
} | |||
fmt.Fprintf(w, `<input type="checkbox" disabled="" data-source-position="%d"`, n.SourcePosition) | |||
if n.IsChecked { | |||
_, _ = w.WriteString(` checked=""`) | |||
} | |||
if r.XHTML { | |||
_, _ = w.WriteString(` />`) | |||
} else { | |||
_ = w.WriteByte('>') | |||
} | |||
fc := n.FirstChild() | |||
if fc != nil { | |||
if _, ok := fc.(*ast.TextBlock); !ok { | |||
_ = w.WriteByte('\n') | |||
} | |||
} | |||
} else { | |||
_, _ = w.WriteString("</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 | |||
} | |||
func (g *ASTTransformer) transformList(ctx *markup.RenderContext, v *ast.List, reader text.Reader, rc *RenderConfig) { | |||
if v.HasChildren() { | |||
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) | |||
if !child.HasChildren() || !child.FirstChild().HasChildren() { | |||
v.AppendChild(v, child) | |||
continue | |||
} | |||
taskCheckBox, ok := child.FirstChild().FirstChild().(*east.TaskCheckBox) | |||
if !ok { | |||
v.AppendChild(v, child) | |||
continue | |||
} | |||
newChild := NewTaskCheckBoxListItem(listItem) | |||
newChild.IsChecked = taskCheckBox.IsChecked | |||
newChild.SetAttributeString("class", []byte("task-list-item")) | |||
segments := newChild.FirstChild().Lines() | |||
if segments.Len() > 0 { | |||
segment := segments.At(0) | |||
newChild.SourcePosition = rc.metaLength + segment.Start | |||
} | |||
v.AppendChild(v, newChild) | |||
} | |||
} | |||
g.applyElementDir(v) | |||
} |