4
4
package markdown
5
5
6
6
import (
7
- "bytes"
8
7
"fmt"
9
8
"regexp"
10
9
"strings"
11
10
12
11
"code.gitea.io/gitea/modules/container"
13
12
"code.gitea.io/gitea/modules/markup"
14
- "code.gitea.io/gitea/modules/markup/common"
15
13
"code.gitea.io/gitea/modules/setting"
16
- "code.gitea.io/gitea/modules/svg"
17
- giteautil "code.gitea.io/gitea/modules/util"
18
14
19
- "github.com/microcosm-cc/bluemonday/css"
20
15
"github.com/yuin/goldmark/ast"
21
16
east "github.com/yuin/goldmark/extension/ast"
22
17
"github.com/yuin/goldmark/parser"
@@ -28,12 +23,12 @@ import (
28
23
29
24
// ASTTransformer is a default transformer of the goldmark tree.
30
25
type ASTTransformer struct {
31
- AttentionTypes container.Set [string ]
26
+ attentionTypes container.Set [string ]
32
27
}
33
28
34
29
func NewASTTransformer () * ASTTransformer {
35
30
return & ASTTransformer {
36
- AttentionTypes : container .SetOf ("note" , "tip" , "important" , "warning" , "caution" ),
31
+ attentionTypes : container .SetOf ("note" , "tip" , "important" , "warning" , "caution" ),
37
32
}
38
33
}
39
34
@@ -66,123 +61,15 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
66
61
67
62
switch v := n .(type ) {
68
63
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 )
84
65
case * ast.Paragraph :
85
66
g .applyElementDir (v )
86
67
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 )
136
69
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 )
153
71
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 )
186
73
case * ast.Text :
187
74
if v .SoftLineBreak () && ! v .HardLineBreak () {
188
75
if ctx .Metas ["mode" ] != "document" {
@@ -192,10 +79,7 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
192
79
}
193
80
}
194
81
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 )
199
83
case * ast.Blockquote :
200
84
return g .transformBlockquote (v , reader )
201
85
}
@@ -219,50 +103,6 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa
219
103
}
220
104
}
221
105
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
-
266
106
// NewHTMLRenderer creates a HTMLRenderer to render
267
107
// in the gitea form.
268
108
func NewHTMLRenderer (opts ... html.Option ) renderer.NodeRenderer {
@@ -295,60 +135,6 @@ func (r *HTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
295
135
reg .Register (east .KindTaskCheckBox , r .renderTaskCheckBox )
296
136
}
297
137
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
-
352
138
func (r * HTMLRenderer ) renderDocument (w util.BufWriter , source []byte , node ast.Node , entering bool ) (ast.WalkStatus , error ) {
353
139
n := node .(* ast.Document )
354
140
@@ -435,38 +221,3 @@ func (r *HTMLRenderer) renderIcon(w util.BufWriter, source []byte, node ast.Node
435
221
436
222
return ast .WalkContinue , nil
437
223
}
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