@@ -7,12 +7,16 @@ package markdown
7
7
import (
8
8
"bytes"
9
9
"fmt"
10
+ "regexp"
10
11
"strings"
11
12
13
+ "code.gitea.io/gitea/modules/log"
12
14
"code.gitea.io/gitea/modules/markup"
13
15
"code.gitea.io/gitea/modules/markup/common"
16
+ "code.gitea.io/gitea/modules/setting"
14
17
giteautil "code.gitea.io/gitea/modules/util"
15
18
19
+ meta "github.com/yuin/goldmark-meta"
16
20
"github.com/yuin/goldmark/ast"
17
21
east "github.com/yuin/goldmark/extension/ast"
18
22
"github.com/yuin/goldmark/parser"
@@ -24,17 +28,56 @@ import (
24
28
25
29
var byteMailto = []byte ("mailto:" )
26
30
27
- // GiteaASTTransformer is a default transformer of the goldmark tree.
28
- type GiteaASTTransformer struct {}
31
+ // Header holds the data about a header.
32
+ type Header struct {
33
+ Level int
34
+ Text string
35
+ ID string
36
+ }
37
+
38
+ // ASTTransformer is a default transformer of the goldmark tree.
39
+ type ASTTransformer struct {}
29
40
30
41
// Transform transforms the given AST tree.
31
- func (g * GiteaASTTransformer ) Transform (node * ast.Document , reader text.Reader , pc parser.Context ) {
42
+ func (g * ASTTransformer ) Transform (node * ast.Document , reader text.Reader , pc parser.Context ) {
43
+ metaData := meta .GetItems (pc )
44
+ firstChild := node .FirstChild ()
45
+ createTOC := false
46
+ var toc = []Header {}
47
+ rc := & RenderConfig {
48
+ Meta : "table" ,
49
+ Icon : "table" ,
50
+ Lang : "" ,
51
+ }
52
+ if metaData != nil {
53
+ rc .ToRenderConfig (metaData )
54
+
55
+ metaNode := rc .toMetaNode (metaData )
56
+ if metaNode != nil {
57
+ node .InsertBefore (node , firstChild , metaNode )
58
+ }
59
+ createTOC = rc .TOC
60
+ toc = make ([]Header , 0 , 100 )
61
+ }
62
+
32
63
_ = ast .Walk (node , func (n ast.Node , entering bool ) (ast.WalkStatus , error ) {
33
64
if ! entering {
34
65
return ast .WalkContinue , nil
35
66
}
36
67
37
68
switch v := n .(type ) {
69
+ case * ast.Heading :
70
+ if createTOC {
71
+ text := n .Text (reader .Source ())
72
+ header := Header {
73
+ Text : util .BytesToReadOnlyString (text ),
74
+ Level : v .Level ,
75
+ }
76
+ if id , found := v .AttributeString ("id" ); found {
77
+ header .ID = util .BytesToReadOnlyString (id .([]byte ))
78
+ }
79
+ toc = append (toc , header )
80
+ }
38
81
case * ast.Image :
39
82
// Images need two things:
40
83
//
@@ -91,6 +134,21 @@ func (g *GiteaASTTransformer) Transform(node *ast.Document, reader text.Reader,
91
134
}
92
135
return ast .WalkContinue , nil
93
136
})
137
+
138
+ if createTOC && len (toc ) > 0 {
139
+ lang := rc .Lang
140
+ if len (lang ) == 0 {
141
+ lang = setting .Langs [0 ]
142
+ }
143
+ tocNode := createTOCNode (toc , lang )
144
+ if tocNode != nil {
145
+ node .InsertBefore (node , firstChild , tocNode )
146
+ }
147
+ }
148
+
149
+ if len (rc .Lang ) > 0 {
150
+ node .SetAttributeString ("lang" , []byte (rc .Lang ))
151
+ }
94
152
}
95
153
96
154
type prefixedIDs struct {
@@ -139,10 +197,10 @@ func newPrefixedIDs() *prefixedIDs {
139
197
}
140
198
}
141
199
142
- // NewTaskCheckBoxHTMLRenderer creates a TaskCheckBoxHTMLRenderer to render tasklists
200
+ // NewHTMLRenderer creates a HTMLRenderer to render
143
201
// in the gitea form.
144
- func NewTaskCheckBoxHTMLRenderer (opts ... html.Option ) renderer.NodeRenderer {
145
- r := & TaskCheckBoxHTMLRenderer {
202
+ func NewHTMLRenderer (opts ... html.Option ) renderer.NodeRenderer {
203
+ r := & HTMLRenderer {
146
204
Config : html .NewConfig (),
147
205
}
148
206
for _ , opt := range opts {
@@ -151,19 +209,109 @@ func NewTaskCheckBoxHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
151
209
return r
152
210
}
153
211
154
- // TaskCheckBoxHTMLRenderer is a renderer.NodeRenderer implementation that
155
- // renders checkboxes in list items.
156
- // Overrides the default goldmark one to present the gitea format
157
- type TaskCheckBoxHTMLRenderer struct {
212
+ // HTMLRenderer is a renderer.NodeRenderer implementation that
213
+ // renders gitea specific features.
214
+ type HTMLRenderer struct {
158
215
html.Config
159
216
}
160
217
161
218
// RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
162
- func (r * TaskCheckBoxHTMLRenderer ) RegisterFuncs (reg renderer.NodeRendererFuncRegisterer ) {
219
+ func (r * HTMLRenderer ) RegisterFuncs (reg renderer.NodeRendererFuncRegisterer ) {
220
+ reg .Register (ast .KindDocument , r .renderDocument )
221
+ reg .Register (KindDetails , r .renderDetails )
222
+ reg .Register (KindSummary , r .renderSummary )
223
+ reg .Register (KindIcon , r .renderIcon )
163
224
reg .Register (east .KindTaskCheckBox , r .renderTaskCheckBox )
164
225
}
165
226
166
- func (r * TaskCheckBoxHTMLRenderer ) renderTaskCheckBox (w util.BufWriter , source []byte , node ast.Node , entering bool ) (ast.WalkStatus , error ) {
227
+ func (r * HTMLRenderer ) renderDocument (w util.BufWriter , source []byte , node ast.Node , entering bool ) (ast.WalkStatus , error ) {
228
+ log .Info ("renderDocument %v" , node )
229
+ n := node .(* ast.Document )
230
+
231
+ if val , has := n .AttributeString ("lang" ); has {
232
+ var err error
233
+ if entering {
234
+ _ , err = w .WriteString ("<div" )
235
+ if err == nil {
236
+ _ , err = w .WriteString (fmt .Sprintf (` lang=%q` , val ))
237
+ }
238
+ if err == nil {
239
+ _ , err = w .WriteRune ('>' )
240
+ }
241
+ } else {
242
+ _ , err = w .WriteString ("</div>" )
243
+ }
244
+
245
+ if err != nil {
246
+ return ast .WalkStop , err
247
+ }
248
+ }
249
+
250
+ return ast .WalkContinue , nil
251
+ }
252
+
253
+ func (r * HTMLRenderer ) renderDetails (w util.BufWriter , source []byte , node ast.Node , entering bool ) (ast.WalkStatus , error ) {
254
+ var err error
255
+ if entering {
256
+ _ , err = w .WriteString ("<details>" )
257
+ } else {
258
+ _ , err = w .WriteString ("</details>" )
259
+ }
260
+
261
+ if err != nil {
262
+ return ast .WalkStop , err
263
+ }
264
+
265
+ return ast .WalkContinue , nil
266
+ }
267
+
268
+ func (r * HTMLRenderer ) renderSummary (w util.BufWriter , source []byte , node ast.Node , entering bool ) (ast.WalkStatus , error ) {
269
+ var err error
270
+ if entering {
271
+ _ , err = w .WriteString ("<summary>" )
272
+ } else {
273
+ _ , err = w .WriteString ("</summary>" )
274
+ }
275
+
276
+ if err != nil {
277
+ return ast .WalkStop , err
278
+ }
279
+
280
+ return ast .WalkContinue , nil
281
+ }
282
+
283
+ var validNameRE = regexp .MustCompile ("^[a-z ]+$" )
284
+
285
+ func (r * HTMLRenderer ) renderIcon (w util.BufWriter , source []byte , node ast.Node , entering bool ) (ast.WalkStatus , error ) {
286
+ if ! entering {
287
+ return ast .WalkContinue , nil
288
+ }
289
+
290
+ n := node .(* Icon )
291
+
292
+ name := strings .TrimSpace (strings .ToLower (string (n .Name )))
293
+
294
+ if len (name ) == 0 {
295
+ // skip this
296
+ return ast .WalkContinue , nil
297
+ }
298
+
299
+ if ! validNameRE .MatchString (name ) {
300
+ // skip this
301
+ return ast .WalkContinue , nil
302
+ }
303
+
304
+ var err error
305
+ _ , err = w .WriteString (fmt .Sprintf (`<i class="icon %s"></i>` , name ))
306
+
307
+ if err != nil {
308
+ return ast .WalkStop , err
309
+ }
310
+
311
+ return ast .WalkContinue , nil
312
+ }
313
+
314
+ func (r * HTMLRenderer ) renderTaskCheckBox (w util.BufWriter , source []byte , node ast.Node , entering bool ) (ast.WalkStatus , error ) {
167
315
if ! entering {
168
316
return ast .WalkContinue , nil
169
317
}
0 commit comments