all repos — nirvash @ 87333f9d8759d7e007f889877f7fc34634d2f4c9

modular CMS using the quartzgun library

tweak file manager UI and route, add file-move feature, add EurekaAdapter formatting help, and update README
Iris Lightshard nilix@nilfm.cc
PGP Signature
-----BEGIN PGP SIGNATURE-----

iQIzBAABCAAdFiEEkFh6dA+k/6CXFXU4O3+8IhROY5gFAmKpi2QACgkQO3+8IhRO
Y5ia5g/6AoMurVb9faPeDNDHCyXmLQ6tXc99tgADvKGcrrEw0bjR/kyBKVOTRJCp
xuqZyMdtkQT8a9l/QJ7xgLRXP+ijPZTZDf9O7N7p3MJ1ensQtFX4CKrdYbCviRDw
lufBeHqJlBO9CxXun8ocHq/LK16O4d4x+0Xo6yac2YhdntGnYFTEd+UcYFShDScQ
7XKg94EIIT0CA6uO/7vNZTFshlk9JVQR56KDwi0Yhad+FRLmJx41ghySPuSytVbL
LuLIBR3OB5YOn+xaaqceVqbz1m7s//+fHAKAfHlr2twapbeybROWN9T/Ge2+i8QI
6K9Csa154GCWej+/XQOMjr/YS1/fG1B+rGRyLZffqsASE6pNBxEbnlFnqTOTkb4C
QEqBPRZwhNkJJytpXtoCLcNnkGhKyPILGc3SlNNCY6AgSPZIrOHrQ+uz66+NP6JY
VVZPW1xuWRCoCGowFk3qx1nFB32Bbj8mBKYzeSr8nFmilp9K0TeCXmkHLn8C+DE+
DFdUGtEOz1yzB1vlREVMxr6BwS5bV5i8AaPuQWUttkm2NsWJmpBXYkHP0MRFFNmR
vX9hnLmLWpea3zZXMrKCp8so4zQEF3D+sByW2K86dov5bHW/dnTDOm+h/BAt4FCV
9jftgyWCbXRjBw9z3VbOfylT8lzsanADFwArcu0xXGlHr1jZlXs=
=wvOx
-----END PGP SIGNATURE-----
commit

87333f9d8759d7e007f889877f7fc34634d2f4c9

parent

119d66cd2726ad7bd8058a96dbe162d27da56a61

M README.mdREADME.md

@@ -13,8 +13,11 @@

``` adapter=eureka // one of the supported adapters, currently just eureka root=/path/to/ssg/root // path to where your SSG content root is -assetRoot=/path/to/asset/root // path to the Nirvash static assets (eg static/ directory in this repo) +assetRoot=/path/to/asset/root // path to the parent folder containing Nirvash static/ assets and templates/ directory (eg base directory of this repo) staticRoot=/path/to/static/root // path to static file storage on your webserver +staticShowHTML=false // true or false, show HTML files in the file manager interface +staticShowHidden=false // true or false, show hidden files in the file manager interface +staticMaxUploadMB=25 // integer, maximum size in MB of files uploaded in the file manager interface plugins=none // list of plugins to use, currently none are implemented ```

@@ -30,4 +33,31 @@ ## usage

Running `nirvash` without any arguments starts the webserver on port 8080. -MORE TO COME+Initially the user will be presented with the login screen; upon successful login, the application presents the navbar with these options: + +- `Pages`: the default page, shows a list of existing pages - clicking one enables editing that page; a button is also presented for adding a new page. Each `Adapter` will provide different formatting help and can allow editable slugs/URLs or not (eg, the `EurekaAdapter` builds slugs/URLs directly from the page title). +- `Files`: provides an interface for managing statically hosted files. Files and directories can be added, moved, and deleted. +- `Build`: a simple form to build the site - build options configurable by `Adapter` are present under an accordion. +- `Configuration`: interface to the configuration for the `Adapter`. Each `Adapter` provides its own configuration interface with associated data types (currently supported: `int`, `float`, `string`, and `multilinestring`) +- `Logout`: logs the user out and returns to the login screen + +## adapter interface + +`nirvash` is extensible by `Adapter`s that can interact with almost any static site generator under the hood. + +The `Adapter` interface and associated data types can be found in the [adapter.go](https://nilfm.cc/git/nirvash/tree/archetype/adapter.go) file, but the basic interface looks like this: + +- `Init(cfg *Config)`: set any initial settings that need to be handled - typically import SSG data root from the `nirvash` config file and read the SSG's own config file +- `Name() string`: the name of the adapter, used for the `nirvash.conf` config file +- `EditableSlugs() bool`: whether slugs can be edited independently or are calculated based on, eg, page titles +- `BuildOptions() []string`: a list of names of the build options to present on the `/build` page +- `GetConfig() map[ConfigOption]string`: retrieves the config to present on the `/config` page +- `SetConfig(map[ConfigOption]string) error`: takes the config from the `/config` page and applies it, typically by writing it to a file +- `ListPages() map[string]string`: list the pages in the site; keys are the slugs, values are the titles +- `GetPage(slug string) Page`: given a slug, return the page data +- `FormatPage(string) string`: given the raw page data as input by the user, return HTML suitable for preview (currently unimplemented and unused) +- `FormattingHelp() string`: return a string to be inserted into a `pre` tag on the `/fmt-help` page +- `CreatePage(slug, title, content string) error`: given all the proper arguments, create a new page in the backing store (eg filesystem, db) +- `SavePage(oldSlug, newSlug, title, content string) error`: given all the proper arguments, save a page to the backing store (eg filesystem, db) +- `DeletePage(slug string) error`: given a slug, delete the corresponding page source and possibly its generated HTML, depending on the `Adapter` +- `Build(buildOptions map[string][]string) BuildStatus`: takes a map of build option names to their values and builds the site, returning a `BuildStatus` object containing the success or failure as a boolean and the detailed status (eg, the console ouptut of the build process)
M archetype/adapter.goarchetype/adapter.go

@@ -29,8 +29,8 @@ BuildOptions() []string

GetConfig() map[ConfigOption]string SetConfig(map[ConfigOption]string) error ListPages() map[string]string - GetPage(string) Page - FormatPage(string) string + GetPage(slug string) Page + FormatPage(raw string) string FormattingHelp() string CreatePage(slug, title, content string) error SavePage(oldSlug, newSlug, title, content string) error
M archetype/config.goarchetype/config.go

@@ -5,17 +5,19 @@ "fmt"

"os" "path/filepath" "runtime" + "strconv" "strings" ) type Config struct { - Adapter Adapter // adapter for this instance - Root string // root of the site data - StaticRoot string // root of static files for StaticFileManager - StaticShowHidden bool // whether to show hidden files in the StaticFileManager - StaticShowHtml bool // whether to show html files in the StaticFileManager - AssetRoot string // root of Nirvash dist files (CSS, images) - Plugins map[string]interface{} + Adapter Adapter // adapter for this instance + Root string // root of the site data + StaticRoot string // root of static files for StaticFileManager + StaticShowHidden bool // whether to show hidden files in the FileManager + StaticShowHTML bool // whether to show html files in the FileManager + StaticMaxUploadMB int64 // max size in MB of files uploaded via FileManager + AssetRoot string // root of Nirvash dist files (CSS, images) + Plugins map[string]interface{} } func GetConfigLocation() string {

@@ -63,6 +65,7 @@ }

} func (self *Config) IsNull() bool { + // zero-values for StaticShowHTML, StaticShowHidden, and StaticMaxUploadMB are valid return self.Adapter == nil || len(self.Root) == 0 || len(self.StaticRoot) == 0 || len(self.AssetRoot) == 0 }

@@ -88,12 +91,23 @@ ensureNonEmptyOption(&inputBuf)

self.Root = inputBuf inputBuf = "" - fmt.Printf("static file root? ") ensureNonEmptyOption(&inputBuf) self.StaticRoot = inputBuf inputBuf = "" + fmt.Printf("show HTML files in file manager? ") + self.StaticShowHTML = ensureBooleanOption(&inputBuf) + + inputBuf = "" + fmt.Printf("show hidden files in file manager? ") + self.StaticShowHidden = ensureBooleanOption(&inputBuf) + + inputBuf = "" + fmt.Printf("max upload size (MB)? ") + self.StaticMaxUploadMB = ensureNumberOption(&inputBuf) + + inputBuf = "" fmt.Printf("nirvash asset root? ") ensureNonEmptyOption(&inputBuf) self.AssetRoot = inputBuf

@@ -113,6 +127,31 @@ fmt.Scanln(buffer)

if len(strings.TrimSpace(*buffer)) != 0 { break } + fmt.Println("Please enter a nonempty value") + } +} + +func ensureBooleanOption(buffer *string) bool { + for { + fmt.Scanln(buffer) + trimmedBuf := strings.TrimSpace(*buffer) + v, err := strconv.ParseBool(trimmedBuf) + if err == nil { + return v + } + fmt.Println("Please enter a true or false value") + } +} + +func ensureNumberOption(buffer *string) int64 { + for { + fmt.Scanln(buffer) + trimmedBuf := strings.TrimSpace(*buffer) + v, err := strconv.ParseInt(trimmedBuf, 10, 64) + if err == nil && v > 0 { + return v + } + fmt.Println("Please enter a positive integer") } }

@@ -126,6 +165,9 @@ defer f.Close()

f.WriteString("root=" + cfg.Root + "\n") f.WriteString("staticRoot=" + cfg.StaticRoot + "\n") + f.WriteString("staticShowHTML=" + strconv.FormatBool(cfg.StaticShowHTML) + "\n") + f.WriteString("staticShowHidden=" + strconv.FormatBool(cfg.StaticShowHidden) + "\n") + f.WriteString("staticMaxUploadMB=" + strconv.FormatInt(cfg.StaticMaxUploadMB, 10) + "\n") f.WriteString("assetRoot=" + cfg.AssetRoot + "\n") f.WriteString("adapter=" + cfg.Adapter.Name() + "\n") f.WriteString("plugins=\n")

@@ -159,6 +201,12 @@ case "root":

cfg.Root = v case "staticRoot": cfg.StaticRoot = v + case "staticShowHTML": + cfg.StaticShowHTML, _ = strconv.ParseBool(v) + case "staticShowHidden": + cfg.StaticShowHidden, _ = strconv.ParseBool(v) + case "staticMaxUploadMB": + cfg.StaticMaxUploadMB, _ = strconv.ParseInt(v, 10, 64) case "assetRoot": cfg.AssetRoot = v case "plugins":
M archetype/eureka.goarchetype/eureka.go

@@ -112,7 +112,70 @@ }

func (self *EurekaAdapter) FormattingHelp() string { // TODO: show Eureka formatting guide - return "help!" + return `// shorthand for linking other pages +{page name} + +// shorthand for page transclusion +{/page name} + +// shorthand for arbitary link +{*destination url|text} + +// shorthand for an image you can click to see the full sized version +{:anchor-id|image url|alt text} + +// shorthand for an image with arbitrary link destination +{?anchor-id|destination url|image url|alt text} + +// shorthand for an audio player +{_/path/to/media} + +// shorthand for paragraphs, can embed other markup inside it +{&paragraph text {with a link} {@and some bold text}} + +// shorthand for ordered lists, can embed other markup inside it +{# + {-item one} + {-item two} + {-item three} +} + +// shorthand for unordered lists, can embed other markup inside it +{, + {-item one} + {-item two} + {-item three} +} + +// shorthand for bold +{@bold text} + +// shorthand for italic +{~italic text} + +// shorthand for code +{` + "`" + `short code} + +// shorthand for pre +{$longer code} + +// shorthand for quote +{'short quote} + +// shorthand for blockquote +{>longer quote} + +// shorthand for strikethrough +{\crossed-out text} + +// shorthand for level 3 heading +{!heading text} + +// shorthand for level 4 heading +{.heading text} + +// shorthand for publish date (renders as <time class='publish-date'>) +{+2022-02-22}` } func (self *EurekaAdapter) CreatePage(slug, title, content string) error {
M archetype/fileManager.goarchetype/fileManager.go

@@ -10,9 +10,10 @@ "strings"

) type SimpleFileManager struct { - Root string - ShowHtml bool - ShowHidden bool + Root string + ShowHTML bool + ShowHidden bool + maxUploadMB int64 } type FileData struct {

@@ -37,13 +38,15 @@ GetFileData(slug string) FileData

AddFile(path string, req *http.Request) error MkDir(path, newDir string) error Remove(path string) error - // Rename(old, new string) error + Rename(oldFullPath, newPath, newName string) error + MaxUploadMB() int64 } func (self *SimpleFileManager) Init(cfg *Config) error { self.Root = filepath.Clean(cfg.StaticRoot) - self.ShowHtml = cfg.StaticShowHtml + self.ShowHTML = cfg.StaticShowHTML self.ShowHidden = cfg.StaticShowHidden + self.maxUploadMB = cfg.StaticMaxUploadMB return nil }

@@ -77,7 +80,10 @@ if list.Root != "/" {

list.Up = "/" } if len(levels) >= 2 { - list.Up = "/" + strings.Join(levels[:len(levels)-1], "/") + list.Up = strings.Join(levels[:len(levels)-1], "/") + if !strings.HasPrefix(list.Up, "/") { + list.Up = "/" + list.Up + } } for _, file := range files {

@@ -87,7 +93,7 @@ }

if file.IsDir() { list.SubDirs = append(list.SubDirs, file.Name()) } else { - if !self.ShowHtml && strings.HasSuffix(file.Name(), ".html") { + if !self.ShowHTML && strings.HasSuffix(file.Name(), ".html") { continue } list.Files = append(list.Files, file.Name())

@@ -147,7 +153,7 @@ if !strings.HasPrefix(fullPath, filepath.Clean(self.Root)) {

return errors.New("You cannot escape!") } - req.ParseMultipartForm(250 << 20) + req.ParseMultipartForm(self.maxUploadMB << 20) file, header, err := req.FormFile("file") if err != nil { return err

@@ -192,3 +198,28 @@ }

return os.Mkdir(newDirPath, 0755) } + +func (self *SimpleFileManager) Rename(oldFullPath, newPath, newName string) error { + fullPath := filepath.Join(self.Root, oldFullPath) + _, err := os.Stat(fullPath) + if err != nil { + return err + } + + newParent := filepath.Join(self.Root, newPath) + _, err = os.Stat(newParent) + if err != nil { + return err + } + + if newName == "" { + _, oldName := filepath.Split(oldFullPath) + newName = oldName + } + + return os.Rename(fullPath, filepath.Join(newParent, newName)) +} + +func (self *SimpleFileManager) MaxUploadMB() int64 { + return self.maxUploadMB +}
M lfo/middleware.golfo/middleware.go

@@ -75,21 +75,9 @@

return http.HandlerFunc(handlerFunc) } -func WithFileData(next http.Handler, fileManager core.FileManager) http.Handler { - handlerFunc := func(w http.ResponseWriter, req *http.Request) { - ctx := req.Context() - fileSlug := ctx.Value("params").(map[string]string)["Slug"] - fileData := fileManager.GetFileData(fileSlug) - *req = *req.WithContext(context.WithValue(req.Context(), "file-data", fileData)) - next.ServeHTTP(w, req) - } - - return http.HandlerFunc(handlerFunc) -} - -func PrepareForUpload(next http.Handler) http.Handler { +func PrepareForUpload(next http.Handler, fileManager core.FileManager) http.Handler { handlerFunc := func(w http.ResponseWriter, req *http.Request) { - req.ParseMultipartForm(250 << 20) + req.ParseMultipartForm(fileManager.MaxUploadMB() << 20) next.ServeHTTP(w, req) }
M nirvash.gonirvash.go

@@ -210,13 +210,13 @@ udb,

"/")) rtr.Get( - `/static-mgr`, + `/file-mgr`, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - http.Redirect(w, req, "/static-mgr/", http.StatusSeeOther) + http.Redirect(w, req, "/file-mgr/", http.StatusSeeOther) })) rtr.Get( - `/static-mgr/(?P<Slug>.*)`, + `/file-mgr/(?P<Slug>.*)`, Fortify( Protected( WithFileManager(

@@ -234,18 +234,46 @@ `/file-actions/(?P<Slug>.*)`,

Fortify( Protected( WithFileManager( - WithFileData( - renderer.Template( - pathConcat(templateRoot, "file_actions.html"), - pathConcat(templateRoot, "header.html"), - pathConcat(templateRoot, "footer.html")), - fileManager), + renderer.Template( + pathConcat(templateRoot, "file_actions.html"), + pathConcat(templateRoot, "header.html"), + pathConcat(templateRoot, "footer.html")), + fileManager), + http.MethodGet, + udb, + "/login"))) + + rtr.Get( + `/file-move/(?P<Slug>.*)`, + Fortify( + Protected( + WithFileManager( + renderer.Template( + pathConcat(templateRoot, "file_move.html"), + pathConcat(templateRoot, "header.html"), + pathConcat(templateRoot, "footer.html")), fileManager), http.MethodGet, udb, "/login"))) rtr.Post( + `/file-move-process/(?P<Slug>.*)`, + Defend( + Protected( + WithFileManager( + renderer.Template( + pathConcat(templateRoot, "file_move_process.html"), + pathConcat(templateRoot, "header.html"), + pathConcat(templateRoot, "footer.html")), + fileManager), + http.MethodGet, + udb, + "/login"), + udb, + "/")) + + rtr.Post( `/file-delete/(?P<Slug>.*)`, Defend( Protected(

@@ -290,7 +318,8 @@ http.MethodGet,

udb, "/login"), udb, - "/"))) + "/"), + fileManager)) rtr.Get( `/mkdir/(?P<Slug>.*)`,

@@ -321,12 +350,18 @@ udb,

"/login"), udb, "/")) - // TODO: - // add directory POST performs the action of creating directory - // move-choose POST uses a form to navigate through the file tree - // - to use links to navigate, destination location uses the slug, - // - and file to move uses the POST parameters - // move-do POST moves the file when finalized in move-choose + rtr.Get( + `/fmt-help`, + Protected( + WithAdapter( + renderer.Template( + pathConcat(templateRoot, "format_help.html"), + pathConcat(templateRoot, "header.html"), + pathConcat(templateRoot, "footer.html")), + cfg.Adapter), + http.MethodGet, + udb, + "/login")) http.ListenAndServe(":8080", rtr) }
M static/style.cssstatic/style.css

@@ -149,7 +149,7 @@ height: 0;

overflow-y: visible; } -a.new-page-button { +.new-page-button { position: relative; top: 1em; text-decoration: none;

@@ -164,7 +164,7 @@ display: inline-block;

margin-bottom: 0.2em; } -a.new-page-button:hover { +.new-page-button:hover { background: lightgray; color: black; }

@@ -196,13 +196,13 @@ span.adapter-error {

border-bottom: 2px solid crimson; } -form.editor label, form.build label, .danger-zone label, form.configurator label, form.file-move label, .mkdir label { +form.editor label, form.build label, .danger-zone label, form.configurator label, form.file-move label, .mkdir label, .page-list label { font-size: 80%; color: lightgray; text-transform: uppercase; } -form.editor input, form.build input, form.editor textarea, form.configurator input, form.configurator textarea, .danger-zone input[type="submit"], .file-move input[type="submit"], .uploader label, .uploader input[type="submit"], .mkdir input { +form.editor input, form.build input, form.editor textarea, form.configurator input, form.configurator textarea, .danger-zone input[type="submit"], .file-move input[type="submit"], .uploader label, .uploader input[type="submit"], .mkdir input, .page-list input[type="text"] { display: block; margin: 0; margin-top: 0.2em;

@@ -247,7 +247,7 @@ border: 2px solid cyan;

} form.editor, .wide { - max-width: 80em; + max-width: 80em !important; } form.editor textarea {

@@ -294,10 +294,18 @@ list-style: none;

z-index: 1; } -.page-list ul li a { +.page-list ul li { line-height: 2em; } +.page-list ul li span.file-nolink { + color: #7f7f7f; +} + +.left-pad-uplink { + padding-left: 20px; +} + form input[hidden] { display: none; }

@@ -318,8 +326,13 @@ }

.file-actions-icon { display: inline-block; + opacity: 0; max-height: 16px; width: 16px; +} + +.file-list li:hover > .file-actions-icon, .file-list li:focus-within > .file-actions-icon { + opacity: 1; } input[type="file"] {

@@ -330,7 +343,6 @@ top: 5px;

height: 50px; width: 110px; } - input[type="file"]:not(:valid) + .file-feedback::after { content: "No file selected";
M templates/cms_edit.htmltemplates/cms_edit.html

@@ -29,6 +29,7 @@ {{ end }}

<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/> + <a target="_blank" class="fmt-help" href="/fmt-help">Formatting help</a> <input type="submit" value="Save"/> </form>
M templates/cms_new.htmltemplates/cms_new.html

@@ -19,6 +19,7 @@ <input class="slug-input" id="slug" type="text" name="slug" required/><br/>

{{ end }} <label for="content">Content</label><br/> <textarea class="content-input" id="content" name="content" required></textarea><br/> + <a target="_blank" class="fmt-help" href="/fmt-help">Formatting help</a> <input type="submit" value="Save"/> </form>
M templates/file_actions.htmltemplates/file_actions.html

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

{{ $slug := ((.Context).Value "params").Slug }} -{{ $file := (.Context).Value "file-data" }} +{{ $file := ((.Context).Value "file-manager").GetFileData $slug }} {{ $csrfToken := (.Context).Value "csrfToken" }} {{ template "header" . }}

@@ -17,9 +17,8 @@ <h2>File: {{($file).Name}}</h2>

{{end}} <div class="action-panel"> - <form class="file-move" method="POST" action="/move-select/{{($file).Path}}"> + <form class="file-move" method="GET" action="/file-move/{{($file).Path}}"> <span>/{{($file).Path}}</span> - <input hidden name="csrfToken" value="{{$csrfToken}}"/> <input type="submit" value="Move/Rename"/> </form> <details class="danger-zone"><summary>Danger Zone</summary>
M templates/file_list.htmltemplates/file_list.html

@@ -19,12 +19,12 @@

<div class="page-list"> <ul class="file-list"> {{ if ($fileList).Up }} - <li><a href="/static-mgr{{$fileList.Up}}">..</a></li> + <li><a class="left-pad-uplink" href="/file-mgr{{$fileList.Up}}">..</a></li> {{ end }} {{ range $dir := ($fileList).SubDirs }} <li> <a class="file-actions-icon" href="/file-actions{{($fileList).Root}}{{$dir}}"><img src="/static/actions.png" width="16px" height="16px" alt="actions"/></a> - <a href="/static-mgr{{($fileList).Root}}{{$dir}}">{{$dir}}/</a> + <a href="/file-mgr{{($fileList).Root}}{{$dir}}">{{$dir}}/</a> </li> {{ end }} {{ range $file := ($fileList).Files }}
A templates/file_move.html

@@ -0,0 +1,55 @@

+{{ $slug := ((.Context).Value "params").Slug }} +{{ $dest := .FormValue "dest" }} +{{ $fileList := ((.Context).Value "file-manager").ListSubTree $dest }} +{{ $fileData := ((.Context).Value "file-manager").GetFileData $slug }} +{{ $csrfToken := (.Context).Value "csrfToken" }} + +{{ template "header" .}} + + +{{ if ($fileList).Error}} +<h2>File Listing Error</h2> + +<span class="adapter-error">{{($fileList).Error}}</span> + +{{ else if ($fileData).Error }} + +<h2>File Listing Error</h2> + +<span class="adapter-error">{{($fileData).Error}}</span> + +{{ else }} +<h2>Moving {{($fileData).Name}}: {{($fileList).Root}}</h2> + +<form class="move-rename-file" method="POST" action="/file-move-process/{{($fileData).Path}}"> +<input hidden type="text" name="csrfToken" value="{{$csrfToken}}"/> +<input hidden type="text" name="dest" value="{{($fileList).Root}}"/> +<div class="new-page-button-wrapper"> + <input type="submit" class="new-page-button" value="Move here"/> +</div> + +<div class="page-list"> + <label>New file name + <input type="text" name="filename" value="{{($fileData).Name}}"/> + </label> + <ul class="file-list"> + {{ if ($fileList).Up }} + <li><a href="/file-move/{{($fileData).Path}}?dest={{($fileList).Up}}">..</a></li> + {{ end }} + {{ range $dir := ($fileList).SubDirs }} + <li> + <a href="/file-move/{{($fileData).Path}}?dest={{($fileList).Root}}{{$dir}}">{{$dir}}/</a> + </li> + {{ end }} + {{ range $file := ($fileList).Files }} + <li> + <span class="file-nolink">{{$file}}</span> + </li> + {{ end }} + </ul> +</div> +</form> + +{{ end }} + +{{ template "footer" .}}
A templates/file_move_process.html

@@ -0,0 +1,22 @@

+{{ $slug := ((.Context).Value "params").Slug }} +{{ $dest := .FormValue "dest" }} +{{ $name := .FormValue "filename" }} +{{ $moveError := ((.Context).Value "file-manager").Rename $slug $dest $name }} + +{{ template "header" . }} + +{{ if $moveError }} + +<h2>File Move/Rename Error</h2> + +<span class="adapter-error">{{$moveError}}</span> + +{{ else }} + +<h2>File Move/Rename Success</h2> + +<span class="adapter-success">File moved from {{$slug}} to {{$dest}}{{$name}} </span> + +{{ end }} + +{{ template "footer.html" . }}
M templates/file_upload.htmltemplates/file_upload.html

@@ -1,13 +1,13 @@

{{ $slug := ((.Context).Value "params").Slug }} {{ $csrfToken := (.Context).Value "csrfToken" }} -{{ $fileListing := ((.Context).Value "file-manager").ListSubtree $slug }} +{{ $fileData := ((.Context).Value "file-manager").GetFileData $slug }} {{ template "header" . }} -{{ if ($fileListing).Error }} +{{ if ($fileData).Error }} <h2>Error</h2> -<span class="adapter-error">{{($fileListing).Error}}</span> +<span class="adapter-error">{{($fileData).Error}}</span> {{ else }}
A templates/format_help.html

@@ -0,0 +1,9 @@

+{{ $fmtHelp := ((.Context).Value "adapter").FormattingHelp }} + +{{ template "header" . }} + +<h2>Formatting Help</h2> + +<span class="adapter-success wide"><pre>{{ $fmtHelp }}</pre></span> + +{{ template "footer" . }}
M templates/header.htmltemplates/header.html

@@ -13,7 +13,7 @@ <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="/file-mgr">Files</a></li> <li><a href="/build">Build</a></li> <li><a href="/config">Configuration</a></li> <li><a href="/logout">Logout</a></li>