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>
This commit is contained in:
parent
c1c07e533c
commit
ac88f21ecc
8 changed files with 146 additions and 50 deletions
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
},
|
||||
}}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue