make updates
parent
4274a181a5
commit
669cb2c4ae
|
@ -325,6 +325,12 @@ header p i {
|
|||
font-size: 1em !important;
|
||||
color: rgba(255, 255, 255, .31);
|
||||
}
|
||||
header #logout {
|
||||
background-color: rgba(0,0,0,0.1);
|
||||
border-radius: 0;
|
||||
margin: -0.5em;
|
||||
padding: .5em;
|
||||
}
|
||||
header p i {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
@ -414,6 +420,7 @@ header form input {
|
|||
}
|
||||
.action.disabled {
|
||||
opacity: 0.2;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.action i {
|
||||
padding: 0.5em;
|
||||
|
@ -486,13 +493,30 @@ i.spin {
|
|||
|
||||
/* EDITOR */
|
||||
|
||||
|
||||
#editor .block {
|
||||
box-shadow: 0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12);
|
||||
display: block;
|
||||
border-radius: .2em;
|
||||
padding: .5em;
|
||||
margin-bottom: 1em;
|
||||
break-inside: avoid;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
#editor .frontmatter {
|
||||
border: 1px solid #ddd;
|
||||
background: #fff;
|
||||
/* border: 1px solid #ddd; */
|
||||
/* background: #fff; */
|
||||
column-count: 3;
|
||||
column-gap: 1em;
|
||||
margin-bottom: 1em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
#editor label {
|
||||
display: inline-block;
|
||||
display: block;
|
||||
width: 19%;
|
||||
font-size: 2em;
|
||||
}
|
||||
#editor fieldset {
|
||||
margin: 0;
|
||||
|
@ -503,3 +527,10 @@ i.spin {
|
|||
#editor button {
|
||||
display: none;
|
||||
}
|
||||
#editor textarea[name="content"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#editor h3 {
|
||||
font-size: .8rem;
|
||||
}
|
||||
|
|
|
@ -111,6 +111,9 @@ var handleViewType = function (viewList) {
|
|||
|
||||
// Handles the open file button event
|
||||
var openEvent = function (event) {
|
||||
if (this.classList.contains('disabled')) {
|
||||
return false;
|
||||
}
|
||||
if (selectedItems.length) {
|
||||
window.open(selectedItems[0] + '?raw=true');
|
||||
return false;
|
||||
|
@ -181,6 +184,9 @@ var preventDefault = function (event) {
|
|||
|
||||
// Rename file event
|
||||
var renameEvent = function (event) {
|
||||
if (this.classList.contains('disabled')) {
|
||||
return false;
|
||||
}
|
||||
if (selectedItems.length) {
|
||||
Array.from(selectedItems).forEach(link => {
|
||||
let item = document.getElementById(link);
|
||||
|
@ -248,6 +254,9 @@ var renameEvent = function (event) {
|
|||
|
||||
// Download file event
|
||||
var downloadEvent = function (event) {
|
||||
if (this.classList.contains('disabled')) {
|
||||
return false;
|
||||
}
|
||||
if (selectedItems.length) {
|
||||
Array.from(selectedItems).forEach(item => {
|
||||
window.open(item + "?download=true");
|
||||
|
@ -416,15 +425,39 @@ document.addEventListener("DOMContentLoaded", function (event) {
|
|||
}, false);
|
||||
}
|
||||
|
||||
|
||||
if (document.getElementById('editor')) {
|
||||
handleEditorPage();
|
||||
handleEditorPage();
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
var handleEditorPage = function() {
|
||||
|
||||
return false;
|
||||
var handleEditorPage = function () {
|
||||
let container = document.getElementById('editor');
|
||||
let kind = container.dataset.kind;
|
||||
|
||||
if (kind != 'frontmatter-only') {
|
||||
let editor = document.getElementById('editor-source');
|
||||
let mode = editor.dataset.mode;
|
||||
let textarea = document.querySelector('textarea[name="content"]');
|
||||
let aceEditor = ace.edit('editor-source');
|
||||
aceEditor.getSession().setMode("ace/mode/" + mode);
|
||||
aceEditor.getSession().setValue(textarea.value);
|
||||
aceEditor.getSession().on('change', function() {
|
||||
textarea.value = aceEditor.getSession().getValue();
|
||||
});
|
||||
aceEditor.setOptions({
|
||||
wrap: true,
|
||||
maxLines: Infinity,
|
||||
theme: "ace/theme/github",
|
||||
showPrintMargin: false,
|
||||
fontSize: "1em",
|
||||
minLines: 20
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
{{ end }}
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header>
|
||||
<div>
|
||||
{{ $lnk := .PreviousLink }}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{{ define "content" }}
|
||||
<div id="editor" class="container {{ .Class }}">
|
||||
<div id="editor" class="container" data-kind="{{ .Class }}">
|
||||
<form method="POST" action="./">
|
||||
{{ if or (eq .Class "frontmatter-only") (eq .Class "complete") }}
|
||||
<div class="frontmatter">
|
||||
|
@ -10,7 +10,7 @@
|
|||
|
||||
{{ if or (eq .Class "content-only") (eq .Class "complete") }}
|
||||
<div class="content">
|
||||
<div id="editor" data-mode="{{ .Mode }}"></div>
|
||||
<div id="editor-source" data-mode="{{ .Mode }}"></div>
|
||||
<textarea name="content">{{ .Content }}</textarea>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//go:generate go get github.com/jteeuwen/go-bindata
|
||||
//go:generate go install github.com/jteeuwen/go-bindata/go-bindata
|
||||
//go:generate go-bindata -debug -pkg filemanager -prefix "assets" -o binary.go assets/...
|
||||
//go:generate go-bindata -debug -pkg assets -prefix "assets" -o internal/assets/binary.go assets/...
|
||||
|
||||
// Package filemanager provides middleware for managing files in a directory
|
||||
// when directory path is requested instead of a specific file. Based on browse
|
||||
|
@ -10,31 +10,30 @@ package filemanager
|
|||
import (
|
||||
"io"
|
||||
"log"
|
||||
"mime"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
a "github.com/hacdias/caddy-filemanager/internal/assets"
|
||||
"github.com/hacdias/caddy-filemanager/internal/config"
|
||||
"github.com/hacdias/caddy-filemanager/internal/file"
|
||||
"github.com/mholt/caddy/caddyhttp/httpserver"
|
||||
)
|
||||
|
||||
// AssetsURL is the url of the assets
|
||||
const AssetsURL = "/_filemanagerinternal"
|
||||
|
||||
// FileManager is an http.Handler that can show a file listing when
|
||||
// directories in the given paths are specified.
|
||||
type FileManager struct {
|
||||
Next httpserver.Handler
|
||||
Configs []Config
|
||||
Configs []config.Config
|
||||
}
|
||||
|
||||
// ServeHTTP determines if the request is for this plugin, and if all prerequisites are met.
|
||||
func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
var (
|
||||
c *Config
|
||||
fi *FileInfo
|
||||
c *config.Config
|
||||
fi *file.Info
|
||||
code int
|
||||
err error
|
||||
assets bool
|
||||
|
@ -43,7 +42,7 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
|
|||
for i := range f.Configs {
|
||||
if httpserver.Path(r.URL.Path).Matches(f.Configs[i].BaseURL) {
|
||||
c = &f.Configs[i]
|
||||
assets = httpserver.Path(r.URL.Path).Matches(c.BaseURL + AssetsURL)
|
||||
assets = httpserver.Path(r.URL.Path).Matches(c.BaseURL + a.BaseURL)
|
||||
|
||||
if r.Method != http.MethodPost && !assets {
|
||||
fi, code, err = GetFileInfo(r.URL, c)
|
||||
|
@ -62,7 +61,7 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
|
|||
case http.MethodGet:
|
||||
// Read and show directory or file
|
||||
if assets {
|
||||
return ServeAssets(w, r, c)
|
||||
return a.ServeAssets(w, r, c)
|
||||
}
|
||||
|
||||
if !fi.IsDir {
|
||||
|
@ -113,28 +112,8 @@ func ErrorToHTTPCode(err error) int {
|
|||
}
|
||||
}
|
||||
|
||||
// ServeAssets provides the needed assets for the front-end
|
||||
func ServeAssets(w http.ResponseWriter, r *http.Request, c *Config) (int, error) {
|
||||
// gets the filename to be used with Assets function
|
||||
filename := strings.Replace(r.URL.Path, c.BaseURL+AssetsURL, "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
|
||||
}
|
||||
|
||||
// Upload is used to handle the upload requests to the server
|
||||
func Upload(w http.ResponseWriter, r *http.Request, c *Config) (int, error) {
|
||||
func Upload(w http.ResponseWriter, r *http.Request, c *config.Config) (int, error) {
|
||||
// Parse the multipart form in the request
|
||||
err := r.ParseMultipartForm(100000)
|
||||
if err != nil {
|
||||
|
@ -178,7 +157,7 @@ func Upload(w http.ResponseWriter, r *http.Request, c *Config) (int, error) {
|
|||
}
|
||||
|
||||
// NewFolder makes a new directory
|
||||
func NewFolder(w http.ResponseWriter, r *http.Request, c *Config) (int, error) {
|
||||
func NewFolder(w http.ResponseWriter, r *http.Request, c *config.Config) (int, error) {
|
||||
path := strings.Replace(r.URL.Path, c.BaseURL, c.PathScope, 1)
|
||||
path = filepath.Clean(path)
|
||||
err := os.MkdirAll(path, 0755)
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -10,7 +10,7 @@
|
|||
// assets/templates/single.tmpl
|
||||
// DO NOT EDIT!
|
||||
|
||||
package filemanager
|
||||
package assets
|
||||
|
||||
import (
|
||||
"fmt"
|
|
@ -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
|
||||
}
|
|
@ -1,10 +1,11 @@
|
|||
package filemanager
|
||||
package editor
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/hacdias/caddy-filemanager/internal/file"
|
||||
"github.com/spf13/hugo/parser"
|
||||
)
|
||||
|
||||
|
@ -17,10 +18,10 @@ type Editor struct {
|
|||
}
|
||||
|
||||
// GetEditor gets the editor based on a FileInfo struct
|
||||
func (fi FileInfo) GetEditor() (*Editor, error) {
|
||||
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(fi.Name), ".")
|
||||
editor.Mode = strings.TrimPrefix(filepath.Ext(i.Name), ".")
|
||||
|
||||
switch editor.Mode {
|
||||
case "md", "markdown", "mdown", "mmark":
|
||||
|
@ -41,9 +42,9 @@ func (fi FileInfo) GetEditor() (*Editor, error) {
|
|||
// Handle the content depending on the file extension
|
||||
switch editor.Mode {
|
||||
case "markdown", "asciidoc", "rst":
|
||||
if editor.hasFrontMatterRune(fi.Raw) {
|
||||
if editor.hasFrontMatterRune(i.Raw) {
|
||||
// Starts a new buffer and parses the file using Hugo's functions
|
||||
buffer := bytes.NewBuffer(fi.Raw)
|
||||
buffer := bytes.NewBuffer(i.Raw)
|
||||
page, err = parser.ReadFrom(buffer)
|
||||
if err != nil {
|
||||
return editor, err
|
||||
|
@ -63,10 +64,10 @@ func (fi FileInfo) GetEditor() (*Editor, error) {
|
|||
editor.Class = "frontmatter-only"
|
||||
|
||||
// Checks if the file already has the frontmatter rune and parses it
|
||||
if editor.hasFrontMatterRune(fi.Raw) {
|
||||
editor.FrontMatter, _, err = Pretty(fi.Raw)
|
||||
if editor.hasFrontMatterRune(i.Raw) {
|
||||
editor.FrontMatter, _, err = Pretty(i.Raw)
|
||||
} else {
|
||||
editor.FrontMatter, _, err = Pretty(editor.appendFrontMatterRune(fi.Raw, editor.Mode))
|
||||
editor.FrontMatter, _, err = Pretty(editor.appendFrontMatterRune(i.Raw, editor.Mode))
|
||||
}
|
||||
|
||||
// Check if there were any errors
|
||||
|
@ -76,7 +77,7 @@ func (fi FileInfo) GetEditor() (*Editor, error) {
|
|||
default:
|
||||
// The editor will handle only content
|
||||
editor.Class = "content-only"
|
||||
editor.Content = fi.Content
|
||||
editor.Content = i.Content
|
||||
}
|
||||
|
||||
return editor, nil
|
|
@ -1,4 +1,4 @@
|
|||
package filemanager
|
||||
package file
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -12,11 +12,15 @@ import (
|
|||
"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"
|
||||
)
|
||||
|
||||
// FileInfo is the information about a particular file or directory
|
||||
type FileInfo struct {
|
||||
// Info is the information about a particular file or directory
|
||||
type Info struct {
|
||||
IsDir bool
|
||||
Name string
|
||||
Size int64
|
||||
|
@ -31,33 +35,33 @@ type FileInfo struct {
|
|||
Type string
|
||||
}
|
||||
|
||||
// GetFileInfo gets the file information and, in case of error, returns the
|
||||
// GetInfo gets the file information and, in case of error, returns the
|
||||
// respective HTTP error code
|
||||
func GetFileInfo(url *url.URL, c *Config) (*FileInfo, int, error) {
|
||||
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
|
||||
|
||||
path := c.PathScope + rootPath
|
||||
path = strings.Replace(path, "\\", "/", -1)
|
||||
path = filepath.Clean(path)
|
||||
relpath := c.PathScope + rootPath
|
||||
relpath = strings.Replace(relpath, "\\", "/", -1)
|
||||
relpath = filepath.Clean(relpath)
|
||||
|
||||
file := &FileInfo{
|
||||
file := &Info{
|
||||
URL: url.Path,
|
||||
RootPath: rootPath,
|
||||
Path: path,
|
||||
Path: relpath,
|
||||
}
|
||||
f, err := c.Root.Open(rootPath)
|
||||
if err != nil {
|
||||
return file, ErrorToHTTPCode(err), err
|
||||
return file, errors.ToHTTPCode(err), err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
info, err := f.Stat()
|
||||
if err != nil {
|
||||
return file, ErrorToHTTPCode(err), err
|
||||
return file, errors.ToHTTPCode(err), err
|
||||
}
|
||||
|
||||
file.IsDir = info.IsDir()
|
||||
|
@ -67,107 +71,107 @@ func GetFileInfo(url *url.URL, c *Config) (*FileInfo, int, error) {
|
|||
return file, 0, nil
|
||||
}
|
||||
|
||||
// GetExtendedFileInfo is used to get extra parameters for FileInfo struct
|
||||
func (fi *FileInfo) GetExtendedFileInfo() error {
|
||||
err := fi.Read()
|
||||
// GetExtendedInfo is used to get extra parameters for FileInfo struct
|
||||
func (i *Info) GetExtendedInfo() error {
|
||||
err := i.Read()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fi.Type = SimplifyMimeType(fi.Mimetype)
|
||||
i.Type = SimplifyMimeType(i.Mimetype)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read is used to read a file and store its content
|
||||
func (fi *FileInfo) Read() error {
|
||||
raw, err := ioutil.ReadFile(fi.Path)
|
||||
func (i *Info) Read() error {
|
||||
raw, err := ioutil.ReadFile(i.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fi.Mimetype = http.DetectContentType(raw)
|
||||
fi.Content = string(raw)
|
||||
fi.Raw = raw
|
||||
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 (fi FileInfo) HumanSize() string {
|
||||
return humanize.IBytes(uint64(fi.Size))
|
||||
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 (fi FileInfo) HumanModTime(format string) string {
|
||||
return fi.ModTime.Format(format)
|
||||
func (i Info) HumanModTime(format string) string {
|
||||
return i.ModTime.Format(format)
|
||||
}
|
||||
|
||||
// Delete handles the delete requests
|
||||
func (fi *FileInfo) Delete() (int, error) {
|
||||
func (i *Info) Delete() (int, error) {
|
||||
var err error
|
||||
|
||||
// If it's a directory remove all the contents inside
|
||||
if fi.IsDir {
|
||||
err = os.RemoveAll(fi.Path)
|
||||
if i.IsDir {
|
||||
err = os.RemoveAll(i.Path)
|
||||
} else {
|
||||
err = os.Remove(fi.Path)
|
||||
err = os.Remove(i.Path)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return ErrorToHTTPCode(err), err
|
||||
return errors.ToHTTPCode(err), err
|
||||
}
|
||||
|
||||
return http.StatusOK, nil
|
||||
}
|
||||
|
||||
// Rename function is used tor rename a file or a directory
|
||||
func (fi *FileInfo) Rename(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
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(fi.Path, fi.Name, newname, 1)
|
||||
newpath = strings.Replace(i.Path, i.Name, newname, 1)
|
||||
|
||||
if err := os.Rename(fi.Path, newpath); err != nil {
|
||||
return ErrorToHTTPCode(err), err
|
||||
if err := os.Rename(i.Path, newpath); err != nil {
|
||||
return errors.ToHTTPCode(err), err
|
||||
}
|
||||
|
||||
fi.Path = newpath
|
||||
i.Path = newpath
|
||||
return http.StatusOK, nil
|
||||
}
|
||||
|
||||
// ServeAsHTML is used to serve single file pages
|
||||
func (fi *FileInfo) ServeAsHTML(w http.ResponseWriter, r *http.Request, c *Config) (int, error) {
|
||||
if fi.IsDir {
|
||||
return fi.serveListing(w, r, c)
|
||||
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 fi.serveSingleFile(w, r, c)
|
||||
return i.serveSingleFile(w, r, c)
|
||||
}
|
||||
|
||||
func (fi *FileInfo) serveSingleFile(w http.ResponseWriter, r *http.Request, c *Config) (int, error) {
|
||||
err := fi.GetExtendedFileInfo()
|
||||
func (i *Info) serveSingleFile(w http.ResponseWriter, r *http.Request, c *config.Config) (int, error) {
|
||||
err := i.GetExtendedInfo()
|
||||
if err != nil {
|
||||
return ErrorToHTTPCode(err), err
|
||||
return errors.ToHTTPCode(err), err
|
||||
}
|
||||
|
||||
if fi.Type == "blob" {
|
||||
return fi.ServeRawFile(w, r, c)
|
||||
if i.Type == "blob" {
|
||||
return i.ServeRawFile(w, r, c)
|
||||
}
|
||||
|
||||
page := &Page{
|
||||
Info: &PageInfo{
|
||||
Name: fi.Name,
|
||||
Path: fi.RootPath,
|
||||
page := &p.Page{
|
||||
Info: &p.Info{
|
||||
Name: i.Name,
|
||||
Path: i.RootPath,
|
||||
IsDir: false,
|
||||
Data: fi,
|
||||
Data: i,
|
||||
Config: c,
|
||||
},
|
||||
}
|
||||
|
||||
if CanBeEdited(fi.Name) {
|
||||
editor, err := fi.GetEditor()
|
||||
if editor.CanBeEdited(i.Name) {
|
||||
editor, err := i.GetEditor()
|
||||
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
|
@ -180,16 +184,16 @@ func (fi *FileInfo) serveSingleFile(w http.ResponseWriter, r *http.Request, c *C
|
|||
return page.PrintAsHTML(w, "single")
|
||||
}
|
||||
|
||||
func (fi *FileInfo) serveListing(w http.ResponseWriter, r *http.Request, c *Config) (int, error) {
|
||||
func (i *Info) serveListing(w http.ResponseWriter, r *http.Request, c *config.Config) (int, error) {
|
||||
var err error
|
||||
|
||||
file, err := c.Root.Open(fi.RootPath)
|
||||
file, err := c.Root.Open(i.RootPath)
|
||||
if err != nil {
|
||||
return ErrorToHTTPCode(err), err
|
||||
return errors.ToHTTPCode(err), err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
listing, err := fi.loadDirectoryContents(file, c)
|
||||
listing, err := i.loadDirectoryContents(file, c)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
switch {
|
||||
|
@ -222,10 +226,10 @@ func (fi *FileInfo) serveListing(w http.ResponseWriter, r *http.Request, c *Conf
|
|||
listing.ItemsLimitedTo = limit
|
||||
}
|
||||
|
||||
page := &Page{
|
||||
Info: &PageInfo{
|
||||
page := &p.Page{
|
||||
Info: &p.Info{
|
||||
Name: listing.Name,
|
||||
Path: fi.RootPath,
|
||||
Path: i.RootPath,
|
||||
IsDir: true,
|
||||
Config: c,
|
||||
Data: listing,
|
||||
|
@ -235,19 +239,19 @@ func (fi *FileInfo) serveListing(w http.ResponseWriter, r *http.Request, c *Conf
|
|||
return page.PrintAsHTML(w, "listing")
|
||||
}
|
||||
|
||||
func (fi FileInfo) loadDirectoryContents(file http.File, c *Config) (*Listing, error) {
|
||||
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, fi.RootPath)
|
||||
listing := directoryListing(files, i.RootPath)
|
||||
return &listing, nil
|
||||
}
|
||||
|
||||
func directoryListing(files []os.FileInfo, urlPath string) Listing {
|
||||
var (
|
||||
fileinfos []FileInfo
|
||||
fileinfos []Info
|
||||
dirCount, fileCount int
|
||||
)
|
||||
|
||||
|
@ -263,7 +267,7 @@ func directoryListing(files []os.FileInfo, urlPath string) Listing {
|
|||
|
||||
url := url.URL{Path: "./" + name} // prepend with "./" to fix paths with ':' in the name
|
||||
|
||||
fileinfos = append(fileinfos, FileInfo{
|
||||
fileinfos = append(fileinfos, Info{
|
||||
IsDir: f.IsDir(),
|
||||
Name: f.Name(),
|
||||
Size: f.Size(),
|
||||
|
@ -283,18 +287,18 @@ func directoryListing(files []os.FileInfo, urlPath string) Listing {
|
|||
}
|
||||
|
||||
// ServeRawFile serves raw files
|
||||
func (fi *FileInfo) ServeRawFile(w http.ResponseWriter, r *http.Request, c *Config) (int, error) {
|
||||
err := fi.GetExtendedFileInfo()
|
||||
func (i *Info) ServeRawFile(w http.ResponseWriter, r *http.Request, c *config.Config) (int, error) {
|
||||
err := i.GetExtendedInfo()
|
||||
if err != nil {
|
||||
return ErrorToHTTPCode(err), err
|
||||
return errors.ToHTTPCode(err), err
|
||||
}
|
||||
|
||||
if fi.Type != "text" {
|
||||
fi.Read()
|
||||
if i.Type != "text" {
|
||||
i.Read()
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", fi.Mimetype)
|
||||
w.Write([]byte(fi.Content))
|
||||
w.Header().Set("Content-Type", i.Mimetype)
|
||||
w.Write([]byte(i.Content))
|
||||
return 200, nil
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package filemanager
|
||||
package file
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
@ -16,7 +16,7 @@ type Listing struct {
|
|||
// The full path of the request
|
||||
Path string
|
||||
// The items (files and folders) in the path
|
||||
Items []FileInfo
|
||||
Items []Info
|
||||
// The number of directories in the listing
|
||||
NumDirs int
|
||||
// The number of files (items that aren't directories) in the listing
|
|
@ -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
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package filemanager
|
||||
package page
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
@ -8,37 +8,36 @@ import (
|
|||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/hacdias/caddy-filemanager/variables"
|
||||
"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 *PageInfo
|
||||
*Info
|
||||
}
|
||||
|
||||
// AssetFunc is an Assets function
|
||||
type AssetFunc func(name string) ([]byte, error)
|
||||
|
||||
// PageInfo contains the information of a page
|
||||
type PageInfo struct {
|
||||
// Info contains the information of a page
|
||||
type Info struct {
|
||||
Name string
|
||||
Path string
|
||||
IsDir bool
|
||||
Config *Config
|
||||
Config *config.Config
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
// BreadcrumbMap returns p.Path where every element is a map
|
||||
// of URLs and path segment names.
|
||||
func (p PageInfo) BreadcrumbMap() map[string]string {
|
||||
func (i Info) BreadcrumbMap() map[string]string {
|
||||
result := map[string]string{}
|
||||
|
||||
if len(p.Path) == 0 {
|
||||
if len(i.Path) == 0 {
|
||||
return result
|
||||
}
|
||||
|
||||
// skip trailing slash
|
||||
lpath := p.Path
|
||||
lpath := i.Path
|
||||
if lpath[len(lpath)-1] == '/' {
|
||||
lpath = lpath[:len(lpath)-1]
|
||||
}
|
||||
|
@ -57,17 +56,17 @@ func (p PageInfo) BreadcrumbMap() map[string]string {
|
|||
}
|
||||
|
||||
// PreviousLink returns the path of the previous folder
|
||||
func (p PageInfo) PreviousLink() string {
|
||||
parts := strings.Split(strings.TrimSuffix(p.Path, "/"), "/")
|
||||
func (i Info) PreviousLink() string {
|
||||
parts := strings.Split(strings.TrimSuffix(i.Path, "/"), "/")
|
||||
if len(parts) <= 1 {
|
||||
return ""
|
||||
}
|
||||
|
||||
if parts[len(parts)-2] == "" {
|
||||
if p.Config.BaseURL == "" {
|
||||
if i.Config.BaseURL == "" {
|
||||
return "/"
|
||||
}
|
||||
return p.Config.BaseURL
|
||||
return i.Config.BaseURL
|
||||
}
|
||||
|
||||
return parts[len(parts)-2]
|
||||
|
@ -88,7 +87,7 @@ func (p Page) PrintAsHTML(w http.ResponseWriter, templates ...string) (int, erro
|
|||
// For each template, add it to the the tpl variable
|
||||
for i, t := range templates {
|
||||
// Get the template from the assets
|
||||
page, err := Asset("templates/" + t + ".tmpl")
|
||||
page, err := assets.Asset("templates/" + t + ".tmpl")
|
||||
|
||||
// Check if there is some error. If so, the template doesn't exist
|
||||
if err != nil {
|
71
setup.go
71
setup.go
|
@ -1,11 +1,7 @@
|
|||
package filemanager
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/hacdias/caddy-filemanager/internal/config"
|
||||
"github.com/mholt/caddy"
|
||||
"github.com/mholt/caddy/caddyhttp/httpserver"
|
||||
)
|
||||
|
@ -19,7 +15,7 @@ func init() {
|
|||
|
||||
// setup configures a new FileManager middleware instance.
|
||||
func setup(c *caddy.Controller) error {
|
||||
configs, err := parseConfiguration(c)
|
||||
configs, err := config.Parse(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -30,66 +26,3 @@ func setup(c *caddy.Controller) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// parseConfiguration parses the configuration set by the user so it can
|
||||
// be used by the middleware
|
||||
func parseConfiguration(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
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// Run executes an external command
|
||||
func Run(command string, args []string, path string) error {
|
||||
cmd := exec.Command(command, args...)
|
||||
cmd.Dir = path
|
||||
cmd.Stdout = os.Stderr
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package files
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// CopyFile is used to copy a file
|
||||
func CopyFile(old, new string) error {
|
||||
// Open the file and create a new one
|
||||
r, err := os.Open(old)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
w, err := os.Create(new)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer w.Close()
|
||||
|
||||
// Copy the content
|
||||
_, err = io.Copy(w, r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package files
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestCopyFile(t *testing.T) {
|
||||
err := CopyFile("test_data/file_to_copy.txt", "test_data/copied_file.txt")
|
||||
|
||||
if err != nil {
|
||||
t.Error("Can't copy the file.")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin faucibus augue vel ex auctor molestie. Sed id lorem quis velit eleifend vestibulum. Etiam tincidunt nulla et metus dictum imperdiet. Suspendisse ac auctor risus. Donec tempus eros quis erat interdum fringilla. Duis ac tristique lorem, quis vulputate enim. In ut est elementum, dignissim tellus quis, ultrices diam. Aenean in efficitur velit, ut imperdiet felis. Suspendisse aliquet accumsan ligula, a iaculis sem luctus vel. Integer sagittis, orci viverra lacinia efficitur, eros magna sodales sapien, in tristique ante arcu ut nunc. Maecenas odio dolor, semper quis vehicula ut, malesuada sollicitudin massa. Integer dolor ligula, hendrerit convallis quam at, luctus dignissim nibh. Fusce interdum congue justo, at imperdiet lorem maximus in.
|
||||
|
||||
Curabitur dignissim id turpis ut eleifend. Pellentesque vehicula et purus et consectetur. Nulla facilisis vehicula nunc in imperdiet. Nam eget porta libero, vel dapibus leo. Maecenas iaculis, arcu quis vehicula sollicitudin, massa tortor mattis tortor, vitae venenatis erat neque et dui. Mauris eleifend vel urna ac interdum. Sed convallis, arcu et scelerisque suscipit, nulla urna laoreet mauris, quis aliquam dolor elit id mauris. Pellentesque ac pulvinar nibh. Duis vestibulum ligula orci, et mattis turpis facilisis mattis. Vivamus feugiat neque sed aliquet aliquet. Sed consequat augue sagittis eros imperdiet, ac lacinia erat tempor. Cras gravida mi a orci euismod feugiat. Mauris condimentum turpis id quam vulputate, et maximus dolor hendrerit.
|
||||
|
||||
Ut nisi urna, sollicitudin ac facilisis eu, rhoncus sed urna. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nulla eu maximus mi, sed commodo diam. Nunc scelerisque tincidunt ipsum eget mattis. Fusce id finibus nisi, sit amet tempus tortor. Fusce accumsan lectus at dignissim volutpat. Nullam nisl nulla, rhoncus id arcu vitae, eleifend posuere libero. Aliquam dictum bibendum bibendum. Integer non mattis neque. Phasellus fermentum et ex vel vehicula.
|
||||
|
||||
Proin eget ligula lorem. Ut vehicula quis augue sed aliquet. Nulla viverra turpis nulla. Morbi et ipsum odio. Nulla in nisi suscipit justo blandit tristique ac vitae ipsum. Donec viverra dictum arcu. Duis est elit, ultrices a massa non, lobortis maximus eros. Nulla porttitor rhoncus lectus in finibus. Duis convallis sapien porta, fringilla orci nec, ultricies nisl. Morbi pulvinar quam in nulla rutrum euismod.
|
||||
|
||||
Donec et accumsan lectus, consectetur posuere nunc. Quisque et quam hendrerit, vestibulum quam eget, luctus enim. In hac habitasse platea dictumst. Nunc et est scelerisque, bibendum neque quis, ornare elit. Aliquam erat volutpat. Nulla maximus nibh vitae ex interdum, a efficitur lacus eleifend. Nam vestibulum blandit aliquet. In hac habitasse platea dictumst. Maecenas interdum quam et lorem posuere interdum. Mauris id feugiat augue. Etiam vestibulum pharetra cursus.
|
|
@ -1,4 +1,4 @@
|
|||
package filemanager
|
||||
package frontmatter
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
@ -6,7 +6,7 @@ import (
|
|||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/hacdias/caddy-filemanager/variables"
|
||||
"github.com/hacdias/caddy-hugo/tools/variables"
|
||||
"github.com/spf13/cast"
|
||||
"github.com/spf13/hugo/parser"
|
||||
)
|
|
@ -0,0 +1,38 @@
|
|||
package hugo
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/hacdias/caddy-hugo/config"
|
||||
"github.com/hacdias/caddy-hugo/tools/commands"
|
||||
)
|
||||
|
||||
// Run is used to run the static website generator
|
||||
func Run(c *config.Config.Config, force bool) {
|
||||
os.RemoveAll(c.Path + "public")
|
||||
|
||||
// Prevent running if watching is enabled
|
||||
if b, pos := stringInSlice("--watch", c.Args); b && !force {
|
||||
if len(c.Args) > pos && c.Args[pos+1] != "false" {
|
||||
return
|
||||
}
|
||||
|
||||
if len(c.Args) == pos+1 {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := commands.Run(c.Hugo, c.Args, c.Path); err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func stringInSlice(a string, list []string) (bool, int) {
|
||||
for i, b := range list {
|
||||
if b == a {
|
||||
return true, i
|
||||
}
|
||||
}
|
||||
return false, 0
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package installer
|
||||
|
||||
var (
|
||||
sha256Hash = map[string]string{
|
||||
"hugo_0.16_darwin-arm32.tgz": "683d5d4b4e0ac03a183ca5eb9019981ba696569445c7d6d1efc7e6706bd273a5",
|
||||
"hugo_0.16_dragonfly-64bit.tgz": "63a3ee9a36d4d2166c77b96bb8bf39b2239affe118e44a83b3d0a44374a8921d",
|
||||
"hugo_0.16_freebsd-32bit.tgz": "ea3f84900feeeb9d89573dea49a4349753116e70de561eeec4858f7ffc74f8f9",
|
||||
"hugo_0.16_freebsd-64bit.tgz": "8d9320bb660090a77a4f922ca30b1582593bc6d87c3fd8bd6f5ecbe49cf1d2f2",
|
||||
"hugo_0.16_freebsd-arm32.tgz": "b4c21296e01ea68709ac50d7eb1d314b738f1c8408ff2be223d06ae76604dbea",
|
||||
"hugo_0.16_linux-32bit.tgz": "aed82d156f01a4562c39bd1af41aa81699009140da965e0369c370ba874725c9",
|
||||
"hugo_0.16_linux-64bit.tgz": "13e299dc45bea4fad5bdf8c2640305a5926e2acd02c3aa03b7864403e513920e",
|
||||
"hugo_0.16_linux-arm32.tgz": "bc836def127d93e2457da9994f9c09b0100523e46d61074cd724ef092b11714f",
|
||||
"hugo_0.16_linux-arm64.tgz": "d04486918f43f89f1e0359eebedd8a05d96f7ca40f93e7fd8d7c3f2dac115a8d",
|
||||
"hugo_0.16_netbsd-32bit.tgz": "cb578eebec5b6364b0afd5bb208d94317acab0a3e033b81f04b1511af0669b63",
|
||||
"hugo_0.16_netbsd-64bit.tgz": "d3c766d9800d7fdd268ffd2f28b7af451f13a4de63901bfdae2ee5c96528b8cc",
|
||||
"hugo_0.16_netbsd-arm32.tgz": "51162b2637e71b786582af715a44b778f62bdc62a9a354ccc4a7c8384afe194c",
|
||||
"hugo_0.16_openbsd-32bit.tgz": "2d1e112a7346850897ea77da868c0d987ef90efb7f49c917659437a5a67f89f8",
|
||||
"hugo_0.16_openbsd-64bit.tgz": "7b33ff2565df5a6253c3e4308813d947e34af04c633fb4e01cac83751066e16e",
|
||||
"hugo_0.16_osx-32bit.tgz": "6155dda548bbd1e26c26a4a00472e4c0e55fad9fcd46991ce90987385bd5fd0a",
|
||||
"hugo_0.16_osx-64bit.tgz": "b0cba8f6996946ef34a664184d6461567d79fc2a3e793145d34379902eda0ad9",
|
||||
"hugo_0.16_solaris-64bit.tgz": "af9557403af5e16eb7faf965c04540417a70699efbbbc4e0a7ae4c4703ad1ae8",
|
||||
"hugo_0.16_windows-32bit.zip": "1c72d06843fe02cb62348660d87a523c885ed684a683271fc8762e7234c4210b",
|
||||
"hugo_0.16_windows-64bit.zip": "a3fda0bd30592e4eb3bdde85c8a8ce23a7433073536466d16fd0e97bf7794067",
|
||||
}
|
||||
)
|
|
@ -0,0 +1,216 @@
|
|||
package installer
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
|
||||
"github.com/hacdias/caddy-hugo/tools/files"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/pivotal-golang/archiver/extractor"
|
||||
)
|
||||
|
||||
const (
|
||||
version = "0.16"
|
||||
baseurl = "https://github.com/spf13/hugo/releases/download/v" + version + "/"
|
||||
)
|
||||
|
||||
var caddy, bin, temp, hugo, tempfile, zipname, exename string
|
||||
|
||||
// GetPath retrives the Hugo path for the user or install it if it's not found
|
||||
func GetPath() string {
|
||||
initializeVariables()
|
||||
|
||||
var err error
|
||||
|
||||
// Check if Hugo is already on $PATH
|
||||
if hugo, err = exec.LookPath("hugo"); err == nil {
|
||||
if checkVersion() {
|
||||
return hugo
|
||||
}
|
||||
}
|
||||
|
||||
// Check if Hugo is on $HOME/.caddy/bin
|
||||
if _, err = os.Stat(hugo); err == nil {
|
||||
if checkVersion() {
|
||||
return hugo
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("Unable to find Hugo on your computer.")
|
||||
|
||||
// Create the neccessary folders
|
||||
os.MkdirAll(caddy, 0774)
|
||||
os.Mkdir(bin, 0774)
|
||||
|
||||
if temp, err = ioutil.TempDir("", "caddy-hugo"); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
downloadHugo()
|
||||
checkSHA256()
|
||||
|
||||
fmt.Print("Unzipping... ")
|
||||
|
||||
// Unzip or Ungzip the file
|
||||
switch runtime.GOOS {
|
||||
case "darwin", "windows":
|
||||
zp := extractor.NewZip()
|
||||
err = zp.Extract(tempfile, temp)
|
||||
default:
|
||||
gz := extractor.NewTgz()
|
||||
err = gz.Extract(tempfile, temp)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
fmt.Println("done.")
|
||||
|
||||
var exetorename string
|
||||
|
||||
err = filepath.Walk(temp, func(path string, f os.FileInfo, err error) error {
|
||||
if f.Name() == exename {
|
||||
exetorename = path
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
// Copy the file
|
||||
fmt.Print("Moving Hugo executable... ")
|
||||
err = files.CopyFile(exetorename, hugo)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
err = os.Chmod(hugo, 0755)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
fmt.Println("done.")
|
||||
fmt.Println("Hugo installed at " + hugo)
|
||||
defer os.RemoveAll(temp)
|
||||
return hugo
|
||||
}
|
||||
|
||||
func initializeVariables() {
|
||||
var arch string
|
||||
switch runtime.GOARCH {
|
||||
case "amd64":
|
||||
arch = "64bit"
|
||||
case "386":
|
||||
arch = "32bit"
|
||||
case "arm":
|
||||
arch = "arm32"
|
||||
default:
|
||||
arch = runtime.GOARCH
|
||||
}
|
||||
|
||||
var ops = runtime.GOOS
|
||||
if runtime.GOOS == "darwin" && runtime.GOARCH != "arm" {
|
||||
ops = "osx"
|
||||
}
|
||||
|
||||
exename = "hugo"
|
||||
zipname = "hugo_" + version + "_" + ops + "-" + arch
|
||||
|
||||
homedir, err := homedir.Dir()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
caddy = filepath.Join(homedir, ".caddy")
|
||||
bin = filepath.Join(caddy, "bin")
|
||||
hugo = filepath.Join(bin, "hugo")
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
zipname += ".zip"
|
||||
exename += ".exe"
|
||||
hugo += ".exe"
|
||||
default:
|
||||
zipname += ".tgz"
|
||||
}
|
||||
}
|
||||
|
||||
func checkVersion() bool {
|
||||
out, _ := exec.Command("hugo", "version").Output()
|
||||
|
||||
r := regexp.MustCompile(`v\d\.\d{2}`)
|
||||
v := r.FindStringSubmatch(string(out))[0]
|
||||
v = v[1:len(v)]
|
||||
|
||||
return (v == version)
|
||||
}
|
||||
|
||||
func downloadHugo() {
|
||||
tempfile = filepath.Join(temp, zipname)
|
||||
|
||||
fmt.Print("Downloading Hugo from GitHub releases... ")
|
||||
|
||||
// Create the file
|
||||
out, err := os.Create(tempfile)
|
||||
out.Chmod(0774)
|
||||
if err != nil {
|
||||
defer os.RemoveAll(temp)
|
||||
fmt.Println(err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
// Get the data
|
||||
resp, err := http.Get(baseurl + zipname)
|
||||
if err != nil {
|
||||
fmt.Println("An error ocurred while downloading. If this error persists, try downloading Hugo from \"https://github.com/spf13/hugo/releases/\" and put the executable in " + bin + " and rename it to 'hugo' or 'hugo.exe' if you're on Windows.")
|
||||
fmt.Println(err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Writer the body to file
|
||||
_, err = io.Copy(out, resp.Body)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
fmt.Println("downloaded.")
|
||||
}
|
||||
|
||||
func checkSHA256() {
|
||||
fmt.Print("Checking SHA256...")
|
||||
|
||||
hasher := sha256.New()
|
||||
f, err := os.Open(tempfile)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
if _, err := io.Copy(hasher, f); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if hex.EncodeToString(hasher.Sum(nil)) != sha256Hash[zipname] {
|
||||
fmt.Println("can't verify SHA256.")
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
fmt.Println("checked!")
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// RespondJSON used to send JSON responses to the web server
|
||||
func RespondJSON(w http.ResponseWriter, message interface{}, code int, err error) (int, error) {
|
||||
if message == nil {
|
||||
message = map[string]string{}
|
||||
}
|
||||
|
||||
msg, msgErr := json.Marshal(message)
|
||||
|
||||
if msgErr != nil {
|
||||
return 500, msgErr
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(code)
|
||||
w.Write(msg)
|
||||
return 0, err
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ParseURLComponents parses the components of an URL creating an array
|
||||
func ParseURLComponents(r *http.Request) []string {
|
||||
//The URL that the user queried.
|
||||
path := r.URL.Path
|
||||
path = strings.TrimSpace(path)
|
||||
//Cut off the leading and trailing forward slashes, if they exist.
|
||||
//This cuts off the leading forward slash.
|
||||
if strings.HasPrefix(path, "/") {
|
||||
path = path[1:]
|
||||
}
|
||||
//This cuts off the trailing forward slash.
|
||||
if strings.HasSuffix(path, "/") {
|
||||
cutOffLastCharLen := len(path) - 1
|
||||
path = path[:cutOffLastCharLen]
|
||||
}
|
||||
//We need to isolate the individual components of the path.
|
||||
components := strings.Split(path, "/")
|
||||
return components
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package templates
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/hacdias/caddy-hugo/routes/assets"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Get is used to get a ready to use template based on the url and on
|
||||
// other sent templates
|
||||
func Get(r *http.Request, functions template.FuncMap, templates ...string) (*template.Template, error) {
|
||||
// If this is a pjax request, use the minimal template to send only
|
||||
// the main content
|
||||
if r.Header.Get("X-PJAX") == "true" {
|
||||
templates = append(templates, "base_minimal")
|
||||
} else {
|
||||
templates = append(templates, "base_full")
|
||||
}
|
||||
|
||||
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 new(template.Template), 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 new(template.Template), err
|
||||
}
|
||||
}
|
||||
|
||||
return tpl, nil
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package templates
|
||||
|
||||
import "testing"
|
||||
|
||||
type canBeEdited struct {
|
||||
file string
|
||||
result bool
|
||||
}
|
||||
|
||||
var canBeEditedPairs = []canBeEdited{
|
||||
{"file.markdown", true},
|
||||
{"file.md", true},
|
||||
{"file.json", true},
|
||||
{"file.toml", true},
|
||||
{"file.yaml", true},
|
||||
{"file.css", true},
|
||||
{"file.sass", true},
|
||||
{"file.scss", true},
|
||||
{"file.js", true},
|
||||
{"file.html", true},
|
||||
{"file.git", false},
|
||||
{"file.log", false},
|
||||
{"file.sh", false},
|
||||
{"file.png", false},
|
||||
{"file.jpg", false},
|
||||
}
|
||||
|
||||
func TestCanBeEdited(t *testing.T) {
|
||||
for _, pair := range canBeEditedPairs {
|
||||
v := CanBeEdited(pair.file)
|
||||
if v != pair.result {
|
||||
t.Error(
|
||||
"For", pair.file,
|
||||
"expected", pair.result,
|
||||
"got", v,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package errors
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
// ToHTTPCode gets the respective HTTP code for an error
|
||||
func ToHTTPCode(err error) int {
|
||||
switch {
|
||||
case os.IsPermission(err):
|
||||
return http.StatusForbidden
|
||||
case os.IsNotExist(err):
|
||||
return http.StatusNotFound
|
||||
case os.IsExist(err):
|
||||
return http.StatusGone
|
||||
default:
|
||||
return http.StatusInternalServerError
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue