Start integrating Hugo in the new plugin
Former-commit-id: dcc6bd82b3d3a89637a1032aad1a25d0b1f80046 [formerly 8784cd37bf58e81cdbe1bcec00e6f16b58efc915] [formerly 9e25850e063ae0825de337d5d5f29cee601b9040 [formerly 8b1d36dfb9
]]
Former-commit-id: 04a38bea2d141093570d9289d0d0a056a136fe8a [formerly 5995538504889e698aa6cd35b7da40c38b5d5ddf]
Former-commit-id: 8c81a0b060167e1a2983a99bc87b380838ac07dc
pull/726/head
parent
73eb1950a0
commit
749d3ea3fc
|
@ -27,6 +27,8 @@
|
|||
if (file.match(/\.(js|css)$/)) { %>
|
||||
<link rel="<%= chunk.initial?'preload':'prefetch' %>" href="{{ .BaseURL }}/<%= file %>" as="<%= file.match(/\.css$/)?'style':'script' %>"><% }}} %>
|
||||
|
||||
<!-- Plugins info -->
|
||||
<script>{{ range $index, $plugin := .Plugins }}{{ JS $plugin.JavaScript }}{{ end}}</script>
|
||||
<style>
|
||||
#loading {
|
||||
position: fixed;
|
||||
|
|
|
@ -48,6 +48,7 @@ export default {
|
|||
lineNumbers: (this.req.language !== 'markdown'),
|
||||
viewportMargin: Infinity,
|
||||
autofocus: true,
|
||||
mode: this.req.language,
|
||||
theme: (this.req.language === 'markdown') ? 'markdown' : 'ttcn',
|
||||
lineWrapping: (this.req.language === 'markdown')
|
||||
})
|
||||
|
@ -66,7 +67,8 @@ export default {
|
|||
value: this.req.metadata,
|
||||
viewportMargin: Infinity,
|
||||
lineWrapping: true,
|
||||
theme: 'markdown'
|
||||
theme: 'markdown',
|
||||
mode: this.metalang
|
||||
})
|
||||
|
||||
CodeMirror.autoLoadMode(this.metadata, this.metalang)
|
||||
|
|
|
@ -16,6 +16,20 @@
|
|||
<i class="material-icons" title="Save">save</i>
|
||||
</button>
|
||||
|
||||
<div v-for="plugin in plugins" :key="plugin.name">
|
||||
<button class="action"
|
||||
v-for="action in plugin.header.visible"
|
||||
v-if="action.if(pluginData, $route)"
|
||||
@click="action.click($event, pluginData, $route)"
|
||||
:aria-label="action.name"
|
||||
:id="action.id"
|
||||
:title="action.name"
|
||||
:key="action.name">
|
||||
<i class="material-icons">{{ action.icon }}</i>
|
||||
<span>{{ action.name }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button @click="openMore" id="more" aria-label="More" title="More" class="action">
|
||||
<i class="material-icons">more_vert</i>
|
||||
</button>
|
||||
|
@ -36,6 +50,20 @@
|
|||
<delete-button v-show="showDeleteButton"></delete-button>
|
||||
</div>
|
||||
|
||||
<div v-for="plugin in plugins" :key="plugin.name">
|
||||
<button class="action"
|
||||
v-for="action in plugin.header.hidden"
|
||||
v-if="action.if(pluginData, $route)"
|
||||
@click="action.click($event, pluginData, $route)"
|
||||
:id="action.id"
|
||||
:aria-label="action.name"
|
||||
:title="action.name"
|
||||
:key="action.name">
|
||||
<i class="material-icons">{{ action.icon }}</i>
|
||||
<span>{{ action.name }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<switch-button v-show="showSwitchButton"></switch-button>
|
||||
<download-button v-show="showCommonButton"></download-button>
|
||||
<upload-button v-show="showUpload"></upload-button>
|
||||
|
@ -61,6 +89,8 @@ import DownloadButton from './buttons/Download'
|
|||
import SwitchButton from './buttons/SwitchView'
|
||||
import MoveButton from './buttons/Move'
|
||||
import {mapGetters, mapState} from 'vuex'
|
||||
import api from '@/utils/api'
|
||||
import buttons from '@/utils/buttons'
|
||||
|
||||
export default {
|
||||
name: 'main',
|
||||
|
@ -76,7 +106,13 @@ export default {
|
|||
},
|
||||
data: function () {
|
||||
return {
|
||||
width: window.innerWidth
|
||||
width: window.innerWidth,
|
||||
pluginData: {
|
||||
api,
|
||||
buttons,
|
||||
'store': this.$store,
|
||||
'router': this.$router
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
|
@ -93,7 +129,8 @@ export default {
|
|||
'user',
|
||||
'loading',
|
||||
'reload',
|
||||
'multiple'
|
||||
'multiple',
|
||||
'plugins'
|
||||
]),
|
||||
isMobile () {
|
||||
return this.width <= 736
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
</button>
|
||||
</div>
|
||||
|
||||
<div v-for="plugin in plugins">
|
||||
<button v-for="action in plugin.sidebar" @click="action.click" :aria-label="action.name" :title="action.name" :key="action.name" class="action">
|
||||
<div v-for="plugin in plugins" :key="plugin.name">
|
||||
<button v-for="action in plugin.sidebar" @click="action.click($event, pluginData, $route)" :aria-label="action.name" :title="action.name" :key="action.name" class="action">
|
||||
<i class="material-icons">{{ action.icon }}</i>
|
||||
<span>{{ action.name }}</span>
|
||||
</button>
|
||||
|
@ -36,32 +36,38 @@
|
|||
</button>
|
||||
</div>
|
||||
|
||||
<p class="credits">Served with <a rel="noopener noreferrer" href="https://github.com/hacdias/caddy-filemanager">File Manager</a>.<br><a @click="help">Help</a></p>
|
||||
<p class="credits">
|
||||
<span>Served with <a rel="noopener noreferrer" href="https://github.com/hacdias/caddy-filemanager">File Manager</a>.</span>
|
||||
<span v-for="plugin in plugins" :key="plugin.name" v-html="plugin.credits"><br></span>
|
||||
<span><a @click="help">Help</a></span>
|
||||
</p>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapState} from 'vuex'
|
||||
import auth from '@/utils/auth'
|
||||
import buttons from '@/utils/buttons'
|
||||
import api from '@/utils/api'
|
||||
|
||||
export default {
|
||||
name: 'sidebar',
|
||||
data: () => {
|
||||
data: function () {
|
||||
return {
|
||||
plugins: []
|
||||
pluginData: {
|
||||
api,
|
||||
buttons,
|
||||
'store': this.$store,
|
||||
'router': this.$router
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['user']),
|
||||
...mapState(['user', 'plugins']),
|
||||
active () {
|
||||
return this.$store.state.show === 'sidebar'
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
if (window.plugins !== undefined || window.plugins !== null) {
|
||||
this.plugins = window.plugins
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
help: function () {
|
||||
this.$store.commit('showHover', 'help')
|
||||
|
|
|
@ -11,6 +11,26 @@
|
|||
<error v-else-if="showError"></error>
|
||||
<success v-else-if="showSuccess"></success>
|
||||
|
||||
<template v-for="plugin in plugins">
|
||||
<form class="prompt"
|
||||
v-for="prompt in plugin.prompts"
|
||||
:key="prompt.name"
|
||||
v-if="show === prompt.name"
|
||||
@submit="prompt.submit($event, pluginData, $route)">
|
||||
<h3>{{ prompt.title }}</h3>
|
||||
<p>{{ prompt.description }}</p>
|
||||
<input v-for="input in prompt.inputs"
|
||||
:key="input.name"
|
||||
:type="input.type"
|
||||
:name="input.name"
|
||||
:placeholder="input.placeholder">
|
||||
<div>
|
||||
<input type="submit" class="ok" :value="prompt.ok">
|
||||
<button class="cancel" @click="$store.commit('closeHovers')">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<div v-show="showOverlay" @click="resetPrompts" class="overlay"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -27,6 +47,8 @@ import Success from './Success'
|
|||
import NewFile from './NewFile'
|
||||
import NewDir from './NewDir'
|
||||
import { mapState } from 'vuex'
|
||||
import buttons from '@/utils/buttons'
|
||||
import api from '@/utils/api'
|
||||
|
||||
export default {
|
||||
name: 'prompts',
|
||||
|
@ -42,8 +64,18 @@ export default {
|
|||
NewDir,
|
||||
Help
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
pluginData: {
|
||||
api,
|
||||
buttons,
|
||||
'store': this.$store,
|
||||
'router': this.$router
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['show']),
|
||||
...mapState(['show', 'plugins']),
|
||||
showError: function () { return this.show === 'error' },
|
||||
showSuccess: function () { return this.show === 'success' },
|
||||
showInfo: function () { return this.show === 'info' },
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
|
||||
transform: scale(0);
|
||||
transition: .1s ease-in-out transform;
|
||||
transform-origin: top right;
|
||||
z-index: 99999;
|
||||
}
|
||||
#dropdown > div {
|
||||
|
|
|
@ -37,11 +37,12 @@
|
|||
margin: .5em 0 1em;
|
||||
}
|
||||
|
||||
.prompt input {
|
||||
.prompt input:not([type="submit"]) {
|
||||
width: 100%;
|
||||
border: 1px solid #dadada;
|
||||
line-height: 1;
|
||||
padding: .3em;
|
||||
margin: .3em 0;
|
||||
}
|
||||
|
||||
.prompt code {
|
||||
|
|
|
@ -175,6 +175,11 @@
|
|||
color: #a5a5a5;
|
||||
}
|
||||
|
||||
.credits span {
|
||||
display: block;
|
||||
margin: .3em 0;
|
||||
}
|
||||
|
||||
.credits a,
|
||||
.credits a:hover {
|
||||
color: inherit;
|
||||
|
|
|
@ -8,6 +8,7 @@ Vue.use(Vuex)
|
|||
const state = {
|
||||
user: {},
|
||||
req: {},
|
||||
plugins: window.plugins || [],
|
||||
baseURL: document.querySelector('meta[name="base"]').getAttribute('content'),
|
||||
jwt: '',
|
||||
loading: false,
|
||||
|
|
|
@ -375,5 +375,6 @@ export default {
|
|||
updatePassword,
|
||||
updateCSS,
|
||||
getCommands,
|
||||
updateCommands
|
||||
updateCommands,
|
||||
removePrefix
|
||||
}
|
||||
|
|
24
auth.go
24
auth.go
|
@ -13,7 +13,7 @@ import (
|
|||
)
|
||||
|
||||
// authHandler proccesses the authentication for the user.
|
||||
func authHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
func authHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
// Receive the credentials from the request and unmarshal them.
|
||||
var cred User
|
||||
if r.Body == nil {
|
||||
|
@ -26,7 +26,7 @@ func authHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int
|
|||
}
|
||||
|
||||
// Checks if the user exists.
|
||||
u, ok := c.fm.Users[cred.Username]
|
||||
u, ok := c.FM.Users[cred.Username]
|
||||
if !ok {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
@ -36,19 +36,19 @@ func authHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int
|
|||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
c.us = u
|
||||
c.User = u
|
||||
return printToken(c, w)
|
||||
}
|
||||
|
||||
// renewAuthHandler is used when the front-end already has a JWT token
|
||||
// and is checking if it is up to date. If so, updates its info.
|
||||
func renewAuthHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
func renewAuthHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
ok, u := validateAuth(c, r)
|
||||
if !ok {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
c.us = u
|
||||
c.User = u
|
||||
return printToken(c, w)
|
||||
}
|
||||
|
||||
|
@ -59,11 +59,11 @@ type claims struct {
|
|||
}
|
||||
|
||||
// printToken prints the final JWT token to the user.
|
||||
func printToken(c *requestContext, w http.ResponseWriter) (int, error) {
|
||||
func printToken(c *RequestContext, w http.ResponseWriter) (int, error) {
|
||||
// Creates a copy of the user and removes it password
|
||||
// hash so it never arrives to the user.
|
||||
u := User{}
|
||||
u = *c.us
|
||||
u = *c.User
|
||||
u.Password = ""
|
||||
|
||||
// Builds the claims.
|
||||
|
@ -77,7 +77,7 @@ func printToken(c *requestContext, w http.ResponseWriter) (int, error) {
|
|||
|
||||
// Creates the token and signs it.
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
string, err := token.SignedString(c.fm.key)
|
||||
string, err := token.SignedString(c.FM.key)
|
||||
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
|
@ -106,9 +106,9 @@ func (e extractor) ExtractToken(r *http.Request) (string, error) {
|
|||
|
||||
// validateAuth is used to validate the authentication and returns the
|
||||
// User if it is valid.
|
||||
func validateAuth(c *requestContext, r *http.Request) (bool, *User) {
|
||||
func validateAuth(c *RequestContext, r *http.Request) (bool, *User) {
|
||||
keyFunc := func(token *jwt.Token) (interface{}, error) {
|
||||
return c.fm.key, nil
|
||||
return c.FM.key, nil
|
||||
}
|
||||
var claims claims
|
||||
token, err := request.ParseFromRequestWithClaims(r,
|
||||
|
@ -121,12 +121,12 @@ func validateAuth(c *requestContext, r *http.Request) (bool, *User) {
|
|||
return false, nil
|
||||
}
|
||||
|
||||
u, ok := c.fm.Users[claims.User.Username]
|
||||
u, ok := c.FM.Users[claims.User.Username]
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
c.us = u
|
||||
c.User = u
|
||||
return true, u
|
||||
}
|
||||
|
||||
|
|
|
@ -1 +1,172 @@
|
|||
package hugo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
rice "github.com/GeertJohan/go.rice"
|
||||
"github.com/hacdias/filemanager"
|
||||
"github.com/hacdias/filemanager/variables"
|
||||
"github.com/robfig/cron"
|
||||
)
|
||||
|
||||
type hugo struct {
|
||||
// Website root
|
||||
Root string
|
||||
// Public folder
|
||||
Public string
|
||||
// Hugo executable path
|
||||
Exe string
|
||||
// Hugo arguments
|
||||
Args []string
|
||||
// Indicates if we should clean public before a new publish.
|
||||
CleanPublic bool
|
||||
// A map of events to a slice of commands.
|
||||
Commands map[string][]string
|
||||
|
||||
// AllowPublish
|
||||
|
||||
javascript string
|
||||
}
|
||||
|
||||
func (h hugo) BeforeAPI(c *filemanager.RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
// If we are using the 'magic url' for the settings, we should redirect the
|
||||
// request for the acutual path.
|
||||
if r.URL.Path == "/settings/" || r.URL.Path == "/settings" {
|
||||
var frontmatter string
|
||||
var err error
|
||||
|
||||
if _, err = os.Stat(filepath.Join(h.Root, "config.yaml")); err == nil {
|
||||
frontmatter = "yaml"
|
||||
}
|
||||
|
||||
if _, err = os.Stat(filepath.Join(h.Root, "config.json")); err == nil {
|
||||
frontmatter = "json"
|
||||
}
|
||||
|
||||
if _, err = os.Stat(filepath.Join(h.Root, "config.toml")); err == nil {
|
||||
frontmatter = "toml"
|
||||
}
|
||||
|
||||
r.URL.Path = "/config." + frontmatter
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// From here on, we only care about 'hugo' router so we can bypass
|
||||
// the others.
|
||||
if c.Router != "hugo" {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
if r.Method != http.MethodPost {
|
||||
return http.StatusMethodNotAllowed, nil
|
||||
}
|
||||
|
||||
if r.Header.Get("Archetype") != "" {
|
||||
filename := filepath.Join(string(c.User.FileSystem), r.URL.Path)
|
||||
filename = filepath.Clean(filename)
|
||||
filename = strings.TrimPrefix(filename, "/")
|
||||
archetype := r.Header.Get("archetype")
|
||||
|
||||
if !strings.HasSuffix(filename, ".md") && !strings.HasSuffix(filename, ".markdown") {
|
||||
return http.StatusBadRequest, errors.New("Your file must be markdown")
|
||||
}
|
||||
|
||||
args := []string{"new", filename, "--kind", archetype}
|
||||
|
||||
if err := Run(h.Exe, args, h.Root); err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
w.Header().Set("Location", "/files/content/"+filename)
|
||||
return http.StatusCreated, nil
|
||||
}
|
||||
|
||||
if r.Header.Get("Regenerate") == "true" {
|
||||
// Before save command handler.
|
||||
path := filepath.Clean(filepath.Join(string(c.User.FileSystem), r.URL.Path))
|
||||
if err := c.FM.Runner("before_publish", path); err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
args := []string{"undraft", path}
|
||||
if err := Run(h.Exe, args, h.Root); err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
h.run(false)
|
||||
|
||||
if err := c.FM.Runner("before_publish", path); err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
return http.StatusOK, nil
|
||||
}
|
||||
|
||||
if r.Header.Get("Schedule") != "" {
|
||||
return h.schedule(c, w, r)
|
||||
}
|
||||
|
||||
return http.StatusNotFound, nil
|
||||
}
|
||||
|
||||
func (h hugo) AfterAPI(c *filemanager.RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (h hugo) JavaScript() string {
|
||||
return rice.MustFindBox("./").MustString("hugo.js")
|
||||
}
|
||||
|
||||
// run runs Hugo with the define arguments.
|
||||
func (h hugo) run(force bool) {
|
||||
// If the CleanPublic option is enabled, clean it.
|
||||
if h.CleanPublic {
|
||||
os.RemoveAll(h.Public)
|
||||
}
|
||||
|
||||
// Prevent running if watching is enabled
|
||||
if b, pos := variables.StringInSlice("--watch", h.Args); b && !force {
|
||||
if len(h.Args) > pos && h.Args[pos+1] != "false" {
|
||||
return
|
||||
}
|
||||
|
||||
if len(h.Args) == pos+1 {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := Run(h.Exe, h.Args, h.Root); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
// schedule schedules a post to be published later.
|
||||
func (h hugo) schedule(c *filemanager.RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
t, err := time.Parse("2006-01-02T15:04", r.Header.Get("Schedule"))
|
||||
path := filepath.Join(string(c.User.FileSystem), r.URL.Path)
|
||||
path = filepath.Clean(path)
|
||||
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
scheduler := cron.New()
|
||||
scheduler.AddFunc(t.Format("05 04 15 02 01 *"), func() {
|
||||
args := []string{"undraft", path}
|
||||
if err := Run(h.Exe, args, h.Root); err != nil {
|
||||
log.Printf(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.run(false)
|
||||
})
|
||||
|
||||
scheduler.Start()
|
||||
return http.StatusOK, nil
|
||||
}
|
||||
|
|
|
@ -1,42 +1,167 @@
|
|||
'use strict'
|
||||
'use strict';
|
||||
|
||||
if (window.plugins === undefined || window.plugins === null) {
|
||||
window.plugins = []
|
||||
}
|
||||
(function () {
|
||||
if (window.plugins === undefined || window.plugins === null) {
|
||||
window.plugins = []
|
||||
}
|
||||
|
||||
window.plugins.append({
|
||||
sidebar: [
|
||||
{
|
||||
click: function (event) {
|
||||
console.log('evt')
|
||||
},
|
||||
icon: 'settings_applications',
|
||||
name: 'Settings'
|
||||
let regenerate = function (data, url) {
|
||||
url = data.api.removePrefix(url)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let request = new window.XMLHttpRequest()
|
||||
request.open('POST', `${data.store.state.baseURL}/api/hugo${url}`, true)
|
||||
request.setRequestHeader('Authorization', `Bearer ${data.store.state.jwt}`)
|
||||
request.setRequestHeader('Regenerate', 'true')
|
||||
|
||||
request.onload = () => {
|
||||
if (request.status === 200) {
|
||||
resolve()
|
||||
} else {
|
||||
reject(request.responseText)
|
||||
}
|
||||
}
|
||||
|
||||
request.onerror = (error) => reject(error)
|
||||
request.send()
|
||||
})
|
||||
}
|
||||
|
||||
let newArchetype = function (data, file, type) {
|
||||
file = data.api.removePrefix(file)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let request = new window.XMLHttpRequest()
|
||||
request.open('POST', `${data.store.state.baseURL}/api/hugo${file}`, true)
|
||||
request.setRequestHeader('Authorization', `Bearer ${data.store.state.jwt}`)
|
||||
request.setRequestHeader('Archetype', encodeURIComponent(type))
|
||||
|
||||
request.onload = () => {
|
||||
if (request.status === 200) {
|
||||
resolve(request.getResponseHeader('Location'))
|
||||
} else {
|
||||
reject(request.responseText)
|
||||
}
|
||||
}
|
||||
|
||||
request.onerror = (error) => reject(error)
|
||||
request.send()
|
||||
})
|
||||
}
|
||||
|
||||
window.plugins.push({
|
||||
name: 'hugo',
|
||||
credits: 'With a flavour of <a rel="noopener noreferrer" href="https://github.com/hacdias/caddy-hugo">Hugo</a>.',
|
||||
header: {
|
||||
visible: [
|
||||
{
|
||||
if: function (data, route) {
|
||||
return (data.store.state.req.kind === 'editor' &&
|
||||
!data.store.state.loading &&
|
||||
data.store.state.req.metadata !== undefined &&
|
||||
data.store.state.req.metadata !== null &&
|
||||
data.store.state.user.allowEdit)
|
||||
// TODO: add allowPublish
|
||||
},
|
||||
click: function (event, data, route) {
|
||||
event.preventDefault()
|
||||
document.getElementById('save-button').click()
|
||||
// TODO: wait for save to finish?
|
||||
data.buttons.loading('publish')
|
||||
|
||||
regenerate(data, route.path)
|
||||
.then(() => {
|
||||
data.buttons.done('publish')
|
||||
data.store.commit('setReload', true)
|
||||
})
|
||||
.catch((error) => {
|
||||
data.buttons.done('publish')
|
||||
data.store.commit('showError', error)
|
||||
})
|
||||
},
|
||||
id: 'publish-button',
|
||||
icon: 'send',
|
||||
name: 'Publish'
|
||||
}
|
||||
],
|
||||
hidden: [
|
||||
{
|
||||
if: function (data, route) {
|
||||
return (data.store.state.req.kind === 'editor' &&
|
||||
!data.store.state.loading &&
|
||||
data.store.state.req.metadata !== undefined &&
|
||||
data.store.state.req.metadata !== null)
|
||||
},
|
||||
click: function (event, data, route) {
|
||||
console.log('Schedule')
|
||||
},
|
||||
id: 'schedule-button',
|
||||
icon: 'alarm',
|
||||
name: 'Schedule'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
click: function (event) {
|
||||
console.log('evt')
|
||||
sidebar: [
|
||||
{
|
||||
click: function (event, data, route) {
|
||||
data.router.push({ path: '/files/settings' })
|
||||
},
|
||||
icon: 'settings_applications',
|
||||
name: 'Settings'
|
||||
},
|
||||
icon: 'remove_red_eye',
|
||||
name: 'Preview'
|
||||
}
|
||||
]
|
||||
})
|
||||
{
|
||||
click: function (event, data, route) {
|
||||
data.store.commit('showHover', 'new-archetype')
|
||||
},
|
||||
icon: 'merge_type',
|
||||
name: 'Hugo new'
|
||||
},
|
||||
{
|
||||
click: function (event, data, route) {
|
||||
console.log('evt')
|
||||
},
|
||||
icon: 'remove_red_eye',
|
||||
name: 'Preview'
|
||||
}
|
||||
],
|
||||
prompts: [
|
||||
{
|
||||
name: 'new-archetype',
|
||||
title: 'New file',
|
||||
description: 'Create a new post based on an archetype. Your file will be created on content folder.',
|
||||
inputs: [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'file',
|
||||
placeholder: 'File name'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
name: 'archetype',
|
||||
placeholder: 'Archetype'
|
||||
}
|
||||
],
|
||||
ok: 'Create',
|
||||
submit: function (event, data, route) {
|
||||
event.preventDefault()
|
||||
|
||||
console.log(event)
|
||||
|
||||
/*
|
||||
{{ 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 }}
|
||||
*/
|
||||
let file = event.currentTarget.querySelector('[name="file"]').value
|
||||
let type = event.currentTarget.querySelector('[name="archetype"]').value
|
||||
if (type === '') type = 'default'
|
||||
|
||||
data.store.commit('closeHovers')
|
||||
|
||||
newArchetype(data, '/' + file, type)
|
||||
.then((url) => {
|
||||
data.router.push({ path: url })
|
||||
})
|
||||
.catch(error => {
|
||||
data.store.commit('showError', error)
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
})()
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
|
||||
hugo.schedule = function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
let date = document.getElementById('date').value;
|
||||
if(document.getElementById('publishDate')) {
|
||||
date = document.getElementById('publishDate').value;
|
||||
}
|
||||
|
||||
buttons.setLoading('publish');
|
||||
|
||||
let data = JSON.stringify(form2js(document.querySelector('form'))),
|
||||
headers = {
|
||||
'Kind': document.getElementById('editor').dataset.kind,
|
||||
'Schedule': 'true'
|
||||
};
|
||||
|
||||
webdav.put(window.location.pathname, data, headers)
|
||||
.then(() => {
|
||||
buttons.setDone('publish');
|
||||
})
|
||||
.catch(e => {
|
||||
console.log(e);
|
||||
buttons.setDone('publish', false)
|
||||
})
|
||||
}
|
|
@ -0,0 +1,176 @@
|
|||
package hugo
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/hacdias/filemanager"
|
||||
"github.com/mholt/caddy"
|
||||
"github.com/mholt/caddy/caddyhttp/httpserver"
|
||||
"golang.org/x/net/webdav"
|
||||
)
|
||||
|
||||
var (
|
||||
errHugoNotFound = errors.New("It seems that tou don't have 'hugo' on your PATH")
|
||||
)
|
||||
|
||||
// setup configures a new FileManager middleware instance.
|
||||
func setup(c *caddy.Controller) error {
|
||||
configs, err := parse(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
|
||||
return plugin{Configs: configs, Next: next}
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parse(c *caddy.Controller) ([]*filemanager.FileManager, error) {
|
||||
var (
|
||||
configs []*filemanager.FileManager
|
||||
)
|
||||
|
||||
for c.Next() {
|
||||
// hugo [directory] [admin] {
|
||||
// database path
|
||||
// }
|
||||
directory := "."
|
||||
admin := "/admin"
|
||||
database := ""
|
||||
|
||||
// Get the baseURL and baseScope
|
||||
args := c.RemainingArgs()
|
||||
|
||||
if len(args) == 1 {
|
||||
directory = args[0]
|
||||
}
|
||||
|
||||
if len(args) > 1 {
|
||||
admin = args[1]
|
||||
}
|
||||
|
||||
for c.NextBlock() {
|
||||
switch c.Val() {
|
||||
case "database":
|
||||
if !c.NextArg() {
|
||||
return nil, c.ArgErr()
|
||||
}
|
||||
|
||||
database = c.Val()
|
||||
}
|
||||
}
|
||||
|
||||
caddyConf := httpserver.GetConfig(c)
|
||||
|
||||
path := filepath.Join(caddy.AssetsPath(), "hugo")
|
||||
err := os.MkdirAll(path, 0700)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// if there is a database path and it is not absolute,
|
||||
// it will be relative to Caddy folder.
|
||||
if !filepath.IsAbs(database) && database != "" {
|
||||
database = filepath.Join(path, database)
|
||||
}
|
||||
|
||||
// If there is no database path on the settings,
|
||||
// store one in .caddy/hugo/name.db.
|
||||
if database == "" {
|
||||
// The name of the database is the hashed value of a string composed
|
||||
// by the host, address path and the baseurl of this File Manager
|
||||
// instance.
|
||||
hasher := md5.New()
|
||||
hasher.Write([]byte(caddyConf.Addr.Host + caddyConf.Addr.Path + admin))
|
||||
sha := hex.EncodeToString(hasher.Sum(nil))
|
||||
database = filepath.Join(path, sha+".db")
|
||||
|
||||
fmt.Println("[WARNING] A database is going to be created for your Hugo instace at " + database +
|
||||
". It is highly recommended that you set the 'database' option to '" + sha + ".db'\n")
|
||||
}
|
||||
|
||||
m, err := filemanager.New(database, filemanager.User{
|
||||
Username: "admin",
|
||||
Password: "admin",
|
||||
AllowCommands: true,
|
||||
AllowEdit: true,
|
||||
AllowNew: true,
|
||||
Commands: []string{"git", "svn", "hg"},
|
||||
Rules: []*filemanager.Rule{{
|
||||
Regex: true,
|
||||
Allow: false,
|
||||
Regexp: &filemanager.Regexp{Raw: "\\/\\..+"},
|
||||
}},
|
||||
CSS: "",
|
||||
FileSystem: webdav.Dir(directory),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Initialize the default settings for Hugo.
|
||||
hugo := &hugo{
|
||||
Root: directory,
|
||||
Public: filepath.Join(directory, "public"),
|
||||
Args: []string{},
|
||||
CleanPublic: true,
|
||||
Commands: map[string][]string{
|
||||
"before_publish": []string{},
|
||||
"after_publish": []string{},
|
||||
},
|
||||
}
|
||||
|
||||
// Try to find the Hugo executable path.
|
||||
if hugo.Exe, err = exec.LookPath("hugo"); err != nil {
|
||||
return nil, errHugoNotFound
|
||||
}
|
||||
|
||||
err = m.RegisterPlugin("hugo", hugo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m.SetBaseURL(admin)
|
||||
m.SetPrefixURL(strings.TrimSuffix(caddyConf.Addr.Path, "/"))
|
||||
configs = append(configs, m)
|
||||
}
|
||||
|
||||
return configs, nil
|
||||
}
|
||||
|
||||
// ServeHTTP determines if the request is for this plugin, and if all prerequisites are met.
|
||||
func (p plugin) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
for i := range p.Configs {
|
||||
// Checks if this Path should be handled by File Manager.
|
||||
if !httpserver.Path(r.URL.Path).Matches(p.Configs[i].BaseURL) {
|
||||
continue
|
||||
}
|
||||
|
||||
return p.Configs[i].ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
return p.Next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func init() {
|
||||
caddy.RegisterPlugin("hugo", caddy.Plugin{
|
||||
ServerType: "http",
|
||||
Action: setup,
|
||||
})
|
||||
}
|
||||
|
||||
type plugin struct {
|
||||
Next httpserver.Handler
|
||||
Configs []*filemanager.FileManager
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package hugo
|
||||
|
||||
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.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
}
|
|
@ -22,7 +22,7 @@ var (
|
|||
)
|
||||
|
||||
// command handles the requests for VCS related commands: git, svn and mercurial
|
||||
func command(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
func command(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
// Upgrades the connection to a websocket and checks for errors.
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
|
@ -51,7 +51,7 @@ func command(c *requestContext, w http.ResponseWriter, r *http.Request) (int, er
|
|||
// Check if the command is allowed
|
||||
allowed := false
|
||||
|
||||
for _, cmd := range c.us.Commands {
|
||||
for _, cmd := range c.User.Commands {
|
||||
if cmd == command[0] {
|
||||
allowed = true
|
||||
}
|
||||
|
@ -77,7 +77,7 @@ func command(c *requestContext, w http.ResponseWriter, r *http.Request) (int, er
|
|||
}
|
||||
|
||||
// Gets the path and initializes a buffer.
|
||||
path := string(c.us.FileSystem) + "/" + r.URL.Path
|
||||
path := string(c.User.FileSystem) + "/" + r.URL.Path
|
||||
path = filepath.Clean(path)
|
||||
buff := new(bytes.Buffer)
|
||||
|
||||
|
|
14
download.go
14
download.go
|
@ -14,17 +14,17 @@ import (
|
|||
|
||||
// downloadHandler creates an archive in one of the supported formats (zip, tar,
|
||||
// tar.gz or tar.bz2) and sends it to be downloaded.
|
||||
func downloadHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
func downloadHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
query := r.URL.Query().Get("format")
|
||||
|
||||
if !c.fi.IsDir {
|
||||
if !c.FI.IsDir {
|
||||
if r.URL.Query().Get("inline") == "true" {
|
||||
w.Header().Set("Content-Disposition", "inline")
|
||||
} else {
|
||||
w.Header().Set("Content-Disposition", "attachment; filename="+c.fi.Name)
|
||||
w.Header().Set("Content-Disposition", "attachment; filename="+c.FI.Name)
|
||||
}
|
||||
|
||||
http.ServeFile(w, r, c.fi.Path)
|
||||
http.ServeFile(w, r, c.FI.Path)
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
|
@ -39,11 +39,11 @@ func downloadHandler(c *requestContext, w http.ResponseWriter, r *http.Request)
|
|||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
files = append(files, filepath.Join(c.fi.Path, name))
|
||||
files = append(files, filepath.Join(c.FI.Path, name))
|
||||
}
|
||||
|
||||
} else {
|
||||
files = append(files, c.fi.Path)
|
||||
files = append(files, c.FI.Path)
|
||||
}
|
||||
|
||||
if query == "true" {
|
||||
|
@ -89,7 +89,7 @@ func downloadHandler(c *requestContext, w http.ResponseWriter, r *http.Request)
|
|||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
name := c.fi.Name
|
||||
name := c.FI.Name
|
||||
if name == "." || name == "" {
|
||||
name = "download"
|
||||
}
|
||||
|
|
12
file.go
12
file.go
|
@ -108,10 +108,10 @@ func getInfo(url *url.URL, c *FileManager, u *User) (*file, error) {
|
|||
}
|
||||
|
||||
// getListing gets the information about a specific directory and its files.
|
||||
func (i *file) getListing(c *requestContext, r *http.Request) error {
|
||||
func (i *file) getListing(c *RequestContext, r *http.Request) error {
|
||||
// Gets the directory information using the Virtual File System of
|
||||
// the user configuration.
|
||||
f, err := c.us.FileSystem.OpenFile(context.TODO(), c.fi.VirtualPath, os.O_RDONLY, 0)
|
||||
f, err := c.User.FileSystem.OpenFile(context.TODO(), c.FI.VirtualPath, os.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -130,7 +130,7 @@ func (i *file) getListing(c *requestContext, r *http.Request) error {
|
|||
|
||||
for _, f := range files {
|
||||
name := f.Name()
|
||||
allowed := c.us.Allowed("/" + name)
|
||||
allowed := c.User.Allowed("/" + name)
|
||||
|
||||
if !allowed {
|
||||
continue
|
||||
|
@ -433,12 +433,14 @@ func editorLanguage(mode string) string {
|
|||
mode = "asciidoc"
|
||||
case "rst":
|
||||
mode = "rst"
|
||||
case "html", "htm":
|
||||
mode = "html"
|
||||
case "html", "htm", "xml":
|
||||
mode = "htmlmixed"
|
||||
case "js":
|
||||
mode = "javascript"
|
||||
case "go":
|
||||
mode = "golang"
|
||||
case "":
|
||||
mode = "text"
|
||||
}
|
||||
|
||||
return mode
|
||||
|
|
|
@ -49,7 +49,7 @@ type FileManager struct {
|
|||
Commands map[string][]string
|
||||
|
||||
// The plugins that have been plugged in.
|
||||
Plugins []*Plugin
|
||||
Plugins map[string]Plugin
|
||||
}
|
||||
|
||||
// Command is a command function.
|
||||
|
@ -111,9 +111,13 @@ type Regexp struct {
|
|||
}
|
||||
|
||||
// Plugin is a File Manager plugin.
|
||||
type Plugin struct {
|
||||
type Plugin interface {
|
||||
// The JavaScript that will be injected into the main page.
|
||||
JavaScript string
|
||||
JavaScript() string
|
||||
// If the Plugin returns (0, nil), the executation of File Manager will procced as usual.
|
||||
// Otherwise it will stop.
|
||||
BeforeAPI(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error)
|
||||
AfterAPI(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error)
|
||||
}
|
||||
|
||||
// DefaultUser is used on New, when no 'base' user is provided.
|
||||
|
@ -134,13 +138,13 @@ var DefaultUser = User{
|
|||
// exists, it will load the users from there. Otherwise, a new user
|
||||
// will be created using the 'base' variable. The 'base' User should
|
||||
// not have the Password field hashed.
|
||||
// TODO: should it ask for a baseURL on New????
|
||||
func New(database string, base User) (*FileManager, error) {
|
||||
// Creates a new File Manager instance with the Users
|
||||
// map and Assets box.
|
||||
m := &FileManager{
|
||||
Users: map[string]*User{},
|
||||
assets: rice.MustFindBox("./assets/dist"),
|
||||
Users: map[string]*User{},
|
||||
assets: rice.MustFindBox("./assets/dist"),
|
||||
Plugins: map[string]Plugin{},
|
||||
}
|
||||
|
||||
// Tries to open a database on the location provided. This
|
||||
|
@ -240,13 +244,33 @@ func (m *FileManager) SetBaseURL(url string) {
|
|||
m.BaseURL = strings.TrimSuffix(url, "/")
|
||||
}
|
||||
|
||||
// RegisterPlugin registers a plugin to a File Manager instance and
|
||||
// loads its options from the database.
|
||||
func (m *FileManager) RegisterPlugin(name string, plugin Plugin) error {
|
||||
if _, ok := m.Plugins[name]; ok {
|
||||
return errors.New("Plugin already registred")
|
||||
}
|
||||
|
||||
err := m.db.Get("plugins", name, &plugin)
|
||||
if err != nil && err == storm.ErrNotFound {
|
||||
err = m.db.Set("plugins", name, plugin)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.Plugins[name] = plugin
|
||||
return nil
|
||||
}
|
||||
|
||||
// ServeHTTP determines if the request is for this plugin, and if all prerequisites are met.
|
||||
func (m *FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
// TODO: Handle errors here and make it compatible with http.Handler
|
||||
code, err := serveHTTP(&requestContext{
|
||||
fm: m,
|
||||
us: nil,
|
||||
fi: nil,
|
||||
code, err := serveHTTP(&RequestContext{
|
||||
FM: m,
|
||||
User: nil,
|
||||
FI: nil,
|
||||
}, w, r)
|
||||
|
||||
if code != 0 && err != nil {
|
||||
|
|
103
http.go
103
http.go
|
@ -8,20 +8,22 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
// requestContext contains the needed information to make handlers work.
|
||||
type requestContext struct {
|
||||
us *User
|
||||
fm *FileManager
|
||||
fi *file
|
||||
// RequestContext contains the needed information to make handlers work.
|
||||
type RequestContext struct {
|
||||
User *User
|
||||
FM *FileManager
|
||||
FI *file
|
||||
// On API handlers, Router is the APi handler we want. TODO: review this
|
||||
Router string
|
||||
}
|
||||
|
||||
// serveHTTP is the main entry point of this HTML application.
|
||||
func serveHTTP(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
func serveHTTP(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
// Checks if the URL contains the baseURL and strips it. Otherwise, it just
|
||||
// returns a 404 error because we're not supposed to be here!
|
||||
p := strings.TrimPrefix(r.URL.Path, c.fm.BaseURL)
|
||||
p := strings.TrimPrefix(r.URL.Path, c.FM.BaseURL)
|
||||
|
||||
if len(p) >= len(r.URL.Path) && c.fm.BaseURL != "" {
|
||||
if len(p) >= len(r.URL.Path) && c.FM.BaseURL != "" {
|
||||
return http.StatusNotFound, nil
|
||||
}
|
||||
|
||||
|
@ -32,9 +34,9 @@ func serveHTTP(c *requestContext, w http.ResponseWriter, r *http.Request) (int,
|
|||
if r.URL.Path == "/sw.js" {
|
||||
return renderFile(
|
||||
w,
|
||||
c.fm.assets.MustString(r.URL.Path),
|
||||
c.FM.assets.MustString(r.URL.Path),
|
||||
"application/javascript",
|
||||
c.fm.RootURL(),
|
||||
c,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -63,29 +65,29 @@ func serveHTTP(c *requestContext, w http.ResponseWriter, r *http.Request) (int,
|
|||
|
||||
return renderFile(
|
||||
w,
|
||||
c.fm.assets.MustString("index.html"),
|
||||
c.FM.assets.MustString("index.html"),
|
||||
"text/html",
|
||||
c.fm.RootURL(),
|
||||
c,
|
||||
)
|
||||
}
|
||||
|
||||
// staticHandler handles the static assets path.
|
||||
func staticHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
func staticHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
if r.URL.Path != "/static/manifest.json" {
|
||||
http.FileServer(c.fm.assets.HTTPBox()).ServeHTTP(w, r)
|
||||
http.FileServer(c.FM.assets.HTTPBox()).ServeHTTP(w, r)
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
return renderFile(
|
||||
w,
|
||||
c.fm.assets.MustString(r.URL.Path),
|
||||
c.FM.assets.MustString(r.URL.Path),
|
||||
"application/json",
|
||||
c.fm.RootURL(),
|
||||
c,
|
||||
)
|
||||
}
|
||||
|
||||
// apiHandler is the main entry point for the /api endpoint.
|
||||
func apiHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
func apiHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
if r.URL.Path == "/auth/get" {
|
||||
return authHandler(c, w, r)
|
||||
}
|
||||
|
@ -99,46 +101,66 @@ func apiHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int,
|
|||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
var router string
|
||||
router, r.URL.Path = cleanURL(r.URL.Path)
|
||||
c.Router, r.URL.Path = cleanURL(r.URL.Path)
|
||||
|
||||
if !c.us.Allowed(r.URL.Path) {
|
||||
if !c.User.Allowed(r.URL.Path) {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
if router == "checksum" || router == "download" {
|
||||
for _, p := range c.FM.Plugins {
|
||||
code, err := p.BeforeAPI(c, w, r)
|
||||
if code != 0 || err != nil {
|
||||
return code, err
|
||||
}
|
||||
}
|
||||
|
||||
if c.Router == "checksum" || c.Router == "download" {
|
||||
var err error
|
||||
c.fi, err = getInfo(r.URL, c.fm, c.us)
|
||||
c.FI, err = getInfo(r.URL, c.FM, c.User)
|
||||
if err != nil {
|
||||
return errorToHTTP(err, false), err
|
||||
}
|
||||
}
|
||||
|
||||
switch router {
|
||||
var code int
|
||||
var err error
|
||||
|
||||
switch c.Router {
|
||||
case "download":
|
||||
return downloadHandler(c, w, r)
|
||||
code, err = downloadHandler(c, w, r)
|
||||
case "checksum":
|
||||
return checksumHandler(c, w, r)
|
||||
code, err = checksumHandler(c, w, r)
|
||||
case "command":
|
||||
return command(c, w, r)
|
||||
code, err = command(c, w, r)
|
||||
case "search":
|
||||
return search(c, w, r)
|
||||
code, err = search(c, w, r)
|
||||
case "resource":
|
||||
return resourceHandler(c, w, r)
|
||||
code, err = resourceHandler(c, w, r)
|
||||
case "users":
|
||||
return usersHandler(c, w, r)
|
||||
code, err = usersHandler(c, w, r)
|
||||
case "commands":
|
||||
return commandsHandler(c, w, r)
|
||||
code, err = commandsHandler(c, w, r)
|
||||
}
|
||||
|
||||
return http.StatusNotFound, nil
|
||||
if code >= 300 || err != nil {
|
||||
return code, err
|
||||
}
|
||||
|
||||
for _, p := range c.FM.Plugins {
|
||||
code, err := p.AfterAPI(c, w, r)
|
||||
if code != 0 || err != nil {
|
||||
return code, err
|
||||
}
|
||||
}
|
||||
|
||||
return code, err
|
||||
}
|
||||
|
||||
// serveChecksum calculates the hash of a file. Supports MD5, SHA1, SHA256 and SHA512.
|
||||
func checksumHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
func checksumHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
query := r.URL.Query().Get("algo")
|
||||
|
||||
val, err := c.fi.Checksum(query)
|
||||
val, err := c.FI.Checksum(query)
|
||||
if err == errInvalidOption {
|
||||
return http.StatusBadRequest, err
|
||||
} else if err != nil {
|
||||
|
@ -167,11 +189,20 @@ func cleanURL(path string) (string, string) {
|
|||
}
|
||||
|
||||
// renderFile renders a file using a template with some needed variables.
|
||||
func renderFile(w http.ResponseWriter, file string, contentType string, baseURL string) (int, error) {
|
||||
tpl := template.Must(template.New("file").Parse(file))
|
||||
func renderFile(w http.ResponseWriter, file string, contentType string, c *RequestContext) (int, error) {
|
||||
functions := template.FuncMap{
|
||||
"JS": func(s string) template.JS {
|
||||
return template.JS(s)
|
||||
},
|
||||
}
|
||||
|
||||
tpl := template.Must(template.New("file").Funcs(functions).Parse(file))
|
||||
w.Header().Set("Content-Type", contentType+"; charset=utf-8")
|
||||
|
||||
err := tpl.Execute(w, map[string]string{"BaseURL": baseURL})
|
||||
err := tpl.Execute(w, map[string]interface{}{
|
||||
"BaseURL": c.FM.RootURL(),
|
||||
"Plugins": c.FM.Plugins,
|
||||
})
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
|
38
resource.go
38
resource.go
|
@ -12,7 +12,7 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
func resourceHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
func resourceHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
return resourceGetHandler(c, w, r)
|
||||
|
@ -20,8 +20,8 @@ func resourceHandler(c *requestContext, w http.ResponseWriter, r *http.Request)
|
|||
return resourceDeleteHandler(c, w, r)
|
||||
case http.MethodPut:
|
||||
// Before save command handler.
|
||||
path := filepath.Join(string(c.us.FileSystem), r.URL.Path)
|
||||
if err := c.fm.Runner("before_save", path); err != nil {
|
||||
path := filepath.Join(string(c.User.FileSystem), r.URL.Path)
|
||||
if err := c.FM.Runner("before_save", path); err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@ func resourceHandler(c *requestContext, w http.ResponseWriter, r *http.Request)
|
|||
}
|
||||
|
||||
// After save command handler.
|
||||
if err := c.fm.Runner("after_save", path); err != nil {
|
||||
if err := c.FM.Runner("after_save", path); err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
|
@ -45,9 +45,9 @@ func resourceHandler(c *requestContext, w http.ResponseWriter, r *http.Request)
|
|||
return http.StatusNotImplemented, nil
|
||||
}
|
||||
|
||||
func resourceGetHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
func resourceGetHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
// Obtains the information of the directory/file.
|
||||
f, err := getInfo(r.URL, c.fm, c.us)
|
||||
f, err := getInfo(r.URL, c.FM, c.User)
|
||||
if err != nil {
|
||||
return errorToHTTP(err, false), err
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ func resourceGetHandler(c *requestContext, w http.ResponseWriter, r *http.Reques
|
|||
|
||||
// If it is a dir, go and serve the listing.
|
||||
if f.IsDir {
|
||||
c.fi = f
|
||||
c.FI = f
|
||||
return listingHandler(c, w, r)
|
||||
}
|
||||
|
||||
|
@ -71,7 +71,7 @@ func resourceGetHandler(c *requestContext, w http.ResponseWriter, r *http.Reques
|
|||
|
||||
// If it can't be edited or the user isn't allowed to,
|
||||
// serve it as a listing, with a preview of the file.
|
||||
if !f.CanBeEdited() || !c.us.AllowEdit {
|
||||
if !f.CanBeEdited() || !c.User.AllowEdit {
|
||||
f.Kind = "preview"
|
||||
} else {
|
||||
// Otherwise, we just bring the editor in!
|
||||
|
@ -86,8 +86,8 @@ func resourceGetHandler(c *requestContext, w http.ResponseWriter, r *http.Reques
|
|||
return renderJSON(w, f)
|
||||
}
|
||||
|
||||
func listingHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
f := c.fi
|
||||
func listingHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
f := c.FI
|
||||
f.Kind = "listing"
|
||||
|
||||
err := f.getListing(c, r)
|
||||
|
@ -97,7 +97,7 @@ func listingHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (
|
|||
|
||||
listing := f.listing
|
||||
|
||||
cookieScope := c.fm.RootURL()
|
||||
cookieScope := c.FM.RootURL()
|
||||
if cookieScope == "" {
|
||||
cookieScope = "/"
|
||||
}
|
||||
|
@ -114,14 +114,14 @@ func listingHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (
|
|||
return renderJSON(w, f)
|
||||
}
|
||||
|
||||
func resourceDeleteHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
func resourceDeleteHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
// Prevent the removal of the root directory.
|
||||
if r.URL.Path == "/" {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
// Remove the file or folder.
|
||||
err := c.us.FileSystem.RemoveAll(context.TODO(), r.URL.Path)
|
||||
err := c.User.FileSystem.RemoveAll(context.TODO(), r.URL.Path)
|
||||
if err != nil {
|
||||
return errorToHTTP(err, true), err
|
||||
}
|
||||
|
@ -129,7 +129,7 @@ func resourceDeleteHandler(c *requestContext, w http.ResponseWriter, r *http.Req
|
|||
return http.StatusOK, nil
|
||||
}
|
||||
|
||||
func resourcePostPutHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
func resourcePostPutHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
// Checks if the current request is for a directory and not a file.
|
||||
if strings.HasSuffix(r.URL.Path, "/") {
|
||||
// If the method is PUT, we return 405 Method not Allowed, because
|
||||
|
@ -139,7 +139,7 @@ func resourcePostPutHandler(c *requestContext, w http.ResponseWriter, r *http.Re
|
|||
}
|
||||
|
||||
// Otherwise we try to create the directory.
|
||||
err := c.us.FileSystem.Mkdir(context.TODO(), r.URL.Path, 0666)
|
||||
err := c.User.FileSystem.Mkdir(context.TODO(), r.URL.Path, 0666)
|
||||
return errorToHTTP(err, false), err
|
||||
}
|
||||
|
||||
|
@ -147,13 +147,13 @@ func resourcePostPutHandler(c *requestContext, w http.ResponseWriter, r *http.Re
|
|||
// desirable to override an already existent file. Thus, we check
|
||||
// if the file already exists. If so, we just return a 409 Conflict.
|
||||
if r.Method == http.MethodPost {
|
||||
if _, err := c.us.FileSystem.Stat(context.TODO(), r.URL.Path); err == nil {
|
||||
if _, err := c.User.FileSystem.Stat(context.TODO(), r.URL.Path); err == nil {
|
||||
return http.StatusConflict, errors.New("There is already a file on that path")
|
||||
}
|
||||
}
|
||||
|
||||
// Create/Open the file.
|
||||
f, err := c.us.FileSystem.OpenFile(context.TODO(), r.URL.Path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
f, err := c.User.FileSystem.OpenFile(context.TODO(), r.URL.Path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
defer f.Close()
|
||||
|
||||
if err != nil {
|
||||
|
@ -178,7 +178,7 @@ func resourcePostPutHandler(c *requestContext, w http.ResponseWriter, r *http.Re
|
|||
return http.StatusOK, nil
|
||||
}
|
||||
|
||||
func resourcePatchHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
func resourcePatchHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
dst := r.Header.Get("Destination")
|
||||
dst, err := url.QueryUnescape(dst)
|
||||
if err != nil {
|
||||
|
@ -191,7 +191,7 @@ func resourcePatchHandler(c *requestContext, w http.ResponseWriter, r *http.Requ
|
|||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
err = c.us.FileSystem.Rename(context.TODO(), src, dst)
|
||||
err = c.User.FileSystem.Rename(context.TODO(), src, dst)
|
||||
return errorToHTTP(err, true), err
|
||||
}
|
||||
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
7a4fe6b9656cc83d0749fefa1346a4aa5f915df0
|
|
@ -43,7 +43,7 @@ func parseSearch(value string) *searchOptions {
|
|||
}
|
||||
|
||||
// search searches for a file or directory.
|
||||
func search(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
func search(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
// Upgrades the connection to a websocket and checks for errors.
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
|
@ -73,7 +73,7 @@ func search(c *requestContext, w http.ResponseWriter, r *http.Request) (int, err
|
|||
search = parseSearch(value)
|
||||
scope := strings.TrimPrefix(r.URL.Path, "/")
|
||||
scope = "/" + scope
|
||||
scope = string(c.us.FileSystem) + scope
|
||||
scope = string(c.User.FileSystem) + scope
|
||||
scope = strings.Replace(scope, "\\", "/", -1)
|
||||
scope = filepath.Clean(scope)
|
||||
|
||||
|
@ -93,7 +93,7 @@ func search(c *requestContext, w http.ResponseWriter, r *http.Request) (int, err
|
|||
}
|
||||
|
||||
if strings.Contains(path, term) {
|
||||
if !c.us.Allowed(path) {
|
||||
if !c.User.Allowed(path) {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
16
settings.go
16
settings.go
|
@ -6,7 +6,7 @@ import (
|
|||
"net/http"
|
||||
)
|
||||
|
||||
func commandsHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
func commandsHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
return commandsGetHandler(c, w, r)
|
||||
|
@ -17,16 +17,16 @@ func commandsHandler(c *requestContext, w http.ResponseWriter, r *http.Request)
|
|||
return http.StatusMethodNotAllowed, nil
|
||||
}
|
||||
|
||||
func commandsGetHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
if !c.us.Admin {
|
||||
func commandsGetHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
if !c.User.Admin {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
return renderJSON(w, c.fm.Commands)
|
||||
return renderJSON(w, c.FM.Commands)
|
||||
}
|
||||
|
||||
func commandsPutHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
if !c.us.Admin {
|
||||
func commandsPutHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
if !c.User.Admin {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
|
@ -42,10 +42,10 @@ func commandsPutHandler(c *requestContext, w http.ResponseWriter, r *http.Reques
|
|||
return http.StatusBadRequest, errors.New("Invalid JSON")
|
||||
}
|
||||
|
||||
if err := c.fm.db.Set("config", "commands", commands); err != nil {
|
||||
if err := c.FM.db.Set("config", "commands", commands); err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
c.fm.Commands = commands
|
||||
c.FM.Commands = commands
|
||||
return http.StatusOK, nil
|
||||
}
|
||||
|
|
46
users.go
46
users.go
|
@ -11,7 +11,7 @@ import (
|
|||
"github.com/asdine/storm"
|
||||
)
|
||||
|
||||
func usersHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
func usersHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
return usersGetHandler(c, w, r)
|
||||
|
@ -29,8 +29,8 @@ func usersHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (in
|
|||
// usersGetHandler is used to handle the GET requests for /api/users. It can print a list
|
||||
// of users or a specific user. The password hash is always removed before being sent to the
|
||||
// client.
|
||||
func usersGetHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
if !c.us.Admin {
|
||||
func usersGetHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
if !c.User.Admin {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
|
@ -38,7 +38,7 @@ func usersGetHandler(c *requestContext, w http.ResponseWriter, r *http.Request)
|
|||
if r.URL.Path == "/" {
|
||||
users := []User{}
|
||||
|
||||
for _, user := range c.fm.Users {
|
||||
for _, user := range c.FM.Users {
|
||||
// Copies the user and removes the password.
|
||||
u := *user
|
||||
u.Password = ""
|
||||
|
@ -62,7 +62,7 @@ func usersGetHandler(c *requestContext, w http.ResponseWriter, r *http.Request)
|
|||
}
|
||||
|
||||
// Searches for the user and prints the one who matches.
|
||||
for _, user := range c.fm.Users {
|
||||
for _, user := range c.FM.Users {
|
||||
if user.ID != id {
|
||||
continue
|
||||
}
|
||||
|
@ -76,8 +76,8 @@ func usersGetHandler(c *requestContext, w http.ResponseWriter, r *http.Request)
|
|||
return http.StatusNotFound, nil
|
||||
}
|
||||
|
||||
func usersPostHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
if !c.us.Admin {
|
||||
func usersPostHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
if !c.User.Admin {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
|
@ -128,7 +128,7 @@ func usersPostHandler(c *requestContext, w http.ResponseWriter, r *http.Request)
|
|||
u.Password = pw
|
||||
|
||||
// Saves the user to the database.
|
||||
err = c.fm.db.Save(&u)
|
||||
err = c.FM.db.Save(&u)
|
||||
if err == storm.ErrAlreadyExists {
|
||||
return http.StatusConflict, err
|
||||
}
|
||||
|
@ -138,7 +138,7 @@ func usersPostHandler(c *requestContext, w http.ResponseWriter, r *http.Request)
|
|||
}
|
||||
|
||||
// Saves the user to the memory.
|
||||
c.fm.Users[u.Username] = &u
|
||||
c.FM.Users[u.Username] = &u
|
||||
|
||||
// Set the Location header and return.
|
||||
w.Header().Set("Location", "/users/"+strconv.Itoa(u.ID))
|
||||
|
@ -146,8 +146,8 @@ func usersPostHandler(c *requestContext, w http.ResponseWriter, r *http.Request)
|
|||
return 0, nil
|
||||
}
|
||||
|
||||
func usersDeleteHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
if !c.us.Admin {
|
||||
func usersDeleteHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
if !c.User.Admin {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
|
@ -165,7 +165,7 @@ func usersDeleteHandler(c *requestContext, w http.ResponseWriter, r *http.Reques
|
|||
return http.StatusNotFound, err
|
||||
}
|
||||
|
||||
err = c.fm.db.DeleteStruct(&User{ID: id})
|
||||
err = c.FM.db.DeleteStruct(&User{ID: id})
|
||||
if err == storm.ErrNotFound {
|
||||
return http.StatusNotFound, err
|
||||
}
|
||||
|
@ -174,17 +174,17 @@ func usersDeleteHandler(c *requestContext, w http.ResponseWriter, r *http.Reques
|
|||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
for _, user := range c.fm.Users {
|
||||
for _, user := range c.FM.Users {
|
||||
if user.ID == id {
|
||||
delete(c.fm.Users, user.Username)
|
||||
delete(c.FM.Users, user.Username)
|
||||
}
|
||||
}
|
||||
|
||||
return http.StatusOK, nil
|
||||
}
|
||||
|
||||
func usersPutHandler(c *requestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
if !c.us.Admin && !(r.URL.Path == "/change-password" || r.URL.Path == "/change-css") {
|
||||
func usersPutHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
if !c.User.Admin && !(r.URL.Path == "/change-password" || r.URL.Path == "/change-css") {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
|
@ -225,8 +225,8 @@ func usersPutHandler(c *requestContext, w http.ResponseWriter, r *http.Request)
|
|||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
c.us.Password = pw
|
||||
err = c.fm.db.UpdateField(&User{ID: c.us.ID}, "Password", pw)
|
||||
c.User.Password = pw
|
||||
err = c.FM.db.UpdateField(&User{ID: c.User.ID}, "Password", pw)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
@ -235,8 +235,8 @@ func usersPutHandler(c *requestContext, w http.ResponseWriter, r *http.Request)
|
|||
}
|
||||
|
||||
if sid == "change-css" {
|
||||
c.us.CSS = u.CSS
|
||||
err = c.fm.db.UpdateField(&User{ID: c.us.ID}, "CSS", u.CSS)
|
||||
c.User.CSS = u.CSS
|
||||
err = c.FM.db.UpdateField(&User{ID: c.User.ID}, "CSS", u.CSS)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
@ -259,7 +259,7 @@ func usersPutHandler(c *requestContext, w http.ResponseWriter, r *http.Request)
|
|||
u.Commands = []string{}
|
||||
}
|
||||
|
||||
ouser, ok := c.fm.Users[u.Username]
|
||||
ouser, ok := c.FM.Users[u.Username]
|
||||
if !ok {
|
||||
return http.StatusNotFound, nil
|
||||
}
|
||||
|
@ -279,11 +279,11 @@ func usersPutHandler(c *requestContext, w http.ResponseWriter, r *http.Request)
|
|||
|
||||
// Updates the whole User struct because we always are supposed
|
||||
// to send a new entire object.
|
||||
err = c.fm.db.Save(&u)
|
||||
err = c.FM.db.Save(&u)
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
||||
c.fm.Users[u.Username] = &u
|
||||
c.FM.Users[u.Username] = &u
|
||||
return http.StatusOK, nil
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue