all repos — nirvash @ 8fdc9ddb460f6dc76363da7c64bdae15927bf430

modular CMS using the quartzgun library

added build/delete, started styling
Iris Lightshard nilix@nilfm.cc
PGP Signature
-----BEGIN PGP SIGNATURE-----

iQIzBAABCAAdFiEEkFh6dA+k/6CXFXU4O3+8IhROY5gFAmKe910ACgkQO3+8IhRO
Y5j9kA/+J+owRG3QqAEBKIR1LTf4cagKyKGSvEFCf9tzuj7NTcO9n3SEkEUDzUNr
ni8nf2Epj+Ed1g/5teCi9eP3vUa8PFfgCYqSUNcuEx9sqStO1zPCcdKB7ywRE36F
eWYSmPpynpOCSoI3Nx/fmUYHPoF9SUgdnqJbyCyGo23cffNXjdThYDa6rUT/sXc6
d+++TpjL6BIgkqzz36n02rcg1N2+K/Y8khU495gs1tJZEooYwQCqt9mOUG3GoWzM
kX78Sabd3AGI0rX/y7Fy6FbeAjOWpJZdA2poUhyHtLWwvsnxq/eGjiKXJebpXUR8
9RQ+VASoQgIRLxSfVtDOeqdWoLG7LqgabQzls2OcnajHnVqvbowtyBADxktpjEi7
VOMRA/Uoe9m4diynnj5fMNcNjb7o6r6lXvFbqAd8F5V5ccY10Ay+RACkH5HO9sSx
omkxVy+lr9JkYpZJoMTxd3X0HdW9c4pbcCzg9HxJFHXySFHX+uXgtvzNA+UE4SPk
DqH0oTjEXrsYwGeidqtbONhHzTZNDsmz2wVA4c7rNoxJd/Psf1yAVPHBpFUZy6d9
KONt094NkSortTJOKS76QCi1sgyQObj9xQVarM/0VOgzaTuO3gqJQI+HaWpdWwg9
nrRiXbgbVVlLndXEG1JnQuTMQl3vNL2Sv7YkmXWXwXy5kHgXne0=
=KK97
-----END PGP SIGNATURE-----
commit

8fdc9ddb460f6dc76363da7c64bdae15927bf430

parent

e7456651350df48a7093462997489ef02f2b4fda

M archetype/adapter.goarchetype/adapter.go

@@ -1,5 +1,10 @@

package archetype +type BuildStatus struct { + Success bool + Message string +} + type Adapter interface { Init(cfg *Config) Name() string

@@ -14,5 +19,5 @@ FormattingHelp() string

CreatePage(slug, title, content string) error SavePage(oldSlug, newSlug, title, content string) error DeletePage(slug string) error - Build(buildOptions map[string]string) (bool, string) + Build(buildOptions map[string][]string) BuildStatus }
M archetype/eureka.goarchetype/eureka.go

@@ -4,6 +4,7 @@ import (

"errors" "io/ioutil" "os" + "os/exec" "path/filepath" "strings" )

@@ -141,7 +142,20 @@ // TODO: delete old html as well

return os.Remove(filepath.Join(self.Root, "inc", slug)) } -func (self *EurekaAdapter) Build(buildOptions map[string]string) (bool, string) { - // TODO: shell out to build.sh with buildOptions, record exit status and output - return true, "Build successful" +func (self *EurekaAdapter) Build(buildOptions map[string][]string) BuildStatus { + + twtxt := buildOptions["twtxt"][0] + cmdArgs := "" + if twtxt != "" { + cmdArgs += " -t " + twtxt + } + + cmd := exec.Command("./build.sh", cmdArgs) + cmd.Dir = self.Root + out, err := cmd.CombinedOutput() + + return BuildStatus{ + Success: err == nil, + Message: string(out), + } }
M nirvash.gonirvash.go

@@ -117,5 +117,50 @@ "/login"),

udb, "/")) + rtr.Get( + `/build`, + middleware.Fortify( + middleware.Protected( + shell.WithAdapter( + renderer.Template( + "templates/build.html", + "templates/header.html", + "templates/footer.html"), + cfg.Adapter), + http.MethodGet, + udb, + "/login"))) + + rtr.Post( + `/build-run`, + middleware.Defend( + middleware.Protected( + shell.WithAdapter( + renderer.Template( + "templates/build_run.html", + "templates/header.html", + "templates/footer.html"), + cfg.Adapter), + http.MethodGet, + udb, + "/login"), + udb, + "/")) + + rtr.Post( + `/delete/(?P<Slug>\S+)`, + middleware.Defend( + middleware.Protected( + shell.WithAdapter( + renderer.Template( + "templates/delete.html", + "templates/header.html", + "templates/footer.html"), + cfg.Adapter), + http.MethodGet, + udb, + "/login"), + udb, + "/")) http.ListenAndServe(":8080", rtr) }
M static/style.cssstatic/style.css

@@ -2,13 +2,20 @@ body {

padding: 0; margin: 0; font-family: sans; + font-size: 16px; + height: 100vh; + background: black; + color: white; + background: url('/static/bg2.png'); + background-size: cover; + background-position: top left; + background-attachment: fixed; } .login-body { background: url('/static/bg.png'); background-size: cover; background-position: center center; - height: 100vh; } .login {

@@ -71,4 +78,160 @@ margin-right: auto;

color: lightgray; border-bottom: 2px solid crimson; width: auto; +} + +h1 { + text-align: center; + font-size: 225%; + text-transform: uppercase; + background: rgba(0,0,0,0.8); + margin: 0; + padding-top: 0.5em; + padding-bottom: 0.5em; +} + +.login h1 { + background: transparent; +} + +nav { + text-align: center; + background: rgba(0,0,0,0.8); + padding-top: 0.5em; + padding-bottom: 0.5em; + position: sticky; + top: 0px; +} + +nav ul { + list-style: none; +} + +nav ul li { + display: inline; + margin-left: 0.5em; + margin-right: 0.5em; +} + +a { + color: white; + font-weight: normal; + text-decoration: underline; +} + +a:hover { + color: cyan; +} + +a.new-page-button { + position: sticky; + top: 0; + text-decoration: none; + background: transparent; + border: solid 2px lightgray; + font-size: 150%; + color: lightgray; + padding: 0.2em; + text-transform: uppercase; + transition: background 1s, color 1s; + float: right; + z-index: 2; +} + +a.new-page-button:hover { + background: lightgray; + color: black; +} + +h2 { + margin: 1em; + font-size: 200%; + text-transform: uppercase; + background: rgba(0,0,0,0.8); + display: block; + border-left: 8px solid cyan; + padding-left: 8px; + position: sticky; + top: 3em; +} + +.page-list, form.editor, form.build, span.adapter-error, span.adapter-success, .danger-zone { + width: 80%; + max-width: 500px; + background: rgba(0,0,0,0.8); + padding: 2em; + margin-left: auto; + margin-right: auto; + max-height: calc(100vh - 20em); + overflow-y: auto; +} + +form.editor label, form.build label { + font-size: 80%; + color: lightgray; + text-transform: uppercase; +} + +form.editor input, form.build input, form.editor textarea { + display: block; + margin: 0; + margin-top: 0.2em; + margin-bottom: 0.2em; + background: transparent; + border: solid 2px lightgray; + color: lightgray; + padding: 0.2em; + transition: border 1s; + outline: none; +} + +form.editor input.title-input { + margin: 0; + width: 100%; + font-size: 150%; +} + +form.editor, .danger-zone { + max-width: 80em; +} + +form.editor textarea { + margin: 0; + width: 80em; + font-size: 16px; + height: 25em; +} + +form.editor input[type="submit"], form.build input[type="submit"] { + margin-left: auto; + margin-right: 0; + font-size: 150%; + text-transform: uppercase; + transition: background 1s, color 1s; +} + +form.editor input[type="submit"]:hover { + background: lightgray; + color: black; +} + +.edited-time { + font-size: 75%; + color: lightgray; + float: right; +} + +.page-list ul { + margin: 0; + position: relative; + list-style: none; + z-index: 1; +} + +.page-list ul li a { + line-height: 2em; +} + +form input[hidden] { + display: none; }
A templates/build.html

@@ -0,0 +1,23 @@

+{{ $buildOpts := ((.Context).Value "adapter").BuildOptions }} +{{ $csrfToken := (.Context).Value "csrfToken" }} + +{{ template "header" . }} + +<h2>Build</h2> + +<form class="build" method="POST" action="/build-run"> +<input hidden type="text" name="csrfToken" value="{{$csrfToken}}"/> +<details><summary>Build Options</summary> +{{ if $buildOpts }} + {{ range $optName := $buildOpts }} + <label>{{$optName}} <input type="text" name="{{$optName}}"/></label> + {{ end }} +{{ else }} +<span>There are no build options for this adapter.</span> +{{ end }} +</details> + +<input type="submit" value="Build"/> +</form> + +{{ template "footer" . }}
A templates/build_run.html

@@ -0,0 +1,14 @@

+{{ $buildOpts := .PostForm }} +{{ $status := ((.Context).Value "adapter").Build $buildOpts }} + +{{ template "header" . }} + +{{ if ne ($status).Success true }} + <h2>Build Error</h2> + <span class="adapter-error"><pre>{{($status).Message}}</pre></span> +{{ else }} + <h2>Build Successful</h2> + <span class="adapter-success"><pre>{{($status).Message}}</pre></span> +{{ end }} + +{{ template "footer" . }}
M templates/cms_create.htmltemplates/cms_create.html

@@ -6,8 +6,10 @@

{{ template "header" . }} {{ if $createErr }} + <h2>Page Creation Error</h2> <span class="adapter-error">There was an error creating the page: {{ ($createErr).Error }}</span> {{ else }} + <h2>Page Created</h2> <span class="adapter-success">Page '{{ $title }}' created successfully</span> {{ end }}
M templates/cms_edit.htmltemplates/cms_edit.html

@@ -5,7 +5,9 @@ {{ $csrfToken := (.Context).Value "csrfToken" }}

{{ $noEmpty := .FormValue "no-empty" }} {{ template "header" . }} -<a href="/">&laquo;</a> + +<h2>Edit Page</h2> + <form class="editor" method="POST" action="/save/{{$slug}}"> {{ if $noEmpty }} <span class="edit-error">Empty fields are not allowed - please try again</span><br/>

@@ -13,12 +15,27 @@ {{ end }}

<input hidden name="csrfToken" value="{{$csrfToken}}"/> <input hidden name="oldSlug" value="{{$slug}}"/> {{ if $editableSlugs }} - <input class="slug-input" type="text" name="slug" value="{{$slug}}"/><br/> + <label for="slug">Slug</label><br/> + <input class="slug-input" id="slug" type="text" name="slug" value="{{$slug}}" required/><br/> {{ end }} - <input class="title-input" type="text" name="title" value="{{($page).Title}}"/><br/> - <span class="edited-time">last edited {{($page).Edited}}</span><br/> - <textarea class="content-input" name="content">{{($page).Content}}</textarea><br/> + <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/> + <label for="content">Content</label><br/> + <textarea class="content-input" id="content" name="content" required>{{($page).Content}}</textarea><br/> <input type="submit" value="Save"/> </form> -{{ template "footer" . }}+<details class="danger-zone"><summary>Danger Zone</summary> + <form class="eraser" method="POST" action="/delete/{{$slug}}"> + <input hidden name="csrfToken" value="{{$csrfToken}}"/> + <label>I want to delete this page + <input type="checkbox" required/> + </label><br/> + <label>Yes, I'm sure + <input type="checkbox" required/> + </label><br/> + <input type="submit" value="DELETE"/> + </form> +</details> +{{ template "footer" . }}
M templates/cms_list.htmltemplates/cms_list.html

@@ -1,8 +1,11 @@

{{ $pages := ((.Context).Value "adapter").ListPages }} {{ template "header" .}} + +<h2>Pages</h2> + <div class="page-list"> - <a class="new-page-button" href="/new/">New Page</a> + <a class="new-page-button" href="/new">New Page</a> <ul> {{ range $slug, $title := $pages }} <li><a href="/edit/{{$slug}}">{{$title}}</a></li>
M templates/cms_new.htmltemplates/cms_new.html

@@ -3,18 +3,23 @@ {{ $csrfToken := (.Context).Value "csrfToken" }}

{{ $noEmpty := .FormValue "no-empty" }} {{ template "header" . }} -<a href="/">&laquo;</a> + +<h2>New Page</h2> + <form class="editor" method="POST" action="/create"> {{ if $noEmpty }} <span class="edit-error">Empty fields are not allowed - please try again</span><br/> {{ end }} <input hidden name="csrfToken" value="{{$csrfToken}}"/> {{ if $editableSlugs }} - <input class="slug-input" type="text" name="slug"/><br/> + <label for="slug">Slug</label><br/> + <input class="slug-input" id="slug" type="text" name="slug" required/><br/> {{ end }} - <input class="title-input" type="text" name="title"/><br/> - <textarea class="content-input" name="content"></textarea><br/> + <label for="title">Title</label><br/> + <input class="title-input" id="title" type="text" name="title" required/><br/> + <label for="content">Content</label><br/> + <textarea class="content-input" id="content" name="content" required></textarea><br/> <input type="submit" value="Save"/> </form> -{{ template "footer" . }}+{{ template "footer" . }}
M templates/cms_save.htmltemplates/cms_save.html

@@ -7,8 +7,10 @@

{{ template "header" . }} {{ if $saveErr }} + <h2>Page Save Error</h2> <span class="adapter-error">There was an error saving the page: {{ ($saveErr).Error }}</span> {{ else }} + <h2>Page Saved</h2> <span class="adapter-success">Page '{{ $title }}' saved successfully</span> {{ end }}
A templates/delete.html

@@ -0,0 +1,14 @@

+{{ $slug := ((.Context).Value "params").Slug }} +{{ $deleteErr := ((.Context).Value "adapter").DeletePage $slug }} + +{{ template "header" . }} + +{{ if $deleteErr }} + <h2>Deletion Error</h2> + <span class="adapter-error">There was an error deleting the page: {{ ($deleteErr).Error }}</span> +{{ else }} + <h2>Page Deleted</h2> + <span class="adapter-success">Page at '{{ $slug }}' was deleted</span> +{{ end }} + +{{ template "footer" . }}
M templates/header.htmltemplates/header.html

@@ -5,14 +5,17 @@ <head>

<meta charset='utf-8'> <meta name='description' content='Nirvash CMS'/> <meta name='viewport' content='width=device-width,initial-scale=1'> + <link rel='stylesheet' type='text/css' href='/static/style.css'> <title>Nirvash &mdash; CMS</title> </head> <body> + <header><h1>Nirvash CMS</h1></header> <nav> <ul> <li><a href="/">Pages</a></li> <li><a href="/static-mgr">Static Files</a></li> <li><a href="/build">Build</a></li> + <li><a href="/config">Configuration</a></li> <li><a href="/logout">Logout</a></li> </ul> </nav>