44package markdown
55
66import (
7- "bytes"
87 "fmt"
98 "regexp"
109 "strings"
1110
1211 "code.gitea.io/gitea/modules/container"
1312 "code.gitea.io/gitea/modules/markup"
14- "code.gitea.io/gitea/modules/markup/common"
1513 "code.gitea.io/gitea/modules/setting"
16- "code.gitea.io/gitea/modules/svg"
17- giteautil "code.gitea.io/gitea/modules/util"
1814
19- "github.com/microcosm-cc/bluemonday/css"
2015 "github.com/yuin/goldmark/ast"
2116 east "github.com/yuin/goldmark/extension/ast"
2217 "github.com/yuin/goldmark/parser"
@@ -28,12 +23,12 @@ import (
2823
2924// ASTTransformer is a default transformer of the goldmark tree.
3025type ASTTransformer struct {
31- AttentionTypes container.Set [string ]
26+ attentionTypes container.Set [string ]
3227}
3328
3429func NewASTTransformer () * ASTTransformer {
3530 return & ASTTransformer {
36- AttentionTypes : container .SetOf ("note" , "tip" , "important" , "warning" , "caution" ),
31+ attentionTypes : container .SetOf ("note" , "tip" , "important" , "warning" , "caution" ),
3732 }
3833}
3934
@@ -66,123 +61,15 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
6661
6762 switch v := n .(type ) {
6863 case * ast.Heading :
69- for _ , attr := range v .Attributes () {
70- if _ , ok := attr .Value .([]byte ); ! ok {
71- v .SetAttribute (attr .Name , []byte (fmt .Sprintf ("%v" , attr .Value )))
72- }
73- }
74- txt := n .Text (reader .Source ())
75- header := markup.Header {
76- Text : util .BytesToReadOnlyString (txt ),
77- Level : v .Level ,
78- }
79- if id , found := v .AttributeString ("id" ); found {
80- header .ID = util .BytesToReadOnlyString (id .([]byte ))
81- }
82- tocList = append (tocList , header )
83- g .applyElementDir (v )
64+ g .transformHeading (ctx , v , reader , & tocList )
8465 case * ast.Paragraph :
8566 g .applyElementDir (v )
8667 case * ast.Image :
87- // Images need two things:
88- //
89- // 1. Their src needs to munged to be a real value
90- // 2. If they're not wrapped with a link they need a link wrapper
91-
92- // Check if the destination is a real link
93- if len (v .Destination ) > 0 && ! markup .IsFullURLBytes (v .Destination ) {
94- v .Destination = []byte (giteautil .URLJoin (
95- ctx .Links .ResolveMediaLink (ctx .IsWiki ),
96- strings .TrimLeft (string (v .Destination ), "/" ),
97- ))
98- }
99-
100- parent := n .Parent ()
101- // Create a link around image only if parent is not already a link
102- if _ , ok := parent .(* ast.Link ); ! ok && parent != nil {
103- next := n .NextSibling ()
104-
105- // Create a link wrapper
106- wrap := ast .NewLink ()
107- wrap .Destination = v .Destination
108- wrap .Title = v .Title
109- wrap .SetAttributeString ("target" , []byte ("_blank" ))
110-
111- // Duplicate the current image node
112- image := ast .NewImage (ast .NewLink ())
113- image .Destination = v .Destination
114- image .Title = v .Title
115- for _ , attr := range v .Attributes () {
116- image .SetAttribute (attr .Name , attr .Value )
117- }
118- for child := v .FirstChild (); child != nil ; {
119- next := child .NextSibling ()
120- image .AppendChild (image , child )
121- child = next
122- }
123-
124- // Append our duplicate image to the wrapper link
125- wrap .AppendChild (wrap , image )
126-
127- // Wire in the next sibling
128- wrap .SetNextSibling (next )
129-
130- // Replace the current node with the wrapper link
131- parent .ReplaceChild (parent , n , wrap )
132-
133- // But most importantly ensure the next sibling is still on the old image too
134- v .SetNextSibling (next )
135- }
68+ g .transformImage (ctx , v , reader )
13669 case * ast.Link :
137- // Links need their href to munged to be a real value
138- link := v .Destination
139- isAnchorFragment := len (link ) > 0 && link [0 ] == '#'
140- if ! isAnchorFragment && ! markup .IsFullURLBytes (link ) {
141- base := ctx .Links .Base
142- if ctx .IsWiki {
143- base = ctx .Links .WikiLink ()
144- } else if ctx .Links .HasBranchInfo () {
145- base = ctx .Links .SrcLink ()
146- }
147- link = []byte (giteautil .URLJoin (base , string (link )))
148- }
149- if isAnchorFragment {
150- link = []byte ("#user-content-" + string (link )[1 :])
151- }
152- v .Destination = link
70+ g .transformLink (ctx , v , reader )
15371 case * ast.List :
154- if v .HasChildren () {
155- children := make ([]ast.Node , 0 , v .ChildCount ())
156- child := v .FirstChild ()
157- for child != nil {
158- children = append (children , child )
159- child = child .NextSibling ()
160- }
161- v .RemoveChildren (v )
162-
163- for _ , child := range children {
164- listItem := child .(* ast.ListItem )
165- if ! child .HasChildren () || ! child .FirstChild ().HasChildren () {
166- v .AppendChild (v , child )
167- continue
168- }
169- taskCheckBox , ok := child .FirstChild ().FirstChild ().(* east.TaskCheckBox )
170- if ! ok {
171- v .AppendChild (v , child )
172- continue
173- }
174- newChild := NewTaskCheckBoxListItem (listItem )
175- newChild .IsChecked = taskCheckBox .IsChecked
176- newChild .SetAttributeString ("class" , []byte ("task-list-item" ))
177- segments := newChild .FirstChild ().Lines ()
178- if segments .Len () > 0 {
179- segment := segments .At (0 )
180- newChild .SourcePosition = rc .metaLength + segment .Start
181- }
182- v .AppendChild (v , newChild )
183- }
184- }
185- g .applyElementDir (v )
72+ g .transformList (ctx , v , reader , rc )
18673 case * ast.Text :
18774 if v .SoftLineBreak () && ! v .HardLineBreak () {
18875 if ctx .Metas ["mode" ] != "document" {
@@ -192,10 +79,7 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
19279 }
19380 }
19481 case * ast.CodeSpan :
195- colorContent := n .Text (reader .Source ())
196- if css .ColorHandler (strings .ToLower (string (colorContent ))) {
197- v .AppendChild (v , NewColorPreview (colorContent ))
198- }
82+ g .transformCodeSpan (ctx , v , reader )
19983 case * ast.Blockquote :
20084 return g .transformBlockquote (v , reader )
20185 }
@@ -219,50 +103,6 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
219103 }
220104}
221105
222- type prefixedIDs struct {
223- values container.Set [string ]
224- }
225-
226- // Generate generates a new element id.
227- func (p * prefixedIDs ) Generate (value []byte , kind ast.NodeKind ) []byte {
228- dft := []byte ("id" )
229- if kind == ast .KindHeading {
230- dft = []byte ("heading" )
231- }
232- return p .GenerateWithDefault (value , dft )
233- }
234-
235- // GenerateWithDefault generates a new element id.
236- func (p * prefixedIDs ) GenerateWithDefault (value , dft []byte ) []byte {
237- result := common .CleanValue (value )
238- if len (result ) == 0 {
239- result = dft
240- }
241- if ! bytes .HasPrefix (result , []byte ("user-content-" )) {
242- result = append ([]byte ("user-content-" ), result ... )
243- }
244- if p .values .Add (util .BytesToReadOnlyString (result )) {
245- return result
246- }
247- for i := 1 ; ; i ++ {
248- newResult := fmt .Sprintf ("%s-%d" , result , i )
249- if p .values .Add (newResult ) {
250- return []byte (newResult )
251- }
252- }
253- }
254-
255- // Put puts a given element id to the used ids table.
256- func (p * prefixedIDs ) Put (value []byte ) {
257- p .values .Add (util .BytesToReadOnlyString (value ))
258- }
259-
260- func newPrefixedIDs () * prefixedIDs {
261- return & prefixedIDs {
262- values : make (container.Set [string ]),
263- }
264- }
265-
266106// NewHTMLRenderer creates a HTMLRenderer to render
267107// in the gitea form.
268108func NewHTMLRenderer (opts ... html.Option ) renderer.NodeRenderer {
@@ -295,60 +135,6 @@ func (r *HTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
295135 reg .Register (east .KindTaskCheckBox , r .renderTaskCheckBox )
296136}
297137
298- // renderCodeSpan renders CodeSpan elements (like goldmark upstream does) but also renders ColorPreview elements.
299- // See #21474 for reference
300- func (r * HTMLRenderer ) renderCodeSpan (w util.BufWriter , source []byte , n ast.Node , entering bool ) (ast.WalkStatus , error ) {
301- if entering {
302- if n .Attributes () != nil {
303- _ , _ = w .WriteString ("<code" )
304- html .RenderAttributes (w , n , html .CodeAttributeFilter )
305- _ = w .WriteByte ('>' )
306- } else {
307- _ , _ = w .WriteString ("<code>" )
308- }
309- for c := n .FirstChild (); c != nil ; c = c .NextSibling () {
310- switch v := c .(type ) {
311- case * ast.Text :
312- segment := v .Segment
313- value := segment .Value (source )
314- if bytes .HasSuffix (value , []byte ("\n " )) {
315- r .Writer .RawWrite (w , value [:len (value )- 1 ])
316- r .Writer .RawWrite (w , []byte (" " ))
317- } else {
318- r .Writer .RawWrite (w , value )
319- }
320- case * ColorPreview :
321- _ , _ = w .WriteString (fmt .Sprintf (`<span class="color-preview" style="background-color: %v"></span>` , string (v .Color )))
322- }
323- }
324- return ast .WalkSkipChildren , nil
325- }
326- _ , _ = w .WriteString ("</code>" )
327- return ast .WalkContinue , nil
328- }
329-
330- // renderAttention renders a quote marked with i.e. "> **Note**" or "> **Warning**" with a corresponding svg
331- func (r * HTMLRenderer ) renderAttention (w util.BufWriter , source []byte , node ast.Node , entering bool ) (ast.WalkStatus , error ) {
332- if entering {
333- n := node .(* Attention )
334- var octiconName string
335- switch n .AttentionType {
336- case "tip" :
337- octiconName = "light-bulb"
338- case "important" :
339- octiconName = "report"
340- case "warning" :
341- octiconName = "alert"
342- case "caution" :
343- octiconName = "stop"
344- default : // including "note"
345- octiconName = "info"
346- }
347- _ , _ = w .WriteString (string (svg .RenderHTML ("octicon-" + octiconName , 16 , "attention-icon attention-" + n .AttentionType )))
348- }
349- return ast .WalkContinue , nil
350- }
351-
352138func (r * HTMLRenderer ) renderDocument (w util.BufWriter , source []byte , node ast.Node , entering bool ) (ast.WalkStatus , error ) {
353139 n := node .(* ast.Document )
354140
@@ -435,38 +221,3 @@ func (r *HTMLRenderer) renderIcon(w util.BufWriter, source []byte, node ast.Node
435221
436222 return ast .WalkContinue , nil
437223}
438-
439- func (r * HTMLRenderer ) renderTaskCheckBoxListItem (w util.BufWriter , source []byte , node ast.Node , entering bool ) (ast.WalkStatus , error ) {
440- n := node .(* TaskCheckBoxListItem )
441- if entering {
442- if n .Attributes () != nil {
443- _ , _ = w .WriteString ("<li" )
444- html .RenderAttributes (w , n , html .ListItemAttributeFilter )
445- _ = w .WriteByte ('>' )
446- } else {
447- _ , _ = w .WriteString ("<li>" )
448- }
449- fmt .Fprintf (w , `<input type="checkbox" disabled="" data-source-position="%d"` , n .SourcePosition )
450- if n .IsChecked {
451- _ , _ = w .WriteString (` checked=""` )
452- }
453- if r .XHTML {
454- _ , _ = w .WriteString (` />` )
455- } else {
456- _ = w .WriteByte ('>' )
457- }
458- fc := n .FirstChild ()
459- if fc != nil {
460- if _ , ok := fc .(* ast.TextBlock ); ! ok {
461- _ = w .WriteByte ('\n' )
462- }
463- }
464- } else {
465- _ , _ = w .WriteString ("</li>\n " )
466- }
467- return ast .WalkContinue , nil
468- }
469-
470- func (r * HTMLRenderer ) renderTaskCheckBox (w util.BufWriter , source []byte , node ast.Node , entering bool ) (ast.WalkStatus , error ) {
471- return ast .WalkContinue , nil
472- }
0 commit comments