Start integrating Hugo in the new plugin
Former-commit-id: dcc6bd82b3d3a89637a1032aad1a25d0b1f80046 [formerly 8784cd37bf58e81cdbe1bcec00e6f16b58efc915] [formerly 9e25850e063ae0825de337d5d5f29cee601b9040 [formerly 8b1d36dfb9ebfa001ddeef98034bb5a73d135c94]] Former-commit-id: 04a38bea2d141093570d9289d0d0a056a136fe8a [formerly 5995538504889e698aa6cd35b7da40c38b5d5ddf] Former-commit-id: 8c81a0b060167e1a2983a99bc87b380838ac07dc
This commit is contained in:
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
})()
|
||||
|
||||
26
caddy/hugo/old.js
Normal file
26
caddy/hugo/old.js
Normal file
@@ -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)
|
||||
})
|
||||
}
|
||||
176
caddy/hugo/setup.go
Normal file
176
caddy/hugo/setup.go
Normal file
@@ -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
|
||||
}
|
||||
15
caddy/hugo/utils.go
Normal file
15
caddy/hugo/utils.go
Normal file
@@ -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()
|
||||
}
|
||||
Reference in New Issue
Block a user