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 = $('', { "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 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"}); var $list = $('
            ').appendTo($dirContent); // display sub directories Object.keys(root).forEach(function (key) { if (typeof(root[key]) === "string") { return; } var $name = $('', { 'class': 'folder-element' }).text(key).prepend($folderIcon.clone()); var $element = $('
          • ').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 = $('', { 'class': 'file-element' }).text(key).prepend($fileIcon.clone()); var $element = $('
          • ').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 = $('
              ').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 = $('', { '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]); }); };*/ });