feat: add miller document layout

This commit is contained in:
2026-04-29 09:26:37 -04:00
parent 6e244d55db
commit 8c454de809
5 changed files with 333 additions and 72 deletions

View File

@@ -11,6 +11,7 @@ import (
"net/http"
"os"
"path/filepath"
"sort"
"strings"
"time"
@@ -28,15 +29,16 @@ type layoutData struct {
}
type indexData struct {
Documents []docs.DocumentRecord
Browser browserData
}
type documentData struct {
Title string
Path string
Tags []string
Hash string
HTML template.HTML
Browser browserData
Title string
Path string
Tags []string
Hash string
HTML template.HTML
}
type errorData struct {
@@ -44,6 +46,23 @@ type errorData struct {
Message string
}
type browserData struct {
Columns []browserColumn
}
type browserColumn struct {
Title string
Items []browserItem
}
type browserItem struct {
Name string
Path string
URL string
IsFolder bool
Active bool
}
func (s *Server) handleIndex(w http.ResponseWriter, r *http.Request) {
items, err := s.documents.ListDocuments(r.Context())
if err != nil {
@@ -51,13 +70,14 @@ func (s *Server) handleIndex(w http.ResponseWriter, r *http.Request) {
return
}
activeFolder := strings.Trim(r.URL.Query().Get("folder"), "/")
s.renderTemplate(w, http.StatusOK, "index.gohtml", layoutData{
Title: "MD Hub Secure",
WebEnabled: s.webEnabled,
BodyClass: "page-index",
BodyTemplate: "index_content",
Data: indexData{
Documents: items,
Browser: buildBrowser(items, activeFolder),
},
})
}
@@ -78,17 +98,24 @@ func (s *Server) handleDocument(w http.ResponseWriter, r *http.Request) {
return
}
items, err := s.documents.ListDocuments(r.Context())
if err != nil {
s.renderError(w, r, http.StatusInternalServerError, err.Error())
return
}
s.renderTemplate(w, http.StatusOK, "document.gohtml", layoutData{
Title: page.Title,
WebEnabled: s.webEnabled,
BodyClass: "page-document",
BodyTemplate: "document_content",
Data: documentData{
Title: page.Title,
Path: page.Path,
Tags: page.Tags,
Hash: page.Hash,
HTML: template.HTML(page.HTML),
Browser: buildBrowser(items, page.Path),
Title: page.Title,
Path: page.Path,
Tags: page.Tags,
Hash: page.Hash,
HTML: template.HTML(page.HTML),
},
})
}
@@ -240,3 +267,108 @@ func writeJSONWithStatus(w http.ResponseWriter, status int, payload any) {
w.WriteHeader(status)
_ = json.NewEncoder(w).Encode(payload)
}
func buildBrowser(records []docs.DocumentRecord, activePath string) browserData {
paths := make([]string, 0, len(records))
titleByPath := make(map[string]string, len(records))
for _, record := range records {
paths = append(paths, record.Path)
titleByPath[record.Path] = record.Title
}
prefixes := []string{""}
if activePath != "" {
parts := strings.Split(activePath, "/")
limit := len(parts)
if strings.HasSuffix(activePath, ".md") {
limit = len(parts) - 1
}
for i := 1; i <= limit; i++ {
prefixes = append(prefixes, strings.Join(parts[:i], "/"))
}
}
columns := make([]browserColumn, 0, len(prefixes))
for _, prefix := range prefixes {
column := browserColumn{
Title: columnTitle(prefix),
Items: buildBrowserItems(paths, titleByPath, prefix, activePath),
}
if len(column.Items) > 0 {
columns = append(columns, column)
}
}
return browserData{Columns: columns}
}
func buildBrowserItems(paths []string, titleByPath map[string]string, prefix string, activePath string) []browserItem {
seenFolders := make(map[string]browserItem)
files := make([]browserItem, 0)
prefixWithSlash := ""
if prefix != "" {
prefixWithSlash = prefix + "/"
}
for _, path := range paths {
if prefix != "" && !strings.HasPrefix(path, prefixWithSlash) {
continue
}
remainder := strings.TrimPrefix(path, prefixWithSlash)
if remainder == "" {
continue
}
if slash := strings.Index(remainder, "/"); slash >= 0 {
name := remainder[:slash]
folderPath := name
if prefix != "" {
folderPath = prefix + "/" + name
}
seenFolders[folderPath] = browserItem{
Name: name,
Path: folderPath,
URL: "/?folder=" + folderPath,
IsFolder: true,
Active: activePath == folderPath || strings.HasPrefix(activePath, folderPath+"/"),
}
continue
}
files = append(files, browserItem{
Name: displayDocumentName(path, titleByPath[path]),
Path: path,
URL: "/docs/" + strings.TrimSuffix(path, ".md"),
Active: activePath == path,
})
}
folders := make([]browserItem, 0, len(seenFolders))
for _, item := range seenFolders {
folders = append(folders, item)
}
sort.Slice(folders, func(i, j int) bool {
return strings.ToLower(folders[i].Name) < strings.ToLower(folders[j].Name)
})
sort.Slice(files, func(i, j int) bool {
return strings.ToLower(files[i].Name) < strings.ToLower(files[j].Name)
})
items := append(folders, files...)
return items
}
func columnTitle(prefix string) string {
if prefix == "" {
return "Content"
}
return filepath.Base(prefix)
}
func displayDocumentName(path string, title string) string {
if title != "" && title != "Untitled" {
return title
}
return strings.TrimSuffix(filepath.Base(path), ".md")
}