package mdserver
|
|
|
|
import (
|
|
"html/template"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/russross/blackfriday/v2"
|
|
)
|
|
|
|
type Breadcrumb struct {
|
|
Name string
|
|
URL string
|
|
}
|
|
|
|
type DirItem struct {
|
|
Name string
|
|
URL string
|
|
IsDir bool
|
|
IsMarkdown bool
|
|
}
|
|
|
|
type Context struct {
|
|
// Full paths in filesystem.
|
|
fullPath string
|
|
|
|
IsDir bool
|
|
URL string
|
|
Breadcrumbs []Breadcrumb
|
|
Children []DirItem
|
|
|
|
MarkdownCSS template.CSS
|
|
MDContent template.HTML
|
|
}
|
|
|
|
func NewContext(root, fullPath string, fi os.FileInfo) (ctx Context) {
|
|
ctx.fullPath = filepath.Clean(fullPath)
|
|
|
|
if fi.IsDir() {
|
|
if _, err := os.Stat(filepath.Join(ctx.fullPath, "index.md")); err == nil {
|
|
ctx.fullPath = filepath.Join(ctx.fullPath, "index.md")
|
|
} else {
|
|
ctx.IsDir = true
|
|
}
|
|
}
|
|
|
|
ctx.URL, _ = filepath.Rel(root, ctx.fullPath)
|
|
ctx.URL = strings.TrimPrefix(ctx.URL, ".")
|
|
ctx.URL = filepath.ToSlash("/" + ctx.URL)
|
|
|
|
ctx.Breadcrumbs = ctx.breadcrumbs()
|
|
ctx.Children = ctx.listDir()
|
|
ctx.MarkdownCSS = template.CSS(markdownCSS)
|
|
ctx.MDContent = ctx.renderMD()
|
|
return ctx
|
|
}
|
|
|
|
func (ctx Context) breadcrumbs() []Breadcrumb {
|
|
prev := Breadcrumb{Name: "root", URL: "/"}
|
|
bc := []Breadcrumb{}
|
|
|
|
url := ctx.URL
|
|
if len(url) > 0 && (url[0] == '/' || url[0] == '.') {
|
|
url = url[1:]
|
|
}
|
|
|
|
for len(url) > 0 {
|
|
parts := strings.SplitN(url, "/", 2)
|
|
name := parts[0]
|
|
prev = Breadcrumb{
|
|
Name: name,
|
|
URL: filepath.Join(prev.URL, name),
|
|
}
|
|
bc = append(bc, prev)
|
|
|
|
if len(parts) == 1 {
|
|
url = ""
|
|
} else {
|
|
url = parts[1]
|
|
}
|
|
}
|
|
return bc
|
|
}
|
|
|
|
func (ctx Context) listDir() (l []DirItem) {
|
|
dir := ctx.fullPath
|
|
dirURL := ctx.URL
|
|
if !ctx.IsDir {
|
|
dir = filepath.Dir(dir)
|
|
dirURL = path.Dir(dirURL)
|
|
}
|
|
|
|
dis, err := os.ReadDir(dir)
|
|
if err != nil {
|
|
log.Printf("Failed to list dir: %v", err)
|
|
return l
|
|
}
|
|
|
|
for _, di := range dis {
|
|
if strings.HasPrefix(di.Name(), ".") {
|
|
continue
|
|
}
|
|
l = append(l, DirItem{
|
|
Name: di.Name(),
|
|
URL: path.Join(dirURL, di.Name()),
|
|
IsDir: di.IsDir(),
|
|
IsMarkdown: strings.HasSuffix(di.Name(), ".md") && !di.IsDir(),
|
|
})
|
|
}
|
|
|
|
sort.Slice(l, func(i, j int) bool {
|
|
ii := l[i]
|
|
ij := l[j]
|
|
|
|
if ii.IsDir {
|
|
if ij.IsDir {
|
|
return ii.Name < ij.Name
|
|
}
|
|
return true
|
|
}
|
|
|
|
if ii.IsMarkdown {
|
|
if ij.IsDir {
|
|
return false
|
|
}
|
|
if ij.IsMarkdown {
|
|
return ii.Name < ij.Name
|
|
}
|
|
return true
|
|
}
|
|
|
|
if ij.IsDir || ij.IsMarkdown {
|
|
return false
|
|
}
|
|
|
|
return ii.Name < ij.Name
|
|
})
|
|
|
|
return l
|
|
}
|
|
|
|
func (ctx Context) renderMD() template.HTML {
|
|
if ctx.IsDir {
|
|
return ""
|
|
}
|
|
raw, err := ioutil.ReadFile(ctx.fullPath)
|
|
if err != nil {
|
|
log.Printf("Failed to read markdown file: %v", err)
|
|
return ""
|
|
}
|
|
return template.HTML(
|
|
blackfriday.Run(
|
|
raw,
|
|
blackfriday.WithRenderer(&CustomRenderer{})))
|
|
}
|