diff --git a/modules/markup/html.go b/modules/markup/html.go
index 1c2ae6918d..acd2f18471 100644
--- a/modules/markup/html.go
+++ b/modules/markup/html.go
@@ -255,25 +255,28 @@ func RenderTocHeadingItems(ctx *RenderContext, nodeDetailsAttrs map[string]strin
indent := []byte{' ', ' '}
_, _ = htmlutil.HTMLPrint(out, "
\n")
for _, header := range ctx.TocHeadingItems {
+ // Go deeper: open nested elements (wrapped in - for valid HTML)
for currentLevel < header.HeadingLevel {
_, _ = out.Write(indent)
- _, _ = htmlutil.HTMLPrint(out, "
\n")
+ _, _ = htmlutil.HTMLPrint(out, "\n")
indent = append(indent, ' ', ' ')
currentLevel++
}
+ // Go shallower: close nested
elements
for currentLevel > header.HeadingLevel {
indent = indent[:len(indent)-2]
_, _ = out.Write(indent)
- _, _ = htmlutil.HTMLPrint(out, "
\n")
+ _, _ = htmlutil.HTMLPrint(out, "
\n")
currentLevel--
}
_, _ = out.Write(indent)
_, _ = htmlutil.HTMLPrintf(out, "- %s
\n", header.AnchorID, header.InnerText)
}
+ // Close any remaining nested levels
for currentLevel > baseLevel {
indent = indent[:len(indent)-2]
_, _ = out.Write(indent)
- _, _ = htmlutil.HTMLPrint(out, "
\n")
+ _, _ = htmlutil.HTMLPrint(out, "\n")
currentLevel--
}
_, _ = htmlutil.HTMLPrint(out, "\n\n")
diff --git a/modules/markup/html_toc_test.go b/modules/markup/html_toc_test.go
index e93cfc9346..1493d50e86 100644
--- a/modules/markup/html_toc_test.go
+++ b/modules/markup/html_toc_test.go
@@ -39,15 +39,15 @@ include_toc: true
expected := `toc
+
diff --git a/modules/markup/orgmode/orgmode.go b/modules/markup/orgmode/orgmode.go
index fd3071645a..2ec3ffce3d 100644
--- a/modules/markup/orgmode/orgmode.go
+++ b/modules/markup/orgmode/orgmode.go
@@ -70,7 +70,18 @@ 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)
+ doc := org.New().Silent().Parse(input, "")
+ if doc.Error != nil {
+ return fmt.Errorf("orgmode.Parse failed: %w", doc.Error)
+ }
+
+ // Enable TOC extraction in post-process step for orgmode files
+ // The actual TOC items will be extracted from HTML headings during post-processing
+ if ctx.RenderOptions.EnableHeadingIDGeneration {
+ ctx.TocShowInSection = markup.TocShowInSidebar
+ }
+
+ res, err := doc.Write(w)
if err != nil {
return fmt.Errorf("orgmode.Render failed: %w", err)
}
diff --git a/modules/markup/render.go b/modules/markup/render.go
index c0d44c72fc..94be1b1d76 100644
--- a/modules/markup/render.go
+++ b/modules/markup/render.go
@@ -38,6 +38,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
diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go
index 7136b87058..9c9b7efdda 100644
--- a/routers/web/repo/view.go
+++ b/routers/web/repo/view.go
@@ -174,6 +174,15 @@ func markupRenderToHTML(ctx *context.Context, renderCtx *markup.RenderContext, r
return escaped, output, err
}
+func renderSidebarTocHTML(rctx *markup.RenderContext) template.HTML {
+ if rctx.TocShowInSection == markup.TocShowInSidebar && len(rctx.TocHeadingItems) > 0 {
+ sb := strings.Builder{}
+ markup.RenderTocHeadingItems(rctx, map[string]string{"open": ""}, &sb)
+ return template.HTML(sb.String())
+ }
+ return ""
+}
+
func checkHomeCodeViewable(ctx *context.Context) {
if ctx.Repo.HasUnits() {
if ctx.Repo.Repository.IsBeingCreated() {
diff --git a/routers/web/repo/view_file.go b/routers/web/repo/view_file.go
index 44bc8543b0..dedff1d563 100644
--- a/routers/web/repo/view_file.go
+++ b/routers/web/repo/view_file.go
@@ -86,6 +86,8 @@ func handleFileViewRenderMarkup(ctx *context.Context, prefetchBuf []byte, utf8Re
ctx.ServerError("Render", err)
return true
}
+
+ ctx.Data["FileSidebarHTML"] = renderSidebarTocHTML(rctx)
return true
}
diff --git a/routers/web/repo/view_readme.go b/routers/web/repo/view_readme.go
index eba3ffc36f..f3c7aacbbd 100644
--- a/routers/web/repo/view_readme.go
+++ b/routers/web/repo/view_readme.go
@@ -202,6 +202,8 @@ func prepareToRenderReadmeFile(ctx *context.Context, subfolder string, readmeFil
log.Error("Render failed for %s in %-v: %v Falling back to rendering source", readmeFile.Name(), ctx.Repo.Repository, err)
delete(ctx.Data, "IsMarkup")
}
+
+ ctx.Data["FileSidebarHTML"] = renderSidebarTocHTML(rctx)
}
if ctx.Data["IsMarkup"] != true {
diff --git a/templates/repo/view_file.tmpl b/templates/repo/view_file.tmpl
index d91235e0ea..2d50921fce 100644
--- a/templates/repo/view_file.tmpl
+++ b/templates/repo/view_file.tmpl
@@ -35,7 +35,10 @@
{{end}}
+ {{if .FileSidebarHTML}}
+
+ {{end}}