diff --git a/internal/docs/http_handler.go b/internal/docs/http_handler.go index f18035f7..3041d33f 100644 --- a/internal/docs/http_handler.go +++ b/internal/docs/http_handler.go @@ -48,7 +48,10 @@ func (h *HTTPHandler) currentUser(c *echo.Context) templates.User { // renderDocPage renders a document inside the docs layout. func (h *HTTPHandler) renderDocPage(c *echo.Context, doc *templates.Document) error { - currentPath := c.Request().URL.Path + // .md suffixes are canonicalized to clean URLs before rendering, but + // normalize here too so the nav expansion + active-link styling can + // never see an extension the nav URLs don't carry. + currentPath := strings.TrimSuffix(c.Request().URL.Path, ".md") nav := h.docs.BuildNavigation(currentPath) var buf bytes.Buffer @@ -73,6 +76,20 @@ func (h *HTTPHandler) ShowDocumentation(c *echo.Context) error { // Get the document path from URL parameter docPath := c.Param("*") + // Canonicalize ".md" URLs to the clean form. Markdown links across the + // docs tree point at `area/file.md`; the handler loads them fine, but + // the sidebar logic keys on the URL path (contains-URL expansion, the + // active-link styling) whose nav URLs are extension-less — and a raw + // .md path would render the page with its menu closed and the current + // item unhighlighted. 301 keeps the browser URL canonical. + if strings.HasSuffix(docPath, ".md") { + target := strings.TrimSuffix("/docs/"+docPath, ".md") + if c.Request().URL.RawQuery != "" { + target += "?" + c.Request().URL.RawQuery + } + return c.Redirect(http.StatusMovedPermanently, target) + } + // Load the document doc, err := h.docs.LoadDocument(docPath) if err != nil {