make updates
This commit is contained in:
33
internal/assets/assets.go
Normal file
33
internal/assets/assets.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package assets
|
||||
|
||||
import (
|
||||
"mime"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/hacdias/caddy-filemanager/internal/config"
|
||||
)
|
||||
|
||||
// BaseURL is the url of the assets
|
||||
const BaseURL = "/_filemanagerinternal"
|
||||
|
||||
// ServeAssets provides the needed assets for the front-end
|
||||
func ServeAssets(w http.ResponseWriter, r *http.Request, c *config.Config) (int, error) {
|
||||
// gets the filename to be used with Assets function
|
||||
filename := strings.Replace(r.URL.Path, c.BaseURL+BaseURL, "public", 1)
|
||||
file, err := Asset(filename)
|
||||
if err != nil {
|
||||
return http.StatusNotFound, nil
|
||||
}
|
||||
|
||||
// Get the file extension and its mimetype
|
||||
extension := filepath.Ext(filename)
|
||||
mediatype := mime.TypeByExtension(extension)
|
||||
|
||||
// Write the header with the Content-Type and write the file
|
||||
// content to the buffer
|
||||
w.Header().Set("Content-Type", mediatype)
|
||||
w.Write(file)
|
||||
return 200, nil
|
||||
}
|
||||
347
internal/assets/binary.go
Normal file
347
internal/assets/binary.go
Normal file
@@ -0,0 +1,347 @@
|
||||
// Code generated by go-bindata.
|
||||
// sources:
|
||||
// assets/public/css/styles.css
|
||||
// assets/public/js/application.js
|
||||
// assets/templates/actions.tmpl
|
||||
// assets/templates/base.tmpl
|
||||
// assets/templates/editor.tmpl
|
||||
// assets/templates/frontmatter.tmpl
|
||||
// assets/templates/listing.tmpl
|
||||
// assets/templates/single.tmpl
|
||||
// DO NOT EDIT!
|
||||
|
||||
package assets
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// bindataRead reads the given file from disk. It returns an error on failure.
|
||||
func bindataRead(path, name string) ([]byte, error) {
|
||||
buf, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error reading asset %s at %s: %v", name, path, err)
|
||||
}
|
||||
return buf, err
|
||||
}
|
||||
|
||||
type asset struct {
|
||||
bytes []byte
|
||||
info os.FileInfo
|
||||
}
|
||||
|
||||
// publicCssStylesCss reads file data from disk. It returns an error on failure.
|
||||
func publicCssStylesCss() (*asset, error) {
|
||||
path := "D:\\Code\\Go\\src\\github.com\\hacdias\\caddy-filemanager\\assets\\public\\css\\styles.css"
|
||||
name := "public/css/styles.css"
|
||||
bytes, err := bindataRead(path, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error reading asset info %s at %s: %v", name, path, err)
|
||||
}
|
||||
|
||||
a := &asset{bytes: bytes, info: fi}
|
||||
return a, err
|
||||
}
|
||||
|
||||
// publicJsApplicationJs reads file data from disk. It returns an error on failure.
|
||||
func publicJsApplicationJs() (*asset, error) {
|
||||
path := "D:\\Code\\Go\\src\\github.com\\hacdias\\caddy-filemanager\\assets\\public\\js\\application.js"
|
||||
name := "public/js/application.js"
|
||||
bytes, err := bindataRead(path, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error reading asset info %s at %s: %v", name, path, err)
|
||||
}
|
||||
|
||||
a := &asset{bytes: bytes, info: fi}
|
||||
return a, err
|
||||
}
|
||||
|
||||
// templatesActionsTmpl reads file data from disk. It returns an error on failure.
|
||||
func templatesActionsTmpl() (*asset, error) {
|
||||
path := "D:\\Code\\Go\\src\\github.com\\hacdias\\caddy-filemanager\\assets\\templates\\actions.tmpl"
|
||||
name := "templates/actions.tmpl"
|
||||
bytes, err := bindataRead(path, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error reading asset info %s at %s: %v", name, path, err)
|
||||
}
|
||||
|
||||
a := &asset{bytes: bytes, info: fi}
|
||||
return a, err
|
||||
}
|
||||
|
||||
// templatesBaseTmpl reads file data from disk. It returns an error on failure.
|
||||
func templatesBaseTmpl() (*asset, error) {
|
||||
path := "D:\\Code\\Go\\src\\github.com\\hacdias\\caddy-filemanager\\assets\\templates\\base.tmpl"
|
||||
name := "templates/base.tmpl"
|
||||
bytes, err := bindataRead(path, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error reading asset info %s at %s: %v", name, path, err)
|
||||
}
|
||||
|
||||
a := &asset{bytes: bytes, info: fi}
|
||||
return a, err
|
||||
}
|
||||
|
||||
// templatesEditorTmpl reads file data from disk. It returns an error on failure.
|
||||
func templatesEditorTmpl() (*asset, error) {
|
||||
path := "D:\\Code\\Go\\src\\github.com\\hacdias\\caddy-filemanager\\assets\\templates\\editor.tmpl"
|
||||
name := "templates/editor.tmpl"
|
||||
bytes, err := bindataRead(path, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error reading asset info %s at %s: %v", name, path, err)
|
||||
}
|
||||
|
||||
a := &asset{bytes: bytes, info: fi}
|
||||
return a, err
|
||||
}
|
||||
|
||||
// templatesFrontmatterTmpl reads file data from disk. It returns an error on failure.
|
||||
func templatesFrontmatterTmpl() (*asset, error) {
|
||||
path := "D:\\Code\\Go\\src\\github.com\\hacdias\\caddy-filemanager\\assets\\templates\\frontmatter.tmpl"
|
||||
name := "templates/frontmatter.tmpl"
|
||||
bytes, err := bindataRead(path, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error reading asset info %s at %s: %v", name, path, err)
|
||||
}
|
||||
|
||||
a := &asset{bytes: bytes, info: fi}
|
||||
return a, err
|
||||
}
|
||||
|
||||
// templatesListingTmpl reads file data from disk. It returns an error on failure.
|
||||
func templatesListingTmpl() (*asset, error) {
|
||||
path := "D:\\Code\\Go\\src\\github.com\\hacdias\\caddy-filemanager\\assets\\templates\\listing.tmpl"
|
||||
name := "templates/listing.tmpl"
|
||||
bytes, err := bindataRead(path, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error reading asset info %s at %s: %v", name, path, err)
|
||||
}
|
||||
|
||||
a := &asset{bytes: bytes, info: fi}
|
||||
return a, err
|
||||
}
|
||||
|
||||
// templatesSingleTmpl reads file data from disk. It returns an error on failure.
|
||||
func templatesSingleTmpl() (*asset, error) {
|
||||
path := "D:\\Code\\Go\\src\\github.com\\hacdias\\caddy-filemanager\\assets\\templates\\single.tmpl"
|
||||
name := "templates/single.tmpl"
|
||||
bytes, err := bindataRead(path, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error reading asset info %s at %s: %v", name, path, err)
|
||||
}
|
||||
|
||||
a := &asset{bytes: bytes, info: fi}
|
||||
return a, err
|
||||
}
|
||||
|
||||
// Asset loads and returns the asset for the given name.
|
||||
// It returns an error if the asset could not be found or
|
||||
// could not be loaded.
|
||||
func Asset(name string) ([]byte, error) {
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[cannonicalName]; ok {
|
||||
a, err := f()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
|
||||
}
|
||||
return a.bytes, nil
|
||||
}
|
||||
return nil, fmt.Errorf("Asset %s not found", name)
|
||||
}
|
||||
|
||||
// MustAsset is like Asset but panics when Asset would return an error.
|
||||
// It simplifies safe initialization of global variables.
|
||||
func MustAsset(name string) []byte {
|
||||
a, err := Asset(name)
|
||||
if err != nil {
|
||||
panic("asset: Asset(" + name + "): " + err.Error())
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
// AssetInfo loads and returns the asset info for the given name.
|
||||
// It returns an error if the asset could not be found or
|
||||
// could not be loaded.
|
||||
func AssetInfo(name string) (os.FileInfo, error) {
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[cannonicalName]; ok {
|
||||
a, err := f()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
|
||||
}
|
||||
return a.info, nil
|
||||
}
|
||||
return nil, fmt.Errorf("AssetInfo %s not found", name)
|
||||
}
|
||||
|
||||
// AssetNames returns the names of the assets.
|
||||
func AssetNames() []string {
|
||||
names := make([]string, 0, len(_bindata))
|
||||
for name := range _bindata {
|
||||
names = append(names, name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// _bindata is a table, holding each asset generator, mapped to its name.
|
||||
var _bindata = map[string]func() (*asset, error){
|
||||
"public/css/styles.css": publicCssStylesCss,
|
||||
"public/js/application.js": publicJsApplicationJs,
|
||||
"templates/actions.tmpl": templatesActionsTmpl,
|
||||
"templates/base.tmpl": templatesBaseTmpl,
|
||||
"templates/editor.tmpl": templatesEditorTmpl,
|
||||
"templates/frontmatter.tmpl": templatesFrontmatterTmpl,
|
||||
"templates/listing.tmpl": templatesListingTmpl,
|
||||
"templates/single.tmpl": templatesSingleTmpl,
|
||||
}
|
||||
|
||||
// AssetDir returns the file names below a certain
|
||||
// directory embedded in the file by go-bindata.
|
||||
// For example if you run go-bindata on data/... and data contains the
|
||||
// following hierarchy:
|
||||
// data/
|
||||
// foo.txt
|
||||
// img/
|
||||
// a.png
|
||||
// b.png
|
||||
// then AssetDir("data") would return []string{"foo.txt", "img"}
|
||||
// AssetDir("data/img") would return []string{"a.png", "b.png"}
|
||||
// AssetDir("foo.txt") and AssetDir("notexist") would return an error
|
||||
// AssetDir("") will return []string{"data"}.
|
||||
func AssetDir(name string) ([]string, error) {
|
||||
node := _bintree
|
||||
if len(name) != 0 {
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
pathList := strings.Split(cannonicalName, "/")
|
||||
for _, p := range pathList {
|
||||
node = node.Children[p]
|
||||
if node == nil {
|
||||
return nil, fmt.Errorf("Asset %s not found", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
if node.Func != nil {
|
||||
return nil, fmt.Errorf("Asset %s not found", name)
|
||||
}
|
||||
rv := make([]string, 0, len(node.Children))
|
||||
for childName := range node.Children {
|
||||
rv = append(rv, childName)
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
type bintree struct {
|
||||
Func func() (*asset, error)
|
||||
Children map[string]*bintree
|
||||
}
|
||||
var _bintree = &bintree{nil, map[string]*bintree{
|
||||
"public": &bintree{nil, map[string]*bintree{
|
||||
"css": &bintree{nil, map[string]*bintree{
|
||||
"styles.css": &bintree{publicCssStylesCss, map[string]*bintree{}},
|
||||
}},
|
||||
"js": &bintree{nil, map[string]*bintree{
|
||||
"application.js": &bintree{publicJsApplicationJs, map[string]*bintree{}},
|
||||
}},
|
||||
}},
|
||||
"templates": &bintree{nil, map[string]*bintree{
|
||||
"actions.tmpl": &bintree{templatesActionsTmpl, map[string]*bintree{}},
|
||||
"base.tmpl": &bintree{templatesBaseTmpl, map[string]*bintree{}},
|
||||
"editor.tmpl": &bintree{templatesEditorTmpl, map[string]*bintree{}},
|
||||
"frontmatter.tmpl": &bintree{templatesFrontmatterTmpl, map[string]*bintree{}},
|
||||
"listing.tmpl": &bintree{templatesListingTmpl, map[string]*bintree{}},
|
||||
"single.tmpl": &bintree{templatesSingleTmpl, map[string]*bintree{}},
|
||||
}},
|
||||
}}
|
||||
|
||||
// RestoreAsset restores an asset under the given directory
|
||||
func RestoreAsset(dir, name string) error {
|
||||
data, err := Asset(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
info, err := AssetInfo(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RestoreAssets restores an asset under the given directory recursively
|
||||
func RestoreAssets(dir, name string) error {
|
||||
children, err := AssetDir(name)
|
||||
// File
|
||||
if err != nil {
|
||||
return RestoreAsset(dir, name)
|
||||
}
|
||||
// Dir
|
||||
for _, child := range children {
|
||||
err = RestoreAssets(dir, filepath.Join(name, child))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func _filePath(dir, name string) string {
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
|
||||
}
|
||||
|
||||
73
internal/config/config.go
Normal file
73
internal/config/config.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/mholt/caddy"
|
||||
)
|
||||
|
||||
// Config is a configuration for browsing in a particualr path.
|
||||
type Config struct {
|
||||
PathScope string
|
||||
Root http.FileSystem
|
||||
BaseURL string
|
||||
StyleSheet string // Costum stylesheet
|
||||
HugoEnabled bool // This must be only used by Hugo plugin
|
||||
}
|
||||
|
||||
// Parse parses the configuration set by the user so it can
|
||||
// be used by the middleware
|
||||
func Parse(c *caddy.Controller) ([]Config, error) {
|
||||
var configs []Config
|
||||
|
||||
appendConfig := func(cfg Config) error {
|
||||
for _, c := range configs {
|
||||
if c.PathScope == cfg.PathScope {
|
||||
return fmt.Errorf("duplicate file managing config for %s", c.PathScope)
|
||||
}
|
||||
}
|
||||
configs = append(configs, cfg)
|
||||
return nil
|
||||
}
|
||||
|
||||
for c.Next() {
|
||||
var cfg = Config{PathScope: ".", BaseURL: "", HugoEnabled: false}
|
||||
for c.NextBlock() {
|
||||
switch c.Val() {
|
||||
case "show":
|
||||
if !c.NextArg() {
|
||||
return configs, c.ArgErr()
|
||||
}
|
||||
cfg.PathScope = c.Val()
|
||||
cfg.PathScope = strings.TrimSuffix(cfg.PathScope, "/")
|
||||
case "on":
|
||||
if !c.NextArg() {
|
||||
return configs, c.ArgErr()
|
||||
}
|
||||
cfg.BaseURL = c.Val()
|
||||
cfg.BaseURL = strings.TrimPrefix(cfg.BaseURL, "/")
|
||||
cfg.BaseURL = strings.TrimSuffix(cfg.BaseURL, "/")
|
||||
cfg.BaseURL = "/" + cfg.BaseURL
|
||||
case "styles":
|
||||
if !c.NextArg() {
|
||||
return configs, c.ArgErr()
|
||||
}
|
||||
tplBytes, err := ioutil.ReadFile(c.Val())
|
||||
if err != nil {
|
||||
return configs, err
|
||||
}
|
||||
cfg.StyleSheet = string(tplBytes)
|
||||
}
|
||||
}
|
||||
|
||||
cfg.Root = http.Dir(cfg.PathScope)
|
||||
if err := appendConfig(cfg); err != nil {
|
||||
return configs, err
|
||||
}
|
||||
}
|
||||
|
||||
return configs, nil
|
||||
}
|
||||
125
internal/editor/editor.go
Normal file
125
internal/editor/editor.go
Normal file
@@ -0,0 +1,125 @@
|
||||
package editor
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/hacdias/caddy-filemanager/internal/file"
|
||||
"github.com/spf13/hugo/parser"
|
||||
)
|
||||
|
||||
// Editor contains the information for the editor page
|
||||
type Editor struct {
|
||||
Class string
|
||||
Mode string
|
||||
Content string
|
||||
FrontMatter interface{}
|
||||
}
|
||||
|
||||
// GetEditor gets the editor based on a FileInfo struct
|
||||
func (i *file.Info) GetEditor() (*Editor, error) {
|
||||
// Create a new editor variable and set the mode
|
||||
editor := new(Editor)
|
||||
editor.Mode = strings.TrimPrefix(filepath.Ext(i.Name), ".")
|
||||
|
||||
switch editor.Mode {
|
||||
case "md", "markdown", "mdown", "mmark":
|
||||
editor.Mode = "markdown"
|
||||
case "asciidoc", "adoc", "ad":
|
||||
editor.Mode = "asciidoc"
|
||||
case "rst":
|
||||
editor.Mode = "rst"
|
||||
case "html", "htm":
|
||||
editor.Mode = "html"
|
||||
case "js":
|
||||
editor.Mode = "javascript"
|
||||
}
|
||||
|
||||
var page parser.Page
|
||||
var err error
|
||||
|
||||
// Handle the content depending on the file extension
|
||||
switch editor.Mode {
|
||||
case "markdown", "asciidoc", "rst":
|
||||
if editor.hasFrontMatterRune(i.Raw) {
|
||||
// Starts a new buffer and parses the file using Hugo's functions
|
||||
buffer := bytes.NewBuffer(i.Raw)
|
||||
page, err = parser.ReadFrom(buffer)
|
||||
if err != nil {
|
||||
return editor, err
|
||||
}
|
||||
|
||||
// Parses the page content and the frontmatter
|
||||
editor.Content = strings.TrimSpace(string(page.Content()))
|
||||
editor.FrontMatter, _, err = Pretty(page.FrontMatter())
|
||||
editor.Class = "complete"
|
||||
} else {
|
||||
// The editor will handle only content
|
||||
editor.Class = "content-only"
|
||||
editor.Content = fi.Content
|
||||
}
|
||||
case "json", "toml", "yaml":
|
||||
// Defines the class and declares an error
|
||||
editor.Class = "frontmatter-only"
|
||||
|
||||
// Checks if the file already has the frontmatter rune and parses it
|
||||
if editor.hasFrontMatterRune(i.Raw) {
|
||||
editor.FrontMatter, _, err = Pretty(i.Raw)
|
||||
} else {
|
||||
editor.FrontMatter, _, err = Pretty(editor.appendFrontMatterRune(i.Raw, editor.Mode))
|
||||
}
|
||||
|
||||
// Check if there were any errors
|
||||
if err != nil {
|
||||
return editor, err
|
||||
}
|
||||
default:
|
||||
// The editor will handle only content
|
||||
editor.Class = "content-only"
|
||||
editor.Content = i.Content
|
||||
}
|
||||
|
||||
return editor, nil
|
||||
}
|
||||
|
||||
func (e Editor) hasFrontMatterRune(file []byte) bool {
|
||||
return strings.HasPrefix(string(file), "---") ||
|
||||
strings.HasPrefix(string(file), "+++") ||
|
||||
strings.HasPrefix(string(file), "{")
|
||||
}
|
||||
|
||||
func (e Editor) appendFrontMatterRune(frontmatter []byte, language string) []byte {
|
||||
switch language {
|
||||
case "yaml":
|
||||
return []byte("---\n" + string(frontmatter) + "\n---")
|
||||
case "toml":
|
||||
return []byte("+++\n" + string(frontmatter) + "\n+++")
|
||||
case "json":
|
||||
return frontmatter
|
||||
}
|
||||
|
||||
return frontmatter
|
||||
}
|
||||
|
||||
// CanBeEdited checks if the extension of a file is supported by the editor
|
||||
func CanBeEdited(filename string) bool {
|
||||
extensions := [...]string{
|
||||
"md", "markdown", "mdown", "mmark",
|
||||
"asciidoc", "adoc", "ad",
|
||||
"rst",
|
||||
".json", ".toml", ".yaml",
|
||||
".css", ".sass", ".scss",
|
||||
".js",
|
||||
".html",
|
||||
".txt",
|
||||
}
|
||||
|
||||
for _, extension := range extensions {
|
||||
if strings.HasSuffix(filename, extension) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
328
internal/file/file.go
Normal file
328
internal/file/file.go
Normal file
@@ -0,0 +1,328 @@
|
||||
package file
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/hacdias/caddy-filemanager/internal/config"
|
||||
"github.com/hacdias/caddy-filemanager/internal/editor"
|
||||
p "github.com/hacdias/caddy-filemanager/internal/page"
|
||||
"github.com/hacdias/caddy-filemanager/utils/errors"
|
||||
"github.com/mholt/caddy/caddyhttp/httpserver"
|
||||
)
|
||||
|
||||
// Info is the information about a particular file or directory
|
||||
type Info struct {
|
||||
IsDir bool
|
||||
Name string
|
||||
Size int64
|
||||
URL string
|
||||
Path string // The relative Path of the file/directory relative to Caddyfile.
|
||||
RootPath string // The Path of the file/directory on http.FileSystem.
|
||||
ModTime time.Time
|
||||
Mode os.FileMode
|
||||
Mimetype string
|
||||
Content string
|
||||
Raw []byte
|
||||
Type string
|
||||
}
|
||||
|
||||
// GetInfo gets the file information and, in case of error, returns the
|
||||
// respective HTTP error code
|
||||
func GetInfo(url *url.URL, c *config.Config) (*Info, int, error) {
|
||||
var err error
|
||||
|
||||
rootPath := strings.Replace(url.Path, c.BaseURL, "", 1)
|
||||
rootPath = strings.TrimPrefix(rootPath, "/")
|
||||
rootPath = "/" + rootPath
|
||||
|
||||
relpath := c.PathScope + rootPath
|
||||
relpath = strings.Replace(relpath, "\\", "/", -1)
|
||||
relpath = filepath.Clean(relpath)
|
||||
|
||||
file := &Info{
|
||||
URL: url.Path,
|
||||
RootPath: rootPath,
|
||||
Path: relpath,
|
||||
}
|
||||
f, err := c.Root.Open(rootPath)
|
||||
if err != nil {
|
||||
return file, errors.ToHTTPCode(err), err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
info, err := f.Stat()
|
||||
if err != nil {
|
||||
return file, errors.ToHTTPCode(err), err
|
||||
}
|
||||
|
||||
file.IsDir = info.IsDir()
|
||||
file.ModTime = info.ModTime()
|
||||
file.Name = info.Name()
|
||||
file.Size = info.Size()
|
||||
return file, 0, nil
|
||||
}
|
||||
|
||||
// GetExtendedInfo is used to get extra parameters for FileInfo struct
|
||||
func (i *Info) GetExtendedInfo() error {
|
||||
err := i.Read()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
i.Type = SimplifyMimeType(i.Mimetype)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read is used to read a file and store its content
|
||||
func (i *Info) Read() error {
|
||||
raw, err := ioutil.ReadFile(i.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
i.Mimetype = http.DetectContentType(raw)
|
||||
i.Content = string(raw)
|
||||
i.Raw = raw
|
||||
return nil
|
||||
}
|
||||
|
||||
// HumanSize returns the size of the file as a human-readable string
|
||||
// in IEC format (i.e. power of 2 or base 1024).
|
||||
func (i Info) HumanSize() string {
|
||||
return humanize.IBytes(uint64(i.Size))
|
||||
}
|
||||
|
||||
// HumanModTime returns the modified time of the file as a human-readable string.
|
||||
func (i Info) HumanModTime(format string) string {
|
||||
return i.ModTime.Format(format)
|
||||
}
|
||||
|
||||
// Delete handles the delete requests
|
||||
func (i *Info) Delete() (int, error) {
|
||||
var err error
|
||||
|
||||
// If it's a directory remove all the contents inside
|
||||
if i.IsDir {
|
||||
err = os.RemoveAll(i.Path)
|
||||
} else {
|
||||
err = os.Remove(i.Path)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return errors.ToHTTPCode(err), err
|
||||
}
|
||||
|
||||
return http.StatusOK, nil
|
||||
}
|
||||
|
||||
// Rename function is used tor rename a file or a directory
|
||||
func (i *Info) Rename(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
newname := r.Header.Get("Rename-To")
|
||||
if newname == "" {
|
||||
return http.StatusBadRequest, nil
|
||||
}
|
||||
|
||||
newpath := filepath.Clean(newname)
|
||||
newpath = strings.Replace(i.Path, i.Name, newname, 1)
|
||||
|
||||
if err := os.Rename(i.Path, newpath); err != nil {
|
||||
return errors.ToHTTPCode(err), err
|
||||
}
|
||||
|
||||
i.Path = newpath
|
||||
return http.StatusOK, nil
|
||||
}
|
||||
|
||||
// ServeAsHTML is used to serve single file pages
|
||||
func (i *Info) ServeAsHTML(w http.ResponseWriter, r *http.Request, c *config.Config) (int, error) {
|
||||
if i.IsDir {
|
||||
return i.serveListing(w, r, c)
|
||||
}
|
||||
|
||||
return i.serveSingleFile(w, r, c)
|
||||
}
|
||||
|
||||
func (i *Info) serveSingleFile(w http.ResponseWriter, r *http.Request, c *config.Config) (int, error) {
|
||||
err := i.GetExtendedInfo()
|
||||
if err != nil {
|
||||
return errors.ToHTTPCode(err), err
|
||||
}
|
||||
|
||||
if i.Type == "blob" {
|
||||
return i.ServeRawFile(w, r, c)
|
||||
}
|
||||
|
||||
page := &p.Page{
|
||||
Info: &p.Info{
|
||||
Name: i.Name,
|
||||
Path: i.RootPath,
|
||||
IsDir: false,
|
||||
Data: i,
|
||||
Config: c,
|
||||
},
|
||||
}
|
||||
|
||||
if editor.CanBeEdited(i.Name) {
|
||||
editor, err := i.GetEditor()
|
||||
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
page.Info.Data = editor
|
||||
return page.PrintAsHTML(w, "frontmatter", "editor")
|
||||
}
|
||||
|
||||
return page.PrintAsHTML(w, "single")
|
||||
}
|
||||
|
||||
func (i *Info) serveListing(w http.ResponseWriter, r *http.Request, c *config.Config) (int, error) {
|
||||
var err error
|
||||
|
||||
file, err := c.Root.Open(i.RootPath)
|
||||
if err != nil {
|
||||
return errors.ToHTTPCode(err), err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
listing, err := i.loadDirectoryContents(file, c)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
switch {
|
||||
case os.IsPermission(err):
|
||||
return http.StatusForbidden, err
|
||||
case os.IsExist(err):
|
||||
return http.StatusGone, err
|
||||
default:
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
}
|
||||
|
||||
listing.Context = httpserver.Context{
|
||||
Root: c.Root,
|
||||
Req: r,
|
||||
URL: r.URL,
|
||||
}
|
||||
|
||||
// Copy the query values into the Listing struct
|
||||
var limit int
|
||||
listing.Sort, listing.Order, limit, err = handleSortOrder(w, r, c.PathScope)
|
||||
if err != nil {
|
||||
return http.StatusBadRequest, err
|
||||
}
|
||||
|
||||
listing.applySort()
|
||||
|
||||
if limit > 0 && limit <= len(listing.Items) {
|
||||
listing.Items = listing.Items[:limit]
|
||||
listing.ItemsLimitedTo = limit
|
||||
}
|
||||
|
||||
page := &p.Page{
|
||||
Info: &p.Info{
|
||||
Name: listing.Name,
|
||||
Path: i.RootPath,
|
||||
IsDir: true,
|
||||
Config: c,
|
||||
Data: listing,
|
||||
},
|
||||
}
|
||||
|
||||
return page.PrintAsHTML(w, "listing")
|
||||
}
|
||||
|
||||
func (i Info) loadDirectoryContents(file http.File, c *config.Config) (*Listing, error) {
|
||||
files, err := file.Readdir(-1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
listing := directoryListing(files, i.RootPath)
|
||||
return &listing, nil
|
||||
}
|
||||
|
||||
func directoryListing(files []os.FileInfo, urlPath string) Listing {
|
||||
var (
|
||||
fileinfos []Info
|
||||
dirCount, fileCount int
|
||||
)
|
||||
|
||||
for _, f := range files {
|
||||
name := f.Name()
|
||||
|
||||
if f.IsDir() {
|
||||
name += "/"
|
||||
dirCount++
|
||||
} else {
|
||||
fileCount++
|
||||
}
|
||||
|
||||
url := url.URL{Path: "./" + name} // prepend with "./" to fix paths with ':' in the name
|
||||
|
||||
fileinfos = append(fileinfos, Info{
|
||||
IsDir: f.IsDir(),
|
||||
Name: f.Name(),
|
||||
Size: f.Size(),
|
||||
URL: url.String(),
|
||||
ModTime: f.ModTime().UTC(),
|
||||
Mode: f.Mode(),
|
||||
})
|
||||
}
|
||||
|
||||
return Listing{
|
||||
Name: path.Base(urlPath),
|
||||
Path: urlPath,
|
||||
Items: fileinfos,
|
||||
NumDirs: dirCount,
|
||||
NumFiles: fileCount,
|
||||
}
|
||||
}
|
||||
|
||||
// ServeRawFile serves raw files
|
||||
func (i *Info) ServeRawFile(w http.ResponseWriter, r *http.Request, c *config.Config) (int, error) {
|
||||
err := i.GetExtendedInfo()
|
||||
if err != nil {
|
||||
return errors.ToHTTPCode(err), err
|
||||
}
|
||||
|
||||
if i.Type != "text" {
|
||||
i.Read()
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", i.Mimetype)
|
||||
w.Write([]byte(i.Content))
|
||||
return 200, nil
|
||||
}
|
||||
|
||||
// SimplifyMimeType returns the base type of a file
|
||||
func SimplifyMimeType(name string) string {
|
||||
if strings.HasPrefix(name, "video") {
|
||||
return "video"
|
||||
}
|
||||
|
||||
if strings.HasPrefix(name, "audio") {
|
||||
return "audio"
|
||||
}
|
||||
|
||||
if strings.HasPrefix(name, "image") {
|
||||
return "image"
|
||||
}
|
||||
|
||||
if strings.HasPrefix(name, "text") {
|
||||
return "text"
|
||||
}
|
||||
|
||||
if strings.HasPrefix(name, "application/javascript") {
|
||||
return "text"
|
||||
}
|
||||
|
||||
return "blob"
|
||||
}
|
||||
141
internal/file/listing.go
Normal file
141
internal/file/listing.go
Normal file
@@ -0,0 +1,141 @@
|
||||
package file
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/mholt/caddy/caddyhttp/httpserver"
|
||||
)
|
||||
|
||||
// A Listing is the context used to fill out a template.
|
||||
type Listing struct {
|
||||
// The name of the directory (the last element of the path)
|
||||
Name string
|
||||
// The full path of the request
|
||||
Path string
|
||||
// The items (files and folders) in the path
|
||||
Items []Info
|
||||
// The number of directories in the listing
|
||||
NumDirs int
|
||||
// The number of files (items that aren't directories) in the listing
|
||||
NumFiles int
|
||||
// Which sorting order is used
|
||||
Sort string
|
||||
// And which order
|
||||
Order string
|
||||
// If ≠0 then Items have been limited to that many elements
|
||||
ItemsLimitedTo int
|
||||
httpserver.Context
|
||||
}
|
||||
|
||||
// handleSortOrder gets and stores for a Listing the 'sort' and 'order',
|
||||
// and reads 'limit' if given. The latter is 0 if not given. Sets cookies.
|
||||
func handleSortOrder(w http.ResponseWriter, r *http.Request, scope string) (sort string, order string, limit int, err error) {
|
||||
sort, order, limitQuery := r.URL.Query().Get("sort"), r.URL.Query().Get("order"), r.URL.Query().Get("limit")
|
||||
|
||||
// If the query 'sort' or 'order' is empty, use defaults or any values previously saved in Cookies
|
||||
switch sort {
|
||||
case "":
|
||||
sort = "name"
|
||||
if sortCookie, sortErr := r.Cookie("sort"); sortErr == nil {
|
||||
sort = sortCookie.Value
|
||||
}
|
||||
case "name", "size", "type":
|
||||
http.SetCookie(w, &http.Cookie{Name: "sort", Value: sort, Path: scope, Secure: r.TLS != nil})
|
||||
}
|
||||
|
||||
switch order {
|
||||
case "":
|
||||
order = "asc"
|
||||
if orderCookie, orderErr := r.Cookie("order"); orderErr == nil {
|
||||
order = orderCookie.Value
|
||||
}
|
||||
case "asc", "desc":
|
||||
http.SetCookie(w, &http.Cookie{Name: "order", Value: order, Path: scope, Secure: r.TLS != nil})
|
||||
}
|
||||
|
||||
if limitQuery != "" {
|
||||
limit, err = strconv.Atoi(limitQuery)
|
||||
if err != nil { // if the 'limit' query can't be interpreted as a number, return err
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Implement sorting for Listing
|
||||
type byName Listing
|
||||
type bySize Listing
|
||||
type byTime Listing
|
||||
|
||||
// By Name
|
||||
func (l byName) Len() int { return len(l.Items) }
|
||||
func (l byName) Swap(i, j int) { l.Items[i], l.Items[j] = l.Items[j], l.Items[i] }
|
||||
|
||||
// Treat upper and lower case equally
|
||||
func (l byName) Less(i, j int) bool {
|
||||
if l.Items[i].IsDir && !l.Items[j].IsDir {
|
||||
return true
|
||||
}
|
||||
|
||||
if !l.Items[i].IsDir && l.Items[j].IsDir {
|
||||
return false
|
||||
}
|
||||
|
||||
return strings.ToLower(l.Items[i].Name) < strings.ToLower(l.Items[j].Name)
|
||||
}
|
||||
|
||||
// By Size
|
||||
func (l bySize) Len() int { return len(l.Items) }
|
||||
func (l bySize) Swap(i, j int) { l.Items[i], l.Items[j] = l.Items[j], l.Items[i] }
|
||||
|
||||
const directoryOffset = -1 << 31 // = math.MinInt32
|
||||
func (l bySize) Less(i, j int) bool {
|
||||
iSize, jSize := l.Items[i].Size, l.Items[j].Size
|
||||
if l.Items[i].IsDir {
|
||||
iSize = directoryOffset + iSize
|
||||
}
|
||||
if l.Items[j].IsDir {
|
||||
jSize = directoryOffset + jSize
|
||||
}
|
||||
return iSize < jSize
|
||||
}
|
||||
|
||||
// By Time
|
||||
func (l byTime) Len() int { return len(l.Items) }
|
||||
func (l byTime) Swap(i, j int) { l.Items[i], l.Items[j] = l.Items[j], l.Items[i] }
|
||||
func (l byTime) Less(i, j int) bool { return l.Items[i].ModTime.Before(l.Items[j].ModTime) }
|
||||
|
||||
// Add sorting method to "Listing"
|
||||
// it will apply what's in ".Sort" and ".Order"
|
||||
func (l Listing) applySort() {
|
||||
// Check '.Order' to know how to sort
|
||||
if l.Order == "desc" {
|
||||
switch l.Sort {
|
||||
case "name":
|
||||
sort.Sort(sort.Reverse(byName(l)))
|
||||
case "size":
|
||||
sort.Sort(sort.Reverse(bySize(l)))
|
||||
case "time":
|
||||
sort.Sort(sort.Reverse(byTime(l)))
|
||||
default:
|
||||
// If not one of the above, do nothing
|
||||
return
|
||||
}
|
||||
} else { // If we had more Orderings we could add them here
|
||||
switch l.Sort {
|
||||
case "name":
|
||||
sort.Sort(byName(l))
|
||||
case "size":
|
||||
sort.Sort(bySize(l))
|
||||
case "time":
|
||||
sort.Sort(byTime(l))
|
||||
default:
|
||||
sort.Sort(byName(l))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
173
internal/frontmatter/frontmatter.go
Normal file
173
internal/frontmatter/frontmatter.go
Normal file
@@ -0,0 +1,173 @@
|
||||
package frontmatter
|
||||
|
||||
import (
|
||||
"log"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/hacdias/caddy-filemanager/utils/variables"
|
||||
"github.com/spf13/cast"
|
||||
"github.com/spf13/hugo/parser"
|
||||
)
|
||||
|
||||
const (
|
||||
mainName = "#MAIN#"
|
||||
objectType = "object"
|
||||
arrayType = "array"
|
||||
)
|
||||
|
||||
var mainTitle = ""
|
||||
|
||||
// Pretty creates a new FrontMatter object
|
||||
func Pretty(content []byte) (interface{}, string, error) {
|
||||
frontType := parser.DetectFrontMatter(rune(content[0]))
|
||||
front, err := frontType.Parse(content)
|
||||
|
||||
if err != nil {
|
||||
return []string{}, mainTitle, err
|
||||
}
|
||||
|
||||
object := new(frontmatter)
|
||||
object.Type = objectType
|
||||
object.Name = mainName
|
||||
|
||||
return rawToPretty(front, object), mainTitle, nil
|
||||
}
|
||||
|
||||
type frontmatter struct {
|
||||
Name string
|
||||
Title string
|
||||
Content interface{}
|
||||
Type string
|
||||
HTMLType string
|
||||
Parent *frontmatter
|
||||
}
|
||||
|
||||
func rawToPretty(config interface{}, parent *frontmatter) interface{} {
|
||||
objects := []*frontmatter{}
|
||||
arrays := []*frontmatter{}
|
||||
fields := []*frontmatter{}
|
||||
|
||||
cnf := map[string]interface{}{}
|
||||
|
||||
if reflect.TypeOf(config) == reflect.TypeOf(map[interface{}]interface{}{}) {
|
||||
for key, value := range config.(map[interface{}]interface{}) {
|
||||
cnf[key.(string)] = value
|
||||
}
|
||||
} else if reflect.TypeOf(config) == reflect.TypeOf([]interface{}{}) {
|
||||
for key, value := range config.([]interface{}) {
|
||||
cnf[string(key)] = value
|
||||
}
|
||||
} else {
|
||||
cnf = config.(map[string]interface{})
|
||||
}
|
||||
|
||||
for name, element := range cnf {
|
||||
if variables.IsMap(element) {
|
||||
objects = append(objects, handleObjects(element, parent, name))
|
||||
} else if variables.IsSlice(element) {
|
||||
arrays = append(arrays, handleArrays(element, parent, name))
|
||||
} else {
|
||||
if name == "title" && parent.Name == mainName {
|
||||
mainTitle = element.(string)
|
||||
}
|
||||
|
||||
fields = append(fields, handleFlatValues(element, parent, name))
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(sortByTitle(objects))
|
||||
sort.Sort(sortByTitle(arrays))
|
||||
sort.Sort(sortByTitle(fields))
|
||||
|
||||
settings := []*frontmatter{}
|
||||
settings = append(settings, fields...)
|
||||
settings = append(settings, arrays...)
|
||||
settings = append(settings, objects...)
|
||||
return settings
|
||||
}
|
||||
|
||||
type sortByTitle []*frontmatter
|
||||
|
||||
func (f sortByTitle) Len() int { return len(f) }
|
||||
func (f sortByTitle) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
|
||||
func (f sortByTitle) Less(i, j int) bool {
|
||||
return strings.ToLower(f[i].Name) < strings.ToLower(f[j].Name)
|
||||
}
|
||||
|
||||
func handleObjects(content interface{}, parent *frontmatter, name string) *frontmatter {
|
||||
c := new(frontmatter)
|
||||
c.Parent = parent
|
||||
c.Type = objectType
|
||||
c.Title = name
|
||||
|
||||
if parent.Name == mainName {
|
||||
c.Name = c.Title
|
||||
} else if parent.Type == arrayType {
|
||||
c.Name = parent.Name + "[]"
|
||||
} else {
|
||||
c.Name = parent.Name + "[" + c.Title + "]"
|
||||
}
|
||||
|
||||
c.Content = rawToPretty(content, c)
|
||||
return c
|
||||
}
|
||||
|
||||
func handleArrays(content interface{}, parent *frontmatter, name string) *frontmatter {
|
||||
c := new(frontmatter)
|
||||
c.Parent = parent
|
||||
c.Type = arrayType
|
||||
c.Title = name
|
||||
|
||||
if parent.Name == mainName {
|
||||
c.Name = name
|
||||
} else {
|
||||
c.Name = parent.Name + "[" + name + "]"
|
||||
}
|
||||
|
||||
c.Content = rawToPretty(content, c)
|
||||
return c
|
||||
}
|
||||
|
||||
func handleFlatValues(content interface{}, parent *frontmatter, name string) *frontmatter {
|
||||
c := new(frontmatter)
|
||||
c.Parent = parent
|
||||
|
||||
switch reflect.ValueOf(content).Kind() {
|
||||
case reflect.Bool:
|
||||
c.Type = "boolean"
|
||||
case reflect.Int, reflect.Float32, reflect.Float64:
|
||||
c.Type = "number"
|
||||
default:
|
||||
c.Type = "string"
|
||||
}
|
||||
|
||||
c.Content = content
|
||||
|
||||
switch strings.ToLower(name) {
|
||||
case "description":
|
||||
c.HTMLType = "textarea"
|
||||
case "date", "publishdate":
|
||||
c.HTMLType = "datetime"
|
||||
c.Content = cast.ToTime(content)
|
||||
default:
|
||||
c.HTMLType = "text"
|
||||
}
|
||||
|
||||
if parent.Type == arrayType {
|
||||
c.Name = parent.Name + "[]"
|
||||
c.Title = content.(string)
|
||||
} else if parent.Type == objectType {
|
||||
c.Title = name
|
||||
c.Name = parent.Name + "[" + name + "]"
|
||||
|
||||
if parent.Name == mainName {
|
||||
c.Name = name
|
||||
}
|
||||
} else {
|
||||
log.Panic("Parent type not allowed in handleFlatValues.")
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
133
internal/page/page.go
Normal file
133
internal/page/page.go
Normal file
@@ -0,0 +1,133 @@
|
||||
package page
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/hacdias/caddy-filemanager/internal/assets"
|
||||
"github.com/hacdias/caddy-filemanager/internal/config"
|
||||
"github.com/hacdias/caddy-filemanager/utils/variables"
|
||||
)
|
||||
|
||||
// Page contains the informations and functions needed to show the page
|
||||
type Page struct {
|
||||
*Info
|
||||
}
|
||||
|
||||
// Info contains the information of a page
|
||||
type Info struct {
|
||||
Name string
|
||||
Path string
|
||||
IsDir bool
|
||||
Config *config.Config
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
// BreadcrumbMap returns p.Path where every element is a map
|
||||
// of URLs and path segment names.
|
||||
func (i Info) BreadcrumbMap() map[string]string {
|
||||
result := map[string]string{}
|
||||
|
||||
if len(i.Path) == 0 {
|
||||
return result
|
||||
}
|
||||
|
||||
// skip trailing slash
|
||||
lpath := i.Path
|
||||
if lpath[len(lpath)-1] == '/' {
|
||||
lpath = lpath[:len(lpath)-1]
|
||||
}
|
||||
|
||||
parts := strings.Split(lpath, "/")
|
||||
for i, part := range parts {
|
||||
if i == 0 && part == "" {
|
||||
// Leading slash (root)
|
||||
result["/"] = "/"
|
||||
continue
|
||||
}
|
||||
result[strings.Join(parts[:i+1], "/")] = part
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// PreviousLink returns the path of the previous folder
|
||||
func (i Info) PreviousLink() string {
|
||||
parts := strings.Split(strings.TrimSuffix(i.Path, "/"), "/")
|
||||
if len(parts) <= 1 {
|
||||
return ""
|
||||
}
|
||||
|
||||
if parts[len(parts)-2] == "" {
|
||||
if i.Config.BaseURL == "" {
|
||||
return "/"
|
||||
}
|
||||
return i.Config.BaseURL
|
||||
}
|
||||
|
||||
return parts[len(parts)-2]
|
||||
}
|
||||
|
||||
// PrintAsHTML formats the page in HTML and executes the template
|
||||
func (p Page) PrintAsHTML(w http.ResponseWriter, templates ...string) (int, error) {
|
||||
// Create the functions map, then the template, check for erros and
|
||||
// execute the template if there aren't errors
|
||||
functions := template.FuncMap{
|
||||
"SplitCapitalize": variables.SplitCapitalize,
|
||||
"Defined": variables.Defined,
|
||||
}
|
||||
|
||||
templates = append(templates, "actions", "base")
|
||||
var tpl *template.Template
|
||||
|
||||
// For each template, add it to the the tpl variable
|
||||
for i, t := range templates {
|
||||
// Get the template from the assets
|
||||
page, err := assets.Asset("templates/" + t + ".tmpl")
|
||||
|
||||
// Check if there is some error. If so, the template doesn't exist
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
// If it's the first iteration, creates a new template and add the
|
||||
// functions map
|
||||
if i == 0 {
|
||||
tpl, err = template.New(t).Funcs(functions).Parse(string(page))
|
||||
} else {
|
||||
tpl, err = tpl.Parse(string(page))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
err := tpl.Execute(buf, p.Info)
|
||||
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
_, err = buf.WriteTo(w)
|
||||
return http.StatusOK, nil
|
||||
}
|
||||
|
||||
// PrintAsJSON prints the current page infromation in JSON
|
||||
func (p Page) PrintAsJSON(w http.ResponseWriter) (int, error) {
|
||||
marsh, err := json.Marshal(p.Info.Data)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
return w.Write(marsh)
|
||||
}
|
||||
Reference in New Issue
Block a user