mirror of
https://github.com/filebrowser/filebrowser.git
synced 2024-06-07 23:00:43 +00:00
Updates
This commit is contained in:
parent
2de07a3d11
commit
35d375188e
@ -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
|
||||
|
@ -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"),
|
||||
|
@ -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 => {
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
|
@ -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>
|
||||
|
25
file/info.go
25
file/info.go
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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":
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user