diff --git a/www/file/inner.html b/www/file/inner.html index 1a4772167..ea8ad82db 100644 --- a/www/file/inner.html +++ b/www/file/inner.html @@ -3,52 +3,22 @@ + + -
+ diff --git a/www/file/main.js b/www/file/main.js index 05d6fba22..e4303ec59 100644 --- a/www/file/main.js +++ b/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 = $('', { "class": "fa fa-folder folder", style: "font-family: FontAwesome" }); + var $folderEmptyIcon = $('', { + "class": "fa fa-folder-o folder", + style: "font-family: FontAwesome" + }); + var $folderOpenedIcon = $('', { + "class": "fa fa-folder-open folder", + style: "font-family: FontAwesome" + }); + var $folderOpenedEmptyIcon = $('', { + "class": "fa fa-folder-open-o folder", + style: "font-family: FontAwesome" + }); var $fileIcon = $('', { "class": "fa fa-file file", style: "font-family: FontAwesome" }); + var $upIcon = $('', { + "class": "fa fa-arrow-circle-up", + style: "font-family: FontAwesome" + }); + var $trashIcon = $('', { + "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 = $('', { + 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 = $('

').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 = $('
', {id: "folderContent"}); + var $list = $('
    ').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 = $('', { 'class': 'folder-element element' }).text(key); + var $element = $('
  • ', { + 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 = $('', { 'class': 'file-element element' }).text(key); + var $element = $('
  • ', { + 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 = $('', { 'class': 'folder-element' }).text(name).prepend($icon) + .click(function () { + displayDirectory(path); + }); + var $element = $('
  • ', { + 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 = $('
      ').append($rootElement).appendTo($container); + $container = $rootElement; + } + + // Display root content + var $list = $('
        ').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 = $('', { + 'class': 'tree-trash' + }).text(TRASH_NAME).prepend($trashIcon.clone()) + .click(function () { + displayDirectory(path); + }); + $trash.data('path', ['trash']); + var $trashElement = $('
      • ').append($trash); + $trashElement.on('dragover', function (e) { + e.preventDefault(); + }); + $trashElement.on('drop', function (e) { + onDrop(e.originalEvent); + }); + var $trashList = $('
          ').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 = $('

          ').text(name); var $dirContent = $('
          ', {id: "folderContent"}); @@ -63,8 +477,8 @@ define([ // display sub directories Object.keys(root).forEach(function (key) { if (typeof(root[key]) === "string") { return; } - var $name = $('').text(key).prepend($folderIcon.clone()); - var $element = $('
        • ').append($name).click(function () { + var $name = $('', { 'class': 'folder-element' }).text(key).prepend($folderIcon.clone()); + var $element = $('
        • ').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 = $('').text(key).prepend($fileIcon.clone()); - var $element = $('
        • ').append($name).click(function () { + var $name = $('', { 'class': 'file-element' }).text(key).prepend($fileIcon.clone()); + var $element = $('
        • ').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 = $('
            ').appendTo($container); Object.keys(root).forEach(function (key) { // Do not display files in the menu if (typeof(root[key]) === "string") { return; } - var $name = $('').text(key).click(function () { - displayDirectory(key, root[key]); - }); + var $icon = Object.keys(root[key]).length === 0 ? $folderEmptyIcon.clone() : $folderIcon.clone(); + var $name = $('', { 'class': 'folder-element' }).text(key).prepend($icon) + .click(function () { + displayDirectory(key, root[key]); + }); var $element = $('
          • ').append($name); $element.appendTo($list); createTree(root[key], $element[0]); }); - }; - createTree(files.root, $tree); + };*/ });