docs(nav): Administration section lives in docs/admin/; sidebar ordered by usability
Two structural changes, one owner-approved plan: - Move the admin guides out of docs/user/ into their own docs/admin/ area — admin-guide.md becomes the Administration section landing page (pinned first), and the 32-line admin-library-management.md stub becomes admin/libraries.md, which the admin-docs build-out rebuilds into the real Libraries page next. Retired URLs 301 via a new docRedirects map in the docs HTTP handler (checked before .md canonicalization so even .md spellings one-hop to the new clean URL); the two inbound links (docs index, user-guide front door) updated. - Sidebar direct items now sort by usabilityOrder — an explicit, per-section display order — instead of alphabetically: User Guide mirrors the 'Where to go' table on user-guide.md, Devices orders Android → KOReader → Kobo (alphabetically Kobo used to sort ahead of KOReader), and Administration pre-lists its planned pages so each lands in place as the admin build writes it. Unlisted docs fall back to alphabetical after the listed ones. Display-order only by construction: the URL-driven expand/highlight machinery keys on containsURL membership and never sees sort order — nav tests updated to pin the new orders plus a real-tree test (TestBuildNavigationUsabilityOrder) guarding all three sections.
This commit is contained in:
1 parent
7e2e1c4c60
commit
393a00e8dd
7 files changed
+152
-15
No files matched your search
@@ -3,6 +3,7 @@ package docs
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"bookhoard/templates"
|
||||
@@ -18,7 +19,8 @@ func TestPlaceDoc(t *testing.T) {
|
||||
{"index.md", "Getting Started", "", true},
|
||||
{"user/user-guide.md", "User Guide", "", true},
|
||||
{"user/dashboard.md", "User Guide", "", false},
|
||||
{"user/admin-guide.md", "Administration", "", false},
|
||||
{"admin/admin-guide.md", "Administration", "", true},
|
||||
{"admin/libraries.md", "Administration", "", false},
|
||||
{"user/devices/kobo-setup.md", "Devices", "", false},
|
||||
{"developer/api/api-reference.md", "API Reference", "", true},
|
||||
{"developer/api/dashboard.md", "API Reference", "", false},
|
||||
@@ -52,6 +54,7 @@ func TestDocTitle(t *testing.T) {
|
||||
tests := map[string]string{
|
||||
"index.md": "Overview",
|
||||
"user/user-guide.md": "User Guide",
|
||||
"admin/admin-guide.md": "Admin Guide",
|
||||
"user/searching-and-filtering.md": "Searching and Filtering",
|
||||
"user/devices/koreader-setup.md": "KOReader Setup",
|
||||
"developer/development.md": "Developer Portal",
|
||||
@@ -86,7 +89,7 @@ func buildTestDocs(t *testing.T) *DocsHandler {
|
||||
"index.md",
|
||||
"user/user-guide.md",
|
||||
"user/dashboard.md",
|
||||
"user/admin-guide.md",
|
||||
"admin/admin-guide.md",
|
||||
"user/devices/kobo-setup.md",
|
||||
"user/devices/koreader-setup.md",
|
||||
"developer/api/api-reference.md",
|
||||
@@ -148,8 +151,11 @@ func TestBuildNavigation(t *testing.T) {
|
||||
t.Errorf("User Guide direct items = %+v, want User Guide pinned first", userGuide.Items)
|
||||
}
|
||||
|
||||
// Devices: own top-level section, expanded, no groups. (Administration
|
||||
// sits above it in the order; covered by the collapsed sweep below.)
|
||||
// Devices: own top-level section, expanded, no groups. KOReader sorts
|
||||
// ahead of Kobo because usabilityOrder ranks the device guides by how
|
||||
// the docs front door presents them (Android → KOReader → Kobo), not
|
||||
// alphabetically. (Administration sits above it in the section order;
|
||||
// covered by the collapsed sweep below.)
|
||||
devices := nav.Sections[3]
|
||||
if devices.Collapsed {
|
||||
t.Error("Devices section should be expanded: it contains the current page")
|
||||
@@ -157,8 +163,8 @@ func TestBuildNavigation(t *testing.T) {
|
||||
if len(devices.Groups) != 0 {
|
||||
t.Errorf("Devices groups = %+v, want none", devices.Groups)
|
||||
}
|
||||
if len(devices.Items) != 2 || devices.Items[0].Title != "Kobo Setup" {
|
||||
t.Errorf("Devices items = %+v, want Kobo Setup + KOReader Setup", devices.Items)
|
||||
if len(devices.Items) != 2 || devices.Items[0].Title != "KOReader Setup" {
|
||||
t.Errorf("Devices items = %+v, want KOReader Setup before Kobo Setup (usability order)", devices.Items)
|
||||
}
|
||||
|
||||
// Everything else starts collapsed (Administration included).
|
||||
@@ -194,6 +200,56 @@ func TestBuildNavigation(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestBuildNavigationUsabilityOrder (2026-11-02): the REAL docs tree must
|
||||
// present its direct nav items in usability order (usabilityOrder), not
|
||||
// alphabetically — User Guide mirrors the "Where to go" table on
|
||||
// user-guide.md, Devices orders Android → KOReader → Kobo, Administration
|
||||
// mirrors the app's own admin sidebar. Display-order only: expansion and
|
||||
// highlighting stay URL-driven (order-blind) by construction.
|
||||
func TestBuildNavigationUsabilityOrder(t *testing.T) {
|
||||
handler := NewDocsHandler("../../docs")
|
||||
nav := handler.BuildNavigation("/docs")
|
||||
|
||||
find := func(title string) *templates.NavSection {
|
||||
for i := range nav.Sections {
|
||||
if nav.Sections[i].Title == title {
|
||||
return &nav.Sections[i]
|
||||
}
|
||||
}
|
||||
t.Fatalf("section %q missing", title)
|
||||
return nil
|
||||
}
|
||||
|
||||
titles := func(s *templates.NavSection) []string {
|
||||
out := make([]string, 0, len(s.Items))
|
||||
for _, item := range s.Items {
|
||||
out = append(out, item.Title)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
wantUserGuide := []string{
|
||||
"User Guide", "Dashboard", "Library Browsing", "Collections",
|
||||
"Searching and Filtering", "Podcasts", "Reader Guide", "Themes",
|
||||
"Profile Guide", "Sync Guide", "Calibre Integration",
|
||||
}
|
||||
if got := titles(find("User Guide")); !reflect.DeepEqual(got, wantUserGuide) {
|
||||
t.Errorf("User Guide order = %v, want %v", got, wantUserGuide)
|
||||
}
|
||||
|
||||
wantDevices := []string{"Android App", "KOReader Setup", "Kobo Setup"}
|
||||
if got := titles(find("Devices")); !reflect.DeepEqual(got, wantDevices) {
|
||||
t.Errorf("Devices order = %v, want %v", got, wantDevices)
|
||||
}
|
||||
|
||||
// Administration grows as its pages are written; pin what exists today:
|
||||
// the guide leads (pinned landing), Libraries follows.
|
||||
wantAdmin := []string{"Admin Guide", "Libraries"}
|
||||
if got := titles(find("Administration")); !reflect.DeepEqual(got, wantAdmin) {
|
||||
t.Errorf("Administration order = %v, want %v", got, wantAdmin)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildNavigationNothingCurrent(t *testing.T) {
|
||||
h := buildTestDocs(t)
|
||||
nav := h.BuildNavigation("/docs/nonexistent")
|
||||
|
||||
Reference in new issue
Block a user