package setup import ( "fmt" "io/ioutil" "text/template" "github.com/mholt/caddy/middleware" "github.com/mholt/caddy/middleware/browse" ) // Browse configures a new Browse middleware instance. func Browse(c *Controller) (middleware.Middleware, error) { configs, err := browseParse(c) if err != nil { return nil, err } browse := browse.Browse{ Root: c.Root, Configs: configs, IgnoreIndexes: false, } return func(next middleware.Handler) middleware.Handler { browse.Next = next return browse }, nil } func browseParse(c *Controller) ([]browse.Config, error) { var configs []browse.Config appendCfg := func(bc browse.Config) error { for _, c := range configs { if c.PathScope == bc.PathScope { return fmt.Errorf("duplicate browsing config for %s", c.PathScope) } } configs = append(configs, bc) return nil } for c.Next() { var bc browse.Config // First argument is directory to allow browsing; default is site root if c.NextArg() { bc.PathScope = c.Val() } else { bc.PathScope = "/" } // Second argument would be the template file to use var tplText string if c.NextArg() { tplBytes, err := ioutil.ReadFile(c.Val()) if err != nil { return configs, err } tplText = string(tplBytes) } else { tplText = defaultTemplate } // Build the template tpl, err := template.New("listing").Parse(tplText) if err != nil { return configs, err } bc.Template = tpl // Save configuration err = appendCfg(bc) if err != nil { return configs, err } } return configs, nil } // The default template to use when serving up directory listings const defaultTemplate = `<!DOCTYPE html> <html> <head> <title>{{.Name}}</title> <meta charset="utf-8"> <style> * { padding: 0; margin: 0; } body { padding: 1% 2%; font: 16px Arial; } header { font-size: 45px; padding: 25px; } header a { text-decoration: none; color: inherit; } header .up { display: inline-block; height: 50px; width: 50px; text-align: center; margin-right: 20px; } header a.up:hover { background: #000; color: #FFF; } h1 { font-size: 30px; display: inline; } table { border: 0; border-collapse: collapse; max-width: 750px; margin: 0 auto; } th, td { padding: 4px 20px; vertical-align: middle; line-height: 1.5em; /* emoji are kind of odd heights */ } th { text-align: left; } th a { color: #000; text-decoration: none; } @media (max-width: 700px) { .hideable { display: none; } body { padding: 0; } header, header h1 { font-size: 16px; } header { position: fixed; top: 0; width: 100%; background: #333; color: #FFF; padding: 15px; text-align: center; } header .up { height: auto; width: auto; display: none; } header a.up { display: inline-block; position: absolute; left: 0; top: 0; width: 40px; height: 48px; font-size: 35px; } header h1 { font-weight: normal; } main { margin-top: 70px; } } .name { white-space: pre; } </style> </head> <body> <header> {{if .CanGoUp}} <a href=".." class="up" title="Up one level">⬑</a> {{else}} <div class="up"> </div> {{end}} <h1>{{.Path}}</h1> </header> <main> <table> <tr> <th> {{if and (eq .Sort "name") (ne .Order "desc")}} <a href="?sort=name&order=desc">Name ▲</a> {{else if and (eq .Sort "name") (ne .Order "asc")}} <a href="?sort=name&order=asc">Name ▼</a> {{else}} <a href="?sort=name&order=asc">Name</a> {{end}} </th> <th> {{if and (eq .Sort "size") (ne .Order "desc")}} <a href="?sort=size&order=desc">Size ▲</a> {{else if and (eq .Sort "size") (ne .Order "asc")}} <a href="?sort=size&order=asc">Size ▼</a> {{else}} <a href="?sort=size&order=asc">Size</a> {{end}} </th> <th class="hideable"> {{if and (eq .Sort "time") (ne .Order "desc")}} <a href="?sort=time&order=desc">Modified ▲</a> {{else if and (eq .Sort "time") (ne .Order "asc")}} <a href="?sort=time&order=asc">Modified ▼</a> {{else}} <a href="?sort=time&order=asc">Modified</a> {{end}} </th> </tr> {{range .Items}} <tr> <td> {{if .IsDir}}📂{{else}}📄{{end}} <a href="{{.URL}}" class="name">{{.Name}}</a> </td> <td>{{.HumanSize}}</td> <td class="hideable">{{.HumanModTime "01/02/2006 3:04:05 PM -0700"}}</td> </tr> {{end}} </table> </main> </body> </html>`