From 4fe8ea3083842f5865f0b103d37746d5b81d8a52 Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Thu, 2 Jun 2022 16:47:30 +0100 Subject: [PATCH 1/4] Automatically render wiki TOC Automatically add sidebar in the wiki view containing a TOC for the wiki page. Fix #822 Signed-off-by: Andrew Thornton --- modules/markup/markdown/goldmark.go | 43 ++++++++++++----------------- modules/markup/markdown/markdown.go | 8 ++++-- modules/markup/markdown/toc.go | 3 +- modules/markup/renderer.go | 28 ++++++++++++------- modules/templates/helper.go | 36 ++++++++++++++++++++++++ routers/web/repo/wiki.go | 2 ++ templates/repo/wiki/view.tmpl | 35 ++++++++++++++++------- web_src/less/_repository.less | 12 ++++++++ 8 files changed, 117 insertions(+), 50 deletions(-) diff --git a/modules/markup/markdown/goldmark.go b/modules/markup/markdown/goldmark.go index 9b6cd3aaefba..1750128dec85 100644 --- a/modules/markup/markdown/goldmark.go +++ b/modules/markup/markdown/goldmark.go @@ -27,13 +27,6 @@ import ( var byteMailto = []byte("mailto:") -// Header holds the data about a header. -type Header struct { - Level int - Text string - ID string -} - // ASTTransformer is a default transformer of the goldmark tree. type ASTTransformer struct{} @@ -42,12 +35,13 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa metaData := meta.GetItems(pc) firstChild := node.FirstChild() createTOC := false - toc := []Header{} + ctx := pc.Get(renderContextKey).(*markup.RenderContext) rc := &RenderConfig{ Meta: "table", Icon: "table", Lang: "", } + if metaData != nil { rc.ToRenderConfig(metaData) @@ -56,7 +50,7 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa node.InsertBefore(node, firstChild, metaNode) } createTOC = rc.TOC - toc = make([]Header, 0, 100) + ctx.TableOfContents = make([]markup.Header, 0, 100) } _ = ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) { @@ -66,23 +60,20 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa switch v := n.(type) { case *ast.Heading: - if createTOC { - text := n.Text(reader.Source()) - header := Header{ - Text: util.BytesToReadOnlyString(text), - Level: v.Level, - } - if id, found := v.AttributeString("id"); found { - header.ID = util.BytesToReadOnlyString(id.([]byte)) - } - toc = append(toc, header) - } else { - for _, attr := range v.Attributes() { - if _, ok := attr.Value.([]byte); !ok { - v.SetAttribute(attr.Name, []byte(fmt.Sprintf("%v", attr.Value))) - } + for _, attr := range v.Attributes() { + if _, ok := attr.Value.([]byte); !ok { + v.SetAttribute(attr.Name, []byte(fmt.Sprintf("%v", attr.Value))) } } + text := n.Text(reader.Source()) + header := markup.Header{ + Text: util.BytesToReadOnlyString(text), + Level: v.Level, + } + if id, found := v.AttributeString("id"); found { + header.ID = util.BytesToReadOnlyString(id.([]byte)) + } + ctx.TableOfContents = append(ctx.TableOfContents, header) case *ast.Image: // Images need two things: // @@ -199,12 +190,12 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa return ast.WalkContinue, nil }) - if createTOC && len(toc) > 0 { + if createTOC && len(ctx.TableOfContents) > 0 { lang := rc.Lang if len(lang) == 0 { lang = setting.Langs[0] } - tocNode := createTOCNode(toc, lang) + tocNode := createTOCNode(ctx.TableOfContents, lang) if tocNode != nil { node.InsertBefore(node, firstChild, tocNode) } diff --git a/modules/markup/markdown/markdown.go b/modules/markup/markdown/markdown.go index 320c2f7f8278..7ebdfea6c4ad 100644 --- a/modules/markup/markdown/markdown.go +++ b/modules/markup/markdown/markdown.go @@ -34,9 +34,10 @@ var ( ) var ( - urlPrefixKey = parser.NewContextKey() - isWikiKey = parser.NewContextKey() - renderMetasKey = parser.NewContextKey() + urlPrefixKey = parser.NewContextKey() + isWikiKey = parser.NewContextKey() + renderMetasKey = parser.NewContextKey() + renderContextKey = parser.NewContextKey() ) type limitWriter struct { @@ -67,6 +68,7 @@ func newParserContext(ctx *markup.RenderContext) parser.Context { pc.Set(urlPrefixKey, ctx.URLPrefix) pc.Set(isWikiKey, ctx.IsWiki) pc.Set(renderMetasKey, ctx.Metas) + pc.Set(renderContextKey, ctx) return pc } diff --git a/modules/markup/markdown/toc.go b/modules/markup/markdown/toc.go index 9d11b771f7f3..fec45103e588 100644 --- a/modules/markup/markdown/toc.go +++ b/modules/markup/markdown/toc.go @@ -8,12 +8,13 @@ import ( "fmt" "net/url" + "code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/translation/i18n" "github.com/yuin/goldmark/ast" ) -func createTOCNode(toc []Header, lang string) ast.Node { +func createTOCNode(toc []markup.Header, lang string) ast.Node { details := NewDetails() summary := NewSummary() diff --git a/modules/markup/renderer.go b/modules/markup/renderer.go index cf8b9bace70b..53ecbfce2bf3 100644 --- a/modules/markup/renderer.go +++ b/modules/markup/renderer.go @@ -33,18 +33,26 @@ func Init() { } } +// Header holds the data about a header. +type Header struct { + Level int + Text string + ID string +} + // RenderContext represents a render context type RenderContext struct { - Ctx context.Context - Filename string - Type string - IsWiki bool - URLPrefix string - Metas map[string]string - DefaultLink string - GitRepo *git.Repository - ShaExistCache map[string]bool - cancelFn func() + Ctx context.Context + Filename string + Type string + IsWiki bool + URLPrefix string + Metas map[string]string + DefaultLink string + GitRepo *git.Repository + ShaExistCache map[string]bool + cancelFn func() + TableOfContents []Header } // Cancel runs any cleanup functions that have been registered for this Ctx diff --git a/modules/templates/helper.go b/modules/templates/helper.go index cc0fed744220..e7289d0a74c0 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -390,6 +390,42 @@ func NewFuncMap() []template.FuncMap { "Join": strings.Join, "QueryEscape": url.QueryEscape, "DotEscape": DotEscape, + "Iterate": func(arg interface{}) (items []uint64) { + count := uint64(0) + switch arg.(type) { + case uint64: + count = arg.(uint64) + case *uint64: + count = *(arg.(*uint64)) + case int64: + count = uint64(arg.(int64)) + case *int64: + count = uint64(*(arg.(*int64))) + case int: + count = uint64(arg.(int)) + case *int: + count = uint64(*arg.(*int)) + case uint: + count = uint64(arg.(uint)) + case *uint: + count = uint64(*arg.(*uint)) + case int32: + count = uint64(arg.(int32)) + case *int32: + count = uint64(*arg.(*int32)) + case uint32: + count = uint64(arg.(uint32)) + case *uint32: + count = uint64(*arg.(*uint32)) + } + if count <= 0 { + return items + } + for i := uint64(0); i < count; i++ { + items = append(items, i) + } + return items + }, }} } diff --git a/routers/web/repo/wiki.go b/routers/web/repo/wiki.go index 77f60a1dfaff..f4aabbf480f8 100644 --- a/routers/web/repo/wiki.go +++ b/routers/web/repo/wiki.go @@ -280,6 +280,8 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { ctx.Data["footerPresent"] = false } + ctx.Data["toc"] = rctx.TableOfContents + // get commit count - wiki revisions commitsCount, _ := wikiRepo.FileCommitsCount("master", pageFilename) ctx.Data["CommitCount"] = commitsCount diff --git a/templates/repo/wiki/view.tmpl b/templates/repo/wiki/view.tmpl index 04faa90b9e66..e6287e124845 100644 --- a/templates/repo/wiki/view.tmpl +++ b/templates/repo/wiki/view.tmpl @@ -64,20 +64,35 @@

{{.FormatWarning}}

{{end}} -
-
+
+
{{template "repo/unicode_escape_prompt" dict "EscapeStatus" .EscapeStatus "root" $}} {{.content | Safe}}
- {{if .sidebarPresent}} + {{if or .sidebarPresent .toc}}
- + {{if .toc}} +
+
{{.i18n.Tr "toc"}}
+ {{$level := 0}} + {{range .toc}} + {{if lt $level .Level}}{{range Iterate (Subtract .Level $level)}}
    {{end}}{{end}} + {{if gt $level .Level}}{{range Iterate (Subtract $level .Level)}}
{{end}}{{end}} + {{$level = .Level}} +
  • {{.Text}}
  • + {{end}} + {{range Iterate $level}}{{end}} +
    + {{end}} + {{if .sidebarPresent}} + + {{end}}
    {{end}}
    diff --git a/web_src/less/_repository.less b/web_src/less/_repository.less index 37c8b9cc2169..f66f1b8fc5f4 100644 --- a/web_src/less/_repository.less +++ b/web_src/less/_repository.less @@ -3080,6 +3080,18 @@ td.blob-excerpt { } } +.wiki-content-toc { + > ul > li { + margin-bottom: 4px; + } + + ul { + margin: 0; + list-style: none; + padding-left: 1em; + } +} + /* fomantic's last-child selector does not work with hidden last child */ .ui.buttons .unescape-button { border-top-right-radius: .28571429rem; From 478ae195250a0baabceba37204e5ab57da12b597 Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Thu, 2 Jun 2022 20:50:29 +0100 Subject: [PATCH 2/4] placate lint Signed-off-by: Andrew Thornton --- modules/templates/helper.go | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/modules/templates/helper.go b/modules/templates/helper.go index e7289d0a74c0..da3b3f70250e 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -392,31 +392,31 @@ func NewFuncMap() []template.FuncMap { "DotEscape": DotEscape, "Iterate": func(arg interface{}) (items []uint64) { count := uint64(0) - switch arg.(type) { + switch val := arg.(type) { case uint64: - count = arg.(uint64) + count = val case *uint64: - count = *(arg.(*uint64)) + count = *val case int64: - count = uint64(arg.(int64)) + count = uint64(val) case *int64: - count = uint64(*(arg.(*int64))) + count = uint64(*val) case int: - count = uint64(arg.(int)) + count = uint64(val) case *int: - count = uint64(*arg.(*int)) + count = uint64(*val) case uint: - count = uint64(arg.(uint)) + count = uint64(val) case *uint: - count = uint64(*arg.(*uint)) + count = uint64(*val) case int32: - count = uint64(arg.(int32)) + count = uint64(val) case *int32: - count = uint64(*arg.(*int32)) + count = uint64(*val) case uint32: - count = uint64(arg.(uint32)) + count = uint64(val) case *uint32: - count = uint64(*arg.(*uint32)) + count = uint64(*val) } if count <= 0 { return items From adb192f4f6d895dba9a09ea55cdc0f0e20f4bb1a Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Fri, 3 Jun 2022 15:31:59 +0100 Subject: [PATCH 3/4] Make the TOC collapsable Signed-off-by: Andrew Thornton --- templates/repo/wiki/view.tmpl | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/templates/repo/wiki/view.tmpl b/templates/repo/wiki/view.tmpl index e6287e124845..3189ed64caf2 100644 --- a/templates/repo/wiki/view.tmpl +++ b/templates/repo/wiki/view.tmpl @@ -73,15 +73,19 @@
    {{if .toc}}
    -
    {{.i18n.Tr "toc"}}
    - {{$level := 0}} - {{range .toc}} - {{if lt $level .Level}}{{range Iterate (Subtract .Level $level)}}
      {{end}}{{end}} - {{if gt $level .Level}}{{range Iterate (Subtract $level .Level)}}
    {{end}}{{end}} - {{$level = .Level}} -
  • {{.Text}}
  • - {{end}} - {{range Iterate $level}}{{end}} +
    + +
    {{.i18n.Tr "toc"}}
    +
    + {{$level := 0}} + {{range .toc}} + {{if lt $level .Level}}{{range Iterate (Subtract .Level $level)}}
      {{end}}{{end}} + {{if gt $level .Level}}{{range Iterate (Subtract $level .Level)}}
    {{end}}{{end}} + {{$level = .Level}} +
  • {{.Text}}
  • + {{end}} + {{range Iterate $level}}{{end}} +
    {{end}} {{if .sidebarPresent}} From e59651b9a0e76b5c490a3eea563a638bb4acebd8 Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Tue, 7 Jun 2022 21:45:29 +0100 Subject: [PATCH 4/4] as per gusted Signed-off-by: Andrew Thornton --- modules/templates/helper.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/modules/templates/helper.go b/modules/templates/helper.go index da3b3f70250e..ef7b70c09f8a 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -18,6 +18,7 @@ import ( "reflect" "regexp" "runtime" + "strconv" "strings" texttmpl "text/template" "time" @@ -398,25 +399,49 @@ func NewFuncMap() []template.FuncMap { case *uint64: count = *val case int64: + if val < 0 { + val = 0 + } count = uint64(val) case *int64: + if *val < 0 { + *val = 0 + } count = uint64(*val) case int: + if val < 0 { + val = 0 + } count = uint64(val) case *int: + if *val < 0 { + *val = 0 + } count = uint64(*val) case uint: count = uint64(val) case *uint: count = uint64(*val) case int32: + if val < 0 { + val = 0 + } count = uint64(val) case *int32: + if *val < 0 { + *val = 0 + } count = uint64(*val) case uint32: count = uint64(val) case *uint32: count = uint64(*val) + case string: + cnt, _ := strconv.ParseInt(val, 10, 64) + if cnt < 0 { + cnt = 0 + } + count = uint64(cnt) } if count <= 0 { return items