tweak file manager UI and route, add file-move feature, add EurekaAdapter formatting help, and update README
This commit is contained in:
parent
119d66cd27
commit
87333f9d87
17 changed files with 361 additions and 67 deletions
34
README.md
34
README.md
|
@ -13,8 +13,11 @@ Clone this repository and run `go build` to build `nirvash`. Just running `./nir
|
|||
```
|
||||
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 @@ User management is done from the command line as well:
|
|||
|
||||
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)
|
|
@ -29,8 +29,8 @@ type Adapter interface {
|
|||
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
|
||||
|
|
|
@ -5,17 +5,19 @@ import (
|
|||
"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) SetAdapter(adapter string) {
|
|||
}
|
||||
|
||||
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,11 +91,22 @@ func (self *Config) RunWizard() {
|
|||
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)
|
||||
|
@ -113,6 +127,31 @@ func ensureNonEmptyOption(buffer *string) {
|
|||
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 @@ func writeConfig(cfg *Config, configFile string) error {
|
|||
|
||||
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 @@ func parseConfig(configFile string) *Config {
|
|||
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":
|
||||
|
|
|
@ -112,7 +112,70 @@ func (self *EurekaAdapter) FormatPage(raw string) string {
|
|||
|
||||
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
|
||||
{¶graph 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 {
|
||||
|
|
|
@ -10,9 +10,10 @@ import (
|
|||
)
|
||||
|
||||
type SimpleFileManager struct {
|
||||
Root string
|
||||
ShowHtml bool
|
||||
ShowHidden bool
|
||||
Root string
|
||||
ShowHTML bool
|
||||
ShowHidden bool
|
||||
maxUploadMB int64
|
||||
}
|
||||
|
||||
type FileData struct {
|
||||
|
@ -37,13 +38,15 @@ type FileManager interface {
|
|||
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 @@ func (self *SimpleFileManager) ListSubTree(root string) FileListing {
|
|||
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 @@ func (self *SimpleFileManager) ListSubTree(root string) FileListing {
|
|||
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 @@ func (self *SimpleFileManager) AddFile(path string, req *http.Request) error {
|
|||
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 @@ func (self *SimpleFileManager) MkDir(path, newDir string) error {
|
|||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -75,21 +75,9 @@ func FormMapToAdapterConfig(next http.Handler, adapter core.Adapter) http.Handle
|
|||
return http.HandlerFunc(handlerFunc)
|
||||
}
|
||||
|
||||
func WithFileData(next http.Handler, fileManager core.FileManager) http.Handler {
|
||||
func PrepareForUpload(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 {
|
||||
handlerFunc := func(w http.ResponseWriter, req *http.Request) {
|
||||
req.ParseMultipartForm(250 << 20)
|
||||
req.ParseMultipartForm(fileManager.MaxUploadMB() << 20)
|
||||
next.ServeHTTP(w, req)
|
||||
}
|
||||
|
||||
|
|
67
nirvash.go
67
nirvash.go
|
@ -210,13 +210,13 @@ func main() {
|
|||
"/"))
|
||||
|
||||
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,17 +234,45 @@ func main() {
|
|||
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(
|
||||
|
@ -290,7 +318,8 @@ func main() {
|
|||
udb,
|
||||
"/login"),
|
||||
udb,
|
||||
"/")))
|
||||
"/"),
|
||||
fileManager))
|
||||
|
||||
rtr.Get(
|
||||
`/mkdir/(?P<Slug>.*)`,
|
||||
|
@ -321,12 +350,18 @@ func main() {
|
|||
"/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)
|
||||
}
|
||||
|
|
|
@ -149,7 +149,7 @@ a:hover {
|
|||
overflow-y: visible;
|
||||
}
|
||||
|
||||
a.new-page-button {
|
||||
.new-page-button {
|
||||
position: relative;
|
||||
top: 1em;
|
||||
text-decoration: none;
|
||||
|
@ -164,7 +164,7 @@ a.new-page-button {
|
|||
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 @@ form input:focus, form textarea:focus {
|
|||
}
|
||||
|
||||
form.editor, .wide {
|
||||
max-width: 80em;
|
||||
max-width: 80em !important;
|
||||
}
|
||||
|
||||
form.editor textarea {
|
||||
|
@ -294,10 +294,18 @@ form.editor input[type="submit"]:hover,form.build input[type="submit"]:hover, .d
|
|||
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,10 +326,15 @@ form input[readonly] {
|
|||
|
||||
.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"] {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
|
@ -331,7 +344,6 @@ input[type="file"] {
|
|||
width: 110px;
|
||||
}
|
||||
|
||||
|
||||
input[type="file"]:not(:valid) + .file-feedback::after {
|
||||
content: "No file selected";
|
||||
height: 1em;
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
<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>
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
{{ 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>
|
||||
|
||||
|
|
|
@ -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 @@
|
|||
{{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>
|
||||
|
|
|
@ -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 }}
|
||||
|
|
55
templates/file_move.html
Normal file
55
templates/file_move.html
Normal file
|
@ -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" .}}
|
22
templates/file_move_process.html
Normal file
22
templates/file_move_process.html
Normal file
|
@ -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" . }}
|
|
@ -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 }}
|
||||
|
||||
|
|
9
templates/format_help.html
Normal file
9
templates/format_help.html
Normal file
|
@ -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" . }}
|
|
@ -13,7 +13,7 @@
|
|||
<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>
|
||||
|
|
Loading…
Reference in a new issue