diff options
author | zeripath <art27@cantab.net> | 2022-06-08 09:59:16 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-06-08 16:59:16 +0800 |
commit | ac88f21ecc5612befe51f7ab6ffcb76c681daba5 (patch) | |
tree | cbe281221d0703da0f1ecce428888e036ef311d7 | |
parent | c1c07e533c9ca3be13b4af057b6b989402c12f6e (diff) | |
download | gitea-ac88f21ecc5612befe51f7ab6ffcb76c681daba5.tar.gz gitea-ac88f21ecc5612befe51f7ab6ffcb76c681daba5.zip |
Automatically render wiki TOC (#19873)
Automatically add sidebar in the wiki view containing a TOC for the wiki page.
Make the TOC collapsable
Signed-off-by: Andrew Thornton <art27@cantab.net>
-rw-r--r-- | modules/markup/markdown/goldmark.go | 43 | ||||
-rw-r--r-- | modules/markup/markdown/markdown.go | 8 | ||||
-rw-r--r-- | modules/markup/markdown/toc.go | 3 | ||||
-rw-r--r-- | modules/markup/renderer.go | 28 | ||||
-rw-r--r-- | modules/templates/helper.go | 61 | ||||
-rw-r--r-- | routers/web/repo/wiki.go | 2 | ||||
-rw-r--r-- | templates/repo/wiki/view.tmpl | 39 | ||||
-rw-r--r-- | web_src/less/_repository.less | 12 |
8 files changed, 146 insertions, 50 deletions
diff --git a/modules/markup/markdown/goldmark.go b/modules/markup/markdown/goldmark.go index 9b6cd3aaef..1750128dec 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 320c2f7f82..7ebdfea6c4 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 9d11b771f7..fec45103e5 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 cf8b9bace7..53ecbfce2b 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 cc0fed7442..ef7b70c09f 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" @@ -390,6 +391,66 @@ func NewFuncMap() []template.FuncMap { "Join": strings.Join, "QueryEscape": url.QueryEscape, "DotEscape": DotEscape, + "Iterate": func(arg interface{}) (items []uint64) { + count := uint64(0) + switch val := arg.(type) { + case uint64: + count = val + 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 + } + 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 77f60a1dfa..f4aabbf480 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 04faa90b9e..3189ed64ca 100644 --- a/templates/repo/wiki/view.tmpl +++ b/templates/repo/wiki/view.tmpl @@ -64,20 +64,39 @@ <p>{{.FormatWarning}}</p> </div> {{end}} - <div class="ui {{if .sidebarPresent}}grid equal width{{end}}" style="margin-top: 1rem;"> - <div class="ui {{if .sidebarPresent}}eleven wide column{{end}} segment markup wiki-content-main"> + <div class="ui {{if or .sidebarPresent .toc}}grid equal width{{end}}" style="margin-top: 1rem;"> + <div class="ui {{if or .sidebarPresent .toc}}eleven wide column{{end}} segment markup wiki-content-main"> {{template "repo/unicode_escape_prompt" dict "EscapeStatus" .EscapeStatus "root" $}} {{.content | Safe}} </div> - {{if .sidebarPresent}} + {{if or .sidebarPresent .toc}} <div class="column" style="padding-top: 0;"> - <div class="ui segment wiki-content-sidebar"> - {{if and .CanWriteWiki (not .Repository.IsMirror)}} - <a class="ui right floated muted" href="{{.RepoLink}}/wiki/_Sidebar?action=_edit" aria-label="{{.i18n.Tr "repo.wiki.edit_page_button"}}">{{svg "octicon-pencil"}}</a> - {{end}} - {{template "repo/unicode_escape_prompt" dict "EscapeStatus" .sidebarEscapeStatus "root" $}} - {{.sidebarContent | Safe}} - </div> + {{if .toc}} + <div class="ui segment wiki-content-toc"> + <details open> + <summary> + <div class="ui header">{{.i18n.Tr "toc"}}</div> + </summary> + {{$level := 0}} + {{range .toc}} + {{if lt $level .Level}}{{range Iterate (Subtract .Level $level)}}<ul>{{end}}{{end}} + {{if gt $level .Level}}{{range Iterate (Subtract $level .Level)}}</ul>{{end}}{{end}} + {{$level = .Level}} + <li><a href="#{{.ID}}">{{.Text}}</a></li> + {{end}} + {{range Iterate $level}}</ul>{{end}} + </details> + </div> + {{end}} + {{if .sidebarPresent}} + <div class="ui segment wiki-content-sidebar"> + {{if and .CanWriteWiki (not .Repository.IsMirror)}} + <a class="ui right floated muted" href="{{.RepoLink}}/wiki/_Sidebar?action=_edit" aria-label="{{.i18n.Tr "repo.wiki.edit_page_button"}}">{{svg "octicon-pencil"}}</a> + {{end}} + {{template "repo/unicode_escape_prompt" dict "EscapeStatus" .sidebarEscapeStatus "root" $}} + {{.sidebarContent | Safe}} + </div> + {{end}} </div> {{end}} </div> diff --git a/web_src/less/_repository.less b/web_src/less/_repository.less index 0cae04d115..d73cb90330 100644 --- a/web_src/less/_repository.less +++ b/web_src/less/_repository.less @@ -3088,6 +3088,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; |