Various small nice fixes
This commit is contained in:
parent
7fa422e42f
commit
3b4bd4e978
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
||||||
bin/
|
bin/
|
||||||
dist/
|
dist/
|
||||||
root/.private
|
user-root/
|
||||||
|
user-root/.private
|
||||||
|
|
2
Makefile
2
Makefile
|
@ -21,7 +21,7 @@ build:
|
||||||
# `make run` to run it in debug mode.
|
# `make run` to run it in debug mode.
|
||||||
.PHONY: run
|
.PHONY: run
|
||||||
run:
|
run:
|
||||||
./go-reload cmd/blog/main.go -debug root
|
./go-reload cmd/blog/main.go -debug user-root
|
||||||
|
|
||||||
# `make test` to run unit tests.
|
# `make test` to run unit tests.
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
|
|
|
@ -33,12 +33,25 @@ func editorHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
// Are they saving?
|
// Are they saving?
|
||||||
if saving {
|
if saving {
|
||||||
fp = filepath.Join(*render.UserRoot, file)
|
fp = filepath.Join(*render.UserRoot, file)
|
||||||
body = []byte(r.FormValue("body"))
|
body = []byte(strings.Replace(r.FormValue("body"), "\r\n", "\n", -1))
|
||||||
err := ioutil.WriteFile(fp, body, 0644)
|
|
||||||
|
// Ensure the folders exist.
|
||||||
|
dir, _ := filepath.Split(fp)
|
||||||
|
err := os.MkdirAll(dir, 0755)
|
||||||
|
if err != nil {
|
||||||
|
responses.Flash(w, r, "Error saving: can't create folder %s: %s", dir, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the file.
|
||||||
|
err = ioutil.WriteFile(fp, body, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
responses.Flash(w, r, "Error saving: %s", err)
|
responses.Flash(w, r, "Error saving: %s", err)
|
||||||
} else {
|
} else {
|
||||||
responses.FlashAndRedirect(w, r, "/admin/editor?file="+url.QueryEscape(file), "Page saved successfully!")
|
if render.HasHTMLSuffix(file) {
|
||||||
|
responses.FlashAndRedirect(w, r, render.URLFromPath(file), "Page saved successfully!")
|
||||||
|
} else {
|
||||||
|
responses.FlashAndRedirect(w, r, "/admin/editor?file="+url.QueryEscape(file), "Page saved successfully!")
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else if deleting {
|
} else if deleting {
|
||||||
|
@ -62,8 +75,11 @@ func editorHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
f, err := os.Stat(fp)
|
f, err := os.Stat(fp)
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
fp = filepath.Join(*render.DocumentRoot, file)
|
fp = filepath.Join(*render.DocumentRoot, file)
|
||||||
fromCore = true
|
|
||||||
f, err = os.Stat(fp)
|
f, err = os.Stat(fp)
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
// The file was found in the core.
|
||||||
|
fromCore = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it exists, load it.
|
// If it exists, load it.
|
||||||
|
@ -114,6 +130,11 @@ func editorFileList(w http.ResponseWriter, r *http.Request) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hide vendored files.
|
||||||
|
if i == 1 && strings.HasPrefix(rel, "js/ace-editor") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Only text files.
|
// Only text files.
|
||||||
ext := strings.ToLower(filepath.Ext(path))
|
ext := strings.ToLower(filepath.Ext(path))
|
||||||
okTypes := []string{
|
okTypes := []string{
|
||||||
|
|
|
@ -9,10 +9,10 @@ import (
|
||||||
|
|
||||||
"github.com/kirsle/blog/internal/markdown"
|
"github.com/kirsle/blog/internal/markdown"
|
||||||
"github.com/kirsle/blog/internal/middleware/auth"
|
"github.com/kirsle/blog/internal/middleware/auth"
|
||||||
"github.com/kirsle/blog/models/posts"
|
|
||||||
"github.com/kirsle/blog/internal/render"
|
"github.com/kirsle/blog/internal/render"
|
||||||
"github.com/kirsle/blog/internal/responses"
|
"github.com/kirsle/blog/internal/responses"
|
||||||
"github.com/kirsle/blog/internal/types"
|
"github.com/kirsle/blog/internal/types"
|
||||||
|
"github.com/kirsle/blog/models/posts"
|
||||||
)
|
)
|
||||||
|
|
||||||
// editHandler is the blog writing and editing page.
|
// editHandler is the blog writing and editing page.
|
||||||
|
@ -21,6 +21,7 @@ func editHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
"preview": "",
|
"preview": "",
|
||||||
}
|
}
|
||||||
var post *posts.Post
|
var post *posts.Post
|
||||||
|
var isNew bool
|
||||||
|
|
||||||
// Are we editing an existing post?
|
// Are we editing an existing post?
|
||||||
if idStr := r.FormValue("id"); idStr != "" {
|
if idStr := r.FormValue("id"); idStr != "" {
|
||||||
|
@ -30,10 +31,12 @@ func editHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
v["Error"] = errors.New("that post ID was not found")
|
v["Error"] = errors.New("that post ID was not found")
|
||||||
post = posts.New()
|
post = posts.New()
|
||||||
|
isNew = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
post = posts.New()
|
post = posts.New()
|
||||||
|
isNew = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Method == http.MethodPost {
|
if r.Method == http.MethodPost {
|
||||||
|
@ -55,8 +58,14 @@ func editHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
author, _ := auth.CurrentUser(r)
|
author, _ := auth.CurrentUser(r)
|
||||||
post.AuthorID = author.ID
|
post.AuthorID = author.ID
|
||||||
|
|
||||||
post.Updated = time.Now().UTC()
|
// When editing, allow to not touch the last updated time.
|
||||||
|
if !isNew && r.FormValue("no-update") == "true" {
|
||||||
|
post.Updated = post.Created
|
||||||
|
} else {
|
||||||
|
post.Updated = time.Now().UTC()
|
||||||
|
}
|
||||||
err = post.Save()
|
err = post.Save()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
v["Error"] = err
|
v["Error"] = err
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -2,6 +2,7 @@ package render
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -15,6 +16,17 @@ var (
|
||||||
DocumentRoot *string
|
DocumentRoot *string
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// File extensions and URL suffixes that map to real files on disk, but which
|
||||||
|
// have suffixes hidden from the URL.
|
||||||
|
var hiddenSuffixes = []string{
|
||||||
|
".gohtml",
|
||||||
|
".html",
|
||||||
|
"/index.gohtml",
|
||||||
|
"/index.html",
|
||||||
|
".md",
|
||||||
|
"/index.md",
|
||||||
|
}
|
||||||
|
|
||||||
// Filepath represents a file discovered in the document roots, and maintains
|
// Filepath represents a file discovered in the document roots, and maintains
|
||||||
// both its relative and absolute components.
|
// both its relative and absolute components.
|
||||||
type Filepath struct {
|
type Filepath struct {
|
||||||
|
@ -71,15 +83,7 @@ func ResolvePath(path string) (Filepath, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try some supported suffixes.
|
// Try some supported suffixes.
|
||||||
suffixes := []string{
|
for _, suffix := range hiddenSuffixes {
|
||||||
".gohtml",
|
|
||||||
".html",
|
|
||||||
"/index.gohtml",
|
|
||||||
"/index.html",
|
|
||||||
".md",
|
|
||||||
"/index.md",
|
|
||||||
}
|
|
||||||
for _, suffix := range suffixes {
|
|
||||||
test := absPath + suffix
|
test := absPath + suffix
|
||||||
if stat, err := os.Stat(test); !os.IsNotExist(err) && !stat.IsDir() {
|
if stat, err := os.Stat(test); !os.IsNotExist(err) && !stat.IsDir() {
|
||||||
debug("Filepath found via suffix %s: %s", suffix, test)
|
debug("Filepath found via suffix %s: %s", suffix, test)
|
||||||
|
@ -90,3 +94,36 @@ func ResolvePath(path string) (Filepath, error) {
|
||||||
|
|
||||||
return Filepath{}, errors.New("not found")
|
return Filepath{}, errors.New("not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HasHTMLSuffix returns whether the file path will be renderable as HTML
|
||||||
|
// for the front-end. Basically, whether it ends with a .gohtml, .html or .md
|
||||||
|
// suffix and/or is an index page.
|
||||||
|
func HasHTMLSuffix(path string) bool {
|
||||||
|
for _, suffix := range hiddenSuffixes {
|
||||||
|
if strings.HasSuffix(path, suffix) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// URLFromPath returns an HTTP path that matches the file path on disk.
|
||||||
|
//
|
||||||
|
// For example, given the file path "folder/page.md" it would return the string
|
||||||
|
// "/folder/page"
|
||||||
|
func URLFromPath(path string) string {
|
||||||
|
// Strip leading slashes.
|
||||||
|
if path[0] == '/' {
|
||||||
|
path = strings.TrimPrefix(path, "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide-able suffixes.
|
||||||
|
for _, suffix := range hiddenSuffixes {
|
||||||
|
if strings.HasSuffix(path, suffix) {
|
||||||
|
path = strings.TrimSuffix(path, suffix)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("/%s", path)
|
||||||
|
}
|
||||||
|
|
|
@ -79,7 +79,7 @@
|
||||||
|
|
||||||
{{ template "content" . }}
|
{{ template "content" . }}
|
||||||
|
|
||||||
{{ if and .CurrentUser.Admin .Editable (ne .TemplatePath ".markdown") }}
|
{{ if and .CurrentUser.Admin .Editable (ne .TemplatePath ".markdown.gohtml") }}
|
||||||
<p class="mt-4">
|
<p class="mt-4">
|
||||||
<strong>Admin:</strong> [<a href="/admin/editor?file={{ .TemplatePath }}">edit this page</a>]
|
<strong>Admin:</strong> [<a href="/admin/editor?file={{ .TemplatePath }}">edit this page</a>]
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -2,9 +2,23 @@
|
||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
<h1>Page Editor</h1>
|
<h1>Page Editor</h1>
|
||||||
|
|
||||||
|
<form class="form-inline" method="GET" action="/admin/editor">
|
||||||
|
Create a new page:
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text"
|
||||||
|
name="file"
|
||||||
|
class="form-control ml-2"
|
||||||
|
placeholder="about.md">
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button type="submit" class="btn btn-primary">Go</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
{{ range .Data.FileTrees }}
|
{{ range .Data.FileTrees }}
|
||||||
{{ if .UserRoot }}
|
{{ if .UserRoot }}
|
||||||
<h2>User Root</h2>
|
<h2>User Root</h2>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
These are your custom web files that override those in the CoreRoot.
|
These are your custom web files that override those in the CoreRoot.
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
Preview
|
Preview
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
<h1 class="blog-title">{{ .Data.post.Title }}</h1>
|
||||||
|
|
||||||
{{ .Data.preview }}
|
{{ .Data.preview }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -139,6 +141,17 @@
|
||||||
> Enable comments on this post
|
> Enable comments on this post
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
{{ if .ID }}
|
||||||
|
<div class="form-check">
|
||||||
|
<label class="form-check-label">
|
||||||
|
<input type="checkbox"
|
||||||
|
class="form-check-label"
|
||||||
|
name="no-update"
|
||||||
|
value="true"
|
||||||
|
> <strong>Editing:</strong> do not show a "last updated" label.
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|
Loading…
Reference in New Issue
Block a user