diff --git a/assets/public/css/styles.css b/assets/public/css/styles.css index 52d130ec..e66e5ab2 100644 --- a/assets/public/css/styles.css +++ b/assets/public/css/styles.css @@ -100,7 +100,25 @@ button, select { text-transform: none } button, html [type="button"], [type="reset"], [type="submit"] { - -webkit-appearance: button + -webkit-appearance: button; + text-decoration: none; + color: #fff; + background-color: #26a69a; + text-align: center; + letter-spacing: .5px; + transition: .2s ease-out; + cursor: pointer; + 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; + display: inline-block; + height: 36px; + line-height: 36px; + outline: 0; + padding: 0 2rem; + text-transform: uppercase; + vertical-align: middle; + -webkit-tap-highlight-color: transparent; } button::-moz-focus-inner, [type="button"]::-moz-focus-inner, [type="reset"]::-moz-focus-inner, [type="submit"]::-moz-focus-inner { border-style: none; @@ -110,9 +128,10 @@ button:-moz-focusring, [type="button"]:-moz-focusring, [type="reset"]:-moz-focus outline: 1px dotted ButtonText } fieldset { - border: 1px solid #c0c0c0; - margin: 0 2px; - padding: 0.35em 0.625em 0.75em + border: none; + margin: 0; + padding: 0; + break-inside: avoid; } legend { box-sizing: border-box; @@ -326,10 +345,10 @@ header p i { color: rgba(255, 255, 255, .31); } header #logout { - background-color: rgba(0,0,0,0.1); - border-radius: 0; - margin: -0.5em -0.5em -0.5em 0; - padding: .5em; + background-color: rgba(0, 0, 0, 0.1); + border-radius: 0; + margin: -0.5em -0.5em -0.5em 0; + padding: .5em; } header p i { vertical-align: middle; @@ -430,22 +449,15 @@ header form input { .action:hover i { background-color: rgba(0, 0, 0, .1); } - - .floating { - position: fixed; - bottom: 1em; - right: 1em; + position: fixed; + bottom: 1em; + right: 1em; } - .floating .action { - background-color: #68EFAD; - color: #306e50; - box-shadow: 0 1px 3px rgba(0, 0, 0, .06), 0 1px 2px rgba(0, 0, 0, .12); -} - -#newfolder i { - transform: rotate(45deg); + background-color: #68EFAD; + color: #306e50; + box-shadow: 0 1px 3px rgba(0, 0, 0, .06), 0 1px 2px rgba(0, 0, 0, .12); } /* LISTING */ @@ -510,44 +522,65 @@ 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; */ - column-count: 3; + column-count: 2; column-gap: 1em; margin-bottom: 1em; - display: flex; - flex-direction: column; } -#editor label { +#editor .group { + box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12); display: block; - width: 19%; - font-size: 2em; + border-radius: .2em; + padding: .5em; + margin-bottom: 1em; + break-inside: avoid; + background-color: #fff; } -#editor fieldset { +#editor .block { + border-bottom: 1px solid #eee; + margin-bottom: .5em; + padding-bottom: .5em; +} +#editor .block:last-child { + border: 0; margin: 0; padding: 0; - border: 0; - background-color: rgba(0, 0, 0, .05); } -#editor button { - display: none; +#editor .block label { + display: block; + color: #212121; + font-weight: 500; } -#editor textarea[name="content"] { - display: none; -} - #editor h3 { - font-size: .8rem; + margin: 0 0 .5em; + display: inline-block; + vertical-align: middle; + width: calc(100% - 2.5em); +} +#editor .block input, #editor .block .actions { + display: inline-block; +} +#editor .block input, #editor .block textarea { + border: 0; + background-color: transparent; + overflow: hidden; + color: #9E9E9E; + resize: none; + width: calc(100% - 1.5em); +} +#editor .action { + border: 0; + background-color: transparent; + font-size: .8em; + margin: 0; +} +#editor .delete { + color: #E53935; +} +#editor i { + padding: 0; +} +#editor .delete i:hover { + color: #B71C1C; + background-color: transparent; } diff --git a/assets/templates/base.tmpl b/assets/templates/base.tmpl index 573ef4a1..a428f6fc 100644 --- a/assets/templates/base.tmpl +++ b/assets/templates/base.tmpl @@ -80,7 +80,7 @@ {{ if .IsDir }}
- close + add
{{ end }} diff --git a/assets/templates/frontmatter.tmpl b/assets/templates/frontmatter.tmpl index 81eaaab3..0659bca5 100644 --- a/assets/templates/frontmatter.tmpl +++ b/assets/templates/frontmatter.tmpl @@ -1,44 +1,55 @@ {{ define "blocks" }} -{{ range $key, $value := . }} + {{ if .Fields }}
{{ end }} + {{ range $key, $value := .Fields }} + {{ if eq $value.Parent.Type "array" }} +
+ {{ template "value" $value }} +
+ close +
+
+ {{ else }} +
+ + {{ template "value" $value }} +
+ close +
+
+ {{ end }} + {{ end }} + {{ if .Fields }}
{{ end }} - {{ if or (eq $value.Type "object") (eq $value.Type "array") }} -
-

{{ SplitCapitalize $value.Title }}

- - - - - {{ template "blocks" $value.Content }} -
- {{ else }} + {{ range $key, $value := .Arrays }} + {{ template "fielset" $value }} + {{ end }} - {{ if not (eq $value.Parent.Type "array") }} -
- - - - - - {{ end }} - - {{ if eq $value.Parent.Type "array" }} -
- {{ end }} - - {{ if eq $value.HTMLType "textarea" }} - - {{ else if eq $value.HTMLType "datetime" }} - - {{ else }} - - {{ end }} - - {{ if not (eq $value.Parent.Type "array") }}
{{ end }} - - {{ if eq $value.Parent.Type "array" }} -
- {{ end }} + {{ range $key, $value := .Objects }} + {{ template "fielset" $value }} + {{ end }} +{{ end }} +{{ define "value" }} +{{ if eq .HTMLType "textarea" }} + +{{ else if eq .HTMLType "datetime" }} + +{{ else }} + {{ end }} {{ end }} + + +{{ define "fielset" }} +
+

{{ SplitCapitalize .Title }}

+
+ add +
+
+ close +
+ {{ template "blocks" .Content }} + +
{{ end }} diff --git a/filemanager.go b/filemanager.go index 98218312..d071d31f 100644 --- a/filemanager.go +++ b/filemanager.go @@ -84,6 +84,7 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err } // VCS commands if r.Header.Get("Command") != "" { + // TODO: not implemented on frontend vcs.Handle(w, r, c) } // Creates a new folder diff --git a/internal/file/editor.go b/internal/file/editor.go index 74117e5b..361a4686 100644 --- a/internal/file/editor.go +++ b/internal/file/editor.go @@ -5,7 +5,7 @@ import ( "path/filepath" "strings" - "github.com/hacdias/caddy-hugo/tools/frontmatter" + "github.com/hacdias/caddy-filemanager/internal/frontmatter" "github.com/spf13/hugo/parser" ) @@ -14,7 +14,7 @@ type Editor struct { Class string Mode string Content string - FrontMatter interface{} + FrontMatter *frontmatter.Content } // GetEditor gets the editor based on a FileInfo struct diff --git a/internal/frontmatter/frontmatter.go b/internal/frontmatter/frontmatter.go index f357abdf..2eedbb5f 100644 --- a/internal/frontmatter/frontmatter.go +++ b/internal/frontmatter/frontmatter.go @@ -1,14 +1,19 @@ package frontmatter import ( + "bytes" + "encoding/json" + "errors" "log" "reflect" "sort" "strings" + "gopkg.in/yaml.v2" + + "github.com/BurntSushi/toml" "github.com/hacdias/caddy-filemanager/utils/variables" "github.com/spf13/cast" - "github.com/spf13/hugo/parser" ) const ( @@ -20,42 +25,77 @@ const ( 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) +func Pretty(content []byte) (*Content, string, error) { + mark := rune(content[0]) + var data interface{} - if err != nil { - return []string{}, mainTitle, err + switch mark { + case '-': + // If it's YAML + if err := yaml.Unmarshal(content, &data); err != nil { + return &Content{}, "", err + } + case '+': + // If it's TOML + content = bytes.Replace(content, []byte("+"), []byte(""), -1) + if _, err := toml.Decode(string(content), &data); err != nil { + return &Content{}, "", err + } + case '{', '[': + // If it's JSON + if err := json.Unmarshal(content, &data); err != nil { + return &Content{}, "", err + } + default: + return &Content{}, "", errors.New("Invalid frontmatter type.") } - object := new(frontmatter) + kind := reflect.ValueOf(data).Kind() + + object := new(Block) object.Type = objectType object.Name = mainName - return rawToPretty(front, object), mainTitle, nil + if kind == reflect.Map { + object.Type = objectType + } else if kind == reflect.Slice || kind == reflect.Array { + object.Type = arrayType + } + + return rawToPretty(data, object), mainTitle, nil } -type frontmatter struct { +// Content is the block content +type Content struct { + Other interface{} + Fields []*Block + Arrays []*Block + Objects []*Block +} + +// Block is a block +type Block struct { Name string Title string - Content interface{} Type string HTMLType string - Parent *frontmatter + Content *Content + Parent *Block } -func rawToPretty(config interface{}, parent *frontmatter) interface{} { - objects := []*frontmatter{} - arrays := []*frontmatter{} - fields := []*frontmatter{} +func rawToPretty(config interface{}, parent *Block) *Content { + objects := []*Block{} + arrays := []*Block{} + fields := []*Block{} cnf := map[string]interface{}{} + kind := reflect.TypeOf(config) - if reflect.TypeOf(config) == reflect.TypeOf(map[interface{}]interface{}{}) { + if kind == 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{}{}) { + } else if kind == reflect.TypeOf([]interface{}{}) { for key, value := range config.([]interface{}) { cnf[string(key)] = value } @@ -77,18 +117,17 @@ func rawToPretty(config interface{}, parent *frontmatter) interface{} { } } - 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 + sort.Sort(sortByTitle(arrays)) + sort.Sort(sortByTitle(objects)) + return &Content{ + Fields: fields, + Arrays: arrays, + Objects: objects, + } } -type sortByTitle []*frontmatter +type sortByTitle []*Block func (f sortByTitle) Len() int { return len(f) } func (f sortByTitle) Swap(i, j int) { f[i], f[j] = f[j], f[i] } @@ -96,8 +135,8 @@ 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) +func handleObjects(content interface{}, parent *Block, name string) *Block { + c := new(Block) c.Parent = parent c.Type = objectType c.Title = name @@ -114,8 +153,8 @@ func handleObjects(content interface{}, parent *frontmatter, name string) *front return c } -func handleArrays(content interface{}, parent *frontmatter, name string) *frontmatter { - c := new(frontmatter) +func handleArrays(content interface{}, parent *Block, name string) *Block { + c := new(Block) c.Parent = parent c.Type = arrayType c.Title = name @@ -130,8 +169,8 @@ func handleArrays(content interface{}, parent *frontmatter, name string) *frontm return c } -func handleFlatValues(content interface{}, parent *frontmatter, name string) *frontmatter { - c := new(frontmatter) +func handleFlatValues(content interface{}, parent *Block, name string) *Block { + c := new(Block) c.Parent = parent switch reflect.ValueOf(content).Kind() { @@ -143,14 +182,14 @@ func handleFlatValues(content interface{}, parent *frontmatter, name string) *fr c.Type = "string" } - c.Content = content + c.Content = &Content{Other: content} switch strings.ToLower(name) { case "description": c.HTMLType = "textarea" case "date", "publishdate": c.HTMLType = "datetime" - c.Content = cast.ToTime(content) + c.Content = &Content{Other: cast.ToTime(content)} default: c.HTMLType = "text" }