mirror of
https://github.com/go-gitea/gitea.git
synced 2026-01-24 06:25:54 +01:00
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.
This commit is contained in:
parent
694d5510bf
commit
ed9eb57fc7
@ -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}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
78
modules/markup/sidebar_toc.go
Normal file
78
modules/markup/sidebar_toc.go
Normal file
@ -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 <details> element with nested <ul> lists representing the header hierarchy.
|
||||
func RenderSidebarTocHTML(headers []Header, lang string) template.HTML {
|
||||
if len(headers) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
|
||||
// Start with <details open>
|
||||
sb.WriteString(`<details open>`)
|
||||
sb.WriteString(`<summary>`)
|
||||
sb.WriteString(html.EscapeString(translation.NewLocale(lang).TrString("toc")))
|
||||
sb.WriteString(`</summary>`)
|
||||
|
||||
// Find the minimum level to start with
|
||||
minLevel := 6
|
||||
for _, header := range headers {
|
||||
if header.Level < minLevel {
|
||||
minLevel = header.Level
|
||||
}
|
||||
}
|
||||
|
||||
// Build nested list structure
|
||||
currentLevel := minLevel
|
||||
sb.WriteString(`<ul>`)
|
||||
openLists := 1
|
||||
|
||||
for _, header := range headers {
|
||||
// Close lists if we need to go up levels
|
||||
for currentLevel > header.Level {
|
||||
sb.WriteString(`</ul>`)
|
||||
openLists--
|
||||
currentLevel--
|
||||
}
|
||||
|
||||
// Open new lists if we need to go down levels
|
||||
for currentLevel < header.Level {
|
||||
sb.WriteString(`<ul>`)
|
||||
openLists++
|
||||
currentLevel++
|
||||
}
|
||||
|
||||
// Write the list item with link
|
||||
sb.WriteString(`<li>`)
|
||||
sb.WriteString(`<a href="#`)
|
||||
sb.WriteString(url.QueryEscape(header.ID))
|
||||
sb.WriteString(`">`)
|
||||
sb.WriteString(html.EscapeString(header.Text))
|
||||
sb.WriteString(`</a>`)
|
||||
sb.WriteString(`</li>`)
|
||||
}
|
||||
|
||||
// Close all remaining open lists
|
||||
for openLists > 0 {
|
||||
sb.WriteString(`</ul>`)
|
||||
openLists--
|
||||
}
|
||||
|
||||
sb.WriteString(`</details>`)
|
||||
|
||||
return template.HTML(sb.String())
|
||||
}
|
||||
|
||||
@ -180,7 +180,12 @@ func markupRender(ctx *context.Context, renderCtx *markup.RenderContext, input i
|
||||
return escaped, output, err
|
||||
}
|
||||
|
||||
func renderSidebarTocHTML(rctx *markup.RenderContext) template.HTML {
|
||||
func renderSidebarTocHTML(rctx *markup.RenderContext, lang string) template.HTML {
|
||||
// Prefer the new generic SidebarTocHeaders
|
||||
if len(rctx.SidebarTocHeaders) > 0 {
|
||||
return markup.RenderSidebarTocHTML(rctx.SidebarTocHeaders, lang)
|
||||
}
|
||||
// Fallback to legacy SidebarTocNode for backward compatibility
|
||||
if rctx.SidebarTocNode == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
@ -93,7 +93,7 @@ func handleFileViewRenderMarkup(ctx *context.Context, filename string, sniffedTy
|
||||
return true
|
||||
}
|
||||
|
||||
ctx.Data["FileSidebarHTML"] = renderSidebarTocHTML(rctx)
|
||||
ctx.Data["FileSidebarHTML"] = renderSidebarTocHTML(rctx, ctx.Locale.Language())
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@ -207,7 +207,7 @@ func prepareToRenderReadmeFile(ctx *context.Context, subfolder string, readmeFil
|
||||
delete(ctx.Data, "IsMarkup")
|
||||
}
|
||||
|
||||
ctx.Data["FileSidebarHTML"] = renderSidebarTocHTML(rctx)
|
||||
ctx.Data["FileSidebarHTML"] = renderSidebarTocHTML(rctx, ctx.Locale.Language())
|
||||
}
|
||||
|
||||
if ctx.Data["IsMarkup"] != true {
|
||||
|
||||
@ -277,7 +277,10 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if rctx.SidebarTocNode != nil {
|
||||
// Render sidebar TOC - prefer generic headers, fallback to legacy node
|
||||
if len(rctx.SidebarTocHeaders) > 0 {
|
||||
ctx.Data["WikiSidebarTocHTML"] = markup.RenderSidebarTocHTML(rctx.SidebarTocHeaders, ctx.Locale.Language())
|
||||
} else if rctx.SidebarTocNode != nil {
|
||||
sb := strings.Builder{}
|
||||
if err = markdown.SpecializedMarkdown(rctx).Renderer().Render(&sb, nil, rctx.SidebarTocNode); err != nil {
|
||||
log.Error("Failed to render wiki sidebar TOC: %v", err)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user