Add drag and drop, rename and delete actions
parent
ce9eb47351
commit
8e1bff706b
|
@ -3,52 +3,22 @@
|
|||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<link rel="stylesheet" href="/bower_components/components-font-awesome/css/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="/bower_components/bootstrap/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="file.css" />
|
||||
<script src="/bower_components/jquery/dist/jquery.min.js"></script>
|
||||
<style>
|
||||
html, body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
position: relative;
|
||||
}
|
||||
#tree {
|
||||
position:absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 70%;
|
||||
border: 2px solid blue;
|
||||
box-sizing: border-box;
|
||||
background: white;
|
||||
}
|
||||
#tree li {
|
||||
cursor: pointer;
|
||||
}
|
||||
#tree li span:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
#content {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 30%;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
border: 2px solid green;
|
||||
box-sizing: border-box;
|
||||
background: #eee;
|
||||
}
|
||||
.folder, .file {
|
||||
margin-right: 5px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="tree">
|
||||
</div>
|
||||
<div id="content">
|
||||
</div>
|
||||
<div id="contextMenu" class="dropdown clearfix">
|
||||
<ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu" style="display:block;position:static;margin-bottom:5px;">
|
||||
<li><a tabindex="-1" href="#" class="open">Open</a></li>
|
||||
<li><a tabindex="-1" href="#" class="rename">Rename</a></li>
|
||||
<li><a tabindex="-1" href="#" class="delete">Delete</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
|
439
www/file/main.js
439
www/file/main.js
|
@ -16,15 +16,18 @@ define([
|
|||
'/bower_components/file-saver/FileSaver.min.js',
|
||||
'/bower_components/diff-dom/diffDOM.js',
|
||||
'/bower_components/jquery/dist/jquery.min.js',
|
||||
'/bower_components/bootstrap/dist/js/bootstrap.min.js',
|
||||
'/customize/pad.js'
|
||||
], function (Messages, Crypto, realtimeInput, Hyperjson,
|
||||
Toolbar, Cursor, JsonOT, TypingTest, JSONSortify, TextPatcher, Cryptpad,
|
||||
Visible, Notify) {
|
||||
var module = {};
|
||||
|
||||
var $ = window.jQuery;
|
||||
var saveAs = window.saveAs;
|
||||
var $iframe = $('#pad-iframe').contents();
|
||||
var ifrw = $('#pad-iframe')[0].contentWindow;
|
||||
var files = {
|
||||
var files = module.files = {
|
||||
root: {
|
||||
"Directory 1": {
|
||||
"Dir A": {
|
||||
|
@ -43,19 +46,430 @@ define([
|
|||
"File Z": "#hash_Z"
|
||||
}
|
||||
};
|
||||
var currentPath = module.currentPath = ['root'];
|
||||
var lastSelectTime;
|
||||
var selectedElement;
|
||||
|
||||
// TODO translate
|
||||
// TODO translate contextmenu in inner.html
|
||||
var ROOT_NAME = "My files";
|
||||
var TRASH_NAME = "Trash";
|
||||
var TIME_BEFORE_RENAME = 1000;
|
||||
|
||||
var $tree = $iframe.find("#tree");
|
||||
var $content = $iframe.find("#content");
|
||||
var $contextMenu = $iframe.find("#contextMenu");
|
||||
var $folderIcon = $('<span>', {
|
||||
"class": "fa fa-folder folder",
|
||||
style: "font-family: FontAwesome"
|
||||
});
|
||||
var $folderEmptyIcon = $('<span>', {
|
||||
"class": "fa fa-folder-o folder",
|
||||
style: "font-family: FontAwesome"
|
||||
});
|
||||
var $folderOpenedIcon = $('<span>', {
|
||||
"class": "fa fa-folder-open folder",
|
||||
style: "font-family: FontAwesome"
|
||||
});
|
||||
var $folderOpenedEmptyIcon = $('<span>', {
|
||||
"class": "fa fa-folder-open-o folder",
|
||||
style: "font-family: FontAwesome"
|
||||
});
|
||||
var $fileIcon = $('<span>', {
|
||||
"class": "fa fa-file file",
|
||||
style: "font-family: FontAwesome"
|
||||
});
|
||||
var $upIcon = $('<span>', {
|
||||
"class": "fa fa-arrow-circle-up",
|
||||
style: "font-family: FontAwesome"
|
||||
});
|
||||
var $trashIcon = $('<span>', {
|
||||
"class": "fa fa-trash",
|
||||
style: "font-family: FontAwesome"
|
||||
});
|
||||
|
||||
var displayDirectory = function (name, root) {
|
||||
var removeSelected = function () {
|
||||
$content.find('.selected').removeClass("selected");
|
||||
};
|
||||
var removeInput = function () {
|
||||
$content.find('li > span:hidden').show();
|
||||
$content.find('li > input').remove();
|
||||
};
|
||||
|
||||
var comparePath = function (a, b) {
|
||||
if (!a || !b || !$.isArray(a) || !$.isArray(b)) { return false; }
|
||||
if (a.length !== b.length) { return false; }
|
||||
var result = true;
|
||||
var i = a.length - 1;
|
||||
while (result && i >= 0) {
|
||||
result = a[i] === b[i];
|
||||
i--;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
var now = function () {
|
||||
return new Date().getTime();
|
||||
};
|
||||
|
||||
// Find an element in a object following a path, resursively
|
||||
var findElement = function (root, pathInput) {
|
||||
if (!pathInput) {
|
||||
console.error("Invalid path:\n", pathInput, "\nin root\n", root);
|
||||
//TODO
|
||||
return;
|
||||
}
|
||||
if (pathInput.length === 0) { return root; }
|
||||
var path = pathInput.slice();
|
||||
var key = path.shift();
|
||||
if (typeof root[key] === "undefined") {
|
||||
console.error("Unable to find the key '" + key + "' in the root object provided:\n", root);
|
||||
//TODO
|
||||
return;
|
||||
}
|
||||
return findElement(root[key], path);
|
||||
};
|
||||
|
||||
var moveElement = function (elementPath, newParentPath) {
|
||||
if (comparePath(elementPath, newParentPath)) { return; } // Nothing to do...
|
||||
var element = findElement(files, elementPath);
|
||||
var parentPath = elementPath.slice();
|
||||
var name = parentPath.pop();
|
||||
var parentEl = findElement(files, parentPath);
|
||||
var newParent = findElement(files, newParentPath);
|
||||
if (typeof(newParent[name]) !== "undefined") {
|
||||
console.error("A file with the same name already exist at the new location");
|
||||
//TODO
|
||||
return;
|
||||
}
|
||||
newParent[name] = element;
|
||||
delete parentEl[name];
|
||||
displayDirectory(newParentPath);
|
||||
};
|
||||
|
||||
var removeElement = function (path) {
|
||||
moveElement(path, ['trash']);
|
||||
};
|
||||
|
||||
var onDrag = function (ev, path) {
|
||||
console.log("dragging", path);
|
||||
var data = {
|
||||
'path': path
|
||||
};
|
||||
ev.dataTransfer.setData("data", JSON.stringify(data));
|
||||
};
|
||||
|
||||
var onDrop = function (ev) {
|
||||
ev.preventDefault();
|
||||
var data = ev.dataTransfer.getData("data");
|
||||
var oldPath = JSON.parse(data).path;
|
||||
var newPath = $(ev.target).data('path') || $(ev.target).parent('li').data('path');
|
||||
console.log("dropping ", oldPath, " to ", newPath);
|
||||
if (!oldPath || !newPath) { return; }
|
||||
moveElement(oldPath, newPath);
|
||||
};
|
||||
|
||||
var renameElement = function (path, newName) {
|
||||
if (path.length <= 1) {
|
||||
console.error('Renaming "root" is forbidden');
|
||||
//TODO
|
||||
return;
|
||||
}
|
||||
if (!newName || newName.trim() === "") { return; }
|
||||
var isCurrentDirectory = comparePath(path, currentPath);
|
||||
// Copy the element path and remove the last value to have the parent path and the old name
|
||||
var element = findElement(files, path);
|
||||
var parentPath = path.slice();
|
||||
var oldName = parentPath.pop();
|
||||
if (oldName === newName) {
|
||||
// Nothing to do...
|
||||
// TODO ?
|
||||
return;
|
||||
}
|
||||
var parentEl = findElement(files, parentPath);
|
||||
if (typeof(parentEl[newName]) !== "undefined") {
|
||||
console.error('Name already used.');
|
||||
//TODO
|
||||
return;
|
||||
}
|
||||
parentEl[newName] = element;
|
||||
delete parentEl[oldName];
|
||||
resetTree();
|
||||
displayDirectory(currentPath);
|
||||
};
|
||||
|
||||
var displayRenameInput = function ($element, path) {
|
||||
if (!path || path.length < 2) { return; } // TODO error
|
||||
$element.hide();
|
||||
removeSelected();
|
||||
var name = path[path.length - 1];
|
||||
var $input = $('<input>', {
|
||||
placeholder: name,
|
||||
value: name
|
||||
});
|
||||
$input.on('keyup', function (e) {
|
||||
if (e.which === 13) {
|
||||
renameElement(path, $input.val());
|
||||
removeInput();
|
||||
}
|
||||
});
|
||||
$input.insertAfter($element);
|
||||
$input.focus();
|
||||
$input.select();
|
||||
$input.click(function (e) {
|
||||
removeSelected();
|
||||
e.stopPropagation();
|
||||
});
|
||||
};
|
||||
|
||||
var onElementClick = function ($element, path) {
|
||||
// If the element was already selected, check if the rename action is available
|
||||
/*if ($element.hasClass("selected")) {
|
||||
if($content.find('.selected').length === 1 &&
|
||||
lastSelectTime &&
|
||||
(now() - lastSelectTime) > TIME_BEFORE_RENAME) {
|
||||
//$element.
|
||||
renameElement(path, "File renamed");
|
||||
}
|
||||
return;
|
||||
}*/
|
||||
removeSelected();
|
||||
if ($element.not('li')) {
|
||||
$element = $element.parent('li');
|
||||
}
|
||||
if (!$element.length) { return ; } //TODO error
|
||||
if (!$element.hasClass("selected")) {
|
||||
$element.addClass("selected");
|
||||
lastSelectTime = now();
|
||||
}
|
||||
};
|
||||
|
||||
var openContextMenu = function (e) {
|
||||
onElementClick($(e.target));
|
||||
e.stopPropagation();
|
||||
var path = $(e.target).data('path') || $(e.target).parent('li').data('path');
|
||||
if (!path) { return; }
|
||||
$contextMenu.css({
|
||||
display: "block",
|
||||
left: e.pageX,
|
||||
top: e.pageY
|
||||
});
|
||||
$contextMenu.find('a').data('path', path);
|
||||
$contextMenu.find('a').data('element', $(e.target));
|
||||
return false;
|
||||
};
|
||||
|
||||
var displayDirectory = function (path) {
|
||||
currentPath = path;
|
||||
module.resetTree();
|
||||
$content.html("");
|
||||
if (!path || path.length === 0) {
|
||||
path = ['root'];
|
||||
}
|
||||
var root = findElement(files, path);
|
||||
if (typeof(root) === "undefined") {
|
||||
// TODO translate
|
||||
// TODO error
|
||||
$content.html("Unable to locate the selected directory...");
|
||||
return;
|
||||
}
|
||||
|
||||
// Forbid drag&drop inside the trash
|
||||
var droppable = root[0] !== "trash";
|
||||
|
||||
// Display title and "Up" icon
|
||||
var name = path[path.length - 1];
|
||||
if (name === "root" && path.length === 1) { name = ROOT_NAME; }
|
||||
else if (name === "trash" && path.length === 1) { name = TRASH_NAME; }
|
||||
var $title = $('<h1>').text(name);
|
||||
if (path.length > 1) {
|
||||
var $parentFolder = $upIcon.clone().addClass("parentFolder")
|
||||
.click(function() {
|
||||
var newPath = path.slice();
|
||||
newPath.pop();
|
||||
var name = newPath[newPath.length -1];
|
||||
if (name === "root" && newPath.length === 1) { name = ROOT_NAME; }
|
||||
displayDirectory(newPath);
|
||||
});
|
||||
$title.append($parentFolder);
|
||||
}
|
||||
var $dirContent = $('<div>', {id: "folderContent"});
|
||||
var $list = $('<ul>').appendTo($dirContent);
|
||||
|
||||
// display sub directories
|
||||
Object.keys(root).forEach(function (key) {
|
||||
if (typeof(root[key]) === "string") { return; }
|
||||
var newPath = path.slice();
|
||||
newPath.push(key);
|
||||
var $icon = Object.keys(root[key]).length === 0 ? $folderEmptyIcon.clone() : $folderIcon.clone();
|
||||
var $name = $('<span>', { 'class': 'folder-element element' }).text(key);
|
||||
var $element = $('<li>', {
|
||||
draggable: true
|
||||
}).append($icon).append($name).dblclick(function () {
|
||||
displayDirectory(newPath);
|
||||
});
|
||||
$element.data('path', newPath);
|
||||
$element.on('dragstart', function (e) {
|
||||
onDrag(e.originalEvent, newPath);
|
||||
});
|
||||
if (droppable) {
|
||||
$element.on('dragover', function (e) {
|
||||
e.preventDefault();
|
||||
});
|
||||
$element.on('drop', function (e) {
|
||||
onDrop(e.originalEvent);
|
||||
});
|
||||
}
|
||||
$element.click(function(e) {
|
||||
e.stopPropagation();
|
||||
onElementClick($element, newPath);
|
||||
});
|
||||
$element.contextmenu(openContextMenu);
|
||||
$element.appendTo($list);
|
||||
});
|
||||
// display files
|
||||
Object.keys(root).forEach(function (key) {
|
||||
if (typeof(root[key]) !== "string") { return; }
|
||||
var newPath = path.slice();
|
||||
newPath.push(key);
|
||||
var $name = $('<span>', { 'class': 'file-element element' }).text(key);
|
||||
var $element = $('<li>', {
|
||||
draggable: true
|
||||
}).append($fileIcon.clone()).append($name).dblclick(function () {
|
||||
window.location.hash = root[key];
|
||||
});
|
||||
$element.data('path', newPath);
|
||||
$element.on('dragstart', function (e) {
|
||||
console.log(e.target);
|
||||
onDrag(e.originalEvent, newPath);
|
||||
});
|
||||
$element.click(function(e) {
|
||||
e.stopPropagation();
|
||||
onElementClick($element, newPath);
|
||||
});
|
||||
$element.contextmenu(openContextMenu);
|
||||
$element.appendTo($list);
|
||||
});
|
||||
$content.append($title).append($dirContent);
|
||||
};
|
||||
|
||||
// TODO: add + and - in the tree (collapse), and link elements with lines
|
||||
// Cf: https://codepen.io/khoama/pen/hpljA
|
||||
var createTreeElement = function (name, $icon, path, draggable) {
|
||||
var $name = $('<span>', { 'class': 'folder-element' }).text(name).prepend($icon)
|
||||
.click(function () {
|
||||
displayDirectory(path);
|
||||
});
|
||||
var $element = $('<li>', {
|
||||
draggable: draggable
|
||||
}).append($name);
|
||||
$element.data('path', path);
|
||||
$element.on('dragstart', function (e) {
|
||||
e.stopPropagation();
|
||||
onDrag(e.originalEvent, path);
|
||||
});
|
||||
$element.on('dragover', function (e) {
|
||||
e.preventDefault();
|
||||
});
|
||||
$element.on('drop', function (e) {
|
||||
onDrop(e.originalEvent);
|
||||
});
|
||||
return $element;
|
||||
};
|
||||
var createTree = function ($container, path) {
|
||||
var root = findElement(files, path);
|
||||
if (Object.keys(root).length === 0) { return; }
|
||||
|
||||
// Display the root elemnt in the tree
|
||||
var displayingRoot = comparePath(['root'], path);
|
||||
if (displayingRoot) {
|
||||
var isRootOpened = comparePath(['root'], currentPath);
|
||||
var $rootIcon = Object.keys(files['root']).length === 0 ?
|
||||
(isRootOpened ? $folderOpenedEmptyIcon : $folderEmptyIcon) :
|
||||
(isRootOpened ? $folderOpenedIcon : $folderIcon);
|
||||
var $rootElement = createTreeElement(ROOT_NAME, $rootIcon.clone(), ['root'], false);
|
||||
var $root = $('<ul>').append($rootElement).appendTo($container);
|
||||
$container = $rootElement;
|
||||
}
|
||||
|
||||
// Display root content
|
||||
var $list = $('<ul>').appendTo($container);
|
||||
Object.keys(root).forEach(function (key) {
|
||||
// Do not display files in the menu
|
||||
if (typeof(root[key]) === "string") { return; }
|
||||
var newPath = path.slice();
|
||||
newPath.push(key);
|
||||
var isCurrentFolder = comparePath(newPath, currentPath);
|
||||
var $icon = Object.keys(root[key]).length === 0 ?
|
||||
(isCurrentFolder ? $folderOpenedEmptyIcon : $folderEmptyIcon) :
|
||||
(isCurrentFolder ? $folderOpenedIcon : $folderIcon);
|
||||
var $element = createTreeElement(key, $icon.clone(), newPath, true);
|
||||
$element.appendTo($list);
|
||||
createTree($element, newPath);
|
||||
});
|
||||
};
|
||||
|
||||
var createTrash = function ($container, path) {
|
||||
var $trash = $('<span>', {
|
||||
'class': 'tree-trash'
|
||||
}).text(TRASH_NAME).prepend($trashIcon.clone())
|
||||
.click(function () {
|
||||
displayDirectory(path);
|
||||
});
|
||||
$trash.data('path', ['trash']);
|
||||
var $trashElement = $('<li>').append($trash);
|
||||
$trashElement.on('dragover', function (e) {
|
||||
e.preventDefault();
|
||||
});
|
||||
$trashElement.on('drop', function (e) {
|
||||
onDrop(e.originalEvent);
|
||||
});
|
||||
var $trashList = $('<ul>').append($trashElement);
|
||||
$container.append($trashList);
|
||||
};
|
||||
|
||||
var resetTree = module.resetTree = function () {
|
||||
$tree.html('');
|
||||
createTree($tree, ['root']);
|
||||
createTrash($tree, ['trash']);
|
||||
};
|
||||
displayDirectory(currentPath);
|
||||
//resetTree(); //already called by displayDirectory
|
||||
|
||||
var hideMenu = function () {
|
||||
$contextMenu.hide();
|
||||
};
|
||||
$contextMenu.on("click", "a", function(e) {
|
||||
e.stopPropagation();
|
||||
var path = $(this).data('path');
|
||||
var $element = $(this).data('element');
|
||||
if (!$element || !path || path.length < 2) { return; } // TODO: error
|
||||
if ($(this).hasClass("rename")) {
|
||||
displayRenameInput($element, path);
|
||||
}
|
||||
else if($(this).hasClass("delete")) {
|
||||
var name = path[path.length - 1];
|
||||
// TODO translate
|
||||
Cryptpad.confirm("Are you sure you want to move " + name + " to the trash?", function(res) {
|
||||
if (!res) { return; }
|
||||
console.log("Removing ", path);
|
||||
removeElement(path);
|
||||
});
|
||||
}
|
||||
else if ($(this).hasClass('open')) {
|
||||
$element.dblclick();
|
||||
}
|
||||
hideMenu();
|
||||
});
|
||||
|
||||
$(ifrw).on('click', function (e) {
|
||||
if (e.which !== 1) { return ; }
|
||||
removeSelected(e);
|
||||
removeInput(e);
|
||||
hideMenu(e);
|
||||
});
|
||||
|
||||
/* var displayDirectory = function (name, root, path) {
|
||||
$content.html("");
|
||||
var $title = $('<h1>').text(name);
|
||||
var $dirContent = $('<div>', {id: "folderContent"});
|
||||
|
@ -63,8 +477,8 @@ define([
|
|||
// display sub directories
|
||||
Object.keys(root).forEach(function (key) {
|
||||
if (typeof(root[key]) === "string") { return; }
|
||||
var $name = $('<span>').text(key).prepend($folderIcon.clone());
|
||||
var $element = $('<li>').append($name).click(function () {
|
||||
var $name = $('<span>', { 'class': 'folder-element' }).text(key).prepend($folderIcon.clone());
|
||||
var $element = $('<li>').append($name).dblclick(function () {
|
||||
displayDirectory(key, root[key]);
|
||||
});
|
||||
$element.appendTo($list);
|
||||
|
@ -72,8 +486,8 @@ define([
|
|||
// display files
|
||||
Object.keys(root).forEach(function (key) {
|
||||
if (typeof(root[key]) !== "string") { return; }
|
||||
var $name = $('<span>').text(key).prepend($fileIcon.clone());
|
||||
var $element = $('<li>').append($name).click(function () {
|
||||
var $name = $('<span>', { 'class': 'file-element' }).text(key).prepend($fileIcon.clone());
|
||||
var $element = $('<li>').append($name).dblclick(function () {
|
||||
window.location.hash = root[key];
|
||||
});
|
||||
$element.appendTo($list);
|
||||
|
@ -81,19 +495,20 @@ define([
|
|||
$content.append($title).append($dirContent);
|
||||
};
|
||||
|
||||
var createTree = function (root, $container) {
|
||||
var createTree = function ($container, root, path) {
|
||||
if (Object.keys(root).length === 0) { return; }
|
||||
var $list = $('<ul>').appendTo($container);
|
||||
Object.keys(root).forEach(function (key) {
|
||||
// Do not display files in the menu
|
||||
if (typeof(root[key]) === "string") { return; }
|
||||
var $name = $('<span>').text(key).click(function () {
|
||||
displayDirectory(key, root[key]);
|
||||
});
|
||||
var $icon = Object.keys(root[key]).length === 0 ? $folderEmptyIcon.clone() : $folderIcon.clone();
|
||||
var $name = $('<span>', { 'class': 'folder-element' }).text(key).prepend($icon)
|
||||
.click(function () {
|
||||
displayDirectory(key, root[key]);
|
||||
});
|
||||
var $element = $('<li>').append($name);
|
||||
$element.appendTo($list);
|
||||
createTree(root[key], $element[0]);
|
||||
});
|
||||
};
|
||||
createTree(files.root, $tree);
|
||||
};*/
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue