package server import ( "bytes" "fmt" "html/template" "net/http" "os" "strings" "git.else-if.org/jess/cs-midi-docs/internal/content" "git.else-if.org/jess/cs-midi-docs/internal/render" ) func (s *Server) handleRoot(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/" { http.NotFound(w, r) return } tree := s.watcher.Tree() first := tree.FirstPage() if first == nil { http.Error(w, "no pages found", http.StatusNotFound) return } http.Redirect(w, r, "/p/"+first.Path, http.StatusFound) } func (s *Server) handlePage(w http.ResponseWriter, r *http.Request) { path := r.PathValue("path") tree := s.watcher.Tree() // Check if it's a directory path with _index.md if dir := tree.FindDirByPath(path); dir != nil { idxPath := dir.IndexPath() if _, err := os.Stat(idxPath); err == nil { html, err := s.cache.Get(idxPath) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } s.renderPage(w, dir.Title, template.HTML(html), tree, path) return } // No _index.md, redirect to first child if first := dir.FirstPage(); first != nil { http.Redirect(w, r, "/p/"+first.Path, http.StatusFound) return } } node := tree.FindByPath(path) if node == nil { http.NotFound(w, r) return } html, err := s.cache.Get(node.FilePath) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } s.renderPage(w, node.Title, template.HTML(html), tree, path) } func (s *Server) renderPage(w http.ResponseWriter, title string, body template.HTML, tree *content.Node, currentPath string) { pages := tree.Flatten() var prev, next *content.Node for i, p := range pages { if p.Path == currentPath { if i > 0 { prev = pages[i-1] } if i < len(pages)-1 { next = pages[i+1] } break } } data := PageData{ Title: title, SiteTitle: s.cfg.Title, Body: body, Nav: s.buildNav(tree, currentPath), } if prev != nil { data.PrevTitle = prev.Title data.PrevPath = "/p/" + prev.Path } if next != nil { data.NextTitle = next.Title data.NextPath = "/p/" + next.Path } var buf bytes.Buffer if err := s.tmpl.page.ExecuteTemplate(&buf, "base.html", data); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Write(buf.Bytes()) } func (s *Server) handleSearch(w http.ResponseWriter, r *http.Request) { query := r.URL.Query().Get("q") results := s.index.Search(query, 50) var sr []SearchResult for _, res := range results { sr = append(sr, SearchResult{ Title: res.Title, Path: "/p/" + res.Path, Snippet: res.Snippet, }) } data := SearchData{ SiteTitle: s.cfg.Title, Query: query, Results: sr, Nav: s.buildNav(s.watcher.Tree(), ""), } var buf bytes.Buffer if err := s.tmpl.search.ExecuteTemplate(&buf, "base.html", data); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Write(buf.Bytes()) } func (s *Server) handleDownloadPage(w http.ResponseWriter, r *http.Request) { path := r.PathValue("path") path = strings.TrimSuffix(path, ".md") tree := s.watcher.Tree() node := tree.FindByPath(path) if node == nil { http.NotFound(w, r) return } data, err := os.ReadFile(node.FilePath) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } filename := node.Title + ".md" w.Header().Set("Content-Type", "text/markdown; charset=utf-8") w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filename)) w.Write(data) } func (s *Server) handleDownloadBook(w http.ResponseWriter, r *http.Request) { tree := s.watcher.Tree() data, err := render.BookMarkdown(tree) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } title := s.cfg.Title if title == "" { title = "book" } w.Header().Set("Content-Type", "text/markdown; charset=utf-8") w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.md"`, title)) w.Write(data) } func (s *Server) handleDownloadPDF(w http.ResponseWriter, r *http.Request) { tree := s.watcher.Tree() data, err := render.GeneratePDF(tree, s.cfg.Title) if err != nil { // Fallback to print HTML s.handleDownloadPrintHTML(w, r) return } title := s.cfg.Title if title == "" { title = "book" } w.Header().Set("Content-Type", "application/pdf") w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.pdf"`, title)) w.Write(data) } func (s *Server) handleDownloadPrintHTML(w http.ResponseWriter, r *http.Request) { tree := s.watcher.Tree() data, err := render.PrintHTML(tree, s.cfg.Title) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Write(data) } func (s *Server) buildNav(tree *content.Node, currentPath string) template.HTML { var buf strings.Builder buf.WriteString(``) return template.HTML(buf.String()) } func (s *Server) renderNavNode(buf *strings.Builder, node *content.Node, currentPath string) { if node.IsDir { buf.WriteString(`
`) } else { active := "" if node.Path == currentPath { active = ` class="active"` } buf.WriteString(fmt.Sprintf(`