feat: add miller document layout
This commit is contained in:
@@ -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")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user