Various small nice fixes

This commit is contained in:
Noah 2018-04-10 19:07:25 -07:00
parent 7fa422e42f
commit 3b4bd4e978
8 changed files with 113 additions and 18 deletions

3
.gitignore vendored
View File

@ -1,3 +1,4 @@
bin/
dist/
root/.private
user-root/
user-root/.private

View File

@ -21,7 +21,7 @@ build:
# `make run` to run it in debug mode.
.PHONY: 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.
.PHONY: test

View File

@ -33,12 +33,25 @@ func editorHandler(w http.ResponseWriter, r *http.Request) {
// Are they saving?
if saving {
fp = filepath.Join(*render.UserRoot, file)
body = []byte(r.FormValue("body"))
err := ioutil.WriteFile(fp, body, 0644)
body = []byte(strings.Replace(r.FormValue("body"), "\r\n", "\n", -1))
// 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 {
responses.Flash(w, r, "Error saving: %s", err)
} else {
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
}
} else if deleting {
@ -62,8 +75,11 @@ func editorHandler(w http.ResponseWriter, r *http.Request) {
f, err := os.Stat(fp)
if os.IsNotExist(err) {
fp = filepath.Join(*render.DocumentRoot, file)
fromCore = true
f, err = os.Stat(fp)
if !os.IsNotExist(err) {
// The file was found in the core.
fromCore = true
}
}
// If it exists, load it.
@ -114,6 +130,11 @@ func editorFileList(w http.ResponseWriter, r *http.Request) {
return nil
}
// Hide vendored files.
if i == 1 && strings.HasPrefix(rel, "js/ace-editor") {
return nil
}
// Only text files.
ext := strings.ToLower(filepath.Ext(path))
okTypes := []string{

View File

@ -9,10 +9,10 @@ import (
"github.com/kirsle/blog/internal/markdown"
"github.com/kirsle/blog/internal/middleware/auth"
"github.com/kirsle/blog/models/posts"
"github.com/kirsle/blog/internal/render"
"github.com/kirsle/blog/internal/responses"
"github.com/kirsle/blog/internal/types"
"github.com/kirsle/blog/models/posts"
)
// editHandler is the blog writing and editing page.
@ -21,6 +21,7 @@ func editHandler(w http.ResponseWriter, r *http.Request) {
"preview": "",
}
var post *posts.Post
var isNew bool
// Are we editing an existing post?
if idStr := r.FormValue("id"); idStr != "" {
@ -30,10 +31,12 @@ func editHandler(w http.ResponseWriter, r *http.Request) {
if err != nil {
v["Error"] = errors.New("that post ID was not found")
post = posts.New()
isNew = true
}
}
} else {
post = posts.New()
isNew = true
}
if r.Method == http.MethodPost {
@ -55,8 +58,14 @@ func editHandler(w http.ResponseWriter, r *http.Request) {
author, _ := auth.CurrentUser(r)
post.AuthorID = author.ID
// 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()
if err != nil {
v["Error"] = err
} else {

View File

@ -2,6 +2,7 @@ package render
import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"
@ -15,6 +16,17 @@ var (
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
// both its relative and absolute components.
type Filepath struct {
@ -71,15 +83,7 @@ func ResolvePath(path string) (Filepath, error) {
}
// Try some supported suffixes.
suffixes := []string{
".gohtml",
".html",
"/index.gohtml",
"/index.html",
".md",
"/index.md",
}
for _, suffix := range suffixes {
for _, suffix := range hiddenSuffixes {
test := absPath + suffix
if stat, err := os.Stat(test); !os.IsNotExist(err) && !stat.IsDir() {
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")
}
// 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)
}

View File

@ -79,7 +79,7 @@
{{ template "content" . }}
{{ if and .CurrentUser.Admin .Editable (ne .TemplatePath ".markdown") }}
{{ if and .CurrentUser.Admin .Editable (ne .TemplatePath ".markdown.gohtml") }}
<p class="mt-4">
<strong>Admin:</strong> [<a href="/admin/editor?file={{ .TemplatePath }}">edit this page</a>]
</p>

View File

@ -2,9 +2,23 @@
{{ define "content" }}
<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 }}
{{ if .UserRoot }}
<h2>User Root</h2>
<p>
These are your custom web files that override those in the CoreRoot.
</p>

View File

@ -8,6 +8,8 @@
Preview
</div>
<div class="card-body">
<h1 class="blog-title">{{ .Data.post.Title }}</h1>
{{ .Data.preview }}
</div>
</div>
@ -139,6 +141,17 @@
> Enable comments on this post
</label>
</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 class="form-group">