diff --git a/.gitignore b/.gitignore index c572307..c8367bd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ bin/ dist/ -root/.private +user-root/ +user-root/.private diff --git a/Makefile b/Makefile index 1709998..32b3d09 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/internal/controllers/admin/editor.go b/internal/controllers/admin/editor.go index d531d28..37b86f6 100644 --- a/internal/controllers/admin/editor.go +++ b/internal/controllers/admin/editor.go @@ -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 { - 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 } } 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{ diff --git a/internal/controllers/posts/edit.go b/internal/controllers/posts/edit.go index 49e264c..5b6102c 100644 --- a/internal/controllers/posts/edit.go +++ b/internal/controllers/posts/edit.go @@ -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 - 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() + if err != nil { v["Error"] = err } else { diff --git a/internal/render/resolve_paths.go b/internal/render/resolve_paths.go index 1728efd..e5ca8a8 100644 --- a/internal/render/resolve_paths.go +++ b/internal/render/resolve_paths.go @@ -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) +} diff --git a/root/.layout.gohtml b/root/.layout.gohtml index 4060f56..6490a3a 100644 --- a/root/.layout.gohtml +++ b/root/.layout.gohtml @@ -79,7 +79,7 @@ {{ template "content" . }} - {{ if and .CurrentUser.Admin .Editable (ne .TemplatePath ".markdown") }} + {{ if and .CurrentUser.Admin .Editable (ne .TemplatePath ".markdown.gohtml") }}

Admin: [edit this page]

diff --git a/root/admin/filelist.gohtml b/root/admin/filelist.gohtml index 5a4d1b3..341bcd0 100644 --- a/root/admin/filelist.gohtml +++ b/root/admin/filelist.gohtml @@ -2,9 +2,23 @@ {{ define "content" }}

Page Editor

+
+ Create a new page: +
+ +
+ +
+
+
+ {{ range .Data.FileTrees }} {{ if .UserRoot }}

User Root

+

These are your custom web files that override those in the CoreRoot.

diff --git a/root/blog/edit.gohtml b/root/blog/edit.gohtml index 7987fac..657a8db 100644 --- a/root/blog/edit.gohtml +++ b/root/blog/edit.gohtml @@ -8,6 +8,8 @@ Preview
+

{{ .Data.post.Title }}

+ {{ .Data.preview }}
@@ -139,6 +141,17 @@ > Enable comments on this post + {{ if .ID }} +
+ +
+ {{ end }}