updates on editor

Former-commit-id: 2928727a06a94c0ea87ed821a472ae662df803d1 [formerly 098bc4234803078aba013f6312d179158194fffb] [formerly d2bb681fe62ba87a29b9866e291fb975489cd3fc [formerly 3d25185a557dab1fa529499572c0e6d5bf187ca1]]
Former-commit-id: 288ccb95466fbd234d278886800e1d27c54fa8dd [formerly 78c473865b085e97cf435cb230e2afa85559aba0]
Former-commit-id: c5dc56f4d6198c9c306c01573e1a1af5f1827c3a
This commit is contained in:
Henrique Dias 2017-07-01 08:36:28 +01:00
parent 90ba8e18da
commit cc462c8bca
23 changed files with 246 additions and 137 deletions

View File

@ -9,7 +9,7 @@ install:
- go get github.com/gordonklaus/ineffassign - go get github.com/gordonklaus/ineffassign
script: script:
- sed -i 's/\_ \"github.com\/mholt\/caddy\/caddyhttp\"/\_ \"github.com\/mholt\/caddy\/caddyhttp\"\n\_ \"github.com\/hacdias\/filemanager\/caddy\"/g' $GOPATH/src/github.com/mholt/caddy/caddy/caddymain/run.go - sed -i 's/\_ \"github.com\/mholt\/caddy\/caddyhttp\"/\_ \"github.com\/mholt\/caddy\/caddyhttp\"\n\_ \"github.com\/hacdias\/filemanager\/caddy\/filemanager\"/g' $GOPATH/src/github.com/mholt/caddy/caddy/caddymain/run.go
- go build -o binary github.com/mholt/caddy/caddy - go build -o binary github.com/mholt/caddy/caddy
- go build github.com/hacdias/filemanager - go build github.com/hacdias/filemanager
- ineffassign . - ineffassign .

View File

@ -30,27 +30,3 @@
{{ end }} {{ end }}
{{ define "value" }}
{{- if eq .HTMLType "textarea" }}
<textarea class="scroll" name="{{ .Name }}" id="{{.Name }}" data-parent-type="{{ .Parent.Type }}">{{ .Content.Other }}</textarea>
{{- else if eq .HTMLType "datetime" }}
<input name="{{ .Name }}" id="{{ .Name }}" value="{{ .Content.Other.Format "2006-01-02T15:04" }}" type="datetime-local" data-parent-type="{{ .Parent.Type }}"></input>
{{- else }}
<input name="{{ .Name }}" id="{{ .Name }}" value="{{ .Content.Other }}" type="{{ .HTMLType }}" data-parent-type="{{ .Parent.Type }}"></input>
{{- end }}
{{ end }}
{{ define "fielset" }}
<fieldset id="{{ .Name }}" data-type="{{ .Type }}">
{{- if not (eq .Title "") }}
<h3>{{ .Name }}</h3>
{{- end }}
<div class="action add">
<i class="material-icons" title="Add">add</i>
</div>
<div class="action delete" data-delete="{{ .Name }}">
<i class="material-icons" title="Close">close</i>
</div>
{{- template "blocks" .Content }}
</fieldset>
{{ end }}

View File

@ -1,14 +0,0 @@
{{ define "sidebar-addon" }}
<a class="action" href="{{ .BaseURL }}/content/">
<i class="material-icons">subject</i>
<span>Posts and Pages</span>
</a>
<a class="action" href="{{ .BaseURL }}/themes/">
<i class="material-icons">format_paint</i>
<span>Themes</span>
</a>
<a class="action" href="{{ .BaseURL }}/settings/">
<i class="material-icons">settings</i>
<span>Settings</span>
</a>
{{ end }}

View File

@ -1,23 +0,0 @@
{{ define "templates" }}
<template id="question-template">
<form class="prompt">
<h3></h3>
<p></p>
<input autofocus type="text">
<div>
<button type="submit" autofocus class="ok">OK</button>
<button class="cancel" onclick="closePrompt(event);">Cancel</button>
</div>
</form>
</template>
<template id="message-template">
<div class="prompt">
<h3></h3>
<p></p>
<div>
<button type="submit" onclick="closePrompt(event);" class="ok">OK</button>
</div>
</div>
</template>
{{ end }}

View File

@ -41,6 +41,11 @@ module.exports = {
loader: 'vue-loader', loader: 'vue-loader',
options: vueLoaderConfig options: vueLoaderConfig
}, },
{
test: /\.css$/,
include: /node_modules/,
loader: 'style!css'
},
{ {
test: /\.js$/, test: /\.js$/,
loader: 'babel-loader', loader: 'babel-loader',

View File

@ -90,7 +90,7 @@
</style> </style>
{{- if ne .User.StyleSheet "" -}} {{- if ne .User.StyleSheet "" -}}
<style>{{ CSS .User.StyleSheet }}</style> <style>{{ CSS .User.StyleSheet }}</style>
{{- end -}} {{- end -}}
</head> </head>
<body> <body>

View File

@ -26,6 +26,7 @@
<i class="material-icons">folder</i> <i class="material-icons">folder</i>
<span>My Files</span> <span>My Files</span>
</a> </a>
<div v-if="user.allowNew"> <div v-if="user.allowNew">
<button @click="$store.commit('showNewDir', true)" aria-label="New directory" title="New directory" class="action"> <button @click="$store.commit('showNewDir', true)" aria-label="New directory" title="New directory" class="action">
<i class="material-icons">create_new_folder</i> <i class="material-icons">create_new_folder</i>
@ -36,6 +37,14 @@
<span>New file</span> <span>New file</span>
</button> </button>
</div> </div>
<div v-for="plugin in plugins">
<button v-for="action in plugin.sidebar" @click="action.click" :aria-label="action.name" :title="action.name" class="action">
<i class="material-icons">{{ action.icon }}</i>
<span>{{ action.name }}</span>
</button>
</div>
<button class="action" id="logout" tabindex="0" role="button" aria-label="Log out"> <button class="action" id="logout" tabindex="0" role="button" aria-label="Log out">
<i class="material-icons" title="Logout">exit_to_app</i> <i class="material-icons" title="Logout">exit_to_app</i>
<span>Logout</span> <span>Logout</span>
@ -134,10 +143,19 @@ export default {
'showDownload' 'showDownload'
]) ])
}, },
data: function () {
return {
plugins: []
}
},
mounted: function () { mounted: function () {
updateColumnSizes() updateColumnSizes()
window.addEventListener('resize', updateColumnSizes) window.addEventListener('resize', updateColumnSizes)
if (window.plugins !== undefined || window.plugins !== null) {
this.plugins = window.plugins
}
document.title = this.req.data.name document.title = this.req.data.name
window.history.replaceState({ window.history.replaceState({
url: window.location.pathname, url: window.location.pathname,

View File

@ -1,16 +1,30 @@
<template> <template>
<form id="editor"> <form id="editor">
<h2 v-if="editor.type == 'complete'">Metadata</h2> <h2 v-if="req.data.editor.type == 'complete'">Metadata</h2>
<h2 v-if="editor.type == 'complete'">Body</h2> <h2 v-if="req.data.editor.type == 'complete'">Body</h2>
<div v-if="req.data.editor.type !== 'frontmatter-only'" class="content">
<div id="ace"></div>
<textarea id="source" name="content">{{ req.data.content }}</textarea>
</div>
</form> </form>
</template> </template>
<script> <script>
import { mapState } from 'vuex'
export default { export default {
name: 'editor', name: 'editor',
computed: mapState(['req']),
data: function () { data: function () {
return window.info.req.data return {
codemirror: null,
simplemde: null
}
},
mounted: function () {
}, },
methods: { methods: {
} }

View File

@ -0,0 +1,19 @@
<template>
<fieldset :id="name" :data-type="type">
<h3 v-if="title !== ''">{{ name }}</h3>
<div class="action add">
<i class="material-icons" title="Add">add</i>
</div>
<div class="action delete" :data-delete="name">
<i class="material-icons" title="Close">close</i>
</div>
<!-- template blocks w/ content -->
</fieldset>
</template>
<script>
export default {
name: 'array-object',
props: ['name', 'type', 'title', 'content']
}
</script>

View File

@ -0,0 +1,28 @@
<template>
<textarea v-if="htmlType === 'textarea'"
class="scroll"
:name="name"
:id="name"
:data-parent-type="parentType">
{{ content.other }}
</textarea>
<input v-else-if="htmlType ==='datatime'"
:name="name"
:id="name"
:value="content.other"
type="datetime-local"
:data-parent-type="parentType"></input>
<input v-else
:name="name"
:id="name"
:value="content.other"
:type="htmlType"
:data-parent-type="parentType"></input>
</template>
<script>
export default {
name: 'value',
props: ['htmlType', 'name', 'parentType', 'content']
}
</script>

View File

@ -26,7 +26,12 @@ export default {
event.preventDefault() event.preventDefault()
if (this.new === '') return if (this.new === '') return
let url = window.location.pathname + this.name + '/' let url = window.location.pathname
if (this.$store.state.req.kind !== 'listing') {
url = page.removeLastDir(url) + '/'
}
url += this.name + '/'
url = url.replace('//', '/') url = url.replace('//', '/')
// buttons.setLoading('newDir') // buttons.setLoading('newDir')

View File

@ -26,11 +26,19 @@ export default {
event.preventDefault() event.preventDefault()
if (this.new === '') return if (this.new === '') return
let url = window.location.pathname
if (this.$store.state.req.kind !== 'listing') {
url = page.removeLastDir(url) + '/'
}
url += this.name
url = url.replace('//', '/')
// buttons.setLoading('newFile') // buttons.setLoading('newFile')
webdav.create(window.location.pathname + this.name) webdav.create(url)
.then(() => { .then(() => {
// buttons.setDone('newFile') // buttons.setDone('newFile')
page.open(window.location.pathname + this.name) page.open(url)
}) })
.catch(e => { .catch(e => {
// buttons.setDone('newFile', false) // buttons.setDone('newFile', false)

View File

@ -51,6 +51,7 @@ export default {
}, },
back: function (event) { back: function (event) {
let url = page.removeLastDir(window.location.pathname) let url = page.removeLastDir(window.location.pathname)
if (url === '') url = '/'
page.open(url) page.open(url)
}, },
allowEdit: function (event) { allowEdit: function (event) {

View File

@ -94,9 +94,9 @@ nav .action {
padding: .5em; padding: .5em;
} }
nav>div { nav > .action:last-child,
nav > div {
border-top: 1px solid rgba(0, 0, 0, 0.05); border-top: 1px solid rgba(0, 0, 0, 0.05);
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
} }
nav .action>* { nav .action>* {

View File

@ -15,7 +15,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/hacdias/filemanager" . "github.com/hacdias/filemanager"
"github.com/mholt/caddy" "github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver" "github.com/mholt/caddy/caddyhttp/httpserver"
) )
@ -27,18 +27,22 @@ func init() {
}) })
} }
// FileManager is an http.Handler that can show a file listing when type plugin struct {
// directories in the given paths are specified.
type FileManager struct {
Next httpserver.Handler Next httpserver.Handler
Configs []*filemanager.FileManager Configs []*config
}
type config struct {
*FileManager
baseURL string
webDavURL string
} }
// ServeHTTP determines if the request is for this plugin, and if all prerequisites are met. // 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) { func (f plugin) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
for i := range f.Configs { for i := range f.Configs {
// Checks if this Path should be handled by File Manager. // Checks if this Path should be handled by File Manager.
if !httpserver.Path(r.URL.Path).Matches(f.Configs[i].BaseURL) { if !httpserver.Path(r.URL.Path).Matches(f.Configs[i].baseURL) {
continue continue
} }
@ -56,21 +60,21 @@ func setup(c *caddy.Controller) error {
} }
httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler { httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
return FileManager{Configs: configs, Next: next} return plugin{Configs: configs, Next: next}
}) })
return nil return nil
} }
func parse(c *caddy.Controller) ([]*filemanager.FileManager, error) { func parse(c *caddy.Controller) ([]*config, error) {
var ( var (
configs []*filemanager.FileManager configs []*config
err error err error
) )
for c.Next() { for c.Next() {
var ( var (
m = filemanager.New(".") m = &config{FileManager: New(".")}
u = m.User u = m.User
name = "" name = ""
) )
@ -79,7 +83,7 @@ func parse(c *caddy.Controller) ([]*filemanager.FileManager, error) {
m.SetPrefixURL(strings.TrimSuffix(caddyConf.Addr.Path, "/")) m.SetPrefixURL(strings.TrimSuffix(caddyConf.Addr.Path, "/"))
m.Commands = []string{"git", "svn", "hg"} m.Commands = []string{"git", "svn", "hg"}
m.Rules = append(m.Rules, &filemanager.Rule{ m.Rules = append(m.Rules, &Rule{
Regex: true, Regex: true,
Allow: false, Allow: false,
Regexp: regexp.MustCompile("\\/\\..+"), Regexp: regexp.MustCompile("\\/\\..+"),
@ -89,18 +93,19 @@ func parse(c *caddy.Controller) ([]*filemanager.FileManager, error) {
args := c.RemainingArgs() args := c.RemainingArgs()
if len(args) > 0 { if len(args) > 0 {
m.baseURL = args[0]
m.webDavURL = "/webdav"
m.SetBaseURL(args[0]) m.SetBaseURL(args[0])
m.SetWebDavURL("/webdav")
} }
for c.NextBlock() { for c.NextBlock() {
switch c.Val() { switch c.Val() {
case "before_save": case "before_save":
if m.BeforeSave, err = makeCommand(c); err != nil { if m.BeforeSave, err = makeCommand(c, m); err != nil {
return configs, err return configs, err
} }
case "after_save": case "after_save":
if m.AfterSave, err = makeCommand(c); err != nil { if m.AfterSave, err = makeCommand(c, m); err != nil {
return configs, err return configs, err
} }
case "webdav": case "webdav":
@ -108,6 +113,7 @@ func parse(c *caddy.Controller) ([]*filemanager.FileManager, error) {
return configs, c.ArgErr() return configs, c.ArgErr()
} }
m.webDavURL = "c.Val()"
m.SetWebDavURL(c.Val()) m.SetWebDavURL(c.Val())
case "show": case "show":
if !c.NextArg() { if !c.NextArg() {
@ -185,7 +191,7 @@ func parse(c *caddy.Controller) ([]*filemanager.FileManager, error) {
ruleType += "_r" ruleType += "_r"
} }
rule := &filemanager.Rule{ rule := &Rule{
Allow: ruleType == "allow" || ruleType == "allow_r", Allow: ruleType == "allow" || ruleType == "allow_r",
Regex: ruleType == "allow_r" || ruleType == "block_r", Regex: ruleType == "allow_r" || ruleType == "block_r",
} }
@ -215,14 +221,16 @@ func parse(c *caddy.Controller) ([]*filemanager.FileManager, error) {
} }
} }
m.baseURL = strings.TrimSuffix(m.baseURL, "/")
m.webDavURL = strings.TrimSuffix(m.webDavURL, "/")
configs = append(configs, m) configs = append(configs, m)
} }
return configs, nil return configs, nil
} }
func makeCommand(c *caddy.Controller) (filemanager.Command, error) { func makeCommand(c *caddy.Controller, m *config) (Command, error) {
fn := func(r *http.Request, c *filemanager.FileManager, u *filemanager.User) error { return nil } fn := func(r *http.Request, c *FileManager, u *User) error { return nil }
args := c.RemainingArgs() args := c.RemainingArgs()
if len(args) == 0 { if len(args) == 0 {
@ -241,8 +249,8 @@ func makeCommand(c *caddy.Controller) (filemanager.Command, error) {
return fn, c.Err(err.Error()) return fn, c.Err(err.Error())
} }
fn = func(r *http.Request, c *filemanager.FileManager, u *filemanager.User) error { fn = func(r *http.Request, c *FileManager, u *User) error {
path := strings.Replace(r.URL.Path, c.WebDavURL, "", 1) path := strings.Replace(r.URL.Path, m.baseURL+m.webDavURL, "", 1)
path = u.Scope() + "/" + path path = u.Scope() + "/" + path
path = filepath.Clean(path) path = filepath.Clean(path)

52
caddy/hugo/README.md Normal file
View File

@ -0,0 +1,52 @@
# hugo - a caddy plugin
[![community](https://img.shields.io/badge/community-forum-ff69b4.svg?style=flat-square)](https://caddy.community)
hugo fills the gap between Hugo and the browser. [Hugo](http://gohugo.io/) is an easy and fast static website generator. This plugin fills the gap between Hugo and the end-user, providing you a web interface to manage the whole website.
Using this plugin, you won't need to have your own computer to edit posts, neither regenerate your static website, because you can do all of that just through your browser.
**Requirements:** you need to have the hugo executable in your PATH. You can download it from its [official page](http://gohugo.io).
### Syntax
```
hugo [directory] [admin] {
clean_public [true|false]
before_publish command
after_publish command
flag name [value]
# other file manager compatible options
}
```
All of the options above are optional.
* **directory** is the folder where the commands are going to be executed. By default, it is the current working directory. Default: `./`.
* **admin** is the path where you will find your administration interface. Default: `/admin`.
* **clean_public** sets if the `public` folder should be removed before generating the website again. Default: `true`.
* **before_publish** and **after_publish** allow you to set a custom command to be executed before publishing and after publishing a post/page. The placeholder `{path}` can be used and it will be replaced by the file path.
* **name** refers to the Hugo available flags. Please use their long form without `--` in the beginning. If no **value** is set, it will be evaluated as `true`.
In spite of these options, you can also use the [filemanager](https://caddyserver.com/docs/http.filemanager) so you can have more control about what can be acceded, the permissions of each user, and so on.
This directive should be used with [root](https://caddyserver.com/docs/root), [basicauth](https://caddyserver.com/docs/basicauth) and [errors](https://caddyserver.com/docs/errors) middleware to have the best experience. See the examples to know more.
### Examples
If you don't already have an Hugo website, don't worry. This plugin will auto-generate it for you. But that's not everything. It is recommended that you take a look at Hugo [documentation](http://gohugo.io/themes/overview/) to learn more about themes, content types, and so on.
A simple Caddyfile to use with Hugo static website generator:
```
root public # the folder where Hugo generates the website
basicauth /admin user pass # protect the admin area using HTTP basic auth
hugo # enable the admin panel
```
### Screenshots
![capture](https://cloud.githubusercontent.com/assets/5447088/25630072/b9a95dfa-2f63-11e7-81fe-00bab89e3391.PNG)
![2](https://cloud.githubusercontent.com/assets/5447088/25630073/b9b00678-2f63-11e7-8f5d-6488c641ed19.PNG)
![3](https://cloud.githubusercontent.com/assets/5447088/25630074/b9cbe3ca-2f63-11e7-8e53-19a3e553f86b.PNG)
![4](https://cloud.githubusercontent.com/assets/5447088/25630075/b9cfa320-2f63-11e7-8262-cdd942d4a3b5.PNG)

1
caddy/hugo/hugo.go Normal file
View File

@ -0,0 +1 @@
package hugo

42
caddy/hugo/hugo.js Normal file
View File

@ -0,0 +1,42 @@
'use strict'
if (window.plugins === undefined || window.plugins === null) {
window.plugins = []
}
window.plugins.append({
sidebar: [
{
click: function (event) {
console.log('evt')
},
icon: 'settings_applications',
name: 'Settings'
},
{
click: function (event) {
console.log('evt')
},
icon: 'remove_red_eye',
name: 'Preview'
}
]
})
/*
{{ define "sidebar-addon" }}
<a class="action" href="{{ .BaseURL }}/content/">
<i class="material-icons">subject</i>
<span>Posts and Pages</span>
</a>
<a class="action" href="{{ .BaseURL }}/themes/">
<i class="material-icons">format_paint</i>
<span>Themes</span>
</a>
<a class="action" href="{{ .BaseURL }}/settings/">
<i class="material-icons">settings</i>
<span>Settings</span>
</a>
{{ end }}
*/

View File

@ -1,31 +0,0 @@
package main
import (
"log"
"net/http"
"github.com/hacdias/filemanager"
)
var m *filemanager.FileManager
func handler(w http.ResponseWriter, r *http.Request) {
// TODO: review return codes and return 0 when everything works.
code, err := m.ServeHTTP(w, r)
if err != nil {
log.Print(err)
}
if code != 0 {
w.WriteHeader(code)
}
}
func main() {
m = filemanager.New("D:\\TEST")
m.SetBaseURL("/vaca")
m.Commands = []string{"git"}
http.HandleFunc("/", handler)
http.ListenAndServe(":80", nil)
}

View File

@ -123,20 +123,20 @@ func Marshal(data interface{}, mark rune) ([]byte, error) {
// Content is the block content // Content is the block content
type Content struct { type Content struct {
Other interface{} Other interface{} `json:"other"`
Fields []*Block Fields []*Block `json:"fields"`
Arrays []*Block Arrays []*Block `json:"arrays"`
Objects []*Block Objects []*Block `json:"objects"`
} }
// Block is a block // Block is a block
type Block struct { type Block struct {
Name string Name string `json:"name"`
Title string Title string `json:"title"`
Type string Type string `json:"type"`
HTMLType string HTMLType string `json:"htmlType"`
Content *Content Content *Content `json:"content"`
Parent *Block `json:"-"` Parent *Block `json:"-"`
} }
func rawToPretty(config interface{}, parent *Block) *Content { func rawToPretty(config interface{}, parent *Block) *Content {

View File

@ -27,7 +27,7 @@ func serveHTTP(c *requestContext, w http.ResponseWriter, r *http.Request) (int,
// Checks if the URL contains the baseURL. If so, it strips it. Otherwise, // Checks if the URL contains the baseURL. If so, it strips it. Otherwise,
// it throws an error. // it throws an error.
if p := strings.TrimPrefix(r.URL.Path, c.fm.baseURL); len(p) < len(r.URL.Path) { if p := strings.TrimPrefix(r.URL.Path, c.fm.baseURL); len(p) < len(r.URL.Path) || len(c.fm.baseURL) == 0 {
r.URL.Path = p r.URL.Path = p
} else { } else {
return http.StatusNotFound, nil return http.StatusNotFound, nil