From ed9eb57fc7f672b4d69c8e19de3d7df76d0262ec Mon Sep 17 00:00:00 2001 From: hamki Date: Thu, 1 Jan 2026 09:10:15 +0800 Subject: [PATCH] feat: introduce SidebarTocHeaders for improved sidebar TOC generation - Add a new Header type to encapsulate header data for generating the sidebar TOC. - Update the rendering logic to utilize SidebarTocHeaders, providing a more flexible structure for TOC generation. - Implement extraction of headers from orgmode documents to populate SidebarTocHeaders. - Ensure backward compatibility by maintaining the legacy SidebarTocNode for existing functionality. --- modules/markup/markdown/goldmark.go | 5 ++ modules/markup/orgmode/orgmode.go | 42 +++++++++++++++- modules/markup/render.go | 10 +++- modules/markup/sidebar_toc.go | 78 +++++++++++++++++++++++++++++ routers/web/repo/view.go | 7 ++- routers/web/repo/view_file.go | 2 +- routers/web/repo/view_readme.go | 2 +- routers/web/repo/wiki.go | 5 +- 8 files changed, 145 insertions(+), 6 deletions(-) create mode 100644 modules/markup/sidebar_toc.go diff --git a/modules/markup/markdown/goldmark.go b/modules/markup/markdown/goldmark.go index b28fa9824e..1bb1601488 100644 --- a/modules/markup/markdown/goldmark.go +++ b/modules/markup/markdown/goldmark.go @@ -88,6 +88,11 @@ func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc pa } else { tocNode := createTOCNode(tocList, rc.Lang, map[string]string{"open": "open"}) ctx.SidebarTocNode = tocNode + // Also set the generic SidebarTocHeaders for the new abstraction + ctx.SidebarTocHeaders = make([]markup.Header, len(tocList)) + for i, h := range tocList { + ctx.SidebarTocHeaders[i] = markup.Header{Level: h.Level, Text: h.Text, ID: h.ID} + } } } diff --git a/modules/markup/orgmode/orgmode.go b/modules/markup/orgmode/orgmode.go index 93c335d244..c015d52636 100644 --- a/modules/markup/orgmode/orgmode.go +++ b/modules/markup/orgmode/orgmode.go @@ -96,7 +96,16 @@ func Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error w := &orgWriter{rctx: ctx, HTMLWriter: htmlWriter} htmlWriter.ExtendingWriter = w - res, err := org.New().Silent().Parse(input, "").Write(w) + // Parse the document first to extract outline for TOC + doc := org.New().Silent().Parse(input, "") + if doc.Error != nil { + return fmt.Errorf("orgmode.Parse failed: %w", doc.Error) + } + + // Extract headers from the document outline for sidebar TOC + ctx.SidebarTocHeaders = extractHeadersFromOutline(doc.Outline) + + res, err := doc.Write(w) if err != nil { return fmt.Errorf("orgmode.Render failed: %w", err) } @@ -104,6 +113,37 @@ func Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error return err } +// extractHeadersFromOutline recursively extracts headers from org document outline +func extractHeadersFromOutline(outline org.Outline) []markup.Header { + var headers []markup.Header + collectHeaders(outline.Section, &headers) + return headers +} + +// collectHeaders recursively collects headers from sections +func collectHeaders(section *org.Section, headers *[]markup.Header) { + if section == nil { + return + } + + // Process current section's headline + if section.Headline != nil { + h := section.Headline + // Convert headline title nodes to plain text + titleText := org.String(h.Title...) + *headers = append(*headers, markup.Header{ + Level: h.Lvl, + Text: titleText, + ID: h.ID(), + }) + } + + // Process child sections + for _, child := range section.Children { + collectHeaders(child, headers) + } +} + // RenderString renders orgmode string to HTML string func RenderString(ctx *markup.RenderContext, content string) (string, error) { var buf strings.Builder diff --git a/modules/markup/render.go b/modules/markup/render.go index c645749065..7f456d0991 100644 --- a/modules/markup/render.go +++ b/modules/markup/render.go @@ -36,6 +36,13 @@ var RenderBehaviorForTesting struct { DisableAdditionalAttributes bool } +// Header holds the data about a header for generating TOC +type Header struct { + Level int + Text string + ID string +} + type RenderOptions struct { UseAbsoluteLink bool @@ -63,7 +70,8 @@ type RenderContext struct { // the context might be used by the "render" function, but it might also be used by "postProcess" function usedByRender bool - SidebarTocNode ast.Node + SidebarTocNode ast.Node // Deprecated: use SidebarTocHeaders instead, keep for compatibility + SidebarTocHeaders []Header // Headers for generating sidebar TOC RenderHelper RenderHelper RenderOptions RenderOptions diff --git a/modules/markup/sidebar_toc.go b/modules/markup/sidebar_toc.go new file mode 100644 index 0000000000..f2e4a59cc4 --- /dev/null +++ b/modules/markup/sidebar_toc.go @@ -0,0 +1,78 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package markup + +import ( + "html" + "html/template" + "net/url" + "strings" + + "code.gitea.io/gitea/modules/translation" +) + +// RenderSidebarTocHTML renders a list of headers into HTML for sidebar TOC display. +// It generates a
element with nested