feat: add folder-based tree sidebar to org wiki (#680)
Deploy MokoGitea / deploy (push) Successful in 3m31s

Replace flat page list with hierarchical folder tree in org wiki sidebar.
_Sidebar.md takes precedence when present; otherwise auto-generates
collapsible folder menus up to 2 levels deep.
This commit is contained in:
Jonathan Miller
2026-06-21 11:00:18 -05:00
parent 9149fa100c
commit bfb4b53da3
2 changed files with 110 additions and 37 deletions
+73 -25
View File
@@ -29,6 +29,14 @@ type OrgWikiPage struct {
SubURL string
}
// OrgWikiTreeNode represents a node in the org wiki folder tree for sidebar navigation.
type OrgWikiTreeNode struct {
Name string
SubURL string
IsDir bool
Children []*OrgWikiTreeNode
}
// Wiki renders the org wiki tab.
func Wiki(ctx *context.Context) {
org := ctx.Org.Organization
@@ -71,31 +79,9 @@ func Wiki(ctx *context.Context) {
}
ctx.Data["WikiRepoLink"] = wikiRepo.Link()
// Build page list from repo root.
entries, err := commit.ListEntries()
if err != nil {
ctx.ServerError("ListEntries", err)
return
}
pages := make([]OrgWikiPage, 0, len(entries))
for _, entry := range entries {
if !entry.IsRegular() {
continue
}
name := entry.Name()
if !isMarkdownFile(name) {
continue
}
displayName := strings.TrimSuffix(name, path.Ext(name))
if strings.EqualFold(displayName, "_sidebar") || strings.EqualFold(displayName, "_footer") {
continue
}
pages = append(pages, OrgWikiPage{
Name: displayName,
SubURL: displayName,
})
}
ctx.Data["Pages"] = pages
// Build folder tree for sidebar navigation.
wikiTree := buildOrgWikiTree(commit)
ctx.Data["WikiTree"] = wikiTree
// Determine which page to render.
pageName := ctx.PathParamRaw("*")
@@ -157,6 +143,68 @@ func Wiki(ctx *context.Context) {
ctx.HTML(http.StatusOK, tplOrgWiki)
}
// buildOrgWikiTree builds a hierarchical folder tree from the org wiki git repo.
// Shows up to 2 levels deep (folders and their immediate children).
func buildOrgWikiTree(commit *git.Commit) []*OrgWikiTreeNode {
if commit == nil {
return nil
}
entries, err := commit.ListEntries()
if err != nil {
return nil
}
var topLevel []*OrgWikiTreeNode
for _, entry := range entries {
name := entry.Name()
if entry.IsDir() {
node := &OrgWikiTreeNode{
Name: name,
SubURL: name,
IsDir: true,
}
// List children of this directory (1 level deep).
subTree := entry.Tree()
if subTree != nil {
children, _ := subTree.ListEntries()
for _, child := range children {
childName := child.Name()
if child.IsDir() {
node.Children = append(node.Children, &OrgWikiTreeNode{
Name: childName,
SubURL: name + "/" + childName,
IsDir: true,
})
} else if isMarkdownFile(childName) {
displayName := strings.TrimSuffix(childName, path.Ext(childName))
if strings.EqualFold(displayName, "_sidebar") || strings.EqualFold(displayName, "_footer") {
continue
}
node.Children = append(node.Children, &OrgWikiTreeNode{
Name: displayName,
SubURL: name + "/" + displayName,
IsDir: false,
})
}
}
}
topLevel = append(topLevel, node)
} else if isMarkdownFile(name) {
displayName := strings.TrimSuffix(name, path.Ext(name))
if strings.EqualFold(displayName, "_sidebar") || strings.EqualFold(displayName, "_footer") {
continue
}
topLevel = append(topLevel, &OrgWikiTreeNode{
Name: displayName,
SubURL: displayName,
IsDir: false,
})
}
}
return topLevel
}
// findOrgWikiCommit locates the profile repo's wiki and returns its HEAD commit.
// The org wiki lives in the .wiki.git sidecar of the profile repo (e.g. .mokogitea.wiki.git).
// Tries fallback repo names (.profile, .github) if the primary doesn't exist.
+37 -12
View File
@@ -11,7 +11,7 @@
This organization doesn't have a wiki yet.
</div>
<p class="tw-text-center">
Enable the wiki on the <code>.profile</code> (public) or <code>.profile-private</code> (members-only)
Enable the wiki on the <code>.mokogitea</code> (public) or <code>.mokogitea-private</code> (members-only)
repository to get started.
</p>
</div>
@@ -47,34 +47,59 @@
<p>The page "{{.CurrentPage}}" does not exist in this wiki.</p>
</div>
</div>
{{if .Pages}}
{{if .WikiTree}}
<h4>Available pages:</h4>
<ul>
{{range .Pages}}
<li><a href="{{$.Org.HomeLink}}/-/wiki/{{.SubURL}}">{{.Name}}</a></li>
{{range .WikiTree}}
{{if .IsDir}}
{{range .Children}}
<li><a href="{{$.Org.HomeLink}}/-/wiki/{{.SubURL}}">{{.Name}}</a></li>
{{end}}
{{else}}
<li><a href="{{$.Org.HomeLink}}/-/wiki/{{.SubURL}}">{{.Name}}</a></li>
{{end}}
{{end}}
</ul>
{{end}}
</div>
{{else}}
<div class="wiki-content-parts">
<div class="render-content markup wiki-content-main {{if or .WikiSidebarHTML .Pages}}with-sidebar{{end}}">
<div class="render-content markup wiki-content-main {{if or .WikiSidebarHTML .WikiTree}}with-sidebar{{end}}">
{{.WikiContent}}
</div>
{{if or .WikiSidebarHTML .Pages}}
{{if or .WikiSidebarHTML .WikiTree}}
<div class="render-content markup wiki-content-sidebar">
{{if .WikiSidebarHTML}}
{{.WikiSidebarHTML}}
<div class="ui divider"></div>
{{end}}
{{if .Pages}}
{{else if .WikiTree}}
<strong>{{svg "octicon-list-unordered" 14}} Pages</strong>
<ul class="wiki-tree-list">
{{range .Pages}}
{{range .WikiTree}}
<li>
{{svg "octicon-file" 14}}
<a href="{{$.Org.HomeLink}}/-/wiki/{{.SubURL}}" {{if eq $.CurrentPage .Name}}class="active"{{end}}>{{.Name}}</a>
{{if .IsDir}}
<details open>
<summary>{{svg "octicon-file-directory" 14}} <strong>{{.Name}}</strong></summary>
{{if .Children}}
<ul>
{{range .Children}}
<li>
{{if .IsDir}}
{{svg "octicon-file-directory" 14}}
<a href="{{$.Org.HomeLink}}/-/wiki/{{.SubURL}}"><strong>{{.Name}}</strong></a>
{{else}}
{{svg "octicon-file" 14}}
<a href="{{$.Org.HomeLink}}/-/wiki/{{.SubURL}}" {{if eq $.CurrentPage .Name}}class="active"{{end}}>{{.Name}}</a>
{{end}}
</li>
{{end}}
</ul>
{{end}}
</details>
{{else}}
{{svg "octicon-file" 14}}
<a href="{{$.Org.HomeLink}}/-/wiki/{{.SubURL}}" {{if eq $.CurrentPage .Name}}class="active"{{end}}>{{.Name}}</a>
{{end}}
</li>
{{end}}
</ul>