From d77970975c933b337dd07d94e6bb21d52bb3ae7d Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 14 Nov 2016 18:41:42 +0100 Subject: [PATCH] Add translations, ability to select multiple elements, clean the code --- customize.dist/translations/messages.fr.js | 27 + customize.dist/translations/messages.js | 27 + www/file/file.css | 3 +- www/file/fileObject.js | 453 +++++ www/file/main.js | 1954 ++++++++------------ 5 files changed, 1326 insertions(+), 1138 deletions(-) create mode 100644 www/file/fileObject.js diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index 03c3a59b4..2525d80e1 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -156,6 +156,33 @@ define(function () { out.poll_titleHint = "Titre"; out.poll_descriptionHint = "Description"; + // File manager + + out.fm_rootName = "Mes documents"; + out.fm_trashName = "Corbeille"; + out.fm_unsortedName = "Fichiers non triés"; + out.fm_filesDataName = "Tous les fichiers"; + out.fm_newFolder = "Nouveau dossier"; + out.fm_newFolderButton = "NOUVEAU DOSSIER"; + out.fm_folderName = "Nom du dossier"; + out.fm_numberOfFolders = "# de dossiers"; + out.fm_numberOfFiles = "# de fichiers"; + out.fm_fileName = "Nom du fichier"; + out.fm_title = "Titre"; + out.fm_lastAccess = "Dernier accès"; + out.fm_creation = "Création"; + out.fm_forbidden = "Action interdite"; + out.fm_emptyTrashDialog = "Êtes-vous sûr de vouloir vider la corbeille ?"; + out.fm_removePermanentlyDialog = "Êtes-vous sûr de vouloir supprimer {0} de la corbeille de manière permanente ?"; + out.fm_restoreDialog = "Êtes-vous sûr de vouloir restaurer {0} à son emplacement précédent ?"; + out.fm_removeSeveralDialog = "Êtes-vous sûr de vouloir déplacer ces {0} éléments vers la corbeille ?" + out.fm_unknownFolderError = "Le dossier sélectionné ou le dernier dossier visité n'existe plus. Ouverture du dossier parent..."; + out.fm_contextMenuError = "Impossible d'ouvrir le menu contextuel pour cet élément. Si le problème persiste, essayez de rechercher la page."; + out.fm_selectError = "Impossible de sélectionner l'élément ciblé. Si le problème persiste, essayez de recharger la page."; + + out.fo_moveUnsortedError = "La liste des éléments non triés ne peut pas contenir de dossiers."; + out.fo_existingNameError = "Ce nom est déjà utilisé dans ce répertoire. Veuillez en choisir un autre."; + // index.html out.main_p1 = 'CryptPad est l\'éditeur collaboratif en temps réel zero knowledge. Le chiffrement est effectué depuis votre navigateur, ce qui protège les données contre le serveur, le cloud, et la NSA. La clé de chiffrement est stockée dans l\'identifieur de fragment de l\'URL qui n\'est jamais envoyée au serveur mais est accessible depuis javascript, de sorte qu\'en partageant l\'URL, vous donnez l\'accès au pad à ceux qui souhaitent participer.'; diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index dd3b9c623..9d5571e8d 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -156,6 +156,33 @@ define(function () { out.poll_titleHint = "Title"; out.poll_descriptionHint = "Description"; + // File manager + + out.fm_rootName = "My documents"; + out.fm_trashName = "Trash"; + out.fm_unsortedName = "Unsorted files"; + out.fm_filesDataName = "All files"; + out.fm_newFolder = "New folder"; + out.fm_newFolderButton = "NEW FOLDER"; + out.fm_folderName = "Folder name"; + out.fm_numberOfFolders = "# of folders"; + out.fm_numberOfFiles = "# of files"; + out.fm_fileName = "File name"; + out.fm_title = "Title"; + out.fm_lastAccess = "Last access"; + out.fm_creation = "Creation"; + out.fm_forbidden = "Forbidden action"; + out.fm_emptyTrashDialog = "Are you sure you want to empty the trash?"; + out.fm_removePermanentlyDialog = "Are you sure you want to remove {0} from the trash permanently?"; + out.fm_removeSeveralDialog = "Are you sure you want to move these {0} elements to the trash?" + out.fm_restoreDialog = "Are you sure you want to restore {0} to its previous location?"; + out.fm_unknownFolderError = "The selected or last visited directory no longer exist. Opening the parent folder..."; + out.fm_contextMenuError = "Unable to open the context menu for that element. If the problem persist, try to reload the page."; + out.fm_selectError = "Unable to select the targetted element. If the problem persist, try to reload the page."; + + out.fo_moveUnsortedError = "You can't move a folder to the list of unsorted pads"; + out.fo_existingNameError = "Name already used in that directory. Please choose another one."; + // index.html out.main_p1 = 'CryptPad is the zero knowledge realtime collaborative editor. Encryption carried out in your web browser protects the data from the server, the cloud, and the NSA. The secret encryption key is stored in the URL fragment identifier which is never sent to the server but is available to javascript so by sharing the URL, you give authorization to others who want to participate.'; diff --git a/www/file/file.css b/www/file/file.css index ba007255a..c9d8b79a4 100644 --- a/www/file/file.css +++ b/www/file/file.css @@ -12,7 +12,7 @@ html, body { } .fa { - min-width: 17px; + /*min-width: 17px;*/ margin-right: 3px; font-family: FontAwesome; } @@ -201,7 +201,6 @@ li { margin: 10px 10px; width: 140px; text-align: center; - height: 70px; vertical-align: top; } diff --git a/www/file/fileObject.js b/www/file/fileObject.js new file mode 100644 index 000000000..e92969266 --- /dev/null +++ b/www/file/fileObject.js @@ -0,0 +1,453 @@ +define([ + '/customize/messages.js', + '/common/cryptpad-common.js', + '/bower_components/jquery/dist/jquery.min.js', +], function (Messages, Cryptpad) { + var $ = window.jQuery; + var module = {}; + + var ROOT = "root"; + var UNSORTED = "unsorted"; + var FILES_DATA = "filesData"; + var TRASH = "trash"; + var NEW_FOLDER_NAME = Messages.fm_newFolder; + + var init = module.init = function (files, config) { + var DEBUG = config.DEBUG || false; + var logging = console.log; + var log = config.log || logging; + var logError = config.logError || logging; + var debug = config.debug || logging; + + var exp = {}; + + var error = exp.error = function() { + exp.fixFiles(); + console.error.apply(arguments); + }; + + var comparePath = exp.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 deleteFromObject = function (path) { + var parentPath = path.slice(); + var key = parentPath.pop(); + var parentEl = findElement(files, parentPath); + if (path.length === 4 && path[0] === TRASH) { + files[TRASH][path[1]].splice(path[2], 1); + } else if (path[0] === UNSORTED) { + parentEl.splice(key, 1); + } else { + delete parentEl[key]; + } + }; + + var isPathInRoot = exp.isPathInRoot = function (path) { + return path[0] && path[0] === ROOT; + }; + var isPathInUnsorted = exp.isPathInUnsorted = function (path) { + return path[0] && path[0] === UNSORTED; + }; + var isPathInTrash = exp.isPathInTrash = function (path) { + return path[0] && path[0] === TRASH; + }; + + var isFile = exp.isFile = function (element) { + return typeof(element) === "string"; + }; + + var isFolder = exp.isFolder = function (element) { + return typeof(element) !== "string"; + }; + + var isFolderEmpty = exp.isFolderEmpty = function (element) { + if (typeof(element) !== "object") { return false; } + return Object.keys(element).length === 0; + }; + + var hasSubfolder = exp.hasSubfolder = function (element, trashRoot) { + if (typeof(element) !== "object") { return false; } + var subfolder = 0; + for (var f in element) { + if (trashRoot) { + if ($.isArray(element[f])) { + element[f].forEach(function (el, idx) { + subfolder += isFolder(el.element) ? 1 : 0; + }); + } + } else { + subfolder += isFolder(element[f]) ? 1 : 0; + } + } + return subfolder; + }; + + var hasFile = exp.hasFile = function (element, trashRoot) { + if (typeof(element) !== "object") { return false; } + var file = 0; + for (var f in element) { + if (trashRoot) { + if ($.isArray(element[f])) { + element[f].forEach(function (el, idx) { + file += isFile(el.element) ? 1 : 0; + }); + } + } else { + file += isFile(element[f]) ? 1 : 0; + } + } + return file; + }; + + var isSubpath = exp.isSubpath = function (path, parentPath) { + var pathA = parentPath.slice(); + var pathB = path.slice(0, pathA.length); + return comparePath(pathA, pathB); + }; + + var getAvailableName = function (parentEl, name) { + if (typeof(parentEl[name]) === "undefined") { return name; } + var newName = name; + var i = 1; + while (typeof(parentEl[newName]) !== "undefined") { + newName = name + "_" + i; + i++; + } + return newName; + }; + + var compareFiles = function (fileA, fileB) { + // Compare string, might change in the future + return fileA === fileB; + }; + + var isInTrashRoot = function (path) { + return path[0] === TRASH && path.length === 4; + }; + + var getTrashElementData = exp.getTrashElementData = function (trashPath) { + if (!isInTrashRoot) { + debug("Called getTrashElementData on a element not in trash root: ", trashpath); + return; + } + var parentPath = trashPath.slice(); + parentPath.pop(); + return findElement(files, parentPath); + }; + + var getUnsortedFiles = exp.getUnsortedFiles = function () { + return files[UNSORTED]; + }; + + + + + // Data from filesData + var getTitle = function (href) { + if (!files[FILES_DATA][href]) { + error("getTitle called with a non-existing href: ", href); + return; + } + return files[FILES_DATA][href].title; + }; + + + // Find an element in a object following a path, resursively + var findElement = exp.findElement = function (root, pathInput) { + if (!pathInput) { + error("Invalid path:\n", pathInput, "\nin root\n", root); + return; + } + if (pathInput.length === 0) { return root; } + var path = pathInput.slice(); + var key = path.shift(); + if (typeof root[key] === "undefined") { + debug("Unable to find the key '" + key + "' in the root object provided:\n", root); + return; + } + return findElement(root[key], path); + }; + + var moveElement = exp.moveElement = function (elementPath, newParentPath, cb) { + if (comparePath(elementPath, newParentPath)) { return; } // Nothing to do... + if (newParentPath[0] && newParentPath[0] === TRASH) { + debug("Moving to trash is forbidden. You have to use the removeElement function"); + return; + } + var element = findElement(files, elementPath); + + var newParent = findElement(files, newParentPath); + + if (isPathInUnsorted(newParentPath)) { + if (isFolder(element)) { + log(Messages.fo_moveUnsortedError); + return; + } else { + if (files[UNSORTED].indexOf(element) === -1) { + files[UNSORTED].push(element); + } + deleteFromObject(elementPath); + cb && cb(); + return; + } + } + + var name; + + if (isPathInUnsorted(elementPath)) { + name = getTitle(element); + } else if (elementPath.length === 4 && elementPath[0] === TRASH) { + // Element from the trash root: elementPath = [TRASH, "{dirName}", 0, 'element'] + name = elementPath[1]; + } else { + name = elementPath[elementPath.length-1]; + } + var newName = !isPathInRoot(elementPath) ? getAvailableName(newParent, name) : name; + + if (typeof(newParent[newName]) !== "undefined") { + log("A file with the same name already exist at the new location. Rename the file and try again."); + return; + } + + newParent[newName] = element; + deleteFromObject(elementPath); + cb && cb(); + }; + + var createNewFolder = exp.createNewFolder = function (folderPath, name, cb) { + var parentEl = findElement(files, folderPath); + var folderName = getAvailableName(parentEl, name || NEW_FOLDER_NAME); + parentEl[folderName] = {}; + var newPath = folderPath.slice(); + newPath.push(folderName); + cb({ + newPath: newPath + }); + }; + + var pushToTrash = function (name, element, path) { + var trash = findElement(files, [TRASH]); + + if (typeof(trash[name]) === "undefined") { + trash[name] = []; + } + var trashArray = trash[name]; + var trashElement = { + element: element, + path: path + }; + trashArray.push(trashElement); + }; + // Move to trash + var removeElement = exp.removeElement = function (path, cb, force) { + if (!path || path.length < 2 || path[0] === TRASH) { + debug("Calling removeElement from a wrong path: ", path); + return; + } + var element = findElement(files, path); + var key = path[path.length - 1]; + var name = isPathInUnsorted(path) ? getTitle(element) : key; + var andThen = function () { + var parentPath = path.slice(); + parentPath.pop(); + pushToTrash(name, element, parentPath); + deleteFromObject(path); + cb && cb(); + }; + if (force) { + andThen(); + return; + } + Cryptpad.confirm("Are you sure you want to move " + name + " to the trash?", function(res) { + if (!res) { return; } + andThen(); + }); + }; + + var removeFromTrashArray = function (element, name) { + var array = files[TRASH][name]; + if (!array || !$.isArray(array)) { return; } + // Remove the element from the trash array + var index = array.indexOf(element); + if (index > -1) { + array.splice(index, 1); + } + // Remove the array is empty to have a cleaner object in chainpad + if (array.length === 0) { + delete files[TRASH][name]; + } + }; + + var restoreTrash = exp.restoreTrash = function (path, cb) { + if (!path || path.length !== 4 || path[0] !== TRASH) { + debug("restoreTrash was called from an element not in the trash root: ", path); + return; + } + var element = findElement(files, path); + var parentEl = getTrashElementData(path); + var newPath = parentEl.path; + if (isPathInUnsorted(newPath)) { + if (files[UNSORTED].indexOf(element) === -1) { + files[UNSORTED].push(element); + removeFromTrashArray(parentEl, path[1]); + cb(); + } + return; + } + // Find the new parent element + var newParentEl = findElement(files, newPath); + var name = getAvailableName(newParentEl, path[1]); + // Move the element + newParentEl[name] = element; + removeFromTrashArray(parentEl, path[1]); + cb(); + }; + + // Delete permanently + var removeFromTrash = exp.removeFromTrash = function (path, cb) { + if (!path || path.length < 4 || path[0] !== TRASH) { return; } + // Remove the last element from the path to get the parent path and the element name + var parentPath = path.slice(); + var name; + if (path.length === 4) { // Trash root + name = path[1]; + parentPath.pop(); + var parentElement = findElement(files, parentPath); + removeFromTrashArray(parentElement, name); + cb(); + return; + } + name = parentPath.pop(); + var parentEl = findElement(files, parentPath); + if (typeof(parentEl[name]) === "undefined") { + logError("Unable to locate the element to remove from trash: ", path); + return; + } + delete parentEl[name]; + cb(); + }; + + var emptyTrash = exp.emptyTrash = function (cb) { + files[TRASH] = {}; + cb(); + }; + + + var renameElement = exp.renameElement = function (path, newName, cb) { + if (path.length <= 1) { + logError('Renaming `root` is forbidden'); + return; + } + if (!newName || newName.trim() === "") { return; } + // 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) { + return; + } + var parentEl = findElement(files, parentPath); + if (typeof(parentEl[newName]) !== "undefined") { + log(Messages.fo_existingNameError); + return; + } + parentEl[newName] = element; + delete parentEl[oldName]; + cb(); + }; + + var fixFiles = exp.fixFiles = function () { + // Explore the tree and check that everything is correct: + // * 'root', 'trash' and 'filesData' exist and are objects + // * Folders are objects + // * Files are href + // * Trash root contains only arrays, each element of the array is an object {element:.., path:..} + // * Data (title, cdate, adte) are stored in filesData. filesData contains only href keys linking to object with title, cdate, adate. + // * Dates (adate, cdate) can be parsed/formatted + debug("Cleaning file system..."); + + // Create a backup + if (typeof(localStorage.oldFileSystem) === "undefined") { + localStorage.oldFileSystem = '[]'; + } + var before = JSON.stringify(files); + + if (typeof(files[ROOT]) !== "object") { debug("ROOT was not an object"); files[ROOT] = {}; } + if (typeof(files[TRASH]) !== "object") { debug("TRASH was not an object"); files[TRASH] = {}; } + if (typeof(files[FILES_DATA]) !== "object") { debug("FILES_DATA was not an object"); files[FILES_DATA] = {}; } + if (!$.isArray(files[UNSORTED])) { debug("UNSORTED was not an array"); files[UNSORTED] = []; } + var fixRoot = function (element) { + for (var el in element) { + if (!isFile(element[el]) && !isFolder(element[el])) { + debug("An element in ROOT was not a folder nor a file. ", element[el]); + delete element[el]; + } else if (isFolder(element[el])) { + fixRoot(element[el]); + } + } + }; + fixRoot(files[ROOT]); + var fixTrashRoot = function (tr) { + var toClean; + for (var el in tr) { + if (!$.isArray(tr[el])) { + debug("An element in TRASH root is not an array. ", tr[el]); + delete tr[el]; + } else { + toClean = []; + tr[el].forEach(function (obj, idx) { + if (typeof(obj) !== "object") { toClean.push(idx); return; } + if (!isFile(obj.element) && !isFolder(obj.element)) { toClean.push(idx); return; } + if (!$.isArray(obj.path)) { toClean.push(idx); return; } + }); + for (var i = toClean.length-1; i>=0; i--) { + tr[el].splice(toClean[i], 1); + } + } + } + }; + fixTrashRoot(files[TRASH]); + var fixFilesData = function (fd) { + for (var el in fd) { + if (typeof(fd[el]) !== "object") { + debug("An element in filesData was not an object. ", fd[el]); + delete fd[el]; + } + }; + }; + fixFilesData(files[FILES_DATA]); + var fixUnsorted = function (us) { + var toClean = []; + us.forEach(function (el, idx) { + if (!isFile(el)) { + toClean.push(idx); + } + }); + }; + fixUnsorted(files[UNSORTED]); + + if (JSON.stringify(files) !== before) { + var backup = JSON.parse(localStorage.oldFileSystem); + backup.push(files); + localStorage.oldFileSystem = JSON.stringify(backup); + debug("Your file system was corrupted. It has been cleaned so that the file manager application can be used"); + return; + } + debug("File system was clean"); + }; + fixFiles(); + + return exp; + }; + + return module; +}); diff --git a/www/file/main.js b/www/file/main.js index e9ca8a3e8..185c64b5c 100644 --- a/www/file/main.js +++ b/www/file/main.js @@ -1,26 +1,13 @@ 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', + '/file/fileObject.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) { +], function (Messages, JSONSortify, Cryptpad, FO) { var module = window.MODULE = {}; var $ = window.jQuery; @@ -29,43 +16,28 @@ define([ var ifrw = $('#pad-iframe')[0].contentWindow; var ROOT = "root"; - var ROOT_NAME = "My files"; + var ROOT_NAME = Messages.fm_rootName; var UNSORTED = "unsorted"; - var UNSORTED_NAME = "Unsorted files"; + var UNSORTED_NAME = Messages.fm_unsortedName; var FILES_DATA = "filesData"; - var FILES_DATA_NAME = "All files"; + var FILES_DATA_NAME = Messages.fm_filesDataName; var TRASH = "trash"; - var TRASH_NAME = "Trash"; + var TRASH_NAME = Messages.fm_trashName; var TIME_BEFORE_RENAME = 1000; var LOCALSTORAGE_LAST = "cryptpad-file-lastOpened"; var LOCALSTORAGE_OPENED = "cryptpad-file-openedFolders"; var LOCALSTORAGE_VIEWMODE = "cryptpad-file-viewMode"; var FOLDER_CONTENT_ID = "folderContent"; - var NEW_FOLDER_NAME = "New folder"; //TODO translate - var DEBUG = true; - - var debug = DEBUG ? console.log : function() {return;}; - var fixFiles = function () { - // debug("Fixing file system..."); - // Not implemented yet - // Explore the tree and check that everything is correct: - // * 'root', 'trash' and 'filesData' exist and are objects - // * Folders are objects - // * Files are href - // * Trash root contains only arrays, each element of the array is an object {element:.., path:..} - // * Data (title, cdate, adte) are stored in filesData. filesData contains only href keys linking to object with title, cdate, adate. - // * Dates (adate, cdate) can be parsed/formatted - // debug("File system fixed"); - }; - var error = function() { - fixFiles(); - console.error.apply(arguments); - }; - var logError = console.error; - var log = Cryptpad.log; + var NEW_FOLDER_NAME = Messages.fm_newFolder; - var files = module.files = { + var config = {}; + var DEBUG = config.DEBUG = true; + var debug = config.debug = DEBUG ? console.log : function() {return;}; + var logError = config.logError = console.error; + var log = config.log = Cryptpad.log; + + var filesObject = module.files = { root: { "Directory 1": { "Dir A": { @@ -208,9 +180,8 @@ define([ }] } }; - module.defaultFiles = JSON.parse(JSON.stringify(files)); - // TODO translate - // TODO translate contextmenu in inner.html + module.defaultFiles = JSON.parse(JSON.stringify(filesObject)); + var getLastOpenedFolder = function () { var path; try { @@ -234,7 +205,6 @@ define([ localStorage[LOCALSTORAGE_OPENED] = '[]'; } }; - initLSOpened(); var wasFolderOpened = function (path) { var store = JSON.parse(localStorage[LOCALSTORAGE_OPENED]); @@ -265,12 +235,15 @@ define([ }; var setViewMode = function (mode) { if (typeof(mode) !== "string") { - console.error("Incorrect view mode: ", mode); + logError("Incorrect view mode: ", mode); return; } localStorage[LOCALSTORAGE_VIEWMODE] = mode; }; + var now = function () { + return new Date().getTime(); + }; var DEBUG = window.DEBUG = { resetLocalStorage : function () { @@ -279,1187 +252,896 @@ define([ } }; - var currentPath = module.currentPath = getLastOpenedFolder(); - var lastSelectTime; - var selectedElement; - - var $tree = $iframe.find("#tree"); - var $content = $iframe.find("#content"); - var $contextMenu = $iframe.find("#contextMenu"); - var $trashTreeContextMenu = $iframe.find("#trashTreeContextMenu"); - var $trashContextMenu = $iframe.find("#trashContextMenu"); - var $folderIcon = $('', {"class": "fa fa-folder folder"}); - var $folderEmptyIcon = $('', {"class": "fa fa-folder-o folder"}); - var $folderOpenedIcon = $('', {"class": "fa fa-folder-open folder"}); - var $folderOpenedEmptyIcon = $('', {"class": "fa fa-folder-open-o folder"}); - var $fileIcon = $('', {"class": "fa fa-file file"}); - var $upIcon = $('', {"class": "fa fa-arrow-circle-up"}); - var $unsortedIcon = $('', {"class": "fa fa-files-o"}); - var $trashIcon = $('', {"class": "fa fa-trash"}); - var $trashEmptyIcon = $('', {"class": "fa fa-trash-o"}); - var $collapseIcon = $('', {"class": "fa fa-minus-square-o expcol"}); - var $expandIcon = $('', {"class": "fa fa-plus-square-o expcol"}); - var $listIcon = $('', {"class": "fa fa-list"}); - var $gridIcon = $('', {"class": "fa fa-th"}); - - var removeSelected = function () { - $iframe.find('.selected').removeClass("selected"); - }; - var removeInput = function () { - $iframe.find('li > span:hidden').show(); - $iframe.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 deleteFromObject = function (path) { - var parentPath = path.slice(); - var key = parentPath.pop(); - var parentEl = findElement(files, parentPath); - if (path.length === 4 && path[0] === TRASH) { - files[TRASH][path[1]].splice(path[2], 1); - } else if (path[0] === UNSORTED) { - parentEl.splice(key, 1); - } else { - delete parentEl[key]; - } - }; - - var now = function () { - return new Date().getTime(); - }; - - var isPathInRoot = function (path) { - return path[0] && path[0] === ROOT; - }; - var isPathInUnsorted = function (path) { - return path[0] && path[0] === UNSORTED; - }; - var isPathInTrash = function (path) { - return path[0] && path[0] === TRASH; - }; - - var isFile = function (element) { - return typeof(element) === "string"; - }; - - var isFolder = function (element) { - return typeof(element) !== "string"; - }; - - var isFolderEmpty = function (element) { - if (typeof(element) !== "object") { return false; } - return Object.keys(element).length === 0; - }; - - var hasSubfolder = function (element, trashRoot) { - if (typeof(element) !== "object") { return false; } - var subfolder = 0; - for (var f in element) { - if (trashRoot) { - if ($.isArray(element[f])) { - element[f].forEach(function (el, idx) { - subfolder += isFolder(el.element) ? 1 : 0; - }); - } - } else { - subfolder += isFolder(element[f]) ? 1 : 0; + var keyPressed = []; + var pressKey = function (key, state) { + if (state) { + if (keyPressed.indexOf(key) === -1) { + keyPressed.push(key); } + return; } - return subfolder; - }; + var idx = keyPressed.indexOf(key); + if (idx !== -1) { + keyPressed.splice(idx, 1); + } + }; + + var init = function (files) { + var filesOp = FO.init(files, config); + + var error = filesOp.error; + + var currentPath = module.currentPath = getLastOpenedFolder(); + var lastSelectTime; + var selectedElement; + + var $tree = $iframe.find("#tree"); + var $content = $iframe.find("#content"); + var $contextMenu = $iframe.find("#contextMenu"); + var $trashTreeContextMenu = $iframe.find("#trashTreeContextMenu"); + var $trashContextMenu = $iframe.find("#trashContextMenu"); + var $folderIcon = $('', {"class": "fa fa-folder folder", style:"color:#FEDE8B;text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black;"}); + //var $folderEmptyIcon = $('', {"class": "fa fa-folder folder", style:"color:pink"}); + var $folderEmptyIcon = $folderIcon.clone(); + var $folderOpenedIcon = $('', {"class": "fa fa-folder-open folder", style:"color:#FEDE8B;text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black;"}); + //var $folderOpenedEmptyIcon = $('', {"class": "fa fa-folder-open-o folder"}); + var $folderOpenedEmptyIcon = $folderOpenedIcon.clone(); + var $fileIcon = $('', {"class": "fa fa-file-text-o file"}); + var $upIcon = $('', {"class": "fa fa-arrow-circle-up"}); + var $unsortedIcon = $('', {"class": "fa fa-files-o"}); + var $trashIcon = $('', {"class": "fa fa-trash"}); + var $trashEmptyIcon = $('', {"class": "fa fa-trash-o"}); + var $collapseIcon = $('', {"class": "fa fa-minus-square-o expcol"}); + var $expandIcon = $('', {"class": "fa fa-plus-square-o expcol"}); + var $listIcon = $('', {"class": "fa fa-list"}); + var $gridIcon = $('', {"class": "fa fa-th"}); + + var removeSelected = function () { + $iframe.find('.selected').removeClass("selected"); + }; + var removeInput = function () { + $iframe.find('li > span:hidden').show(); + $iframe.find('li > input').remove(); + }; - var hasFile = function (element, trashRoot) { - if (typeof(element) !== "object") { return false; } - var file = 0; - for (var f in element) { - if (trashRoot) { - if ($.isArray(element[f])) { - element[f].forEach(function (el, idx) { - file += isFile(el.element) ? 1 : 0; - }); + var compareDays = function (date1, date2) { + var day1 = Date.UTC(date1.getFullYear(), date1.getMonth(), date1.getDate()); + var day2 = Date.UTC(date2.getFullYear(), date2.getMonth(), date2.getDate()); + var ms = Math.abs(day1-day2); + return Math.floor(ms/1000/60/60/24); + }; + + var getDate = function (sDate) { + var ret = sDate.toString(); + try { + var date = new Date(sDate); + var today = new Date(); + var diff = compareDays(date, today); + if (diff === 0) { + ret = date.toLocaleTimeString(); + } else { + ret = date.toLocaleDateString(); } - } else { - file += isFile(element[f]) ? 1 : 0; + } catch (e) { + error("Unable to format that string to a date with .toLocaleString", sDate, e); } - } - return file; - }; - - var isSubpath = function (path, parentPath) { - var pathA = parentPath.slice(); - var pathB = path.slice(0, pathA.length); - return comparePath(pathA, pathB); - }; - - var getAvailableName = function (parentEl, name) { - if (typeof(parentEl[name]) === "undefined") { return name; } - var newName = name; - var i = 1; - while (typeof(parentEl[newName]) !== "undefined") { - newName = name + "_" + i; - i++; - } - return newName; - }; + return ret; + }; - var compareFiles = function (fileA, fileB) { - // Compare string, might change in the future - return fileA === fileB; - }; + var openFile = function (fileEl) { + window.location.hash = fileEl; + }; - var isInTree = function (file, root) { - if (isFile(root)) { - return compareFiles(file, root); - } - var inTree = false; - for (var e in root) { - inTree = isInTree(file, root[e]); - if (inTree) { break; } + var refresh = function () { + module.displayDirectory(currentPath); }; - return inTree; - }; - var isInTrash = function (file) { - var inTrash = false; - var root = files[TRASH]; - for (var e in root) { - if (!$.isArray(root[e])) { - error("Trash contains a non-array element"); + // Replace a file/folder name by an input to change its value + var displayRenameInput = function ($element, path) { + if (!path || path.length < 2) { + logError("Renaming a top level element (root, trash or filesData) is forbidden."); return; } - root[e].some(function (trashEl, idx) { - inTrash = isInTree(file, trashEl.element); - return inTrash; + $element.hide(); + removeSelected(); + var name = path[path.length - 1]; + var $input = $('', { + placeholder: name, + value: name }); - if (inTrash) { break; } - } - return inTrash; - }; - - var isInTrashRoot = function (path) { - return path[0] === TRASH && path.length === 4; - }; - - var getTrashElementData = function (trashPath) { - if (!isInTrashRoot) { - debug("Called getTrashElementData on a element not in trash root: ", trashpath); - return; - } - var parentPath = trashPath.slice(); - parentPath.pop(); - return findElement(files, parentPath); - }; - - var getUnsortedFiles = function () { - return files[UNSORTED]; - }; - - var compareDays = function (date1, date2) { - var day1 = Date.UTC(date1.getFullYear(), date1.getMonth(), date1.getDate()); - var day2 = Date.UTC(date2.getFullYear(), date2.getMonth(), date2.getDate()); - var ms = Math.abs(day1-day2); - return Math.floor(ms/1000/60/60/24); - }; + $input.on('keyup', function (e) { + if (e.which === 13) { + filesOp.renameElement(path, $input.val(), function () { + refresh(); + }); + removeInput(); + } + }); + $input.insertAfter($element); + $input.focus(); + $input.select(); + // We don't want to open the file/folder when clicking on the input + $input.on('click dblclick', function (e) { + removeSelected(); + e.stopPropagation(); + }); + // Remove the browser ability to drag text from the input to avoid + // triggering our drag/drop event handlers + $input.on('dragstart dragleave drag drop', function (e) { + e.preventDefault(); + e.stopPropagation(); + }); + // Make the parent element non-draggable when selecting text in the field + // since it would remove the input + $input.on('mousedown', function () { + $input.parents('li').attr("draggable", false); + }); + $input.on('mouseup', function () { + $input.parents('li').attr("draggable", true); + }); + }; - var getDate = function (sDate) { - var ret = sDate.toString(); - try { - var date = new Date(sDate); - var today = new Date(); - var diff = compareDays(date, today); - if (diff === 0) { - ret = date.toLocaleTimeString(); - } else { - ret = date.toLocaleDateString(); + // Add the "selected" class to the "li" corresponding to the clicked element + var onElementClick = function ($element, path) { + // If "Ctrl" is pressed, do not remove the current selection + if (keyPressed.indexOf(17) === -1) { + removeSelected(); } - } catch (e) { - error("Unable to format that string to a date with .toLocaleString", sDate, e); - } - return ret; - }; - - - // Data from filesData - var getTitle = function (href) { - if (!files[FILES_DATA][href]) { - error("getTitle called with a non-existing href: ", href); - return; - } - return files[FILES_DATA][href].title; - }; - - - // Find an element in a object following a path, resursively - var findElement = function (root, pathInput) { - if (!pathInput) { - error("Invalid path:\n", pathInput, "\nin root\n", root); - return; - } - if (pathInput.length === 0) { return root; } - var path = pathInput.slice(); - var key = path.shift(); - if (typeof root[key] === "undefined") { - debug("Unable to find the key '" + key + "' in the root object provided:\n", root); - return; - } - return findElement(root[key], path); - }; - - var moveElement = function (elementPath, newParentPath) { - if (comparePath(elementPath, newParentPath)) { return; } // Nothing to do... - if (newParentPath[0] && newParentPath[0] === TRASH) { - debug("Moving to trash is forbidden. You have to use the removeElement function"); - return; - } - var element = findElement(files, elementPath); - - var newParent = findElement(files, newParentPath); - - if (isPathInUnsorted(newParentPath)) { - if (isFolder(element)) { - //TODO translate - log("You can't move a folder to the list of unsorted pads"); - return; - } else { - if (files[UNSORTED].indexOf(element) === -1) { - files[UNSORTED].push(element); - } - deleteFromObject(elementPath); - module.displayDirectory(currentPath); + if (!$element.is('li')) { + $element = $element.closest('li'); + } + if (!$element.length) { + log(Messages.fm_selectError); return; } - } - - var name; - - if (isPathInUnsorted(elementPath)) { - name = getTitle(element); - } else if (elementPath.length === 4 && elementPath[0] === TRASH) { - // Element from the trash root: elementPath = [TRASH, "{dirName}", 0, 'element'] - name = elementPath[1]; - } else { - name = elementPath[elementPath.length-1]; - } - var newName = !isPathInRoot(elementPath) ? getAvailableName(newParent, name) : name; - - if (typeof(newParent[newName]) !== "undefined") { - log("A file with the same name already exist at the new location. Rename the file and try again."); - return; - } - - newParent[newName] = element; - deleteFromObject(elementPath); - module.displayDirectory(currentPath); - }; - - var createNewFolder = function (folderPath, name) { - var parentEl = findElement(files, folderPath); - var folderName = getAvailableName(parentEl, name || NEW_FOLDER_NAME); - parentEl[folderName] = {}; - var newPath = folderPath.slice(); - newPath.push(folderName); - module.newFolder = newPath; - displayDirectory(currentPath); - }; - - var pushToTrash = function (name, element, path) { - var trash = findElement(files, [TRASH]); - - if (typeof(trash[name]) === "undefined") { - trash[name] = []; - } - var trashArray = trash[name]; - var trashElement = { - element: element, - path: path - }; - trashArray.push(trashElement); - }; - // Move to trash - var removeElement = function (path, displayTrash, force) { - if (!path || path.length < 2 || path[0] === TRASH) { - debug("Calling removeElement from a wrong path: ", path); - return; - } - var element = findElement(files, path); - var key = path[path.length - 1]; - var name = isPathInUnsorted(path) ? getTitle(element) : key; - var andThen = function () { - var parentPath = path.slice(); - parentPath.pop(); - pushToTrash(name, element, parentPath); - deleteFromObject(path); - if (displayTrash) { - module.displayDirectory([TRASH]); + if (!$element.hasClass("selected")) { + $element.addClass("selected"); + lastSelectTime = now(); } else { - module.displayDirectory(currentPath); + $element.removeClass("selected"); } }; - if (force) { - andThen(); - return; - } - Cryptpad.confirm("Are you sure you want to move " + name + " to the trash?", function(res) { - if (!res) { return; } - andThen(); - }); - }; - var removeFromTrashArray = function (element, name) { - var array = files[TRASH][name]; - if (!array || !$.isArray(array)) { return; } - // Remove the element from the trash array - var index = array.indexOf(element); - if (index > -1) { - array.splice(index, 1); - } - // Remove the array is empty to have a cleaner object in chainpad - if (array.length === 0) { - delete files[TRASH][name]; - } - }; - - var restoreTrash = function (path) { - if (!path || path.length !== 4 || path[0] !== TRASH) { - debug("restoreTrash was called from an element not in the trash root: ", path); - return; - } - var element = findElement(files, path); - var parentEl = getTrashElementData(path); - var newPath = parentEl.path; - if (isPathInUnsorted(newPath)) { - if (files[UNSORTED].indexOf(element) === -1) { - files[UNSORTED].push(element); - removeFromTrashArray(parentEl, path[1]); - module.displayDirectory(currentPath); + // Open the selected context menu on the closest "li" element + var openContextMenu = function (e, $menu) { + module.hideMenu(); + e.stopPropagation(); + var path = $(e.target).closest('li').data('path'); + if (!path) { return; } + $menu.css({ + display: "block", + left: e.pageX, + top: e.pageY + }); + // $element should be the , find it if it's not the case + var $element = $(e.target).closest('li').children('span.element'); + onElementClick($element); + if (!$element.length) { + logError("Unable to locate the .element tag", e.target); + $menu.hide(); + log(Messages.fm_contextMenuError); + return; } - return; - } - // Find the new parent element - var newParentEl = findElement(files, newPath); - var name = getAvailableName(newParentEl, path[1]); - // Move the element - newParentEl[name] = element; - removeFromTrashArray(parentEl, path[1]); - module.displayDirectory(currentPath); - }; - - // Delete permanently - var removeFromTrash = function (path) { - if (!path || path.length < 4 || path[0] !== TRASH) { return; } - // Remove the last element from the path to get the parent path and the element name - var parentPath = path.slice(); - var name; - if (path.length === 4) { // Trash root - name = path[1]; - parentPath.pop(); - var parentElement = findElement(files, parentPath); - removeFromTrashArray(parentElement, name); - module.displayDirectory(currentPath); - return; - } - name = parentPath.pop(); - var parentEl = findElement(files, parentPath); - if (typeof(parentEl[name]) === "undefined") { - console.error("Unable to locate the element to remove from trash: ", path); - return; - } - delete parentEl[name]; - module.displayDirectory(currentPath); - }; - - var emptyTrash = function () { - files[TRASH] = {}; - module.displayDirectory(currentPath); - }; + $menu.find('a').data('path', path); + $menu.find('a').data('element', $element); + return false; + }; - var openFile = function (fileEl) { - window.location.hash = fileEl; - }; + var openDirectoryContextMenu = function (e) { + openContextMenu(e, $contextMenu); + return false; + }; - var renameElement = function (path, newName) { - if (path.length <= 1) { - logError('Renaming `root` is forbidden'); - 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) { - return; - } - var parentEl = findElement(files, parentPath); - if (typeof(parentEl[newName]) !== "undefined") { - log('Name already used in that directory. Please choose another one.'); - //TODO translate - return; - } - parentEl[newName] = element; - delete parentEl[oldName]; - module.resetTree(); - module.displayDirectory(currentPath); - }; + var openTrashTreeContextMenu = function (e) { + openContextMenu(e, $trashTreeContextMenu); + return false; + }; - var displayRenameInput = function ($element, path) { - if (!path || path.length < 2) { - logError("Renaming a top level element (root, trash or filesData) is forbidden."); - return; - } - $element.hide(); - removeSelected(); - var name = path[path.length - 1]; - var $input = $('', { - placeholder: name, - value: name - }); - $input.on('keyup', function (e) { - if (e.which === 13) { - renameElement(path, $input.val()); - removeInput(); + var openTrashContextMenu = function (e) { + var path = $(e.target).closest('li').data('path'); + if (!path) { return; } + $trashContextMenu.find('li').show(); + if (path.length > 4) { + $trashContextMenu.find('a.restore').parent('li').hide(); } - }); - $input.insertAfter($element); - $input.focus(); - $input.select(); - // We don't want to open the file/folder when clicking on the input - $input.on('click dblclick', function (e) { - removeSelected(); - e.stopPropagation(); - }); - // Remove the browser ability to drag text from the input to avoid - // triggering our drag/drop event handlers - $input.on('dragstart dragleave drag drop', function (e) { - e.preventDefault(); - e.stopPropagation(); - }); - // Make the parent element non-draggable when selecting text in the field - // since it would remove the input - $input.on('mousedown', function () { - $input.parents('li').attr("draggable", false); - }); - $input.on('mouseup', function () { - $input.parents('li').attr("draggable", true); - }); - }; + openContextMenu(e, $trashContextMenu); + return false; + }; - 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"); + // Drag & drop: + // The data transferred is a stringified JSON containing the path of the dragged element + var onDrag = function (ev, path) { + var paths = []; + var $element = $(ev.target).closest('li'); + if ($element.hasClass('selected')) { + $selected = $iframe.find('.selected'); + $selected.each(function (idx, elmt) { + if ($(elmt).data('path')) { + paths.push($(elmt).data('path')); + } + }); + } else { + removeSelected(); + $element.addClass('selected'); + paths = [path]; } - return; - }*/ - removeSelected(); - if (!$element.is('li')) { - $element = $element.closest('li'); - } - if (!$element.length) { - // TODO translate - log("Unable to select the targetted element. If the problem persist, try to reload the page"); - return; - } - if (!$element.hasClass("selected")) { - $element.addClass("selected"); - lastSelectTime = now(); - } - }; - - var openContextMenu = function (e, $menu) { - module.hideMenu(); - e.stopPropagation(); - var path = $(e.target).closest('li').data('path'); - if (!path) { return; } - $menu.css({ - display: "block", - left: e.pageX, - top: e.pageY - }); - // $element should be the , find it if it's not the case - var $element = $(e.target).closest('li').children('span.element'); - onElementClick($element); - if (!$element.length) { - console.error("Unable to locate the .element tag", e.target); - $menu.hide(); - log("Unable to open the context menu for that element. If the problem persist, try to reload the page"); - return; - } - $menu.find('a').data('path', path); - $menu.find('a').data('element', $element); - return false; - }; - - var openDirectoryContextMenu = function (e) { - openContextMenu(e, $contextMenu); - return false; - }; - - var openTrashTreeContextMenu = function (e) { - openContextMenu(e, $trashTreeContextMenu); - return false; - }; - - var openTrashContextMenu = function (e) { - var path = $(e.target).closest('li').data('path'); - if (!path) { return; } - $trashContextMenu.find('li').show(); - if (path.length > 4) { - $trashContextMenu.find('a.restore').parent('li').hide(); - } - openContextMenu(e, $trashContextMenu); - return false; - }; - - var onDrag = function (ev, path) { - var data = { - 'path': path + var data = { + 'path': paths + }; + ev.dataTransfer.setData("text", JSON.stringify(data)); }; - ev.dataTransfer.setData("text", JSON.stringify(data)); - }; - - var onDrop = function (ev) { - ev.preventDefault(); - $iframe.find('.droppable').removeClass('droppable'); - var data = ev.dataTransfer.getData("text"); - var oldPath = JSON.parse(data).path; - var newPath = $(ev.target).data('path') || $(ev.target).parent('li').data('path'); - if (!oldPath || !newPath) { return; } - // Call removeElement when trying to move something into the trash - if (newPath[0] === TRASH) { - removeElement(oldPath, true); - return; - } - moveElement(oldPath, newPath); - }; - - var addDragAndDropHandlers = function ($element, path, isFolder, droppable) { - // "dragenter" is fired for an element and all its children - // "dragleave" may be fired when entering a child - // --> We store the number of enter/leave and the element entered and we remove the - // highlighting only when we have left everything - var counter = 0; - var dragenterList = []; - $element.on('dragstart', function (e) { - e.stopPropagation(); - counter = 0; - dragenterList = []; - onDrag(e.originalEvent, path); - }); - - // Add drop handlers if we are not in the trash and if the element is a folder - if (!droppable || !isFolder) { return; } - $element.on('dragover', function (e) { - e.preventDefault(); - }); - $element.on('drop', function (e) { - onDrop(e.originalEvent); - }); - $element.on('dragenter', function (e) { - e.preventDefault(); - e.stopPropagation(); - if (dragenterList.indexOf(e.target) !== -1) { return; } - dragenterList.push(e.target); - counter++; - $element.addClass('droppable'); - }); - $element.on('dragleave', function (e) { - e.preventDefault(); - e.stopPropagation(); - var idx = dragenterList.indexOf(e.target); - dragenterList.splice(idx, 1); - counter--; - if (counter <= 0) { - $element.removeClass('droppable'); + var onDrop = function (ev) { + ev.preventDefault(); + $iframe.find('.droppable').removeClass('droppable'); + var data = ev.dataTransfer.getData("text"); + var oldPaths = JSON.parse(data).path; + console.log(oldPaths); + var newPath = $(ev.target).data('path') || $(ev.target).parent('li').data('path'); + if (!oldPaths || !oldPaths.length || !newPath) { return; } + // Call removeElement when trying to move something into the trash + if (newPath[0] === TRASH) { + if (oldPaths.length === 1) { + filesOp.removeElement(oldPaths[0]); + module.displayDirectory([TRASH]); + } else { + Cryptpad.confirm(Messages._getKey('fm_removeSeveralDialog', [oldPaths.length]), function () { + oldPaths.forEach(function (oldPath) { + filesOp.removeElement(oldPath, null, true); + }); + module.displayDirectory([TRASH]); + }); + } + return; } - }); - }; + oldPaths.forEach(function (oldPath) { + filesOp.moveElement(oldPath, newPath, null); + }); + refresh(); + }; - var addFileData = function (element, key, $span, displayTitle) { - if (!isFile(element)) { return; } + var addDragAndDropHandlers = function ($element, path, isFolder, droppable) { + // "dragenter" is fired for an element and all its children + // "dragleave" may be fired when entering a child + // --> We store the number of enter/leave and the element entered and we remove the + // highlighting only when we have left everything + var counter = 0; + var dragenterList = []; + $element.on('dragstart', function (e) { + e.stopPropagation(); + counter = 0; + dragenterList = []; + onDrag(e.originalEvent, path); + }); - // The element with the class '.name' is underlined when the 'li' is hovered - var $name = $('', {'class': 'name', title: key}).text(key); - $span.html(''); - $span.append($name); + // Add drop handlers if we are not in the trash and if the element is a folder + if (!droppable || !isFolder) { return; } - if (typeof(files[FILES_DATA][element]) === "undefined") { - return; - } - var data = files[FILES_DATA][element]; - var $title = $('', {'class': 'title listElement', title: data.title}).text(data.title); - var $adate = $('', {'class': 'date listElement', title: getDate(data.atime)}).text(getDate(data.atime)); - var $cdate = $('', {'class': 'date listElement', title: getDate(data.ctime)}).text(getDate(data.ctime)); - if (displayTitle) { - $span.append($title); - } - $span.append($adate).append($cdate); - }; + $element.on('dragover', function (e) { + e.preventDefault(); + }); + $element.on('drop', function (e) { + onDrop(e.originalEvent); + }); + $element.on('dragenter', function (e) { + e.preventDefault(); + e.stopPropagation(); + if (dragenterList.indexOf(e.target) !== -1) { return; } + dragenterList.push(e.target); + counter++; + $element.addClass('droppable'); + }); + $element.on('dragleave', function (e) { + e.preventDefault(); + e.stopPropagation(); + var idx = dragenterList.indexOf(e.target); + dragenterList.splice(idx, 1); + counter--; + if (counter <= 0) { + $element.removeClass('droppable'); + } + }); + }; - var addFolderData = function (element, key, $span) { - if (!element || !isFolder(element)) { return; } - $span.html(''); - // The element with the class '.name' is underlined when the 'li' is hovered - var sf = hasSubfolder(element); - var files = hasFile(element); - var $name = $('', {'class': 'name', title: key}).text(key); - var $subfolders = $('', {'class': 'folders listElement', title: sf}).text(sf); - var $files = $('', {'class': 'files listElement', title: files}).text(files); - $span.append($name).append($subfolders).append($files); - }; + // In list mode, display metadata from the filesData object + var addFileData = function (element, key, $span, displayTitle) { + if (!filesOp.isFile(element)) { return; } - var createElement = function (path, elPath, root, isFolder) { - // Forbid drag&drop inside the trash - var isTrash = path[0] === TRASH; - var newPath = path.slice(); - var key; - if (isTrash && $.isArray(elPath)) { - key = elPath[0]; - elPath.forEach(function (k) { newPath.push(k); }); - } else { - key = elPath; - newPath.push(key); - } + // The element with the class '.name' is underlined when the 'li' is hovered + var $name = $('', {'class': 'name', title: key}).text(key); + $span.html(''); + $span.append($name); - var element = findElement(files, newPath); - var $icon = $fileIcon.clone(); - var spanClass = 'file-element element'; - if (isFolder) { - spanClass = 'folder-element element'; - $icon = isFolderEmpty(root[key]) ? $folderEmptyIcon.clone() : $folderIcon.clone(); - } - var $name = $('', { 'class': spanClass }).text(key); - if (isFolder) { - addFolderData(element, key, $name); - } else { - addFileData(element, key, $name, true); - } - var $element = $('
  • ', { - draggable: true - }).append($icon).append($name).dblclick(function () { - if (isFolder) { - module.displayDirectory(newPath); + if (typeof(files[FILES_DATA][element]) === "undefined") { return; } - if (isTrash) { return; } - openFile(root[key]); - }); - $element.data('path', newPath); - addDragAndDropHandlers($element, newPath, isFolder, !isTrash); - $element.click(function(e) { - e.stopPropagation(); - onElementClick($element, newPath); - }); - if (!isTrash) { - $element.contextmenu(openDirectoryContextMenu); - } else { - $element.contextmenu(openTrashContextMenu); - } - var isNewFolder = module.newFolder && comparePath(newPath, module.newFolder); - if (isNewFolder) { - window.setTimeout(function() { - displayRenameInput($name, newPath); - }, 500); - delete module.newFolder; + var data = files[FILES_DATA][element]; + var $title = $('', {'class': 'title listElement', title: data.title}).text(data.title); + var $adate = $('', {'class': 'date listElement', title: getDate(data.atime)}).text(getDate(data.atime)); + var $cdate = $('', {'class': 'date listElement', title: getDate(data.ctime)}).text(getDate(data.ctime)); + if (displayTitle) { + $span.append($title); + } + $span.append($adate).append($cdate); }; - return $element; - }; - // Display the full path in the title when displaying a directory from the trash - var getTrashTitle = function (path) { - if (!path[0] || path[0] !== TRASH) { return; } - var title = TRASH_NAME; - for (var i=1; i', {'class': 'name', title: key}).text(key); + var $subfolders = $('', {'class': 'folders listElement', title: sf}).text(sf); + var $files = $('', {'class': 'files listElement', title: files}).text(files); + $span.append($name).append($subfolders).append($files); + }; + + // Create the "li" element corresponding to the file/folder located in "path" + var createElement = function (path, elPath, root, isFolder) { + // Forbid drag&drop inside the trash + var isTrash = path[0] === TRASH; + var newPath = path.slice(); + var key; + if (isTrash && $.isArray(elPath)) { + key = elPath[0]; + elPath.forEach(function (k) { newPath.push(k); }); } else { - title += " / " + path[i]; + key = elPath; + newPath.push(key); } - } - return title; - }; - var createTitle = function (path) { - var isTrash = path[0] === TRASH; - // Create 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; } - else if (name === UNSORTED && path.length === 1) { name = UNSORTED_NAME; } - else if (name === FILES_DATA && path.length === 1) { name = FILES_DATA_NAME; } - else if (isPathInTrash(path)) { name = getTrashTitle(path); } - var $title = $('

    ').text(name); - if (path.length > 1) { - var $parentFolder = $upIcon.clone().addClass("parentFolder") - .click(function() { - var newPath = path.slice(); - newPath.pop(); - if (isTrash && path.length === 4) { - // path = [TRASH, "{DirName}", 0, 'element'] - // --> parent is TRASH - newPath = [TRASH]; - } - module.displayDirectory(newPath); - }); - $title.append($parentFolder); - } - return $title; - }; - - var createViewModeButton = function () { - var $block = $('
    ', { - 'class': 'btn-group topButtonContainer changeViewModeContainer' - }); - - var $listButton = $('