You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
cryptpad/www/file/main.js

515 lines
18 KiB
JavaScript

require.config({ paths: { 'json.sortify': '/bower_components/json.sortify/dist/JSON.sortify' } });
define([
'/customize/messages.js?app=pad',
'/bower_components/chainpad-crypto/crypto.js',
'/bower_components/chainpad-netflux/chainpad-netflux.js',
'/bower_components/hyperjson/hyperjson.js',
'/common/toolbar.js',
'/common/cursor.js',
'/bower_components/chainpad-json-validator/json-ot.js',
'/common/TypingTests.js',
'json.sortify',
'/bower_components/textpatcher/TextPatcher.amd.js',
'/common/cryptpad-common.js',
'/common/visible.js',
'/common/notify.js',
'/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 = module.files = {
root: {
"Directory 1": {
"Dir A": {
"File a": "#hash_a",
"File b": "#hash_b"
},
"Dir B": {},
"File A": "#hash_A"
},
"Directory 2": {
"File B": "#hash_B",
"File C": "#hash_C"
}
},
trash: {
"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 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"});
var $list = $('<ul>').appendTo($dirContent);
// display sub directories
Object.keys(root).forEach(function (key) {
if (typeof(root[key]) === "string") { return; }
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);
});
// display files
Object.keys(root).forEach(function (key) {
if (typeof(root[key]) !== "string") { return; }
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);
});
$content.append($title).append($dirContent);
};
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 $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]);
});
};*/
});