mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 16:40:24 +08:00 
			
		
		
		
	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:
		@@ -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()
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user