Former-commit-id: 35d375188e7c700484d32a27eb48b30192e20537
This commit is contained in:
Henrique Dias 2017-01-03 15:10:33 +00:00
parent 5fc83eff7e
commit f1f248e4d6
10 changed files with 160 additions and 99 deletions

View File

@ -15,7 +15,7 @@ charset = utf-8
# 4 space indentation # 4 space indentation
[*.go] [*.go]
indent_style = space indent_style = tab
indent_size = 2 indent_size = 2
# Indentation override for all JS under lib directory # Indentation override for all JS under lib directory

View File

@ -4,7 +4,8 @@ var tempID = "_fm_internal_temporary_id",
buttons = {}, buttons = {},
templates = {}, templates = {},
selectedItems = [], selectedItems = [],
overlay, clickOverlay; overlay, clickOverlay,
webdav = {};
// Removes an element, if exists, from an array // Removes an element, if exists, from an array
Array.prototype.removeElement = function(element) { Array.prototype.removeElement = function(element) {
@ -112,6 +113,33 @@ function getCSSRule(rules) {
return result; return result;
} }
/* * * * * * * * * * * * * * * *
* *
* WEBDAV *
* *
* * * * * * * * * * * * * * * */
// TODO: here, we should create an abstraction layer from the webdav.
// We must create functions that do the requests to the webdav backend.
// this functions will contain a 'callback' to be used withing the other function.
webdav.rename = function(oldLink, newLink, callback) {
let request = new XMLHttpRequest();
request.open('MOVE', toWebDavURL(oldLink));
request.setRequestHeader('Destination', toWebDavURL(newLink));
request.send();
request.onreadystatechange = function() {
if (request.readyState == 4) {
if (typeof callback == 'function') {
// This callback argument is a 'success'
callback(request.status == 201 || request.status == 204);
}
}
}
}
/* * * * * * * * * * * * * * * * /* * * * * * * * * * * * * * * *
* * * *
* EVENTS * * EVENTS *
@ -176,11 +204,11 @@ function openEvent(event) {
} }
function selectMoveFolder(event) { function selectMoveFolder(event) {
if(event.target.getAttribute("aria-selected") === "true") { if (event.target.getAttribute("aria-selected") === "true") {
event.target.setAttribute("aria-selected", false); event.target.setAttribute("aria-selected", false);
return; return;
} else { } else {
if(document.querySelector(".file-list li[aria-selected=true]")) { if (document.querySelector(".file-list li[aria-selected=true]")) {
document.querySelector(".file-list li[aria-selected=true]").setAttribute("aria-selected", false); document.querySelector(".file-list li[aria-selected=true]").setAttribute("aria-selected", false);
} }
event.target.setAttribute("aria-selected", true); event.target.setAttribute("aria-selected", true);
@ -189,71 +217,89 @@ function selectMoveFolder(event) {
} }
function loadNextFolder(event) { function loadNextFolder(event) {
let request = new XMLHttpRequest(), let request = new XMLHttpRequest(),
prompt = document.querySelector("form.prompt.active"); prompt = document.querySelector("form.prompt.active");
prompt.addEventListener("submit", moveSelected);
request.open("GET", "/" + event.target.innerHTML); prompt.addEventListener("submit", moveSelected);
request.setRequestHeader("Accept", "application/json");
request.send();
request.onreadystatechange = function() {
if(request.readyState == 4 && request.status == 200) {
prompt.querySelector("ul").innerHTML = "";
for(let f of JSON.parse(request.response)) {
if(f.URL.substr(f.URL.length - 1) == "/") {
if(selectedItems.includes(btoa(f.URL.split("/")[1]))) continue;
let newNode = document.createElement("li");
newNode.innerHTML = (f.URL.replace("/" + event.target.innerHTML, "").split("/").join(""));
newNode.setAttribute("aria-selected", false);
newNode.addEventListener("dblclick", loadNextFolder); request.open("GET", event.target.dataset.url);
newNode.addEventListener("click", selectMoveFolder); request.setRequestHeader("Accept", "application/json");
request.send();
request.onreadystatechange = function() {
if (request.readyState == 4 && request.status == 200) {
let dirs = 0;
prompt.querySelector("ul").appendChild(newNode); prompt.querySelector("ul").innerHTML = "";
prompt.querySelector('code').innerHTML = event.target.dataset.url;
for (let f of JSON.parse(request.response)) {
if (f.IsDir === true) {
dirs++;
let newNode = document.createElement("li");
newNode.dataset.url = f.URL;
newNode.innerHTML = f.Name;
newNode.setAttribute("aria-selected", false);
newNode.addEventListener("dblclick", loadNextFolder);
newNode.addEventListener("click", selectMoveFolder);
prompt.querySelector("div.file-list ul").appendChild(newNode);
}
}
if (dirs === 0) {
prompt.querySelector("p").innerHTML = `There aren't any folders in this directory.`;
}
} }
}
} }
}
} }
function moveSelected(event) { function moveSelected(event) {
event.preventDefault(); event.preventDefault();
let request = new XMLHttpRequest(),
oldLink = toWebDavURL(window.location.pathname), // TODO: this only works for ONE file. What if there are more files selected?
newLink = toWebDavURL(event.srcElement.querySelector("li[aria-selected=true]").innerHTML + "/"); // TODO: use webdav.rename
request.open("MOVE", oldLink);
request.setRequestHeader("Destination", newLink); let request = new XMLHttpRequest(),
request.send(); oldLink = toWebDavURL(window.location.pathname),
request.onreadystatechange = function() { newLink = toWebDavURL(event.srcElement.querySelector("li[aria-selected=true]").innerHTML + "/");
if(request.readyState == 4) {
if(request.status == 200 || request.status == 204) { request.open("MOVE", oldLink);
window.reload(); request.setRequestHeader("Destination", newLink);
} request.send();
request.onreadystatechange = function() {
if (request.readyState == 4) {
if (request.status == 200 || request.status == 204) {
window.reload();
}
}
} }
}
} }
function moveEvent(event) { function moveEvent(event) {
if(event.currentTarget.classList.contains("disabled")) return; if (event.currentTarget.classList.contains("disabled")) return;
let request = new XMLHttpRequest(); let request = new XMLHttpRequest();
request.open("GET", window.location.pathname, true); request.open("GET", window.location.pathname, true);
request.setRequestHeader("Accept", "application/json"); request.setRequestHeader("Accept", "application/json");
request.send(); request.send();
request.onreadystatechange = function() { request.onreadystatechange = function() {
if(request.readyState == 4) { if (request.readyState == 4) {
if(request.status == 200) { if (request.status == 200) {
let prompt = document.importNode(templates.move.content, true); let prompt = document.importNode(templates.move.content, true),
dirs = 0;
prompt.querySelector("p").innerHTML = `Choose new house for your file(s)/folder(s):`;
prompt.querySelector("form").addEventListener("submit", moveSelected); prompt.querySelector("form").addEventListener("submit", moveSelected);
prompt.querySelector('code').innerHTML = window.location.pathname;
for (let f of JSON.parse(request.response)) {
if (f.IsDir === true) {
dirs++;
for(let f of JSON.parse(request.response)) {
if(f.URL.split("/").length == 3) {
if(selectedItems.includes(btoa(f.URL.split("/")[1]))) continue;
let newNode = document.createElement("li"); let newNode = document.createElement("li");
newNode.innerHTML = f.URL.split("/")[1]; newNode.dataset.url = f.URL;
newNode.innerHTML = f.Name;
newNode.setAttribute("aria-selected", false); newNode.setAttribute("aria-selected", false);
newNode.addEventListener("dblclick", loadNextFolder); newNode.addEventListener("dblclick", loadNextFolder);
@ -263,6 +309,10 @@ function moveEvent(event) {
} }
} }
if (dirs === 0) {
prompt.querySelector("p").innerHTML = `There aren't any folders in this directory.`;
}
document.body.appendChild(prompt); document.body.appendChild(prompt);
document.querySelector(".overlay").classList.add("active"); document.querySelector(".overlay").classList.add("active");
document.querySelector(".prompt").classList.add("active"); document.querySelector(".prompt").classList.add("active");
@ -424,6 +474,7 @@ function searchEvent(event) {
} }
} }
function setupSearch() { function setupSearch() {
let search = document.getElementById("search"), let search = document.getElementById("search"),
searchInput = search.querySelector("input"), searchInput = search.querySelector("input"),

View File

@ -128,31 +128,24 @@ listing.rename = function(event) {
event.preventDefault(); event.preventDefault();
let newName = event.currentTarget.querySelector('input').value, let newName = event.currentTarget.querySelector('input').value,
newLink = removeLastDirectoryPartOf(toWebDavURL(link)) + "/" + newName, newLink = removeLastDirectoryPartOf(link) + "/" + newName,
html = buttons.rename.querySelector('i').changeToLoading(), html = buttons.rename.querySelector('i').changeToLoading();
request = new XMLHttpRequest();
request.open('MOVE', toWebDavURL(link)); webdav.rename(link, newLink, success => {
request.setRequestHeader('Destination', newLink); if (success) {
request.send(); listing.reload(() => {
request.onreadystatechange = function() { newName = btoa(newName);
if (request.readyState == 4) { selectedItems = [newName];
if (request.status != 201 && request.status != 204) { document.getElementById(newName).setAttribute("aria-selected", true);
span.innerHTML = name; listing.handleSelectionChange();
} else { });
closePrompt(event); } else {
item.querySelector('.name').innerHTML = name;
listing.reload(() => {
newName = btoa(newName);
selectedItems = [newName];
document.getElementById(newName).setAttribute("aria-selected", true);
listing.handleSelectionChange();
});
}
buttons.rename.querySelector('i').changeToDone((request.status != 201 && request.status != 204), html);
} }
}
closePrompt(event);
buttons.rename.querySelector('i').changeToDone(!success, html);
});
return false; return false;
} }
@ -317,7 +310,6 @@ listing.updateColumns = function(event) {
items.style.width = `calc(${100/columns}% - 1em)`; items.style.width = `calc(${100/columns}% - 1em)`;
} }
listing.addDoubleTapEvent = function() { listing.addDoubleTapEvent = function() {
let items = document.getElementsByClassName('item'), let items = document.getElementsByClassName('item'),
touches = { touches = {

View File

@ -193,17 +193,20 @@
</template> </template>
<template id="move-template"> <template id="move-template">
<!-- TODO: And the back button? :) -->
<form class="prompt"> <form class="prompt">
<h3>Move</h3> <h3>Move</h3>
<p></p> <p>Choose new house for your file(s)/folder(s):</p>
<div class="file-list"> <div class="file-list">
<ul> <ul>
</ul> </ul>
</div> </div>
<p>Currently navigating on: <code></code>.</p>
<div> <div>
<button type="submit" autofocus class="ok">OK</button> <button type="submit" autofocus class="ok">Move</button>
<button class="cancel" onclick="closePrompt(event);">Cancel</button> <button class="cancel" onclick="closePrompt(event);">Cancel</button>
</div> </div>
</form> </form>

View File

@ -8,6 +8,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"time"
humanize "github.com/dustin/go-humanize" humanize "github.com/dustin/go-humanize"
"github.com/hacdias/caddy-filemanager/config" "github.com/hacdias/caddy-filemanager/config"
@ -16,8 +17,12 @@ import (
// Info contains the information about a particular file or directory // Info contains the information about a particular file or directory
type Info struct { type Info struct {
os.FileInfo Name string
Size int64
URL string URL string
ModTime time.Time
Mode os.FileMode
IsDir bool
Path string // Relative path to Caddyfile Path string // Relative path to Caddyfile
VirtualPath string // Relative path to u.FileSystem VirtualPath string // Relative path to u.FileSystem
Mimetype string Mimetype string
@ -40,18 +45,24 @@ func GetInfo(url *url.URL, c *config.Config, u *config.User) (*Info, int, error)
i.Path = strings.Replace(i.Path, "\\", "/", -1) i.Path = strings.Replace(i.Path, "\\", "/", -1)
i.Path = filepath.Clean(i.Path) i.Path = filepath.Clean(i.Path)
i.FileInfo, err = os.Stat(i.Path) info, err := os.Stat(i.Path)
if err != nil { if err != nil {
return i, errors.ErrorToHTTPCode(err, false), err return i, errors.ErrorToHTTPCode(err, false), err
} }
i.Name = info.Name()
i.ModTime = info.ModTime()
i.Mode = info.Mode()
i.IsDir = info.IsDir()
i.Size = info.Size()
return i, 0, nil return i, 0, nil
} }
// RetrieveFileType obtains the mimetype and a simplified internal Type // RetrieveFileType obtains the mimetype and a simplified internal Type
// using the first 512 bytes from the file. // using the first 512 bytes from the file.
func (i *Info) RetrieveFileType() error { func (i *Info) RetrieveFileType() error {
i.Mimetype = mime.TypeByExtension(filepath.Ext(i.Name())) i.Mimetype = mime.TypeByExtension(filepath.Ext(i.Name))
if i.Mimetype == "" { if i.Mimetype == "" {
err := i.Read() err := i.Read()
@ -88,12 +99,12 @@ func (i Info) StringifyContent() string {
// HumanSize returns the size of the file as a human-readable string // HumanSize returns the size of the file as a human-readable string
// in IEC format (i.e. power of 2 or base 1024). // in IEC format (i.e. power of 2 or base 1024).
func (i Info) HumanSize() string { func (i Info) HumanSize() string {
return humanize.IBytes(uint64(i.Size())) return humanize.IBytes(uint64(i.Size))
} }
// HumanModTime returns the modified time of the file as a human-readable string. // HumanModTime returns the modified time of the file as a human-readable string.
func (i Info) HumanModTime(format string) string { func (i Info) HumanModTime(format string) string {
return i.ModTime().Format(format) return i.ModTime.Format(format)
} }
// CanBeEdited checks if the extension of a file is supported by the editor // CanBeEdited checks if the extension of a file is supported by the editor
@ -120,7 +131,7 @@ func (i Info) CanBeEdited() bool {
} }
for _, extension := range extensions { for _, extension := range extensions {
if strings.HasSuffix(i.Name(), extension) { if strings.HasSuffix(i.Name, extension) {
return true return true
} }
} }

View File

@ -74,7 +74,11 @@ func GetListing(u *config.User, filePath string, baseURL string) (*Listing, erro
url := url.URL{Path: baseURL + name} url := url.URL{Path: baseURL + name}
i := Info{ i := Info{
FileInfo: f, Name: f.Name(),
Size: f.Size(),
ModTime: f.ModTime(),
Mode: f.Mode(),
IsDir: f.IsDir(),
URL: url.String(), URL: url.String(),
UserAllowed: allowed, UserAllowed: allowed,
} }
@ -138,15 +142,15 @@ func (l byName) Swap(i, j int) {
// Treat upper and lower case equally // Treat upper and lower case equally
func (l byName) Less(i, j int) bool { func (l byName) Less(i, j int) bool {
if l.Items[i].IsDir() && !l.Items[j].IsDir() { if l.Items[i].IsDir && !l.Items[j].IsDir {
return true return true
} }
if !l.Items[i].IsDir() && l.Items[j].IsDir() { if !l.Items[i].IsDir && l.Items[j].IsDir {
return false return false
} }
return strings.ToLower(l.Items[i].Name()) < strings.ToLower(l.Items[j].Name()) return strings.ToLower(l.Items[i].Name) < strings.ToLower(l.Items[j].Name)
} }
// By Size // By Size
@ -160,11 +164,11 @@ func (l bySize) Swap(i, j int) {
const directoryOffset = -1 << 31 // = math.MinInt32 const directoryOffset = -1 << 31 // = math.MinInt32
func (l bySize) Less(i, j int) bool { func (l bySize) Less(i, j int) bool {
iSize, jSize := l.Items[i].Size(), l.Items[j].Size() iSize, jSize := l.Items[i].Size, l.Items[j].Size
if l.Items[i].IsDir() { if l.Items[i].IsDir {
iSize = directoryOffset + iSize iSize = directoryOffset + iSize
} }
if l.Items[j].IsDir() { if l.Items[j].IsDir {
jSize = directoryOffset + jSize jSize = directoryOffset + jSize
} }
return iSize < jSize return iSize < jSize
@ -178,5 +182,5 @@ func (l byTime) Swap(i, j int) {
l.Items[i], l.Items[j] = l.Items[j], l.Items[i] l.Items[i], l.Items[j] = l.Items[j], l.Items[i]
} }
func (l byTime) Less(i, j int) bool { func (l byTime) Less(i, j int) bool {
return l.Items[i].ModTime().Before(l.Items[j].ModTime()) return l.Items[i].ModTime.Before(l.Items[j].ModTime)
} }

View File

@ -136,7 +136,7 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
// If it's a dir and the path doesn't end with a trailing slash, // If it's a dir and the path doesn't end with a trailing slash,
// redirect the user. // redirect the user.
if fi.IsDir() && !strings.HasSuffix(r.URL.Path, "/") { if fi.IsDir && !strings.HasSuffix(r.URL.Path, "/") {
http.Redirect(w, r, c.AddrPath+r.URL.Path+"/", http.StatusTemporaryRedirect) http.Redirect(w, r, c.AddrPath+r.URL.Path+"/", http.StatusTemporaryRedirect)
return 0, nil return 0, nil
} }
@ -144,10 +144,10 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
switch { switch {
case r.URL.Query().Get("download") != "": case r.URL.Query().Get("download") != "":
code, err = handlers.Download(w, r, c, fi) code, err = handlers.Download(w, r, c, fi)
case r.URL.Query().Get("raw") == "true" && !fi.IsDir(): case r.URL.Query().Get("raw") == "true" && !fi.IsDir:
http.ServeFile(w, r, fi.Path) http.ServeFile(w, r, fi.Path)
code, err = 0, nil code, err = 0, nil
case fi.IsDir(): case fi.IsDir:
code, err = handlers.ServeListing(w, r, c, user, fi) code, err = handlers.ServeListing(w, r, c, user, fi)
default: default:
code, err = handlers.ServeSingle(w, r, c, user, fi) code, err = handlers.ServeSingle(w, r, c, user, fi)

View File

@ -19,8 +19,8 @@ import (
func Download(w http.ResponseWriter, r *http.Request, c *config.Config, i *file.Info) (int, error) { func Download(w http.ResponseWriter, r *http.Request, c *config.Config, i *file.Info) (int, error) {
query := r.URL.Query().Get("download") query := r.URL.Query().Get("download")
if !i.IsDir() { if !i.IsDir {
w.Header().Set("Content-Disposition", "attachment; filename="+i.Name()) w.Header().Set("Content-Disposition", "attachment; filename="+i.Name)
http.ServeFile(w, r, i.Path) http.ServeFile(w, r, i.Path)
return 0, nil return 0, nil
} }
@ -86,7 +86,7 @@ func Download(w http.ResponseWriter, r *http.Request, c *config.Config, i *file.
return http.StatusInternalServerError, err return http.StatusInternalServerError, err
} }
name := i.Name() name := i.Name
if name == "." || name == "" { if name == "." || name == "" {
name = "download" name = "download"
} }

View File

@ -22,7 +22,7 @@ type Editor struct {
func GetEditor(i *file.Info) (*Editor, error) { func GetEditor(i *file.Info) (*Editor, error) {
// Create a new editor variable and set the mode // Create a new editor variable and set the mode
editor := new(Editor) editor := new(Editor)
editor.Mode = strings.TrimPrefix(filepath.Ext(i.Name()), ".") editor.Mode = strings.TrimPrefix(filepath.Ext(i.Name), ".")
switch editor.Mode { switch editor.Mode {
case "md", "markdown", "mdown", "mmark": case "md", "markdown", "mdown", "mmark":

View File

@ -26,7 +26,7 @@ func ServeSingle(w http.ResponseWriter, r *http.Request, c *config.Config, u *co
p := &page.Page{ p := &page.Page{
Info: &page.Info{ Info: &page.Info{
Name: i.Name(), Name: i.Name,
Path: i.VirtualPath, Path: i.VirtualPath,
IsDir: false, IsDir: false,
Data: i, Data: i,