define([ 'jquery', '/bower_components/chainpad-listmap/chainpad-listmap.js', '/bower_components/chainpad-crypto/crypto.js', '/bower_components/textpatcher/TextPatcher.amd.js', 'json.sortify', '/common/cryptpad-common.js', '/common/userObject.js', '/common/toolbar2.js', '/customize/application_config.js', '/common/cryptget.js', '/common/mergeDrive.js' ], function ($, Listmap, Crypto, TextPatcher, JSONSortify, Cryptpad, FO, Toolbar, AppConfig, Get, Merge) { var module = window.MODULE = {}; var Messages = Cryptpad.Messages; //var saveAs = window.saveAs; // Use `$(function () {});` to make sure the html is loaded before doing anything else $(function () { var $iframe = $('#pad-iframe').contents(); var ifrw = $('#pad-iframe')[0].contentWindow; Cryptpad.addLoadingScreen(); var onConnectError = function () { Cryptpad.errorLoadingScreen(Messages.websocketError); }; var APP = window.APP = { editable: false, Cryptpad: Cryptpad, loggedIn: Cryptpad.isLoggedIn(), mobile: function () { return $('body').width() <= 600; } // Menu and content area are not inline-block anymore for mobiles }; var stringify = APP.stringify = function (obj) { return JSONSortify(obj); }; var LIMIT_REFRESH_RATE = 30000; // milliseconds var E_OVER_LIMIT = 'E_OVER_LIMIT'; var SEARCH = "search"; var SEARCH_NAME = Messages.fm_searchName; var ROOT = "root"; var ROOT_NAME = Messages.fm_rootName; var FILES_DATA = Cryptpad.storageKey; var FILES_DATA_NAME = Messages.fm_filesDataName; var TEMPLATE = "template"; var TEMPLATE_NAME = Messages.fm_templateName; var TRASH = "trash"; var TRASH_NAME = Messages.fm_trashName; var LOCALSTORAGE_LAST = "cryptpad-file-lastOpened"; var LOCALSTORAGE_OPENED = "cryptpad-file-openedFolders"; var LOCALSTORAGE_VIEWMODE = "cryptpad-file-viewMode"; var FOLDER_CONTENT_ID = "folderContent"; var config = {}; var DEBUG = config.DEBUG = true; var debug = config.debug = DEBUG ? function () { console.log.apply(console, arguments); } : function () { return; }; var logError = config.logError = function () { console.error.apply(console, arguments); }; var log = config.log = Cryptpad.log; var getLastOpenedFolder = function () { var path; try { path = localStorage[LOCALSTORAGE_LAST] ? JSON.parse(localStorage[LOCALSTORAGE_LAST]) : [ROOT]; } catch (e) { path = [ROOT]; } return path; }; var setLastOpenedFolder = function (path) { if (path[0] === SEARCH) { return; } localStorage[LOCALSTORAGE_LAST] = JSON.stringify(path); }; var initLocalStorage = function () { try { var store = JSON.parse(localStorage[LOCALSTORAGE_OPENED]); if (!$.isArray(store)) { localStorage[LOCALSTORAGE_OPENED] = '[]'; } } catch (e) { localStorage[LOCALSTORAGE_OPENED] = '[]'; } }; var wasFolderOpened = function (path) { var store = JSON.parse(localStorage[LOCALSTORAGE_OPENED]); return store.indexOf(JSON.stringify(path)) !== -1; }; var setFolderOpened = function (path, opened) { var s = JSON.stringify(path); var store = JSON.parse(localStorage[LOCALSTORAGE_OPENED]); if (opened && store.indexOf(s) === -1) { store.push(s); } if (!opened) { var idx = store.indexOf(s); if (idx !== -1) { store.splice(idx, 1); } } localStorage[LOCALSTORAGE_OPENED] = JSON.stringify(store); }; var getViewModeClass = function () { var mode = localStorage[LOCALSTORAGE_VIEWMODE]; if (mode === 'list') { return 'list'; } return 'grid'; }; var getViewMode = function () { return localStorage[LOCALSTORAGE_VIEWMODE] || 'grid'; }; var setViewMode = function (mode) { if (typeof(mode) !== "string") { logError("Incorrect view mode: ", mode); return; } localStorage[LOCALSTORAGE_VIEWMODE] = mode; }; var setSearchCursor = function () { var $input = $iframe.find('#searchInput'); localStorage.searchCursor = $input[0].selectionStart; }; var getSearchCursor = function () { return localStorage.searchCursor || 0; }; /* var now = function () { return new Date().getTime(); }; */ var setEditable = function (state) { APP.editable = state; if (!state) { $iframe.find('#content').addClass('readonly'); $iframe.find('[draggable="true"]').attr('draggable', false); } else { $iframe.find('#content').removeClass('readonly'); $iframe.find('[draggable="false"]').attr('draggable', true); } }; // Icons var $folderIcon = $('', {"class": "fa fa-folder folder icon"}); var $folderEmptyIcon = $folderIcon.clone(); var $folderOpenedIcon = $('', {"class": "fa fa-folder-open folder"}); var $folderOpenedEmptyIcon = $folderOpenedIcon.clone(); var $fileIcon = $('', {"class": "fa fa-file-text-o file icon"}); var $padIcon = $('', {"class": "fa fa-file-word-o file icon"}); var $codeIcon = $('', {"class": "fa fa-file-code-o file icon"}); var $slideIcon = $('', {"class": "fa fa-file-powerpoint-o file icon"}); var $pollIcon = $('', {"class": "fa fa-calendar file icon"}); var $whiteboardIcon = $('', {"class": "fa fa-paint-brush"}); //var $upIcon = $('', {"class": "fa fa-arrow-circle-up"}); var $unsortedIcon = $('', {"class": "fa fa-files-o"}); var $templateIcon = $('', {"class": "fa fa-cubes"}); 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 $sortAscIcon = $('', {"class": "fa fa-angle-up sortasc"}); var $sortDescIcon = $('', {"class": "fa fa-angle-down sortdesc"}); var $closeIcon = $('', {"class": "fa fa-window-close"}); var $backupIcon = $('', {"class": "fa fa-life-ring"}); var history = { isHistoryMode: false, }; var init = function (proxy) { var files = proxy.drive; var isOwnDrive = function () { return Cryptpad.getUserHash() === APP.hash || localStorage.FS_hash === APP.hash; }; var isWorkgroup = function () { return files.workgroup === 1; }; config.workgroup = isWorkgroup(); config.Cryptpad = Cryptpad; var filesOp = FO.init(files, config); filesOp.fixFiles(); var error = filesOp.error; var $tree = $iframe.find("#tree"); var $content = $iframe.find("#content"); var $appContainer = $iframe.find(".app-container"); var $driveToolbar = $iframe.find("#driveToolbar"); var $contextMenu = $iframe.find("#treeContextMenu"); var $contentContextMenu = $iframe.find("#contentContextMenu"); var $defaultContextMenu = $iframe.find("#defaultContextMenu"); var $trashTreeContextMenu = $iframe.find("#trashTreeContextMenu"); var $trashContextMenu = $iframe.find("#trashContextMenu"); // TOOLBAR /* add a "change username" button */ if (!APP.readOnly) { Cryptpad.getLastName(function (err, lastName) { APP.$displayName.text(lastName || Messages.anonymous); }); } else { APP.$displayName.html('' + Messages.readonly + ''); } // FILE MANAGER // _WORKGROUP_ and other people drive : display Documents as main page var currentPath = module.currentPath = isOwnDrive() ? getLastOpenedFolder() : [ROOT]; // Categories dislayed in the menu // _WORKGROUP_ : do not display unsorted var displayedCategories = [ROOT, TRASH, SEARCH]; if (AppConfig.enableTemplates) { displayedCategories.push(TEMPLATE); } if (isWorkgroup()) { displayedCategories = [ROOT, TRASH, SEARCH]; } if (!APP.readOnly) { setEditable(true); } var appStatus = { isReady: true, _onReady: [], onReady: function (handler) { if (appStatus.isReady) { handler(); return; } appStatus._onReady.push(handler); }, ready: function (state) { appStatus.isReady = state; if (state) { appStatus._onReady.forEach(function (h) { h(); }); appStatus._onReady = []; } } }; var findDataHolder = function ($el) { return $el.is('.element-row') ? $el : $el.closest('.element-row'); }; // Selection var sel = {}; var removeSelected = function (keepObj) { $iframe.find('.selected').removeClass("selected"); var $container = $driveToolbar.find('#contextButtonsContainer'); if (!$container.length) { return; } $container.html(''); if (!keepObj) { delete sel.startSelected; delete sel.endSelected; delete sel.oldSelection; } }; sel.refresh = 200; sel.$selectBox = $('
', {'class': 'selectBox'}).appendTo($content); var checkSelected = function () { if (!sel.down) { return; } var pos = sel.pos; var l = $content[0].querySelectorAll('.element:not(.selected):not(.header)'); var p, el; var offset = getViewMode() === "grid" ? 10 : 0; for (var i = 0; i < l.length; i++) { el = l[i]; p = $(el).position(); p.top += offset + $content.scrollTop(); p.left += offset; p.bottom = p.top + $(el).outerHeight(); p.right = p.left + $(el).outerWidth(); if (p.right < pos.left || p.left > pos.right || p.top > pos.bottom || p.bottom < pos.top) { $(el).removeClass('selectedTmp'); } else { $(el).addClass('selectedTmp'); } } }; $content.on('mousedown', function (e) { if (e.which !== 1) { return; } sel.down = true; if (!e.ctrlKey) { removeSelected(); } var rect = e.currentTarget.getBoundingClientRect(); sel.startX = e.clientX - rect.left; sel.startY = e.clientY - rect.top + $content.scrollTop(); sel.$selectBox.show().css({ left: sel.startX + 'px', top: sel.startY + 'px', width: '0px', height: '0px' }); if (sel.move) { return; } sel.move = function (ev) { var rectMove = ev.currentTarget.getBoundingClientRect(), offX = ev.clientX - rectMove.left, offY = ev.clientY - rectMove.top + $content.scrollTop(); var left = sel.startX, top = sel.startY; var width = offX - sel.startX; if (width < 0) { left = Math.max(0, offX); var diffX = left-offX; width = Math.abs(width) - diffX; } var height = offY - sel.startY; if (height < 0) { top = Math.max(0, offY); var diffY = top-offY; height = Math.abs(height) - diffY; } sel.$selectBox.css({ width: width + 'px', left: left + 'px', height: height + 'px', top: top + 'px' }); sel.pos = { top: top, left: left, bottom: top + height, right: left + width }; var diffT = sel.update ? +new Date() - sel.update : sel.refresh; if (diffT < sel.refresh) { if (!sel.to) { sel.to = window.setTimeout(function () { sel.update = +new Date(); checkSelected(); sel.to = undefined; }, (sel.refresh - diffT)); } return; } sel.update = +new Date(); checkSelected(); }; $content.mousemove(sel.move); }); $content.on('mouseup', function (e) { if (e.which !== 1) { return; } sel.down = false; sel.$selectBox.hide(); $content.off('mousemove', sel.move); delete sel.move; $content.find('.selectedTmp').removeClass('selectedTmp').addClass('selected'); }); $(ifrw).keydown(function (e) { var $searchBar = $tree.find('#searchInput'); if ($searchBar.is(':focus') && $searchBar.val()) { return; } var $elements = $content.find('.element:not(.header)'); var ev = {}; if (e.ctrlKey) { ev.ctrlKey = true; } if (e.shiftKey) { ev.shiftKey = true; } var click = function (el) { module.onElementClick(ev, $(el)); }; // Enter if (e.which === 13) { var $allSelected = $content.find('.element.selected'); if ($allSelected.length === 1) { // Open the folder or the file $allSelected.dblclick(); return; } // If more than one, open only the files var $select = $content.find('.file-element.selected'); $select.each(function (idx, el) { $(el).dblclick(); }); return; } // [Left, Up, Right, Down] if ([37, 38, 39, 40].indexOf(e.which) === -1) { return; } var $selection = $content.find('.element.selected'); if ($selection.length === 0) { return void click($elements.first()[0]); } var lastIndex = typeof sel.endSelected === "number" ? sel.endSelected : typeof sel.startSelected === "number" ? sel.startSelected : $elements.index($selection.last()[0]); var length = $elements.length; if (length === 0) { return; } // List mode if (getViewMode() === "list") { if (e.which === 40) { click($elements.get(Math.min(lastIndex+1, length -1))); } if (e.which === 38) { click($elements.get(Math.max(lastIndex-1, 0))); } return; } // Icon mode // Get the vertical and horizontal position of lastIndex // Filter all the elements to get those in the same line/column var pos = $($elements.get(0)).position(); var $line = $elements.filter(function (idx, el) { return $(el).position().top === pos.top; }); var cols = $line.length; var lines = Math.ceil(length/cols); var lastPos = { l : Math.floor(lastIndex/cols), c : lastIndex - Math.floor(lastIndex/cols)*cols }; if (e.which === 37) { if (lastPos.c === 0) { return; } click($elements.get(Math.max(lastIndex-1, 0))); return; } if (e.which === 38) { if (lastPos.l === 0) { return; } click($elements.get(Math.max(lastIndex-cols, 0))); return; } if (e.which === 39) { if (lastPos.c === cols-1) { return; } click($elements.get(Math.min(lastIndex+1, length-1))); return; } if (e.which === 40) { if (lastPos.l === lines-1) { return; } click($elements.get(Math.min(lastIndex+cols, length-1))); return; } }); var removeInput = function (cancel) { if (!cancel && $iframe.find('.element-row > input').length === 1) { var $input = $iframe.find('.element-row > input'); filesOp.rename($input.data('path'), $input.val(), APP.refresh); } $iframe.find('.element-row > input').remove(); $iframe.find('.element-row > span:hidden').removeAttr('style'); }; 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(); } } catch (e) { error("Unable to format that string to a date with .toLocaleString", sDate, e); } return ret; }; var openFile = function (fileEl, name) { if (name) { sessionStorage[Cryptpad.newPadNameKey] = name; } window.open(fileEl); delete sessionStorage[Cryptpad.newPadNameKey]; }; var refresh = APP.refresh = function () { module.displayDirectory(currentPath); }; // Replace a file/folder name by an input to change its value var displayRenameInput = function ($element, path) { // NOTE: setTimeout(f, 0) otherwise the "rename" button in the toolbar is not working window.setTimeout(function () { if (!APP.editable) { return; } if (!path || path.length < 2) { logError("Renaming a top level element (root, trash or filesData) is forbidden."); return; } removeInput(); removeSelected(); var $name = $element.find('.name'); if (!$name.length) { $name = $element.find('> .element'); } $name.hide(); var name = path[path.length - 1]; var $input = $('', { placeholder: name, value: name }).data('path', path); $input.on('keyup', function (e) { if (e.which === 13) { removeInput(true); filesOp.rename(path, $input.val(), refresh); return; } if (e.which === 27) { removeInput(true); } }); //$element.parent().append($input); $name.after($input); $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 (e) { e.stopPropagation(); $input.parents('.element-row').attr("draggable", false); }); $input.on('mouseup', function (e) { e.stopPropagation(); $input.parents('.element-row').attr("draggable", true); }); },0); }; var filterContextMenu = function ($menu, paths) { //var path = $element.data('path'); if (!paths || paths.length === 0) { console.error('no paths'); } var hide = []; var hasFolder = false; paths.forEach(function (p) { var path = p.path; var $element = p.element; if (path.length === 1) { hide.push($menu.find('a.rename')); hide.push($menu.find('a.delete')); } if (!APP.editable) { hide.push($menu.find('a.editable')); } if (!isOwnDrive()) { hide.push($menu.find('a.own')); } if ($element.is('.file-element')) { // No folder in files hide.push($menu.find('a.newfolder')); if ($element.is('.readonly')) { // Keep only open readonly hide.push($menu.find('a.open')); } else if ($element.is('.noreadonly')) { // Keep only open readonly hide.push($menu.find('a.open_ro')); } } else { if (hasFolder) { // More than 1 folder selected: cannot create a new subfolder hide.push($menu.find('a.newfolder')); } hasFolder = true; hide.push($menu.find('a.open_ro')); // TODO: folder properties in the future? hide.push($menu.find('a.properties')); } // If we're in the trash, hide restore and properties for non-root elements if ($menu.find('a.restore').length && path && path.length > 4) { hide.push($menu.find('a.restore')); hide.push($menu.find('a.properties')); } }); if (paths.length > 1) { hide.push($menu.find('a.restore')); hide.push($menu.find('a.properties')); hide.push($menu.find('a.rename')); } if (hasFolder && paths.length > 1) { // Cannot open multiple folders hide.push($menu.find('a.open')); } return hide; }; var updatePathSize = function () { var $context = $iframe.find('#contextButtonsContainer'); var l = 50; if ($context.length) { l += $context.width() || 0; } $driveToolbar.find('.path').css('max-width', 'calc(100vw - '+$tree.width()+'px - '+l+'px)'); }; var getSelectedPaths = function ($element) { var paths = []; if ($iframe.find('.selected').length > 1) { var $selected = $iframe.find('.selected'); $selected.each(function (idx, elmt) { var ePath = $(elmt).data('path'); if (ePath) { paths.push({ path: ePath, element: $(elmt) }); } }); } if (!paths.length) { var path = $element.data('path'); if (!path) { return false; } paths.push({ path: path, element: $element }); } return paths; }; var updateContextButton = function () { var $li = $content.find('.selected'); if ($li.length === 0) { $li = findDataHolder($tree.find('.active')); } var $button = $driveToolbar.find('#contextButton'); if ($button.length) { // mobile if ($li.length !== 1 || !$._data($li[0], 'events').contextmenu || $._data($li[0], 'events').contextmenu.length === 0) { $button.hide(); return; } $button.show(); $button.css({ background: '#000' }); window.setTimeout(function () { $button.css({ background: '' }); }, 500); return; } // Non mobile var $container = $driveToolbar.find('#contextButtonsContainer'); if (!$container.length) { return; } $container.html(''); var $element = $li.length === 1 ? $li : $($li[0]); var paths = getSelectedPaths($element); var $menu = $element.data('context'); if (!$menu) { return; } //var actions = []; var $actions = $menu.find('a'); var toHide = filterContextMenu($menu, paths); $actions = $actions.filter(function (i, el) { for (var j = 0; j < toHide.length; j++) { if ($(el).is(toHide[j])) { return false; } } return true; }); $actions.each(function (i, el) { var $a = $('