mirror of
https://github.com/filebrowser/filebrowser.git
synced 2024-06-07 23:00:43 +00:00
bootstrap new version
This commit is contained in:
parent
1675403010
commit
83d9462ca1
7
.gitignore
vendored
7
.gitignore
vendored
@ -1,7 +0,0 @@
|
|||||||
assets.go
|
|
||||||
node_modules
|
|
||||||
.sass-cache
|
|
||||||
temp
|
|
||||||
copied_file.txt
|
|
||||||
builds
|
|
||||||
release
|
|
@ -1,97 +0,0 @@
|
|||||||
.editor input[type="text"], .editor input[type="datetime-local"], .editor textarea {
|
|
||||||
background-color: #f5f3f3;
|
|
||||||
border-radius: .5em;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
outline: none;
|
|
||||||
padding: .5em;
|
|
||||||
line-height: 1.2em;
|
|
||||||
width: 100%;
|
|
||||||
font-size: 1rem;
|
|
||||||
margin: 0;
|
|
||||||
transition: all 0.3s;
|
|
||||||
}
|
|
||||||
input[type="text"]:focus {
|
|
||||||
border-bottom: 1px solid #26a69a;
|
|
||||||
}
|
|
||||||
.editor .block {
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
.editor fieldset {
|
|
||||||
border: 0;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
.editor h1 textarea {
|
|
||||||
font-size: 2em;
|
|
||||||
font-weight: 400;
|
|
||||||
resize: none;
|
|
||||||
overflow: hidden;
|
|
||||||
padding: 0;
|
|
||||||
line-height: 1em;
|
|
||||||
height: 1em;
|
|
||||||
background-color: transparent;
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
.editor fieldset h3 {
|
|
||||||
display: inline-block
|
|
||||||
}
|
|
||||||
.editor label {
|
|
||||||
font-size: .8em;
|
|
||||||
color: #8b8b8b;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
.editor .options {
|
|
||||||
column-count: 2;
|
|
||||||
column-gap: 1em;
|
|
||||||
}
|
|
||||||
.editor .block {
|
|
||||||
break-inside: avoid;
|
|
||||||
position: relative;
|
|
||||||
z-index: 0;
|
|
||||||
}
|
|
||||||
.editor .block .delete {
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
bottom: 1px;
|
|
||||||
height: 2.5em;
|
|
||||||
padding: 0 .7em;
|
|
||||||
background: transparent;
|
|
||||||
color: rgba(0, 0, 0, 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* BUTTONS */
|
|
||||||
|
|
||||||
.editor button, .editor input[type="submit"] {
|
|
||||||
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
|
|
||||||
border: none;
|
|
||||||
border-radius: 2px;
|
|
||||||
height: 36px;
|
|
||||||
line-height: 36px;
|
|
||||||
outline: 0;
|
|
||||||
padding: 0 2rem;
|
|
||||||
text-transform: uppercase;
|
|
||||||
text-decoration: none;
|
|
||||||
color: #fff;
|
|
||||||
background-color: #26a69a;
|
|
||||||
text-align: center;
|
|
||||||
letter-spacing: .5px;
|
|
||||||
position: relative;
|
|
||||||
cursor: pointer;
|
|
||||||
display: inline-block;
|
|
||||||
overflow: hidden;
|
|
||||||
vertical-align: middle;
|
|
||||||
transition: all .3s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* TOOLBAR */
|
|
||||||
|
|
||||||
.editor #actions {
|
|
||||||
display: flex;
|
|
||||||
margin: 1em 0;
|
|
||||||
}
|
|
||||||
.editor #actions> div {
|
|
||||||
flex-basis: 50%;
|
|
||||||
}
|
|
||||||
.editor #actions div:nth-child(2) {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
@ -1,5 +1 @@
|
|||||||
"use strict";
|
'use strict';
|
||||||
|
|
||||||
var ace = document.createElement('script');
|
|
||||||
ace.src = 'https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.3/ace.js';
|
|
||||||
document.head.appendChild(ace);
|
|
||||||
|
@ -1,62 +0,0 @@
|
|||||||
{{ define "content" }}
|
|
||||||
<div class="container editor {{ .Class }}">
|
|
||||||
{{ if eq .Class "complete" }}
|
|
||||||
<h1>
|
|
||||||
<textarea id="site-title">{{ .Name }}</textarea>
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<form method="POST" action=".">
|
|
||||||
<div class="options">
|
|
||||||
{{ template "frontmatter" .FrontMatter }}
|
|
||||||
<button id="add-field">Add field</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="main">
|
|
||||||
{{ if eq .Mode "markdown" }}
|
|
||||||
<nav>
|
|
||||||
<a id="see-source" class="active">
|
|
||||||
<i class="fa fa-code"></i>
|
|
||||||
Source</a>
|
|
||||||
<a id="see-preview">
|
|
||||||
<i class="fa fa-eye"></i>
|
|
||||||
Preview</a>
|
|
||||||
</nav>
|
|
||||||
{{ end}}
|
|
||||||
|
|
||||||
<div id="editor-source" data-mode="{{ .Mode }}"></div>
|
|
||||||
<textarea name="content">{{ .Content }}</textarea>
|
|
||||||
<div id="editor-preview"></div>
|
|
||||||
</form>
|
|
||||||
{{ else if eq .Class "content-only" }}
|
|
||||||
<form method="POST" action="">
|
|
||||||
<h1 id="site-title">{{ .Name }}</h1>
|
|
||||||
|
|
||||||
<div class="main">
|
|
||||||
<div id="editor-source" data-mode="{{ .Mode }}"></div>
|
|
||||||
<textarea name="content">{{ .Content }}</textarea>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{{ else }}
|
|
||||||
<form method="POST" action="">
|
|
||||||
<h1 id="site-title">{{ .Name }}</h1>
|
|
||||||
|
|
||||||
<div class="options">
|
|
||||||
{{ template "frontmatter" .FrontMatter }}
|
|
||||||
<button id="add-field">Add field</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
<div id="actions">
|
|
||||||
<div>
|
|
||||||
<input type="submit" data-type="{{ .Class }}" data-regenerate="false" data-schedule="false" value="Save">
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{{ if and (eq .Class "complete") ( .IsPost ) }}<input type="submit" data-type="{{ .Class }}" data-schedule="true" data-regenerate="false" value="Schedule">
|
|
||||||
{{ end }}
|
|
||||||
<input type="submit" data-type="{{ .Class }}" data-regenerate="true" data-schedule="false" class="default" value="Publish">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{ end }}
|
|
@ -1,50 +0,0 @@
|
|||||||
{{ define "frontmatter" }}
|
|
||||||
{{ range $key, $value := . }}
|
|
||||||
|
|
||||||
{{ if or (eq $value.Type "object") (eq $value.Type "array") }}
|
|
||||||
<fieldset id="{{ $value.Name }}" data-type="{{ $value.Type }}">
|
|
||||||
<div class="title">
|
|
||||||
<h3>{{ SplitCapitalize $value.Title }}</h3>
|
|
||||||
|
|
||||||
<button class="delete" data-delete="{{ $value.Name }}">−</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="columns">
|
|
||||||
{{ template "frontmatter" $value.Content }}
|
|
||||||
<button class="add">Add field</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</fieldset>
|
|
||||||
{{ else }}
|
|
||||||
|
|
||||||
{{ if not (eq $value.Parent.Type "array") }}
|
|
||||||
<div class="block" id="block-{{ $value.Name }}" data-content="{{ $value.Name }}">
|
|
||||||
<label for="{{ $value.Name }}">{{ SplitCapitalize $value.Title }}</label>
|
|
||||||
<button class="delete" data-delete="block-{{ $value.Name }}">×</button>
|
|
||||||
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
{{ if eq $value.Parent.Type "array" }}
|
|
||||||
<div class="block" id="{{ $value.Name }}-{{ $key }}" data-type="array-item">
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
{{ if eq $value.HTMLType "textarea" }}
|
|
||||||
<textarea class="scroll" name="{{ $value.Name }}:{{ $value.Type }}" id="{{ $value.Name }}" data-parent-type="{{ $value.Parent.Type }}">{{ $value.Content }}</textarea>
|
|
||||||
{{ else if eq $value.HTMLType "datetime" }}
|
|
||||||
<input name="{{ $value.Name }}:{{ $value.Type }}" id="{{ $value.Name }}" value="{{ $value.Content.Format " 2006-01-02T15:04" }}" type="datetime-local" data-parent-type="{{ $value.Parent.Type }}"></input>
|
|
||||||
{{ else }}
|
|
||||||
<input name="{{ $value.Name }}:{{ $value.Type }}" id="{{ $value.Name }}" value="{{ $value.Content }}" type="{{ $value.HTMLType }}" data-parent-type="{{ $value.Parent.Type }}"></input>
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
{{ if not (eq $value.Parent.Type "array") }}
|
|
||||||
</div>
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
{{ if eq $value.Parent.Type "array" }}
|
|
||||||
<button class="delete" data-delete="{{ $value.Name }}">×</button>
|
|
||||||
</div>
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
{{ end }}
|
|
||||||
{{ end }}
|
|
||||||
{{ end }}
|
|
67
binary.go
67
binary.go
@ -1,9 +1,6 @@
|
|||||||
// Code generated by go-bindata.
|
// Code generated by go-bindata.
|
||||||
// sources:
|
// sources:
|
||||||
// assets/public/css/styles.css
|
|
||||||
// assets/public/js/application.js
|
// assets/public/js/application.js
|
||||||
// assets/templates/editor.tmpl
|
|
||||||
// assets/templates/options.tmpl
|
|
||||||
// DO NOT EDIT!
|
// DO NOT EDIT!
|
||||||
|
|
||||||
package hugo
|
package hugo
|
||||||
@ -30,24 +27,6 @@ type asset struct {
|
|||||||
info os.FileInfo
|
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-hugo\\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.
|
// publicJsApplicationJs reads file data from disk. It returns an error on failure.
|
||||||
func publicJsApplicationJs() (*asset, error) {
|
func publicJsApplicationJs() (*asset, error) {
|
||||||
path := "D:\\Code\\Go\\src\\github.com\\hacdias\\caddy-hugo\\assets\\public\\js\\application.js"
|
path := "D:\\Code\\Go\\src\\github.com\\hacdias\\caddy-hugo\\assets\\public\\js\\application.js"
|
||||||
@ -66,42 +45,6 @@ func publicJsApplicationJs() (*asset, error) {
|
|||||||
return a, err
|
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-hugo\\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
|
|
||||||
}
|
|
||||||
|
|
||||||
// templatesOptionsTmpl reads file data from disk. It returns an error on failure.
|
|
||||||
func templatesOptionsTmpl() (*asset, error) {
|
|
||||||
path := "D:\\Code\\Go\\src\\github.com\\hacdias\\caddy-hugo\\assets\\templates\\options.tmpl"
|
|
||||||
name := "templates/options.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.
|
// Asset loads and returns the asset for the given name.
|
||||||
// It returns an error if the asset could not be found or
|
// It returns an error if the asset could not be found or
|
||||||
// could not be loaded.
|
// could not be loaded.
|
||||||
@ -154,10 +97,7 @@ func AssetNames() []string {
|
|||||||
|
|
||||||
// _bindata is a table, holding each asset generator, mapped to its name.
|
// _bindata is a table, holding each asset generator, mapped to its name.
|
||||||
var _bindata = map[string]func() (*asset, error){
|
var _bindata = map[string]func() (*asset, error){
|
||||||
"public/css/styles.css": publicCssStylesCss,
|
|
||||||
"public/js/application.js": publicJsApplicationJs,
|
"public/js/application.js": publicJsApplicationJs,
|
||||||
"templates/editor.tmpl": templatesEditorTmpl,
|
|
||||||
"templates/options.tmpl": templatesOptionsTmpl,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AssetDir returns the file names below a certain
|
// AssetDir returns the file names below a certain
|
||||||
@ -201,17 +141,10 @@ type bintree struct {
|
|||||||
}
|
}
|
||||||
var _bintree = &bintree{nil, map[string]*bintree{
|
var _bintree = &bintree{nil, map[string]*bintree{
|
||||||
"public": &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{
|
"js": &bintree{nil, map[string]*bintree{
|
||||||
"application.js": &bintree{publicJsApplicationJs, map[string]*bintree{}},
|
"application.js": &bintree{publicJsApplicationJs, map[string]*bintree{}},
|
||||||
}},
|
}},
|
||||||
}},
|
}},
|
||||||
"templates": &bintree{nil, map[string]*bintree{
|
|
||||||
"editor.tmpl": &bintree{templatesEditorTmpl, map[string]*bintree{}},
|
|
||||||
"options.tmpl": &bintree{templatesOptionsTmpl, map[string]*bintree{}},
|
|
||||||
}},
|
|
||||||
}}
|
}}
|
||||||
|
|
||||||
// RestoreAsset restores an asset under the given directory
|
// RestoreAsset restores an asset under the given directory
|
||||||
|
36
commands.go
36
commands.go
@ -1,36 +0,0 @@
|
|||||||
package hugo
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"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()
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunHugo is used to run the static website generator
|
|
||||||
func RunHugo(c *Config, force bool) {
|
|
||||||
os.RemoveAll(c.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 := Run(c.Hugo, c.Args, c.Root); err != nil {
|
|
||||||
log.Panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
172
frontmatter.go
172
frontmatter.go
@ -1,172 +0,0 @@
|
|||||||
package hugo
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"reflect"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"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 IsMap(element) {
|
|
||||||
objects = append(objects, handleObjects(element, parent, name))
|
|
||||||
} else if 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
|
|
||||||
}
|
|
174
get.go
174
get.go
@ -1,174 +0,0 @@
|
|||||||
package hugo
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"html/template"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/hacdias/caddy-filemanager"
|
|
||||||
"github.com/spf13/hugo/parser"
|
|
||||||
)
|
|
||||||
|
|
||||||
type editor struct {
|
|
||||||
Name string
|
|
||||||
Class string
|
|
||||||
IsPost bool
|
|
||||||
Mode string
|
|
||||||
Content string
|
|
||||||
BaseURL string
|
|
||||||
FrontMatter interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GET handles the GET method on editor page
|
|
||||||
func (h Hugo) GET(w http.ResponseWriter, r *http.Request, filename string) (int, error) {
|
|
||||||
// Check if the file exists.
|
|
||||||
if _, err := os.Stat(filename); os.IsNotExist(err) {
|
|
||||||
return http.StatusNotFound, err
|
|
||||||
} else if os.IsPermission(err) {
|
|
||||||
return http.StatusForbidden, err
|
|
||||||
} else if err != nil {
|
|
||||||
return http.StatusInternalServerError, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open the file and check if there was some error while opening
|
|
||||||
file, err := ioutil.ReadFile(filename)
|
|
||||||
if os.IsPermission(err) {
|
|
||||||
return http.StatusForbidden, err
|
|
||||||
} else if err != nil {
|
|
||||||
return http.StatusInternalServerError, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new editor variable and set the extension
|
|
||||||
data := new(editor)
|
|
||||||
data.Mode = strings.TrimPrefix(filepath.Ext(filename), ".")
|
|
||||||
data.Name = strings.Replace(filename, h.Config.Root, "", 1)
|
|
||||||
data.IsPost = false
|
|
||||||
data.BaseURL = h.Config.BaseURL
|
|
||||||
data.Mode = sanitizeMode(data.Mode)
|
|
||||||
|
|
||||||
var parserPage parser.Page
|
|
||||||
|
|
||||||
// Handle the content depending on the file extension
|
|
||||||
switch data.Mode {
|
|
||||||
case "markdown", "asciidoc", "rst":
|
|
||||||
if hasFrontMatterRune(file) {
|
|
||||||
// Starts a new buffer and parses the file using Hugo's functions
|
|
||||||
buffer := bytes.NewBuffer(file)
|
|
||||||
parserPage, err = parser.ReadFrom(buffer)
|
|
||||||
if err != nil {
|
|
||||||
return http.StatusInternalServerError, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.Contains(string(parserPage.FrontMatter()), "date") {
|
|
||||||
data.IsPost = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parses the page content and the frontmatter
|
|
||||||
data.Content = strings.TrimSpace(string(parserPage.Content()))
|
|
||||||
data.FrontMatter, data.Name, err = Pretty(parserPage.FrontMatter())
|
|
||||||
data.Class = "complete"
|
|
||||||
} else {
|
|
||||||
// The editor will handle only content
|
|
||||||
data.Class = "content-only"
|
|
||||||
data.Content = string(file)
|
|
||||||
}
|
|
||||||
case "json", "toml", "yaml":
|
|
||||||
// Defines the class and declares an error
|
|
||||||
data.Class = "frontmatter-only"
|
|
||||||
|
|
||||||
// Checks if the file already has the frontmatter rune and parses it
|
|
||||||
if hasFrontMatterRune(file) {
|
|
||||||
data.FrontMatter, _, err = Pretty(file)
|
|
||||||
} else {
|
|
||||||
data.FrontMatter, _, err = Pretty(appendFrontMatterRune(file, data.Mode))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if there were any errors
|
|
||||||
if err != nil {
|
|
||||||
return http.StatusInternalServerError, err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
// The editor will handle only content
|
|
||||||
data.Class = "content-only"
|
|
||||||
data.Content = string(file)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the functions map, then the template, check for erros and
|
|
||||||
// execute the template if there aren't errors
|
|
||||||
functions := template.FuncMap{
|
|
||||||
"SplitCapitalize": SplitCapitalize,
|
|
||||||
"Defined": Defined,
|
|
||||||
}
|
|
||||||
|
|
||||||
var code int
|
|
||||||
|
|
||||||
page := &filemanager.Page{
|
|
||||||
Info: &filemanager.PageInfo{
|
|
||||||
IsDir: false,
|
|
||||||
Config: &h.FileManager.Configs[0],
|
|
||||||
Name: data.Name,
|
|
||||||
Data: data,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
templates := []string{"options", "editor"}
|
|
||||||
for _, t := range templates {
|
|
||||||
code, err = page.AddTemplate(t, Asset, functions)
|
|
||||||
if err != nil {
|
|
||||||
return code, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
templates = []string{"actions", "base"}
|
|
||||||
for _, t := range templates {
|
|
||||||
code, err = page.AddTemplate(t, filemanager.Asset, nil)
|
|
||||||
if err != nil {
|
|
||||||
return code, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
code, err = page.PrintAsHTML(w)
|
|
||||||
fmt.Println(err)
|
|
||||||
return code, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func hasFrontMatterRune(file []byte) bool {
|
|
||||||
return strings.HasPrefix(string(file), "---") ||
|
|
||||||
strings.HasPrefix(string(file), "+++") ||
|
|
||||||
strings.HasPrefix(string(file), "{")
|
|
||||||
}
|
|
||||||
|
|
||||||
func 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
|
|
||||||
}
|
|
||||||
|
|
||||||
func sanitizeMode(extension string) string {
|
|
||||||
switch extension {
|
|
||||||
case "md", "markdown", "mdown", "mmark":
|
|
||||||
return "markdown"
|
|
||||||
case "asciidoc", "adoc", "ad":
|
|
||||||
return "asciidoc"
|
|
||||||
case "rst":
|
|
||||||
return "rst"
|
|
||||||
case "html", "htm":
|
|
||||||
return "html"
|
|
||||||
case "js":
|
|
||||||
return "javascript"
|
|
||||||
default:
|
|
||||||
return extension
|
|
||||||
}
|
|
||||||
}
|
|
109
hugo.go
109
hugo.go
@ -7,6 +7,7 @@
|
|||||||
package hugo
|
package hugo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
"mime"
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@ -14,120 +15,54 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hacdias/caddy-filemanager"
|
"github.com/hacdias/caddy-filemanager"
|
||||||
|
"github.com/hacdias/caddy-filemanager/utils/variables"
|
||||||
|
"github.com/hacdias/caddy-hugo/utils/commands"
|
||||||
"github.com/mholt/caddy/caddyhttp/httpserver"
|
"github.com/mholt/caddy/caddyhttp/httpserver"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AssetsURL is the base url of the assets
|
// Hugo is hugo
|
||||||
const AssetsURL = "/_hugointernal"
|
|
||||||
|
|
||||||
// Hugo contais the next middleware to be run and the configuration
|
|
||||||
// of the current one.
|
|
||||||
type Hugo struct {
|
type Hugo struct {
|
||||||
FileManager *filemanager.FileManager
|
|
||||||
Next httpserver.Handler
|
Next httpserver.Handler
|
||||||
Config *Config
|
Config *Config
|
||||||
|
FileManager *filemanager.FileManager
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeHTTP is the main function of the whole plugin that routes every single
|
|
||||||
// request to its function.
|
|
||||||
func (h Hugo) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
func (h Hugo) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
// Check if the current request if for this plugin
|
|
||||||
if httpserver.Path(r.URL.Path).Matches(h.Config.BaseURL) {
|
if httpserver.Path(r.URL.Path).Matches(h.Config.BaseURL) {
|
||||||
// Check if we are asking for the assets
|
|
||||||
if httpserver.Path(r.URL.Path).Matches(h.Config.BaseURL + AssetsURL) {
|
if httpserver.Path(r.URL.Path).Matches(h.Config.BaseURL + AssetsURL) {
|
||||||
return h.ServeAssets(w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the url matches exactly with /{admin}/settings/, redirect
|
|
||||||
// to the page of the configuration file
|
|
||||||
if r.URL.Path == h.Config.BaseURL+"/settings/" {
|
|
||||||
var frontmatter string
|
|
||||||
|
|
||||||
if _, err := os.Stat(h.Config.Root + "config.yaml"); err == nil {
|
|
||||||
frontmatter = "yaml"
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := os.Stat(h.Config.Root + "config.json"); err == nil {
|
|
||||||
frontmatter = "json"
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := os.Stat(h.Config.Root + "config.toml"); err == nil {
|
|
||||||
frontmatter = "toml"
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, h.Config.BaseURL+"/config."+frontmatter, http.StatusTemporaryRedirect)
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasPrefix(r.URL.Path, h.Config.BaseURL+"/api/git/") && r.Method == http.MethodPost {
|
|
||||||
//return HandleGit(w, r, h.Config)
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if h.ShouldHandle(r) {
|
|
||||||
filename := strings.Replace(r.URL.Path, h.Config.BaseURL, h.Config.Root, 1)
|
|
||||||
switch r.Method {
|
|
||||||
case http.MethodGet:
|
|
||||||
return h.GET(w, r, filename)
|
|
||||||
case http.MethodPost:
|
|
||||||
return h.POST(w, r, filename)
|
|
||||||
default:
|
|
||||||
return h.FileManager.ServeHTTP(w, r)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return h.FileManager.ServeHTTP(w, r)
|
return h.FileManager.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
return h.Next.ServeHTTP(w, r)
|
return h.Next.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
var extensions = []string{
|
// RunHugo is used to run the static website generator
|
||||||
"md", "markdown", "mdown", "mmark",
|
func RunHugo(c *Config, force bool) {
|
||||||
"asciidoc", "adoc", "ad",
|
os.RemoveAll(c.Root + "public")
|
||||||
"rst",
|
|
||||||
"html", "htm",
|
// Prevent running if watching is enabled
|
||||||
"js",
|
if b, pos := variables.StringInSlice("--watch", c.Args); b && !force {
|
||||||
"toml", "yaml", "json",
|
if len(c.Args) > pos && c.Args[pos+1] != "false" {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShouldHandle checks if this extension should be handled by this plugin
|
if len(c.Args) == pos+1 {
|
||||||
func (h Hugo) ShouldHandle(r *http.Request) bool {
|
return
|
||||||
// Checks if the method is get or post
|
|
||||||
if r.Method != http.MethodGet && r.Method != http.MethodPost {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if this request is for FileManager assets
|
|
||||||
if httpserver.Path(r.URL.Path).Matches(h.Config.BaseURL + filemanager.AssetsURL) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this request requires a raw file or a download, return the FileManager
|
|
||||||
query := r.URL.Query()
|
|
||||||
if val, ok := query["raw"]; ok && val[0] == "true" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if val, ok := query["download"]; ok && val[0] == "true" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check by file extension
|
|
||||||
extension := strings.TrimPrefix(filepath.Ext(r.URL.Path), ".")
|
|
||||||
|
|
||||||
for _, ext := range extensions {
|
|
||||||
if ext == extension {
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
if err := commands.Run(c.Hugo, c.Args, c.Root); err != nil {
|
||||||
|
log.Panic(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeAssets provides the needed assets for the front-end
|
// serveAssets provides the needed assets for the front-end
|
||||||
func (h Hugo) ServeAssets(w http.ResponseWriter, r *http.Request) (int, error) {
|
func serveAssets(w http.ResponseWriter, r *http.Request, c *Config) (int, error) {
|
||||||
// gets the filename to be used with Assets function
|
// gets the filename to be used with Assets function
|
||||||
filename := strings.Replace(r.URL.Path, h.Config.BaseURL+AssetsURL, "public", 1)
|
filename := strings.Replace(r.URL.Path, c.BaseURL+AssetsURL, "public", 1)
|
||||||
file, err := Asset(filename)
|
file, err := Asset(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusNotFound, nil
|
return http.StatusNotFound, nil
|
||||||
|
@ -14,6 +14,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/hacdias/caddy-hugo/utils/files"
|
||||||
"github.com/mitchellh/go-homedir"
|
"github.com/mitchellh/go-homedir"
|
||||||
"github.com/pivotal-golang/archiver/extractor"
|
"github.com/pivotal-golang/archiver/extractor"
|
||||||
)
|
)
|
||||||
@ -90,7 +91,7 @@ func GetPath() string {
|
|||||||
|
|
||||||
// Copy the file
|
// Copy the file
|
||||||
fmt.Print("Moving Hugo executable... ")
|
fmt.Print("Moving Hugo executable... ")
|
||||||
err = CopyFile(exetorename, hugo)
|
err = files.CopyFile(exetorename, hugo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(-1)
|
os.Exit(-1)
|
||||||
@ -213,27 +214,3 @@ func checkSHA256() {
|
|||||||
|
|
||||||
fmt.Println("checked!")
|
fmt.Println("checked!")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
200
post.go
200
post.go
@ -1,200 +0,0 @@
|
|||||||
package hugo
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/robfig/cron"
|
|
||||||
"github.com/spf13/cast"
|
|
||||||
"github.com/spf13/hugo/parser"
|
|
||||||
)
|
|
||||||
|
|
||||||
type info struct {
|
|
||||||
ContentType string
|
|
||||||
Schedule bool
|
|
||||||
Regenerate bool
|
|
||||||
Content map[string]interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type response struct {
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// POST handles the POST method on editor page
|
|
||||||
func (h Hugo) POST(w http.ResponseWriter, r *http.Request, filename string) (int, error) {
|
|
||||||
var data info
|
|
||||||
|
|
||||||
// Get the JSON information sent using a buffer
|
|
||||||
rawBuffer := new(bytes.Buffer)
|
|
||||||
rawBuffer.ReadFrom(r.Body)
|
|
||||||
err := json.Unmarshal(rawBuffer.Bytes(), &data)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return RespondJSON(w, &response{"Error decrypting json."}, http.StatusInternalServerError, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initializes the file content to write
|
|
||||||
var file []byte
|
|
||||||
var code int
|
|
||||||
|
|
||||||
switch data.ContentType {
|
|
||||||
case "frontmatter-only":
|
|
||||||
file, code, err = parseFrontMatterOnlyFile(data, filename)
|
|
||||||
if err != nil {
|
|
||||||
return RespondJSON(w, &response{err.Error()}, code, err)
|
|
||||||
}
|
|
||||||
case "content-only":
|
|
||||||
// The main content of the file
|
|
||||||
mainContent := data.Content["content"].(string)
|
|
||||||
mainContent = strings.TrimSpace(mainContent)
|
|
||||||
|
|
||||||
file = []byte(mainContent)
|
|
||||||
case "complete":
|
|
||||||
file, code, err = parseCompleteFile(data, filename, h.Config)
|
|
||||||
if err != nil {
|
|
||||||
return RespondJSON(w, &response{err.Error()}, code, err)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return RespondJSON(w, &response{"Invalid content type."}, http.StatusBadRequest, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the file
|
|
||||||
err = ioutil.WriteFile(filename, file, 0666)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return RespondJSON(w, &response{err.Error()}, http.StatusInternalServerError, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if data.Regenerate {
|
|
||||||
go RunHugo(h.Config, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
return RespondJSON(w, nil, http.StatusOK, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseFrontMatterOnlyFile(data info, filename string) ([]byte, int, error) {
|
|
||||||
frontmatter := strings.TrimPrefix(filepath.Ext(filename), ".")
|
|
||||||
var mark rune
|
|
||||||
|
|
||||||
switch frontmatter {
|
|
||||||
case "toml":
|
|
||||||
mark = rune('+')
|
|
||||||
case "json":
|
|
||||||
mark = rune('{')
|
|
||||||
case "yaml":
|
|
||||||
mark = rune('-')
|
|
||||||
default:
|
|
||||||
return []byte{}, http.StatusBadRequest, errors.New("Can't define the frontmatter.")
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := parser.InterfaceToFrontMatter(data.Content, mark)
|
|
||||||
fString := string(f)
|
|
||||||
|
|
||||||
// If it's toml or yaml, strip frontmatter identifier
|
|
||||||
if frontmatter == "toml" {
|
|
||||||
fString = strings.TrimSuffix(fString, "+++\n")
|
|
||||||
fString = strings.TrimPrefix(fString, "+++\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
if frontmatter == "yaml" {
|
|
||||||
fString = strings.TrimSuffix(fString, "---\n")
|
|
||||||
fString = strings.TrimPrefix(fString, "---\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
f = []byte(fString)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return []byte{}, http.StatusInternalServerError, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return f, http.StatusOK, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseCompleteFile(data info, filename string, c *Config) ([]byte, int, error) {
|
|
||||||
// The main content of the file
|
|
||||||
mainContent := data.Content["content"].(string)
|
|
||||||
mainContent = "\n\n" + strings.TrimSpace(mainContent) + "\n"
|
|
||||||
|
|
||||||
// Removes the main content from the rest of the frontmatter
|
|
||||||
delete(data.Content, "content")
|
|
||||||
|
|
||||||
if _, ok := data.Content["date"]; ok {
|
|
||||||
data.Content["date"] = data.Content["date"].(string) + ":00"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Schedule the post
|
|
||||||
if data.Schedule {
|
|
||||||
t := cast.ToTime(data.Content["date"])
|
|
||||||
|
|
||||||
scheduler := cron.New()
|
|
||||||
scheduler.AddFunc(t.In(time.Now().Location()).Format("05 04 15 02 01 *"), func() {
|
|
||||||
// Set draft to false
|
|
||||||
data.Content["draft"] = false
|
|
||||||
|
|
||||||
// Converts the frontmatter in JSON
|
|
||||||
jsonFrontmatter, err := json.Marshal(data.Content)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Indents the json
|
|
||||||
frontMatterBuffer := new(bytes.Buffer)
|
|
||||||
json.Indent(frontMatterBuffer, jsonFrontmatter, "", " ")
|
|
||||||
|
|
||||||
// Generates the final file
|
|
||||||
f := new(bytes.Buffer)
|
|
||||||
f.Write(frontMatterBuffer.Bytes())
|
|
||||||
f.Write([]byte(mainContent))
|
|
||||||
file := f.Bytes()
|
|
||||||
|
|
||||||
// Write the file
|
|
||||||
if err = ioutil.WriteFile(filename, file, 0666); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
go RunHugo(c, false)
|
|
||||||
})
|
|
||||||
scheduler.Start()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Converts the frontmatter in JSON
|
|
||||||
jsonFrontmatter, err := json.Marshal(data.Content)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return []byte{}, http.StatusInternalServerError, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Indents the json
|
|
||||||
frontMatterBuffer := new(bytes.Buffer)
|
|
||||||
json.Indent(frontMatterBuffer, jsonFrontmatter, "", " ")
|
|
||||||
|
|
||||||
// Generates the final file
|
|
||||||
f := new(bytes.Buffer)
|
|
||||||
f.Write(frontMatterBuffer.Bytes())
|
|
||||||
f.Write([]byte(mainContent))
|
|
||||||
return f.Bytes(), http.StatusOK, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
55
setup.go
55
setup.go
@ -5,15 +5,18 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hacdias/caddy-filemanager"
|
"github.com/hacdias/caddy-filemanager"
|
||||||
|
"github.com/hacdias/caddy-filemanager/config"
|
||||||
"github.com/hacdias/caddy-hugo/installer"
|
"github.com/hacdias/caddy-hugo/installer"
|
||||||
|
"github.com/hacdias/caddy-hugo/utils/commands"
|
||||||
"github.com/mholt/caddy"
|
"github.com/mholt/caddy"
|
||||||
"github.com/mholt/caddy/caddyhttp/httpserver"
|
"github.com/mholt/caddy/caddyhttp/httpserver"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const AssetsURL = "/_hugointernal"
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
caddy.RegisterPlugin("hugo", caddy.Plugin{
|
caddy.RegisterPlugin("hugo", caddy.Plugin{
|
||||||
ServerType: "http",
|
ServerType: "http",
|
||||||
@ -25,7 +28,7 @@ func init() {
|
|||||||
// middleware thing.
|
// middleware thing.
|
||||||
func setup(c *caddy.Controller) error {
|
func setup(c *caddy.Controller) error {
|
||||||
cnf := httpserver.GetConfig(c)
|
cnf := httpserver.GetConfig(c)
|
||||||
conf, _ := ParseHugo(c, cnf.Root)
|
conf, _ := parse(c, cnf.Root)
|
||||||
|
|
||||||
// Checks if there is an Hugo website in the path that is provided.
|
// Checks if there is an Hugo website in the path that is provided.
|
||||||
// If not, a new website will be created.
|
// If not, a new website will be created.
|
||||||
@ -44,7 +47,7 @@ func setup(c *caddy.Controller) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if create {
|
if create {
|
||||||
err := Run(conf.Hugo, []string{"new", "site", conf.Root, "--force"}, ".")
|
err := commands.Run(conf.Hugo, []string{"new", "site", conf.Root, "--force"}, ".")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panic(err)
|
log.Panic(err)
|
||||||
}
|
}
|
||||||
@ -59,8 +62,8 @@ func setup(c *caddy.Controller) error {
|
|||||||
Config: conf,
|
Config: conf,
|
||||||
FileManager: &filemanager.FileManager{
|
FileManager: &filemanager.FileManager{
|
||||||
Next: next,
|
Next: next,
|
||||||
Configs: []filemanager.Config{
|
Configs: []config.Config{
|
||||||
filemanager.Config{
|
config.Config{
|
||||||
HugoEnabled: true,
|
HugoEnabled: true,
|
||||||
PathScope: conf.Root,
|
PathScope: conf.Root,
|
||||||
Root: http.Dir(conf.Root),
|
Root: http.Dir(conf.Root),
|
||||||
@ -76,35 +79,27 @@ func setup(c *caddy.Controller) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config contains the configuration of hugo plugin
|
// Config is a configuration for managing a particular hugo website.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Args []string // Hugo arguments
|
|
||||||
Git bool // Is this site a git repository
|
|
||||||
BaseURL string // Admin URL to listen on
|
|
||||||
Hugo string // Hugo executable path
|
|
||||||
Root string // Hugo website path
|
|
||||||
Public string // Public content path
|
Public string // Public content path
|
||||||
Styles string // Admin stylesheet
|
Root string // Hugo files path
|
||||||
|
Hugo string // Hugo executable location
|
||||||
|
Styles string // Admin styles path
|
||||||
|
Args []string // Hugo arguments
|
||||||
|
BaseURL string // BaseURL of admin interface
|
||||||
|
FileManager *filemanager.FileManager
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseHugo parses the configuration file
|
// Parse parses the configuration set by the user so it can be
|
||||||
func ParseHugo(c *caddy.Controller, root string) (*Config, error) {
|
// used by the middleware
|
||||||
|
func parse(c *caddy.Controller, root string) (*Config, error) {
|
||||||
conf := &Config{
|
conf := &Config{
|
||||||
Public: strings.Replace(root, "./", "", -1),
|
Public: strings.Replace(root, "./", "", -1),
|
||||||
BaseURL: "/admin",
|
BaseURL: "/admin",
|
||||||
Root: "./",
|
Root: "./",
|
||||||
Git: false,
|
|
||||||
Hugo: installer.GetPath(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stlsbytes, err := Asset("public/css/styles.css")
|
conf.Hugo = installer.GetPath()
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return conf, err
|
|
||||||
}
|
|
||||||
|
|
||||||
conf.Styles = string(stlsbytes)
|
|
||||||
|
|
||||||
for c.Next() {
|
for c.Next() {
|
||||||
args := c.RemainingArgs()
|
args := c.RemainingArgs()
|
||||||
|
|
||||||
@ -121,19 +116,17 @@ func ParseHugo(c *caddy.Controller, root string) (*Config, error) {
|
|||||||
if !c.NextArg() {
|
if !c.NextArg() {
|
||||||
return conf, c.ArgErr()
|
return conf, c.ArgErr()
|
||||||
}
|
}
|
||||||
stylesheet, err := ioutil.ReadFile(c.Val())
|
tplBytes, err := ioutil.ReadFile(c.Val())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return conf, err
|
return conf, err
|
||||||
}
|
}
|
||||||
conf.Styles += string(stylesheet)
|
conf.Styles = string(tplBytes)
|
||||||
case "admin":
|
case "admin":
|
||||||
if !c.NextArg() {
|
if !c.NextArg() {
|
||||||
return nil, c.ArgErr()
|
return conf, c.ArgErr()
|
||||||
}
|
}
|
||||||
conf.BaseURL = c.Val()
|
conf.BaseURL = c.Val()
|
||||||
// Remove the beginning slash if it exists or not
|
|
||||||
conf.BaseURL = strings.TrimPrefix(conf.BaseURL, "/")
|
conf.BaseURL = strings.TrimPrefix(conf.BaseURL, "/")
|
||||||
// Add a beginning slash to make a
|
|
||||||
conf.BaseURL = "/" + conf.BaseURL
|
conf.BaseURL = "/" + conf.BaseURL
|
||||||
default:
|
default:
|
||||||
key := "--" + c.Val()
|
key := "--" + c.Val()
|
||||||
@ -148,9 +141,5 @@ func ParseHugo(c *caddy.Controller, root string) (*Config, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := os.Stat(filepath.Join(conf.Root, ".git")); err == nil {
|
|
||||||
conf.Git = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return conf, nil
|
return conf, nil
|
||||||
}
|
}
|
||||||
|
15
utils/commands/commands.go
Normal file
15
utils/commands/commands.go
Normal file
@ -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()
|
||||||
|
}
|
30
utils/files/files.go
Normal file
30
utils/files/files.go
Normal file
@ -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
|
||||||
|
}
|
95
variables.go
95
variables.go
@ -1,95 +0,0 @@
|
|||||||
package hugo
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"log"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"unicode"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Defined checks if variable is defined in a struct
|
|
||||||
func Defined(data interface{}, field string) bool {
|
|
||||||
t := reflect.Indirect(reflect.ValueOf(data)).Type()
|
|
||||||
|
|
||||||
if t.Kind() != reflect.Struct {
|
|
||||||
log.Print("Non-struct type not allowed.")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
_, b := t.FieldByName(field)
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dict allows to send more than one variable into a template
|
|
||||||
func Dict(values ...interface{}) (map[string]interface{}, error) {
|
|
||||||
if len(values)%2 != 0 {
|
|
||||||
return nil, errors.New("invalid dict call")
|
|
||||||
}
|
|
||||||
dict := make(map[string]interface{}, len(values)/2)
|
|
||||||
for i := 0; i < len(values); i += 2 {
|
|
||||||
key, ok := values[i].(string)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("dict keys must be strings")
|
|
||||||
}
|
|
||||||
dict[key] = values[i+1]
|
|
||||||
}
|
|
||||||
|
|
||||||
return dict, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsMap checks if some variable is a map
|
|
||||||
func IsMap(sth interface{}) bool {
|
|
||||||
return reflect.ValueOf(sth).Kind() == reflect.Map
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsSlice checks if some variable is a slice
|
|
||||||
func IsSlice(sth interface{}) bool {
|
|
||||||
return reflect.ValueOf(sth).Kind() == reflect.Slice
|
|
||||||
}
|
|
||||||
|
|
||||||
// StringInSlice checks if a slice contains a string
|
|
||||||
func StringInSlice(a string, list []string) (bool, int) {
|
|
||||||
for i, b := range list {
|
|
||||||
if b == a {
|
|
||||||
return true, i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, 0
|
|
||||||
}
|
|
||||||
|
|
||||||
var splitCapitalizeExceptions = map[string]string{
|
|
||||||
"youtube": "YouTube",
|
|
||||||
"github": "GitHub",
|
|
||||||
"googleplus": "Google Plus",
|
|
||||||
"linkedin": "LinkedIn",
|
|
||||||
}
|
|
||||||
|
|
||||||
// SplitCapitalize splits a string by its uppercase letters and capitalize the
|
|
||||||
// first letter of the string
|
|
||||||
func SplitCapitalize(name string) string {
|
|
||||||
if val, ok := splitCapitalizeExceptions[strings.ToLower(name)]; ok {
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
|
|
||||||
var words []string
|
|
||||||
l := 0
|
|
||||||
for s := name; s != ""; s = s[l:] {
|
|
||||||
l = strings.IndexFunc(s[1:], unicode.IsUpper) + 1
|
|
||||||
if l <= 0 {
|
|
||||||
l = len(s)
|
|
||||||
}
|
|
||||||
words = append(words, s[:l])
|
|
||||||
}
|
|
||||||
|
|
||||||
name = ""
|
|
||||||
|
|
||||||
for _, element := range words {
|
|
||||||
name += element + " "
|
|
||||||
}
|
|
||||||
|
|
||||||
name = strings.ToLower(name[:len(name)-1])
|
|
||||||
name = strings.ToUpper(string(name[0])) + name[1:]
|
|
||||||
|
|
||||||
return name
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user