fix styles and build options, add error handling, start StaticFileManager implementation

This commit is contained in:
Iris Lightshard 2022-06-08 23:13:09 -06:00
parent 3683e53c2a
commit 034d325ae3
Signed by: nilix
GPG key ID: 3B7FBC22144E6398
9 changed files with 152 additions and 83 deletions

View file

@ -1,10 +1,21 @@
package archetype package archetype
import (
"time"
)
type BuildStatus struct { type BuildStatus struct {
Success bool Success bool
Message string Message string
} }
type Page struct {
Title string
Content string
Edited time.Time
Error string
}
type ConfigOption struct { type ConfigOption struct {
Name string Name string
Type string Type string
@ -18,7 +29,7 @@ type Adapter interface {
GetConfig() map[ConfigOption]string GetConfig() map[ConfigOption]string
SetConfig(map[ConfigOption]string) error SetConfig(map[ConfigOption]string) error
ListPages() map[string]string ListPages() map[string]string
GetPage(string) (Page, error) GetPage(string) Page
FormatPage(string) string FormatPage(string) string
FormattingHelp() string FormattingHelp() string
CreatePage(slug, title, content string) error CreatePage(slug, title, content string) error

View file

@ -72,16 +72,25 @@ func (self *EurekaAdapter) ListPages() map[string]string {
return pages return pages
} }
func (self *EurekaAdapter) GetPage(filename string) (Page, error) { func (self *EurekaAdapter) GetPage(filename string) Page {
if strings.Contains(filename, "../") || strings.Contains(filename, "..\\") {
return Page{
Error: "You cannot escape!",
}
}
fullPath := filepath.Join(self.Root, "inc", filename) fullPath := filepath.Join(self.Root, "inc", filename)
f, err := os.ReadFile(fullPath) f, err := os.ReadFile(fullPath)
if err != nil { if err != nil {
return Page{}, err return Page{
Error: err.Error(),
}
} }
if !strings.HasSuffix(filename, ".htm") { if !strings.HasSuffix(filename, ".htm") {
return Page{}, errors.New("Page file extension is not '.htm'") return Page{
Error: "Page file extension is not '.htm'",
}
} }
title := strings.Replace( title := strings.Replace(
@ -93,7 +102,7 @@ func (self *EurekaAdapter) GetPage(filename string) (Page, error) {
Title: title, Title: title,
Content: content, Content: content,
Edited: fileInfo.ModTime(), Edited: fileInfo.ModTime(),
}, nil }
} }
func (self *EurekaAdapter) FormatPage(raw string) string { func (self *EurekaAdapter) FormatPage(raw string) string {
@ -111,6 +120,10 @@ func (self *EurekaAdapter) CreatePage(slug, title, content string) error {
slug = strings.ReplaceAll(title, " ", "_") + ".htm" slug = strings.ReplaceAll(title, " ", "_") + ".htm"
path := filepath.Join(self.Root, "inc", slug) path := filepath.Join(self.Root, "inc", slug)
if strings.Contains(slug, "../") || strings.Contains(slug, "..\\") {
return errors.New("You cannot escape!")
}
_, err := os.Stat(path) _, err := os.Stat(path)
if err == nil || !os.IsNotExist(err) { if err == nil || !os.IsNotExist(err) {
return errors.New("File already exists") return errors.New("File already exists")
@ -127,6 +140,12 @@ func (self *EurekaAdapter) CreatePage(slug, title, content string) error {
func (self *EurekaAdapter) SavePage(oldSlug, newSlug, title, content string) error { func (self *EurekaAdapter) SavePage(oldSlug, newSlug, title, content string) error {
// eureka creates titles from slugs, so we transform the title into the slug // eureka creates titles from slugs, so we transform the title into the slug
newSlug = strings.ReplaceAll(title, " ", "_") + ".htm" newSlug = strings.ReplaceAll(title, " ", "_") + ".htm"
if strings.Contains(newSlug, "../") || strings.Contains(newSlug, "..\\") ||
strings.Contains(oldSlug, "../") || strings.Contains(oldSlug, "..\\") {
return errors.New("You cannot escape!")
}
f, err := os.Create(filepath.Join(self.Root, "inc", newSlug)) f, err := os.Create(filepath.Join(self.Root, "inc", newSlug))
if err != nil { if err != nil {
return err return err
@ -151,6 +170,10 @@ func (self *EurekaAdapter) SavePage(oldSlug, newSlug, title, content string) err
} }
func (self *EurekaAdapter) DeletePage(slug string) error { func (self *EurekaAdapter) DeletePage(slug string) error {
if strings.Contains(slug, "../") || strings.Contains(slug, "..\\") {
return errors.New("You cannot escape!")
}
siteRoot := self.Config[ConfigOption{ siteRoot := self.Config[ConfigOption{
Name: "SITEROOT", Name: "SITEROOT",
Type: "string", Type: "string",
@ -164,14 +187,14 @@ func (self *EurekaAdapter) DeletePage(slug string) error {
} }
func (self *EurekaAdapter) Build(buildOptions map[string][]string) BuildStatus { func (self *EurekaAdapter) Build(buildOptions map[string][]string) BuildStatus {
twtxt := strings.Join(buildOptions["twtxt"], " ")
twtxt := buildOptions["twtxt"][0] cmdArgs := []string{}
cmdArgs := ""
if twtxt != "" { if twtxt != "" {
cmdArgs += "-t " + twtxt cmdArgs = append(cmdArgs, "-t")
cmdArgs = append(cmdArgs, twtxt)
} }
cmd := exec.Command("./build.sh", cmdArgs) cmd := exec.Command("./build.sh", cmdArgs...)
cmd.Dir = self.Root cmd.Dir = self.Root
out, err := cmd.CombinedOutput() out, err := cmd.CombinedOutput()

View file

@ -1,11 +0,0 @@
package archetype
import (
"time"
)
type Page struct {
Title string
Content string
Edited time.Time
}

View file

@ -0,0 +1,16 @@
package archetype
type StaticFileManager struct {
Root string
ShowHtml bool
ShowHidden bool
}
type FileManager interface {
Init(cfg Config) error
ListTree() []string
ListSubTree(root string) []string
AddFile(path string, file interface{}) error
MkDir(path string) error
Remove(path string) error
}

View file

@ -3,9 +3,9 @@ package main
import ( import (
"net/http" "net/http"
core "nilfm.cc/git/nirvash/archetype" core "nilfm.cc/git/nirvash/archetype"
shell "nilfm.cc/git/nirvash/lfo" . "nilfm.cc/git/nirvash/lfo"
"nilfm.cc/git/quartzgun/indentalUserDB" "nilfm.cc/git/quartzgun/indentalUserDB"
"nilfm.cc/git/quartzgun/middleware" . "nilfm.cc/git/quartzgun/middleware"
"nilfm.cc/git/quartzgun/renderer" "nilfm.cc/git/quartzgun/renderer"
"nilfm.cc/git/quartzgun/router" "nilfm.cc/git/quartzgun/router"
"os" "os"
@ -40,14 +40,14 @@ func main() {
rtr.Get("/login", renderer.Template( rtr.Get("/login", renderer.Template(
pathConcat(templateRoot, "login.html"))) pathConcat(templateRoot, "login.html")))
rtr.Post("/login", middleware.Authorize("/", udb, "/login?tryagain=1")) rtr.Post("/login", Authorize("/", udb, "/login?tryagain=1"))
rtr.Get("/logout", middleware.Bunt("/", udb, "/login?tryagain=1")) rtr.Get("/logout", Bunt("/", udb, "/login?tryagain=1"))
rtr.Get( rtr.Get(
"/", "/",
middleware.Protected( Protected(
shell.WithAdapter( WithAdapter(
renderer.Template( renderer.Template(
pathConcat(templateRoot, "cms_list.html"), pathConcat(templateRoot, "cms_list.html"),
pathConcat(templateRoot, "header.html"), pathConcat(templateRoot, "header.html"),
@ -59,9 +59,9 @@ func main() {
rtr.Get( rtr.Get(
`/edit/(?P<Slug>\S+)`, `/edit/(?P<Slug>\S+)`,
middleware.Fortify( Fortify(
middleware.Protected( Protected(
shell.WithAdapter( WithAdapter(
renderer.Template( renderer.Template(
pathConcat(templateRoot, "cms_edit.html"), pathConcat(templateRoot, "cms_edit.html"),
pathConcat(templateRoot, "header.html"), pathConcat(templateRoot, "header.html"),
@ -73,10 +73,10 @@ func main() {
rtr.Post( rtr.Post(
`/save/(?P<Slug>\S+)`, `/save/(?P<Slug>\S+)`,
middleware.Defend( Defend(
middleware.Protected( Protected(
shell.WithAdapter( WithAdapter(
shell.EnsurePageData( EnsurePageData(
renderer.Template( renderer.Template(
pathConcat(templateRoot, "cms_save.html"), pathConcat(templateRoot, "cms_save.html"),
pathConcat(templateRoot, "header.html"), pathConcat(templateRoot, "header.html"),
@ -91,9 +91,9 @@ func main() {
rtr.Get( rtr.Get(
`/new`, `/new`,
middleware.Fortify( Fortify(
middleware.Protected( Protected(
shell.WithAdapter( WithAdapter(
renderer.Template( renderer.Template(
pathConcat(templateRoot, "cms_new.html"), pathConcat(templateRoot, "cms_new.html"),
pathConcat(templateRoot, "header.html"), pathConcat(templateRoot, "header.html"),
@ -105,10 +105,10 @@ func main() {
rtr.Post( rtr.Post(
`/create`, `/create`,
middleware.Defend( Defend(
middleware.Protected( Protected(
shell.WithAdapter( WithAdapter(
shell.EnsurePageData( EnsurePageData(
renderer.Template( renderer.Template(
pathConcat(templateRoot, "cms_create.html"), pathConcat(templateRoot, "cms_create.html"),
pathConcat(templateRoot, "header.html"), pathConcat(templateRoot, "header.html"),
@ -123,9 +123,9 @@ func main() {
rtr.Get( rtr.Get(
`/build`, `/build`,
middleware.Fortify( Fortify(
middleware.Protected( Protected(
shell.WithAdapter( WithAdapter(
renderer.Template( renderer.Template(
pathConcat(templateRoot, "build.html"), pathConcat(templateRoot, "build.html"),
pathConcat(templateRoot, "header.html"), pathConcat(templateRoot, "header.html"),
@ -137,10 +137,10 @@ func main() {
rtr.Post( rtr.Post(
`/build-run`, `/build-run`,
middleware.Defend( Defend(
middleware.Protected( Protected(
shell.SanitizeFormMap( SanitizeFormMap(
shell.WithAdapter( WithAdapter(
renderer.Template( renderer.Template(
pathConcat(templateRoot, "build_run.html"), pathConcat(templateRoot, "build_run.html"),
pathConcat(templateRoot, "header.html"), pathConcat(templateRoot, "header.html"),
@ -154,9 +154,9 @@ func main() {
rtr.Post( rtr.Post(
`/delete/(?P<Slug>\S+)`, `/delete/(?P<Slug>\S+)`,
middleware.Defend( Defend(
middleware.Protected( Protected(
shell.WithAdapter( WithAdapter(
renderer.Template( renderer.Template(
pathConcat(templateRoot, "delete.html"), pathConcat(templateRoot, "delete.html"),
pathConcat(templateRoot, "header.html"), pathConcat(templateRoot, "header.html"),
@ -170,9 +170,9 @@ func main() {
rtr.Get( rtr.Get(
`/config`, `/config`,
middleware.Fortify( Fortify(
middleware.Protected( Protected(
shell.WithAdapter( WithAdapter(
renderer.Template( renderer.Template(
pathConcat(templateRoot, "config.html"), pathConcat(templateRoot, "config.html"),
pathConcat(templateRoot, "header.html"), pathConcat(templateRoot, "header.html"),
@ -184,11 +184,11 @@ func main() {
rtr.Post( rtr.Post(
`/config-set`, `/config-set`,
middleware.Defend( Defend(
middleware.Protected( Protected(
shell.SanitizeFormMap( SanitizeFormMap(
shell.FormMapToAdapterConfig( FormMapToAdapterConfig(
shell.WithAdapter( WithAdapter(
renderer.Template( renderer.Template(
pathConcat(templateRoot, "config_set.html"), pathConcat(templateRoot, "config_set.html"),
pathConcat(templateRoot, "header.html"), pathConcat(templateRoot, "header.html"),

View file

@ -3,7 +3,6 @@ body {
margin: 0; margin: 0;
font-family: sans-serif; font-family: sans-serif;
font-size: 16px; font-size: 16px;
height: 100vh;
background: black; background: black;
color: white; color: white;
background: url('/static/bg2.png'); background: url('/static/bg2.png');
@ -16,6 +15,7 @@ body {
background: url('/static/bg.png'); background: url('/static/bg.png');
background-size: cover; background-size: cover;
background-position: center center; background-position: center center;
height: 100vh;
} }
.login { .login {
@ -48,7 +48,7 @@ body {
} }
.login form input, .login form input:-internal-autofill-selected { .login form input, #user-input, #password-input {
display: block; display: block;
margin: 1em; margin: 1em;
margin-left: auto; margin-left: auto;
@ -60,7 +60,7 @@ body {
padding: 0.2em; padding: 0.2em;
} }
.login form input[type="text"], login form input[type="password"] { .login form input[type="text"], .login form input[type="password"] {
transition: border 1s; transition: border 1s;
outline: none; outline: none;
margin-bottom: -17px; margin-bottom: -17px;
@ -73,7 +73,6 @@ body {
.login form input[type="submit"] { .login form input[type="submit"] {
margin-top: -17px;
text-transform: uppercase; text-transform: uppercase;
transition: background 1s, color 1s; transition: background 1s, color 1s;
} }
@ -84,7 +83,7 @@ body {
} }
.login .error { .login .error {
positon: relative; position: relative;
text-align: center; text-align: center;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
@ -109,11 +108,12 @@ h1 {
nav { nav {
text-align: center; text-align: center;
background: rgba(0,0,0,0.8); background: rgba(0,0,0,0.8);
padding-top: 0.5em; padding-top: 0.5em;
padding-bottom: 0.5em; padding-bottom: 0.5em;
position: sticky; position: sticky;
top: 0px; top: 0px;
z-index: 3;
} }
nav ul { nav ul {
@ -136,9 +136,23 @@ a:hover {
color: cyan; color: cyan;
} }
a.new-page-button { .new-page-button-wrapper {
display: block;
width: 80%;
max-width: 500px;
position: sticky; position: sticky;
top: 0; top: 5em;
margin-left: auto;
margin-right: auto;
z-index: 2;
text-align: right;
height: 0;
overflow-y: visible;
}
a.new-page-button {
position: relative;
top: 1em;
text-decoration: none; text-decoration: none;
background: transparent; background: transparent;
border: solid 2px lightgray; border: solid 2px lightgray;
@ -147,8 +161,6 @@ a.new-page-button {
padding: 0.2em; padding: 0.2em;
text-transform: uppercase; text-transform: uppercase;
transition: background 1s, color 1s; transition: background 1s, color 1s;
float: right;
z-index: 2;
} }
a.new-page-button:hover { a.new-page-button:hover {
@ -164,8 +176,7 @@ h2 {
display: block; display: block;
border-left: 8px solid cyan; border-left: 8px solid cyan;
padding-left: 8px; padding-left: 8px;
position: sticky; position: relative;
top: 3em;
} }
.page-list, form.editor, form.build, form.configurator, span.adapter-error, span.adapter-success, .danger-zone { .page-list, form.editor, form.build, form.configurator, span.adapter-error, span.adapter-success, .danger-zone {
@ -177,7 +188,6 @@ h2 {
padding: 2em; padding: 2em;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
max-height: calc(100vh - 20em);
overflow-y: auto; overflow-y: auto;
} }
@ -210,6 +220,12 @@ form.editor input[type="text"], form.configurator input[type="text"], form.confi
} }
form.editor input.slug-input {
font-size: 100%;
}
form.editor input.title-input { form.editor input.title-input {
font-size: 150%; font-size: 150%;
} }
@ -230,7 +246,7 @@ form.editor textarea {
} }
form.configurator textarea { form.configurator textarea {
marign: 0; margin: 0;
width: 100%; width: 100%;
height: 5em; height: 5em;
} }
@ -272,3 +288,7 @@ form.editor input[type="submit"]:hover,form.build input[type="submit"]:hover, .d
form input[hidden] { form input[hidden] {
display: none; display: none;
} }
form input[readonly] {
border: none;
}

View file

@ -6,6 +6,12 @@
{{ template "header" . }} {{ template "header" . }}
{{ if ($page).Error }}
<h2>Page Error</h2>
<span class="adapter-error">{{($page).Error}}</span>
{{ else }}
<h2>Edit Page</h2> <h2>Edit Page</h2>
<form class="editor" method="POST" action="/save/{{$slug}}"> <form class="editor" method="POST" action="/save/{{$slug}}">
@ -14,12 +20,12 @@
{{ end }} {{ end }}
<input hidden name="csrfToken" value="{{$csrfToken}}"/> <input hidden name="csrfToken" value="{{$csrfToken}}"/>
<input hidden name="oldSlug" value="{{$slug}}"/> <input hidden name="oldSlug" value="{{$slug}}"/>
{{ if $editableSlugs }} <label for="title">Title</label><br/>
<input class="title-input" id="title" type="text" name="title" value="{{($page).Title}}" required/><br/>
{{ if $editableSlugs }}
<label for="slug">Slug</label><br/> <label for="slug">Slug</label><br/>
<input class="slug-input" id="slug" type="text" name="slug" value="{{$slug}}" required/><br/> <input class="slug-input" id="slug" type="text" name="slug" value="{{$slug}}" required/><br/>
{{ end }} {{ end }}
<label for="title">Title</label><br/>
<input class="title-input" id="title" type="text" name="title" value="{{($page).Title}}" required/><br/>
<span class="edited-time">last edited {{($page).Edited.Format "2006-01-02 15:04"}}</span><br/> <span class="edited-time">last edited {{($page).Edited.Format "2006-01-02 15:04"}}</span><br/>
<label for="content">Content</label><br/> <label for="content">Content</label><br/>
<textarea class="content-input" id="content" name="content" required>{{($page).Content}}</textarea><br/> <textarea class="content-input" id="content" name="content" required>{{($page).Content}}</textarea><br/>
@ -38,4 +44,7 @@
<input type="submit" value="DELETE"/> <input type="submit" value="DELETE"/>
</form> </form>
</details> </details>
{{ end }}
{{ template "footer" . }} {{ template "footer" . }}

View file

@ -4,8 +4,11 @@
<h2>Pages</h2> <h2>Pages</h2>
<div class="page-list"> <div class="new-page-button-wrapper">
<a class="new-page-button" href="/new">New Page</a> <a class="new-page-button" href="/new">New Page</a>
</div>
<div class="page-list">
<ul> <ul>
{{ range $slug, $title := $pages }} {{ range $slug, $title := $pages }}
<li><a href="/edit/{{$slug}}">{{$title}}</a></li> <li><a href="/edit/{{$slug}}">{{$title}}</a></li>
@ -13,6 +16,4 @@
</ul> </ul>
</div> </div>
<div class="static-files-list"> {{ template "footer" .}}
</div>
{{ template "footer" .}}

View file

@ -11,12 +11,12 @@
<span class="edit-error">Empty fields are not allowed - please try again</span><br/> <span class="edit-error">Empty fields are not allowed - please try again</span><br/>
{{ end }} {{ end }}
<input hidden name="csrfToken" value="{{$csrfToken}}"/> <input hidden name="csrfToken" value="{{$csrfToken}}"/>
<label for="title">Title</label><br/>
<input class="title-input" id="title" type="text" name="title" required/><br/>
{{ if $editableSlugs }} {{ if $editableSlugs }}
<label for="slug">Slug</label><br/> <label for="slug">Slug</label><br/>
<input class="slug-input" id="slug" type="text" name="slug" required/><br/> <input class="slug-input" id="slug" type="text" name="slug" required/><br/>
{{ end }} {{ end }}
<label for="title">Title</label><br/>
<input class="title-input" id="title" type="text" name="title" required/><br/>
<label for="content">Content</label><br/> <label for="content">Content</label><br/>
<textarea class="content-input" id="content" name="content" required></textarea><br/> <textarea class="content-input" id="content" name="content" required></textarea><br/>
<input type="submit" value="Save"/> <input type="submit" value="Save"/>