This commit is contained in:
Henrique Dias 2015-09-20 09:15:21 +01:00
parent 3c0383d500
commit 0ac1f84f60
9 changed files with 98 additions and 43 deletions

View File

@ -2,6 +2,16 @@
This is an add-on for Caddy which wants to deliver a good UI to edit the content of the website. This is an add-on for Caddy which wants to deliver a good UI to edit the content of the website.
## Add-on configuration
You can define, or not, the admin UI styles. It will **not** replace the default ones, it will be included after it. The path must be relative to ```public``` folder.
```
hugo {
styles [file]
}
```
## Try it ## Try it
### Prepare your machine ### Prepare your machine

View File

@ -6,27 +6,25 @@ import (
"strings" "strings"
"text/template" "text/template"
"github.com/hacdias/caddy-hugo/editor" "github.com/hacdias/caddy-hugo/config"
"github.com/hacdias/caddy-hugo/utils" "github.com/hacdias/caddy-hugo/utils"
"github.com/mholt/caddy/middleware" "github.com/mholt/caddy/middleware"
"github.com/mholt/caddy/middleware/browse" "github.com/mholt/caddy/middleware/browse"
) )
// ServeHTTP is... // ServeHTTP is used to serve the content of Browse page
func ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { // using Browse middleware from Caddy
if r.URL.Path[len(r.URL.Path)-1] != '/' { func ServeHTTP(w http.ResponseWriter, r *http.Request, c *config.Config) (int, error) {
http.Redirect(w, r, r.URL.Path+"/", http.StatusTemporaryRedirect) // Removes the page main path from the URL
return 0, nil
}
r.URL.Path = strings.Replace(r.URL.Path, "/admin/browse", "", 1) r.URL.Path = strings.Replace(r.URL.Path, "/admin/browse", "", 1)
// If the URL is blank now, replace it with a trailing slash
if r.URL.Path == "" { if r.URL.Path == "" {
r.URL.Path = "/" r.URL.Path = "/"
} }
functions := template.FuncMap{ functions := template.FuncMap{
"canBeEdited": editor.CanBeEdited, "CanBeEdited": utils.CanBeEdited,
"Defined": utils.Defined, "Defined": utils.Defined,
} }
@ -45,6 +43,7 @@ func ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
Configs: []browse.Config{ Configs: []browse.Config{
browse.Config{ browse.Config{
PathScope: "/", PathScope: "/",
Variables: c,
Template: tpl, Template: tpl,
}, },
}, },

35
config/config.go Normal file
View File

@ -0,0 +1,35 @@
package config
import (
"strings"
"github.com/mholt/caddy/config/setup"
)
// Config is the add-on configuration set on Caddyfile
type Config struct {
Styles string
}
// ParseHugo parses the configuration file
func ParseHugo(c *setup.Controller) (*Config, error) {
conf := &Config{}
for c.Next() {
for c.NextBlock() {
switch c.Val() {
case "styles":
if !c.NextArg() {
return nil, c.ArgErr()
}
conf.Styles = c.Val()
// Remove the beginning slash if it exists or not
conf.Styles = strings.TrimPrefix(conf.Styles, "/")
// Add a beginning slash to make a
conf.Styles = "/" + conf.Styles
}
}
}
return conf, nil
}

View File

@ -12,6 +12,7 @@ import (
"strings" "strings"
"text/template" "text/template"
"github.com/hacdias/caddy-hugo/config"
"github.com/hacdias/caddy-hugo/frontmatter" "github.com/hacdias/caddy-hugo/frontmatter"
"github.com/hacdias/caddy-hugo/utils" "github.com/hacdias/caddy-hugo/utils"
"github.com/spf13/hugo/parser" "github.com/spf13/hugo/parser"
@ -23,38 +24,21 @@ type editor struct {
Mode string Mode string
Content string Content string
FrontMatter interface{} FrontMatter interface{}
Config *config.Config
} }
// ServeHTTP is... // ServeHTTP serves the editor page
func ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { func ServeHTTP(w http.ResponseWriter, r *http.Request, c *config.Config) (int, error) {
filename := strings.Replace(r.URL.Path, "/admin/edit/", "", 1) filename := strings.Replace(r.URL.Path, "/admin/edit/", "", 1)
if r.Method == "POST" { if r.Method == "POST" {
return post(w, r, filename) return servePost(w, r, filename)
} }
return get(w, r, filename) return serveGet(w, r, c, filename)
} }
// CanBeEdited checks if a file has a supported extension func servePost(w http.ResponseWriter, r *http.Request, filename string) (int, error) {
func CanBeEdited(filename string) bool {
extensions := [...]string{".markdown", ".md",
".json", ".toml", ".yaml",
".css", ".sass", ".scss",
".js",
".html",
}
for _, extension := range extensions {
if strings.HasSuffix(filename, extension) {
return true
}
}
return false
}
func post(w http.ResponseWriter, r *http.Request, filename string) (int, error) {
// Get the JSON information sent using a buffer // Get the JSON information sent using a buffer
rawBuffer := new(bytes.Buffer) rawBuffer := new(bytes.Buffer)
rawBuffer.ReadFrom(r.Body) rawBuffer.ReadFrom(r.Body)
@ -152,10 +136,10 @@ func post(w http.ResponseWriter, r *http.Request, filename string) (int, error)
return 200, nil return 200, nil
} }
func get(w http.ResponseWriter, r *http.Request, filename string) (int, error) { func serveGet(w http.ResponseWriter, r *http.Request, c *config.Config, filename string) (int, error) {
// Check if the file format is supported. If not, send a "Not Acceptable" // Check if the file format is supported. If not, send a "Not Acceptable"
// header and an error // header and an error
if !CanBeEdited(filename) { if !utils.CanBeEdited(filename) {
return 406, errors.New("File format not supported.") return 406, errors.New("File format not supported.")
} }
@ -176,6 +160,7 @@ func get(w http.ResponseWriter, r *http.Request, filename string) (int, error) {
page := new(editor) page := new(editor)
page.Mode = strings.TrimPrefix(filepath.Ext(filename), ".") page.Mode = strings.TrimPrefix(filepath.Ext(filename), ".")
page.Name = filename page.Name = filename
page.Config = c
// Sanitize the extension // Sanitize the extension
page.Mode = sanitizeMode(page.Mode) page.Mode = sanitizeMode(page.Mode)
@ -227,7 +212,7 @@ func get(w http.ResponseWriter, r *http.Request, filename string) (int, error) {
// Create the functions map, then the template, check for erros and // Create the functions map, then the template, check for erros and
// execute the template if there aren't errors // execute the template if there aren't errors
functions := template.FuncMap{ functions := template.FuncMap{
"splitCapitalize": utils.SplitCapitalize, "SplitCapitalize": utils.SplitCapitalize,
"Defined": utils.Defined, "Defined": utils.Defined,
} }

16
hugo.go
View File

@ -11,6 +11,7 @@ import (
"github.com/hacdias/caddy-hugo/assets" "github.com/hacdias/caddy-hugo/assets"
"github.com/hacdias/caddy-hugo/browse" "github.com/hacdias/caddy-hugo/browse"
"github.com/hacdias/caddy-hugo/config"
"github.com/hacdias/caddy-hugo/editor" "github.com/hacdias/caddy-hugo/editor"
"github.com/hacdias/caddy-hugo/utils" "github.com/hacdias/caddy-hugo/utils"
"github.com/mholt/caddy/config/setup" "github.com/mholt/caddy/config/setup"
@ -20,16 +21,21 @@ import (
// Setup configures the middleware // Setup configures the middleware
func Setup(c *setup.Controller) (middleware.Middleware, error) { func Setup(c *setup.Controller) (middleware.Middleware, error) {
config, _ := config.ParseHugo(c)
commands.Execute() commands.Execute()
return func(next middleware.Handler) middleware.Handler { return func(next middleware.Handler) middleware.Handler {
return &handler{Next: next} return &CaddyHugo{Next: next, Config: config}
}, nil }, nil
} }
type handler struct{ Next middleware.Handler } // CaddyHugo main type
type CaddyHugo struct {
Next middleware.Handler
Config *config.Config
}
func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) { func (h CaddyHugo) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
// Only handle /admin path // Only handle /admin path
if middleware.Path(r.URL.Path).Matches("/admin") { if middleware.Path(r.URL.Path).Matches("/admin") {
var err error var err error
@ -103,12 +109,12 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error)
// Browse page // Browse page
if page == "browse" { if page == "browse" {
code, err = browse.ServeHTTP(w, r) code, err = browse.ServeHTTP(w, r, h.Config)
} }
// Edit page // Edit page
if page == "edit" { if page == "edit" {
code, err = editor.ServeHTTP(w, r) code, err = editor.ServeHTTP(w, r, h.Config)
} }
// Whenever the header "X-Refenerate" is true, the website should be // Whenever the header "X-Refenerate" is true, the website should be

View File

@ -10,6 +10,8 @@
<link href='https://fonts.googleapis.com/css?family=Roboto:400,700,400italic,700italic' rel='stylesheet' type='text/css'> <link href='https://fonts.googleapis.com/css?family=Roboto:400,700,400italic,700italic' rel='stylesheet' type='text/css'>
<link rel="stylesheet" href="/admin/assets/css/main.min.css"> <link rel="stylesheet" href="/admin/assets/css/main.min.css">
{{ if and (Defined . "Config")}}{{ if not (eq .Config.Styles "") }}<link rel="stylesheet" href="{{ .Config.Styles }}">{{ end }}{{ end }}
{{ if and (Defined . "User") }}{{ if not (eq .User.Styles "") }}<link rel="stylesheet" href="{{ .User.Styles }}">{{ end }}{{ end }}
<script src="/admin/assets/js/plugins.min.js"></script> <script src="/admin/assets/js/plugins.min.js"></script>
<script src="/admin/assets/js/app.min.js"></script> <script src="/admin/assets/js/app.min.js"></script>
</head> </head>

View File

@ -46,7 +46,7 @@
<tr> <tr>
<td> <td>
{{if .IsDir}} {{if .IsDir}}
<i class="fa fa-folder"></i> <a href="{{.URL}}">{{.Name}}</a> {{else}} {{ if canBeEdited .URL }} <i class="fa fa-folder"></i> <a href="{{.URL}}">{{.Name}}</a> {{else}} {{ if CanBeEdited .URL }}
<i class="fa fa-file"></i> <a class="file" href="/admin/edit{{ $path }}{{.URL}}">{{.Name}}</a> {{ else }} <i class="fa fa-file"></i> <a class="file" href="/admin/edit{{ $path }}{{.URL}}">{{.Name}}</a> {{ else }}
<i class="fa fa-file"></i> {{.Name}} {{ end }} {{ end }} <i class="fa fa-file"></i> {{.Name}} {{ end }} {{ end }}
</td> </td>

View File

@ -1,6 +1,6 @@
{{ define "frontmatter" }} {{ range $key, $value := . }} {{ if or (eq $value.Type "object") (eq $value.Type "array") }} {{ define "frontmatter" }} {{ range $key, $value := . }} {{ if or (eq $value.Type "object") (eq $value.Type "array") }}
<fieldset id="{{ $value.Name }}" data-name="{{ $value.Name }}" data-type="{{ $value.Type }}"> <fieldset id="{{ $value.Name }}" data-name="{{ $value.Name }}" data-type="{{ $value.Type }}">
<h3>{{ splitCapitalize $value.Title }} <h3>{{ SplitCapitalize $value.Title }}
<span class="actions"> <span class="actions">
<button class="delete"><i class="fa fa-minus"></i></button> <button class="delete"><i class="fa fa-minus"></i></button>
<button class="add"><i class="fa fa-plus"></i></button> <button class="add"><i class="fa fa-plus"></i></button>
@ -9,7 +9,7 @@
{{ template "frontmatter" $value.Content }} {{ template "frontmatter" $value.Content }}
</fieldset> </fieldset>
{{ else }} {{ if not (eq $value.Parent.Type "array") }} {{ else }} {{ if not (eq $value.Parent.Type "array") }}
<label for="{{ $value.Name }}">{{ splitCapitalize $value.Title }} <label for="{{ $value.Name }}">{{ SplitCapitalize $value.Title }}
<span class="actions"> <span class="actions">
<button class="delete"><i class="fa fa-minus"></i></button> <button class="delete"><i class="fa fa-minus"></i></button>
</span> </span>

View File

@ -12,6 +12,24 @@ import (
"github.com/hacdias/caddy-hugo/assets" "github.com/hacdias/caddy-hugo/assets"
) )
// CanBeEdited checks if a filename has a supported extension
func CanBeEdited(filename string) bool {
extensions := [...]string{".markdown", ".md",
".json", ".toml", ".yaml",
".css", ".sass", ".scss",
".js",
".html",
}
for _, extension := range extensions {
if strings.HasSuffix(filename, extension) {
return true
}
}
return false
}
// GetTemplate is used to get a ready to use template based on the url and on // GetTemplate is used to get a ready to use template based on the url and on
// other sent templates // other sent templates
func GetTemplate(r *http.Request, functions template.FuncMap, templates ...string) (*template.Template, error) { func GetTemplate(r *http.Request, functions template.FuncMap, templates ...string) (*template.Template, error) {