Former-commit-id: 35d375188e
pull/726/head
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
[*.go]
indent_style = space
indent_style = tab
indent_size = 2
# Indentation override for all JS under lib directory

View File

@ -4,7 +4,8 @@ var tempID = "_fm_internal_temporary_id",
buttons = {},
templates = {},
selectedItems = [],
overlay, clickOverlay;
overlay, clickOverlay,
webdav = {};
// Removes an element, if exists, from an array
Array.prototype.removeElement = function(element) {
@ -112,6 +113,33 @@ function getCSSRule(rules) {
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 *
@ -176,11 +204,11 @@ function openEvent(event) {
}
function selectMoveFolder(event) {
if(event.target.getAttribute("aria-selected") === "true") {
if (event.target.getAttribute("aria-selected") === "true") {
event.target.setAttribute("aria-selected", false);
return;
} 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);
}
event.target.setAttribute("aria-selected", true);
@ -189,71 +217,89 @@ function selectMoveFolder(event) {
}
function loadNextFolder(event) {
let request = new XMLHttpRequest(),
prompt = document.querySelector("form.prompt.active");
prompt.addEventListener("submit", moveSelected);
let request = new XMLHttpRequest(),
prompt = document.querySelector("form.prompt.active");
request.open("GET", "/" + event.target.innerHTML);
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);
prompt.addEventListener("submit", moveSelected);
newNode.addEventListener("dblclick", loadNextFolder);
newNode.addEventListener("click", selectMoveFolder);
request.open("GET", event.target.dataset.url);
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) {
event.preventDefault();
let request = new XMLHttpRequest(),
oldLink = toWebDavURL(window.location.pathname),
newLink = toWebDavURL(event.srcElement.querySelector("li[aria-selected=true]").innerHTML + "/");
request.open("MOVE", oldLink);
request.setRequestHeader("Destination", newLink);
request.send();
request.onreadystatechange = function() {
if(request.readyState == 4) {
if(request.status == 200 || request.status == 204) {
window.reload();
}
event.preventDefault();
// TODO: this only works for ONE file. What if there are more files selected?
// TODO: use webdav.rename
let request = new XMLHttpRequest(),
oldLink = toWebDavURL(window.location.pathname),
newLink = toWebDavURL(event.srcElement.querySelector("li[aria-selected=true]").innerHTML + "/");
request.open("MOVE", oldLink);
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) {
if(event.currentTarget.classList.contains("disabled")) return;
if (event.currentTarget.classList.contains("disabled")) return;
let request = new XMLHttpRequest();
request.open("GET", window.location.pathname, true);
request.setRequestHeader("Accept", "application/json");
request.send();
request.onreadystatechange = function() {
if(request.readyState == 4) {
if(request.status == 200) {
let prompt = document.importNode(templates.move.content, true);
if (request.readyState == 4) {
if (request.status == 200) {
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('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");
newNode.innerHTML = f.URL.split("/")[1];
newNode.dataset.url = f.URL;
newNode.innerHTML = f.Name;
newNode.setAttribute("aria-selected", false);
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.querySelector(".overlay").classList.add("active");
document.querySelector(".prompt").classList.add("active");
@ -424,6 +474,7 @@ function searchEvent(event) {
}
}
function setupSearch() {
let search = document.getElementById("search"),
searchInput = search.querySelector("input"),

View File

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

View File

@ -193,17 +193,20 @@
</template>
<template id="move-template">
<!-- TODO: And the back button? :) -->
<form class="prompt">
<h3>Move</h3>
<p></p>
<p>Choose new house for your file(s)/folder(s):</p>
<div class="file-list">
<ul>
</ul>
</div>
<p>Currently navigating on: <code></code>.</p>
<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>
</div>
</form>

View File

@ -8,6 +8,7 @@ import (
"os"
"path/filepath"
"strings"
"time"
humanize "github.com/dustin/go-humanize"
"github.com/hacdias/caddy-filemanager/config"
@ -16,8 +17,12 @@ import (
// Info contains the information about a particular file or directory
type Info struct {
os.FileInfo
Name string
Size int64
URL string
ModTime time.Time
Mode os.FileMode
IsDir bool
Path string // Relative path to Caddyfile
VirtualPath string // Relative path to u.FileSystem
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 = filepath.Clean(i.Path)
i.FileInfo, err = os.Stat(i.Path)
info, err := os.Stat(i.Path)
if err != nil {
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
}
// RetrieveFileType obtains the mimetype and a simplified internal Type
// using the first 512 bytes from the file.
func (i *Info) RetrieveFileType() error {
i.Mimetype = mime.TypeByExtension(filepath.Ext(i.Name()))
i.Mimetype = mime.TypeByExtension(filepath.Ext(i.Name))
if i.Mimetype == "" {
err := i.Read()
@ -88,12 +99,12 @@ func (i Info) StringifyContent() string {
// HumanSize returns the size of the file as a human-readable string
// in IEC format (i.e. power of 2 or base 1024).
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.
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
@ -113,14 +124,14 @@ func (i Info) CanBeEdited() bool {
".html",
".txt", ".rtf",
".sh", ".bash", ".ps1", ".bat", ".cmd",
".php", ".pl", ".py",
".php", ".pl", ".py",
"Caddyfile",
".c", ".cc", ".h", ".hh", ".cpp", ".hpp", ".f90",
".f", ".bas", ".d", ".ada", ".nim", ".cr", ".java", ".cs", ".vala", ".vapi",
}
for _, extension := range extensions {
if strings.HasSuffix(i.Name(), extension) {
if strings.HasSuffix(i.Name, extension) {
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}
i := Info{
FileInfo: f,
Name: f.Name(),
Size: f.Size(),
ModTime: f.ModTime(),
Mode: f.Mode(),
IsDir: f.IsDir(),
URL: url.String(),
UserAllowed: allowed,
}
@ -138,15 +142,15 @@ func (l byName) Swap(i, j int) {
// Treat upper and lower case equally
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
}
if !l.Items[i].IsDir() && l.Items[j].IsDir() {
if !l.Items[i].IsDir && l.Items[j].IsDir {
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
@ -160,11 +164,11 @@ func (l bySize) Swap(i, j int) {
const directoryOffset = -1 << 31 // = math.MinInt32
func (l bySize) Less(i, j int) bool {
iSize, jSize := l.Items[i].Size(), l.Items[j].Size()
if l.Items[i].IsDir() {
iSize, jSize := l.Items[i].Size, l.Items[j].Size
if l.Items[i].IsDir {
iSize = directoryOffset + iSize
}
if l.Items[j].IsDir() {
if l.Items[j].IsDir {
jSize = directoryOffset + 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]
}
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,
// 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)
return 0, nil
}
@ -144,10 +144,10 @@ func (f FileManager) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, err
switch {
case r.URL.Query().Get("download") != "":
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)
code, err = 0, nil
case fi.IsDir():
case fi.IsDir:
code, err = handlers.ServeListing(w, r, c, user, fi)
default:
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) {
query := r.URL.Query().Get("download")
if !i.IsDir() {
w.Header().Set("Content-Disposition", "attachment; filename="+i.Name())
if !i.IsDir {
w.Header().Set("Content-Disposition", "attachment; filename="+i.Name)
http.ServeFile(w, r, i.Path)
return 0, nil
}
@ -86,7 +86,7 @@ func Download(w http.ResponseWriter, r *http.Request, c *config.Config, i *file.
return http.StatusInternalServerError, err
}
name := i.Name()
name := i.Name
if name == "." || name == "" {
name = "download"
}

View File

@ -22,7 +22,7 @@ type Editor struct {
func GetEditor(i *file.Info) (*Editor, error) {
// Create a new editor variable and set the mode
editor := new(Editor)
editor.Mode = strings.TrimPrefix(filepath.Ext(i.Name()), ".")
editor.Mode = strings.TrimPrefix(filepath.Ext(i.Name), ".")
switch editor.Mode {
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{
Info: &page.Info{
Name: i.Name(),
Name: i.Name,
Path: i.VirtualPath,
IsDir: false,
Data: i,