package docs import ( "sort" "strings" "bookhoard/templates" "golang.org/x/text/cases" "golang.org/x/text/language" ) // BuildNavigation creates the navigation structure from the docs filesystem func (h *DocsHandler) BuildNavigation() *templates.Navigation { docs, err := h.ListDocuments() if err != nil { // Return minimal navigation on error return &templates.Navigation{ Sections: []templates.NavSection{ { Title: "Getting Started", Items: []templates.NavItem{ {Title: "Overview", URL: "/docs"}, }, }, }, } } // Group documents by category categories := make(map[string][]templates.NavItem) for _, doc := range docs { // Skip INDEX.md files in subdirectories (they're section landing pages, not nav items) // Only show root INDEX.md, skip all others like user/INDEX.md, api/INDEX.md, etc. if strings.HasSuffix(doc, "index.md") && doc != "index.md" { continue } item := h.createNavItem(doc) category := h.getCategoryForNav(doc) categories[category] = append(categories[category], item) } // Build navigation sections var sections []templates.NavSection // Define section order sectionOrder := []string{ "Getting Started", "User Guide", "Device Setup", "API Reference", "Contributing", } // Add sections in order for _, sectionTitle := range sectionOrder { if items, exists := categories[sectionTitle]; exists { sections = append(sections, templates.NavSection{ Title: sectionTitle, Items: items, Collapsed: sectionTitle != "Getting Started", // Expand first section }) delete(categories, sectionTitle) } } // Add any remaining sections for title, items := range categories { sections = append(sections, templates.NavSection{ Title: title, Items: items, Collapsed: true, }) } return &templates.Navigation{Sections: sections} } // createNavItem creates a navigation item from a document path func (h *DocsHandler) createNavItem(docPath string) templates.NavItem { title := h.getDocTitle(docPath) url := "/docs/" + strings.TrimSuffix(docPath, ".md") icon := h.getDocIcon(docPath) return templates.NavItem{ Title: title, URL: url, Icon: icon, } } // getDocTitle extracts a readable title from a document path func (h *DocsHandler) getDocTitle(docPath string) string { // Remove .md extension title := strings.TrimSuffix(docPath, ".md") // Split by / parts := strings.Split(title, "/") // Get the last part (filename) filename := parts[len(parts)-1] // Convert to title case title = cases.Title(language.English).String(strings.ReplaceAll(filename, "-", " ")) // Handle special cases switch filename { case "index.md": return "Index" case "user/user-guide.md": return "User Guide" case "developer/development.md": return "Developer Portal" case "operations/operations.md": return "Operations Guide" case "contributing/contributing.md": return "Contributing" case "developer/api-reference.md": return "API Reference" case "developer/api/api-reference.md": return "API Reference" case "developer/api/collections-api.md": return "Collections API" case "developer/websocket-api.md": return "WebSocket API" case "operations/troubleshooting.md": return "Troubleshooting" case "user/sync-guide.md": return "Sync Guide" case "user/devices/kobo-setup.md": return "Kobo Setup" case "user/devices/koreader-setup.md": return "KOReader Setup" case "DEVELOPMENT.md": return "Development Guide" } return title } // getDocIcon returns an appropriate icon for a document func (h *DocsHandler) getDocIcon(docPath string) string { if strings.Contains(docPath, "api") || strings.Contains(docPath, "API") { return "🔌" } if strings.Contains(docPath, "device") { return "📱" } if strings.Contains(docPath, "sync") || strings.Contains(docPath, "SYNC") { return "🔄" } if strings.Contains(docPath, "contributing") || strings.Contains(docPath, "DEVELOPMENT") { return "🛠️" } if strings.Contains(docPath, "index") { return "📚" } if strings.Contains(docPath, "troubleshooting") { return "🔧" } return "📄" } // getCategoryForNav determines the navigation category for a document func (h *DocsHandler) getCategoryForNav(docPath string) string { // API documentation if strings.Contains(docPath, "api") || strings.Contains(docPath, "API") { return "API Reference" } // Device setup if strings.HasPrefix(docPath, "devices/") { return "Device Setup" } // Contributing if strings.HasPrefix(docPath, "contributing/") { return "Contributing" } // Core user documentation docName := strings.ToLower(docPath) switch { case strings.Contains(docName, "sync"): return "User Guide" case strings.Contains(docName, "troubleshoot"): return "Getting Started" case strings.Contains(docName, "index"): return "Getting Started" default: return "Getting Started" } } // GetAPIEndpoints returns a list of API endpoints for the API documentation func (h *DocsHandler) GetAPIEndpoints() []APIEndpoint { // This would be better auto-generated from Go code comments // For now, return a static list based on your API endpoints := []APIEndpoint{ // Authentication {Method: "POST", Path: "/api/auth/register", Category: "Authentication", Title: "Register User"}, {Method: "POST", Path: "/api/auth/login", Category: "Authentication", Title: "Login"}, {Method: "POST", Path: "/api/auth/refresh", Category: "Authentication", Title: "Refresh Token"}, {Method: "POST", Path: "/api/auth/logout", Category: "Authentication", Title: "Logout"}, // Users {Method: "GET", Path: "/api/users/me", Category: "Users", Title: "Get Current User"}, {Method: "PUT", Path: "/api/users/me/profile", Category: "Users", Title: "Update Profile"}, {Method: "PUT", Path: "/api/users/me/theme", Category: "Users", Title: "Update Theme"}, {Method: "PUT", Path: "/api/users/me/password", Category: "Users", Title: "Change Password"}, // Libraries {Method: "GET", Path: "/api/libraries/visible", Category: "Libraries", Title: "Get Visible Libraries"}, {Method: "GET", Path: "/api/libraries/{id}", Category: "Libraries", Title: "Get Library Details"}, {Method: "POST", Path: "/api/libraries", Category: "Libraries", Title: "Create Library"}, // Media Items {Method: "GET", Path: "/api/media-items", Category: "Media Items", Title: "List Media Items"}, {Method: "GET", Path: "/api/media-items/{id}", Category: "Media Items", Title: "Get Media Item"}, {Method: "POST", Path: "/api/media-items/search", Category: "Media Items", Title: "Search Media Items"}, // Progress {Method: "GET", Path: "/api/media-items/{id}/progress", Category: "Progress", Title: "Get Reading Progress"}, {Method: "PUT", Path: "/api/media-items/{id}/progress", Category: "Progress", Title: "Update Reading Progress"}, // Devices {Method: "POST", Path: "/api/devices/register", Category: "Devices", Title: "Register Device"}, {Method: "GET", Path: "/api/devices", Category: "Devices", Title: "List User Devices"}, {Method: "DELETE", Path: "/api/devices/{id}", Category: "Devices", Title: "Revoke Device"}, } sort.Slice(endpoints, func(i, j int) bool { if endpoints[i].Category == endpoints[j].Category { return endpoints[i].Title < endpoints[j].Title } return endpoints[i].Category < endpoints[j].Category }) return endpoints } type APIEndpoint struct { Method string Path string Category string Title string }