some bug fixes
parent
e137f46169
commit
d6c81ec07d
|
@ -578,6 +578,10 @@ header h1 {
|
|||
margin: 0;
|
||||
font-size: 2em;
|
||||
}
|
||||
header a,
|
||||
header a:hover {
|
||||
color: inherit;
|
||||
}
|
||||
header p {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,13 @@
|
|||
|
||||
var selectedItems = [];
|
||||
|
||||
Array.prototype.removeElement = function(element) {
|
||||
var i = this.indexOf(element);
|
||||
if (i != -1) {
|
||||
this.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function(event) {
|
||||
var items = document.getElementsByClassName('item');
|
||||
Array.from(items).forEach(link => {
|
||||
|
@ -12,10 +19,7 @@ document.addEventListener("DOMContentLoaded", function(event) {
|
|||
selectedItems.push(url);
|
||||
} else {
|
||||
link.classList.remove('selected');
|
||||
var i = selectedItems.indexOf(url);
|
||||
if (i != -1) {
|
||||
selectedItems.splice(i, 1);
|
||||
}
|
||||
selectedItems.removeElement(url);
|
||||
}
|
||||
|
||||
var event = new CustomEvent('changed-selected');
|
||||
|
@ -24,49 +28,69 @@ document.addEventListener("DOMContentLoaded", function(event) {
|
|||
});
|
||||
});
|
||||
|
||||
document.getElementById("back").addEventListener("click", backEvent);
|
||||
document.getElementById("open").addEventListener("click", openEvent);
|
||||
if (document.getElementById("back")) {
|
||||
document.getElementById("back").addEventListener("click", backEvent)
|
||||
};
|
||||
document.getElementById("delete").addEventListener("click", deleteEvent);
|
||||
document.getElementById("download").addEventListener("click", downloadEvent);
|
||||
return false;
|
||||
});
|
||||
|
||||
var backEvent = function(event) {
|
||||
var items = document.getElementsByClassName('item');
|
||||
Array.from(items).forEach(link => {
|
||||
link.classList.remove('selected');
|
||||
});
|
||||
selectedItems = [];
|
||||
var openEvent = function(event) {
|
||||
if (selectedItems.length) {
|
||||
|
||||
var event = new CustomEvent('changed-selected');
|
||||
document.dispatchEvent(event);
|
||||
return false;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
window.open(window.location + "?raw=true");
|
||||
return false;
|
||||
}
|
||||
|
||||
var backEvent = function(event) {
|
||||
var items = document.getElementsByClassName('item');
|
||||
Array.from(items).forEach(link => {
|
||||
link.classList.remove('selected');
|
||||
});
|
||||
selectedItems = [];
|
||||
|
||||
var event = new CustomEvent('changed-selected');
|
||||
document.dispatchEvent(event);
|
||||
return false;
|
||||
}
|
||||
|
||||
var deleteEvent = function(event) {
|
||||
Array.from(selectedItems).forEach(item => {
|
||||
var request = new XMLHttpRequest();
|
||||
request.open("DELETE", item);
|
||||
request.send();
|
||||
request.onreadystatechange = function() {
|
||||
if (request.readyState == 4) {
|
||||
if (request.status != 200) {
|
||||
alert("something wrong happened!");
|
||||
return false;
|
||||
}
|
||||
Array.from(selectedItems).forEach(item => {
|
||||
var request = new XMLHttpRequest();
|
||||
request.open("DELETE", item);
|
||||
request.send();
|
||||
request.onreadystatechange = function() {
|
||||
if (request.readyState == 4) {
|
||||
if (request.status != 200) {
|
||||
alert("something wrong happened!");
|
||||
return false;
|
||||
}
|
||||
|
||||
alert(item + " deleted");
|
||||
// Add removing animation
|
||||
}
|
||||
}
|
||||
});
|
||||
return false;
|
||||
selectedItems.removeElement(item);
|
||||
alert(item + " deleted");
|
||||
// Add removing animation
|
||||
}
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
var downloadEvent = function(event) {
|
||||
Array.from(selectedItems).forEach(item => {
|
||||
window.open(item + "?download=true");
|
||||
});
|
||||
return false;
|
||||
if (selectedItems.length) {
|
||||
Array.from(selectedItems).forEach(item => {
|
||||
window.open(item + "?download=true");
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
window.open(window.location + "?download=true");
|
||||
return false;
|
||||
}
|
||||
|
||||
document.addEventListener("changed-selected", function(event) {
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
{{ define "actions" }}
|
||||
<div>
|
||||
<div class="action" id="open"><i class="material-icons">open_in_new</i></div>
|
||||
<div class="action" id="rename"><i class="material-icons">mode_edit</i></div>
|
||||
<div class="action" id="download"><i class="material-icons">file_download</i></div>
|
||||
<div class="action" id="delete"><i class="material-icons">delete</i></div>
|
||||
</div>
|
||||
{{ end }}
|
|
@ -13,31 +13,32 @@
|
|||
<header>
|
||||
<div>
|
||||
{{ $lnk := .PreviousLink }}
|
||||
{{ if ne $lnk ""}}<a href="../../{{.PreviousLink}}"><div class="action" id="prev"><i class="material-icons">subdirectory_arrow_left</i></div></a>{{ else }}
|
||||
{{ if ne $lnk ""}}<a href="{{ if eq $lnk "/" }}/{{else }}../../{{.PreviousLink}}{{ end }}"><div class="action" id="prev"><i class="material-icons">subdirectory_arrow_left</i></div></a>{{ else }}
|
||||
<div class="action disabled" id="prev"><i class="material-icons">subdirectory_arrow_left</i></div>{{ end }}
|
||||
<p><a href="{{ if eq .Config.BaseURL "" }}/{{ else }}{{ .Config.BaseURL }}{{ end }}">File Manager</a> {{ if ne .Name "."}}<i class="material-icons">chevron_right</i> {{ .Name }}</p>{{ end }}
|
||||
<p><a href="{{ if eq .Config.BaseURL "" }}/{{ else }}{{ .Config.BaseURL }}{{ end }}">File Manager</a> {{ if ne .Name "/"}}<i class="material-icons">chevron_right</i> {{ .Name }}</p>{{ end }}
|
||||
</div>
|
||||
{{ if .IsDir}}
|
||||
<div>
|
||||
<form>
|
||||
<i class="material-icons">search</i> <input type="text" placeholder="Search">
|
||||
</form>
|
||||
|
||||
<div class="action" id="view"><i class="material-icons">view_comfy</i></div>
|
||||
<div class="action" id="view"><i class="material-icons">view_headline</i></div>
|
||||
<div class="action" id="upload"><i class="material-icons">file_upload</i></div>
|
||||
</div>
|
||||
{{ else }}
|
||||
{{ template "actions" . }}
|
||||
{{ end }}
|
||||
</header>
|
||||
|
||||
{{ if .IsDir }}
|
||||
<div id="toolbar">
|
||||
<div>
|
||||
<div class="action" id="back"><i class="material-icons">arrow_back</i></div>
|
||||
<p><span id="selected-number">0</span> selected.</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="action" id="open"><i class="material-icons">open_in_new</i></div>
|
||||
<div class="action" id="rename"><i class="material-icons">mode_edit</i></div>
|
||||
<div class="action" id="download"><i class="material-icons">file_download</i></div>
|
||||
<div class="action" id="delete"><i class="material-icons">delete</i></div>
|
||||
</div>
|
||||
{{ template "actions" . }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
<main>
|
||||
{{ template "content" .Data }}
|
||||
|
|
|
@ -24,64 +24,5 @@
|
|||
</div>
|
||||
{{- end}}
|
||||
</div>
|
||||
|
||||
|
||||
<!--
|
||||
<table class="container" aria-describedby="summary">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
{{- if and (eq .Sort "name") (ne .Order "desc")}}
|
||||
<a href="?sort=name&order=desc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}">Name <svg width="1em" height=".4em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#up-arrow"></use></svg></a>
|
||||
{{- else if and (eq .Sort "name") (ne .Order "asc")}}
|
||||
<a href="?sort=name&order=asc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}">Name <svg width="1em" height=".4em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#down-arrow"></use></svg></a>
|
||||
{{- else}}
|
||||
<a href="?sort=name&order=asc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}">Name</a>
|
||||
{{- end}}
|
||||
</th>
|
||||
<th>
|
||||
{{- if and (eq .Sort "size") (ne .Order "desc")}}
|
||||
<a href="?sort=size&order=desc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}">Size <svg width="1em" height=".4em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#up-arrow"></use></svg></a>
|
||||
{{- else if and (eq .Sort "size") (ne .Order "asc")}}
|
||||
<a href="?sort=size&order=asc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}">Size <svg width="1em" height=".4em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#down-arrow"></use></svg></a>
|
||||
{{- else}}
|
||||
<a href="?sort=size&order=asc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}">Size</a>
|
||||
{{- end}}
|
||||
</th>
|
||||
<th class="hideable">
|
||||
{{- if and (eq .Sort "time") (ne .Order "desc")}}
|
||||
<a href="?sort=time&order=desc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}">Modified <svg width="1em" height=".4em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#up-arrow"></use></svg></a>
|
||||
{{- else if and (eq .Sort "time") (ne .Order "asc")}}
|
||||
<a href="?sort=time&order=asc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}">Modified <svg width="1em" height=".4em" version="1.1" viewBox="0 0 12.922194 6.0358899"><use xlink:href="#down-arrow"></use></svg></a>
|
||||
{{- else}}
|
||||
<a href="?sort=time&order=asc{{if ne 0 .ItemsLimitedTo}}&limit={{.ItemsLimitedTo}}{{end}}">Modified</a>
|
||||
{{- end}}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
{{- range .Items}}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{.URL}}">
|
||||
{{- if .IsDir}}
|
||||
<i class="material-icons">folder</i>
|
||||
{{- else}}
|
||||
<i class="material-icons">insert_drive_file</i>
|
||||
{{- end}}
|
||||
<span class="name">{{.Name}}</span>
|
||||
</a>
|
||||
</td>
|
||||
{{- if .IsDir}}
|
||||
<td data-order="-1">—</td>
|
||||
{{- else}}
|
||||
<td data-order="{{.Size}}">{{.HumanSize}}</td>
|
||||
{{- end}}
|
||||
<td class="hideable"><time datetime="{{.HumanModTime "2006-01-02T15:04:05Z"}}">{{.HumanModTime "01/02/2006 03:04:05 PM -07:00"}}</time></td>
|
||||
</tr>
|
||||
{{- end}}
|
||||
</tbody>
|
||||
</table> -->
|
||||
</div>
|
||||
{{ end }}
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
<main class="container">
|
||||
|
||||
{{ if eq .Type "image" }}
|
||||
<img src="{{.Base64}}">
|
||||
<img src="{{ .URL }}?raw=true">
|
||||
{{ else if eq .Type "audio" }}
|
||||
<audio src="{{.Base64}}">
|
||||
<audio src="{{ .URL }}?raw=true">
|
||||
</audio>
|
||||
{{ else if eq .Type "video" }}
|
||||
|
||||
|
|
21
binary.go
21
binary.go
|
@ -2,6 +2,7 @@
|
|||
// sources:
|
||||
// assets/public/css/styles.css
|
||||
// assets/public/js/application.js
|
||||
// assets/templates/actions.tmpl
|
||||
// assets/templates/base.tmpl
|
||||
// assets/templates/listing.tmpl
|
||||
// assets/templates/single.tmpl
|
||||
|
@ -67,6 +68,24 @@ func publicJsApplicationJs() (*asset, error) {
|
|||
return a, err
|
||||
}
|
||||
|
||||
// templatesActionsTmpl reads file data from disk. It returns an error on failure.
|
||||
func templatesActionsTmpl() (*asset, error) {
|
||||
path := "D:\\Code\\Go\\src\\github.com\\hacdias\\caddy-filemanager\\assets\\templates\\actions.tmpl"
|
||||
name := "templates/actions.tmpl"
|
||||
bytes, err := bindataRead(path, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fi, err := os.Stat(path)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error reading asset info %s at %s: %v", name, path, err)
|
||||
}
|
||||
|
||||
a := &asset{bytes: bytes, info: fi}
|
||||
return a, err
|
||||
}
|
||||
|
||||
// templatesBaseTmpl reads file data from disk. It returns an error on failure.
|
||||
func templatesBaseTmpl() (*asset, error) {
|
||||
path := "D:\\Code\\Go\\src\\github.com\\hacdias\\caddy-filemanager\\assets\\templates\\base.tmpl"
|
||||
|
@ -175,6 +194,7 @@ func AssetNames() []string {
|
|||
var _bindata = map[string]func() (*asset, error){
|
||||
"public/css/styles.css": publicCssStylesCss,
|
||||
"public/js/application.js": publicJsApplicationJs,
|
||||
"templates/actions.tmpl": templatesActionsTmpl,
|
||||
"templates/base.tmpl": templatesBaseTmpl,
|
||||
"templates/listing.tmpl": templatesListingTmpl,
|
||||
"templates/single.tmpl": templatesSingleTmpl,
|
||||
|
@ -229,6 +249,7 @@ var _bintree = &bintree{nil, map[string]*bintree{
|
|||
}},
|
||||
}},
|
||||
"templates": &bintree{nil, map[string]*bintree{
|
||||
"actions.tmpl": &bintree{templatesActionsTmpl, map[string]*bintree{}},
|
||||
"base.tmpl": &bintree{templatesBaseTmpl, map[string]*bintree{}},
|
||||
"listing.tmpl": &bintree{templatesListingTmpl, map[string]*bintree{}},
|
||||
"single.tmpl": &bintree{templatesSingleTmpl, map[string]*bintree{}},
|
||||
|
|
53
fileinfo.go
53
fileinfo.go
|
@ -22,9 +22,10 @@ type FileInfo struct {
|
|||
Name string
|
||||
Size int64
|
||||
URL string
|
||||
Path string // The relative Path of the file/directory relative to Caddyfile.
|
||||
RootPath string // The Path of the file/directory on http.FileSystem.
|
||||
ModTime time.Time
|
||||
Mode os.FileMode
|
||||
Path string
|
||||
Mimetype string
|
||||
Content string
|
||||
Type string
|
||||
|
@ -35,12 +36,20 @@ type FileInfo struct {
|
|||
func GetFileInfo(url *url.URL, c *Config) (*FileInfo, int, error) {
|
||||
var err error
|
||||
|
||||
path := strings.Replace(url.Path, c.BaseURL, "", 1)
|
||||
path = filepath.Clean(path)
|
||||
path = strings.Replace(path, "\\", "/", -1)
|
||||
rootPath := strings.Replace(url.Path, c.BaseURL, "", 1)
|
||||
rootPath = strings.TrimPrefix(rootPath, "/")
|
||||
rootPath = "/" + rootPath
|
||||
|
||||
file := &FileInfo{Path: path}
|
||||
f, err := c.Root.Open("/" + path)
|
||||
path := c.PathScope + rootPath
|
||||
path = strings.Replace(path, "\\", "/", -1)
|
||||
path = filepath.Clean(path)
|
||||
|
||||
file := &FileInfo{
|
||||
URL: url.Path,
|
||||
RootPath: rootPath,
|
||||
Path: path,
|
||||
}
|
||||
f, err := c.Root.Open(rootPath)
|
||||
if err != nil {
|
||||
return file, ErrorToHTTPCode(err), err
|
||||
}
|
||||
|
@ -55,8 +64,6 @@ func GetFileInfo(url *url.URL, c *Config) (*FileInfo, int, error) {
|
|||
file.ModTime = info.ModTime()
|
||||
file.Name = info.Name()
|
||||
file.Size = info.Size()
|
||||
file.URL = url.Path
|
||||
|
||||
return file, 0, nil
|
||||
}
|
||||
|
||||
|
@ -128,7 +135,7 @@ func (fi FileInfo) Rename(w http.ResponseWriter, r *http.Request) (int, error) {
|
|||
return ErrorToHTTPCode(err), err
|
||||
}
|
||||
|
||||
http.Redirect(w, r, strings.Replace(fi.URL, fi.Name, newname, 1), http.StatusTemporaryRedirect)
|
||||
http.Redirect(w, r, strings.Replace(r.URL.Path, fi.Name, newname, 1), http.StatusTemporaryRedirect)
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
|
@ -149,8 +156,9 @@ func (fi FileInfo) serveSingleFile(w http.ResponseWriter, r *http.Request, c *Co
|
|||
|
||||
page := &Page{
|
||||
Info: &PageInfo{
|
||||
Name: fi.Path,
|
||||
Path: fi.Path,
|
||||
Name: fi.Name,
|
||||
Path: fi.RootPath,
|
||||
IsDir: false,
|
||||
Data: fi,
|
||||
Config: c,
|
||||
},
|
||||
|
@ -162,7 +170,7 @@ func (fi FileInfo) serveSingleFile(w http.ResponseWriter, r *http.Request, c *Co
|
|||
func (fi FileInfo) serveListing(w http.ResponseWriter, r *http.Request, c *Config) (int, error) {
|
||||
var err error
|
||||
|
||||
file, err := c.Root.Open("/" + fi.Path)
|
||||
file, err := c.Root.Open(fi.RootPath)
|
||||
if err != nil {
|
||||
return ErrorToHTTPCode(err), err
|
||||
}
|
||||
|
@ -204,7 +212,8 @@ func (fi FileInfo) serveListing(w http.ResponseWriter, r *http.Request, c *Confi
|
|||
page := &Page{
|
||||
Info: &PageInfo{
|
||||
Name: listing.Name,
|
||||
Path: listing.Path,
|
||||
Path: fi.RootPath,
|
||||
IsDir: true,
|
||||
Config: c,
|
||||
Data: listing,
|
||||
},
|
||||
|
@ -219,7 +228,7 @@ func (fi FileInfo) loadDirectoryContents(file http.File, c *Config) (*Listing, e
|
|||
return nil, err
|
||||
}
|
||||
|
||||
listing := directoryListing(files, fi.Path)
|
||||
listing := directoryListing(files, fi.RootPath)
|
||||
return &listing, nil
|
||||
}
|
||||
|
||||
|
@ -260,6 +269,22 @@ func directoryListing(files []os.FileInfo, urlPath string) Listing {
|
|||
}
|
||||
}
|
||||
|
||||
// ServeRawFile serves raw files
|
||||
func (fi *FileInfo) ServeRawFile(w http.ResponseWriter, r *http.Request, c *Config) (int, error) {
|
||||
err := fi.GetExtendedFileInfo()
|
||||
if err != nil {
|
||||
return ErrorToHTTPCode(err), err
|
||||
}
|
||||
|
||||
if fi.Type != "text" {
|
||||
fi.Read()
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", fi.Mimetype)
|
||||
w.Write([]byte(fi.Content))
|
||||
return 200, nil
|
||||
}
|
||||
|
||||
// SimplifyMimeType returns the base type of a file
|
||||
func SimplifyMimeType(name string) string {
|
||||
if strings.HasPrefix(name, "video") {
|
||||
|
|
|
@ -66,12 +66,12 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
|
|||
if !fi.IsDir {
|
||||
query := r.URL.Query()
|
||||
if val, ok := query["raw"]; ok && val[0] == "true" {
|
||||
return f.Next.ServeHTTP(w, r)
|
||||
return fi.ServeRawFile(w, r, c)
|
||||
}
|
||||
|
||||
if val, ok := query["download"]; ok && val[0] == "true" {
|
||||
w.Header().Set("Content-Disposition", "attachment; filename="+fi.Name)
|
||||
return f.Next.ServeHTTP(w, r)
|
||||
return fi.ServeRawFile(w, r, c)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
13
page.go
13
page.go
|
@ -17,6 +17,7 @@ type Page struct {
|
|||
type PageInfo struct {
|
||||
Name string
|
||||
Path string
|
||||
IsDir bool
|
||||
Config *Config
|
||||
Data interface{}
|
||||
}
|
||||
|
@ -51,18 +52,24 @@ func (p PageInfo) BreadcrumbMap() map[string]string {
|
|||
|
||||
// PreviousLink returns the path of the previous folder
|
||||
func (p PageInfo) PreviousLink() string {
|
||||
parts := strings.Split(p.Path, "/")
|
||||
|
||||
parts := strings.Split(strings.TrimSuffix(p.Path, "/"), "/")
|
||||
if len(parts) <= 1 {
|
||||
return ""
|
||||
}
|
||||
|
||||
if parts[len(parts)-2] == "" {
|
||||
if p.Config.BaseURL == "" {
|
||||
return "/"
|
||||
}
|
||||
return p.Config.BaseURL
|
||||
}
|
||||
|
||||
return parts[len(parts)-2]
|
||||
}
|
||||
|
||||
// PrintAsHTML formats the page in HTML and executes the template
|
||||
func (p Page) PrintAsHTML(w http.ResponseWriter, templates ...string) (int, error) {
|
||||
templates = append(templates, "base")
|
||||
templates = append(templates, "actions", "base")
|
||||
var tpl *template.Template
|
||||
|
||||
// For each template, add it to the the tpl variable
|
||||
|
|
Loading…
Reference in New Issue