|
|
|
require.config({ paths: { 'json.sortify': '/bower_components/json.sortify/dist/JSON.sortify' } });
|
|
|
|
define([
|
|
|
|
'/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/toolbar.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 $ = window.jQuery;
|
|
|
|
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 (info) {
|
|
|
|
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 SEARCH = "search";
|
|
|
|
var SEARCH_NAME = Messages.fm_searchName;
|
|
|
|
var ROOT = "root";
|
|
|
|
var ROOT_NAME = Messages.fm_rootName;
|
|
|
|
var UNSORTED = "unsorted";
|
|
|
|
var UNSORTED_NAME = Messages.fm_unsortedName;
|
|
|
|
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]) : [UNSORTED];
|
|
|
|
} catch (e) {
|
|
|
|
path = [UNSORTED];
|
|
|
|
}
|
|
|
|
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 = $('<span>', {"class": "fa fa-folder folder icon"});
|
|
|
|
var $folderEmptyIcon = $folderIcon.clone();
|
|
|
|
var $folderOpenedIcon = $('<span>', {"class": "fa fa-folder-open folder"});
|
|
|
|
var $folderOpenedEmptyIcon = $folderOpenedIcon.clone();
|
|
|
|
var $fileIcon = $('<span>', {"class": "fa fa-file-text-o file icon"});
|
|
|
|
var $padIcon = $('<span>', {"class": "fa fa-file-word-o file icon"});
|
|
|
|
var $codeIcon = $('<span>', {"class": "fa fa-file-code-o file icon"});
|
|
|
|
var $slideIcon = $('<span>', {"class": "fa fa-file-powerpoint-o file icon"});
|
|
|
|
var $pollIcon = $('<span>', {"class": "fa fa-calendar file icon"});
|
|
|
|
var $whiteboardIcon = $('<span>', {"class": "fa fa-paint-brush"});
|
|
|
|
var $upIcon = $('<span>', {"class": "fa fa-arrow-circle-up"});
|
|
|
|
var $unsortedIcon = $('<span>', {"class": "fa fa-files-o"});
|
|
|
|
var $templateIcon = $('<span>', {"class": "fa fa-cubes"});
|
|
|
|
var $trashIcon = $('<span>', {"class": "fa fa-trash"});
|
|
|
|
var $trashEmptyIcon = $('<span>', {"class": "fa fa-trash-o"});
|
|
|
|
var $collapseIcon = $('<span>', {"class": "fa fa-minus-square-o expcol"});
|
|
|
|
var $expandIcon = $('<span>', {"class": "fa fa-plus-square-o expcol"});
|
|
|
|
var $listIcon = $('<span>', {"class": "fa fa-list"});
|
|
|
|
var $gridIcon = $('<span>', {"class": "fa fa-th"});
|
|
|
|
var $sortAscIcon = $('<span>', {"class": "fa fa-angle-up sortasc"});
|
|
|
|
var $sortDescIcon = $('<span>', {"class": "fa fa-angle-down sortdesc"});
|
|
|
|
var $closeIcon = $('<span>', {"class": "fa fa-window-close"});
|
|
|
|
var $backupIcon = $('<span>', {"class": "fa fa-life-ring"});
|
|
|
|
|
|
|
|
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('<span class="' + Toolbar.constants.readonly + '">' + Messages.readonly + '</span>');
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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, UNSORTED, TRASH, SEARCH];
|
|
|
|
if (AppConfig.enableTemplates) { displayedCategories.push(TEMPLATE); }
|
|
|
|
if (isWorkgroup()) { displayedCategories = [ROOT, TRASH, SEARCH]; }
|
|
|
|
|
|
|
|
var lastSelectTime;
|
|
|
|
var selectedElement;
|
|
|
|
|
|
|
|
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');
|
|
|
|
};
|
|
|
|
|
|
|
|
var removeSelected = function () {
|
|
|
|
$iframe.find('.selected').removeClass("selected");
|
|
|
|
var $container = $driveToolbar.find('#contextButtonsContainer');
|
|
|
|
if (!$container.length) { return; }
|
|
|
|
$container.html('');
|
|
|
|
};
|
|
|
|
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 = $('<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, i) {
|
|
|
|
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 = $('<button>', {'class': 'element'});
|
|
|
|
if ($(el).attr('data-icon')) {
|
|
|
|
$a.addClass('fa').addClass($(el).attr('data-icon'));
|
|
|
|
$a.attr('title', $(el).text());
|
|
|
|
} else {
|
|
|
|
$a.text($(el).text());
|
|
|
|
}
|
|
|
|
$(el).data('paths', paths);
|
|
|
|
//$(el).data('path', path);
|
|
|
|
//:$(el).data('element', $element);
|
|
|
|
$container.append($a);
|
|
|
|
$a.click(function() { $(el).click(); });
|
|
|
|
});
|
|
|
|
updatePathSize();
|
|
|
|
};
|
|
|
|
|
|
|
|
// Add the "selected" class to the "li" corresponding to the clicked element
|
|
|
|
var onElementClick = function (e, $element, path) {
|
|
|
|
// If "Ctrl" is pressed, do not remove the current selection
|
|
|
|
removeInput();
|
|
|
|
$element = findDataHolder($element);
|
|
|
|
// If we're selecting a new element with the left click, hide the menu
|
|
|
|
if (e) { module.hideMenu(); }
|
|
|
|
// Remove the selection if we don't hold ctrl key or if we are right-clicking
|
|
|
|
if (!e || !e.ctrlKey) {
|
|
|
|
removeSelected();
|
|
|
|
}
|
|
|
|
if (!$element.length) {
|
|
|
|
log(Messages.fm_selectError);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Add the selected class to the clicked / right-clicked element
|
|
|
|
// Remove the class if it already has it (cannot happen if the user is not holding ctrl key)
|
|
|
|
if (!$element.hasClass("selected")) {
|
|
|
|
$element.addClass("selected");
|
|
|
|
lastSelectTime = now();
|
|
|
|
} else {
|
|
|
|
$element.removeClass("selected");
|
|
|
|
}
|
|
|
|
updateContextButton();
|
|
|
|
};
|
|
|
|
|
|
|
|
// Open the selected context menu on the closest "li" element
|
|
|
|
var openContextMenu = function (e, $menu) {
|
|
|
|
module.hideMenu();
|
|
|
|
e.stopPropagation();
|
|
|
|
|
|
|
|
var $element = findDataHolder($(e.target));
|
|
|
|
if (!$element.length) {
|
|
|
|
logError("Unable to locate the .element tag", e.target);
|
|
|
|
$menu.hide();
|
|
|
|
log(Messages.fm_contextMenuError);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$element.hasClass('selected')) { //paths.length === 1) {
|
|
|
|
onElementClick(undefined, $element);
|
|
|
|
}
|
|
|
|
|
|
|
|
var paths = getSelectedPaths($element);
|
|
|
|
|
|
|
|
var toHide = filterContextMenu($menu, paths);
|
|
|
|
toHide.forEach(function ($a) {
|
|
|
|
$a.parent('li').hide();
|
|
|
|
});
|
|
|
|
|
|
|
|
$menu.css({
|
|
|
|
display: "block",
|
|
|
|
left: e.pageX,
|
|
|
|
top: e.pageY
|
|
|
|
});
|
|
|
|
|
|
|
|
if ($menu.find('li:visible').length === 0) {
|
|
|
|
debug("No visible element in the context menu. Abort.");
|
|
|
|
$menu.hide();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
$menu.find('a').data('paths', paths);
|
|
|
|
//$menu.find('a').data('path', path);
|
|
|
|
//$menu.find('a').data('element', $element);
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
|
|
|
var openDirectoryContextMenu = function (e) {
|
|
|
|
$contextMenu.find('li').show();
|
|
|
|
openContextMenu(e, $contextMenu);
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
|
|
|
var openDefaultContextMenu = function (e) {
|
|
|
|
$defaultContextMenu.find('li').show();
|
|
|
|
openContextMenu(e, $defaultContextMenu);
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
|
|
|
var openTrashTreeContextMenu = function (e) {
|
|
|
|
removeSelected();
|
|
|
|
$trashTreeContextMenu.find('li').show();
|
|
|
|
openContextMenu(e, $trashTreeContextMenu);
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
|
|
|
var openTrashContextMenu = function (e) {
|
|
|
|
var path = findDataHolder($(e.target)).data('path');
|
|
|
|
if (!path) { return; }
|
|
|
|
$trashContextMenu.find('li').show();
|
|
|
|
openContextMenu(e, $trashContextMenu);
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
|
|
|
var openContentContextMenu = function (e) {
|
|
|
|
module.hideMenu();
|
|
|
|
e.stopPropagation();
|
|
|
|
var path = $(e.target).closest('#' + FOLDER_CONTENT_ID).data('path');
|
|
|
|
if (!path) { return; }
|
|
|
|
var $menu = $contentContextMenu;
|
|
|
|
removeSelected();
|
|
|
|
|
|
|
|
if (!APP.editable) {
|
|
|
|
$menu.find('a.editable').parent('li').hide();
|
|
|
|
}
|
|
|
|
if (!isOwnDrive()) {
|
|
|
|
$menu.find('a.own').parent('li').hide();
|
|
|
|
}
|
|
|
|
|
|
|
|
$menu.find('[data-type]').each(function (idx, el) {
|
|
|
|
if (AppConfig.availablePadTypes.indexOf($(el).attr('data-type')) === -1) {
|
|
|
|
$(el).hide();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
$menu.css({
|
|
|
|
display: "block",
|
|
|
|
left: e.pageX,
|
|
|
|
top: e.pageY
|
|
|
|
});
|
|
|
|
|
|
|
|
if ($menu.find('li:visible').length === 0) {
|
|
|
|
debug("No visible element in the context menu. Abort.");
|
|
|
|
$menu.hide();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
$menu.find('a').data('path', path);
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
|
|
|
var getElementName = function (path) {
|
|
|
|
// Trash root
|
|
|
|
if (filesOp.isInTrashRoot(path)) { return path[0]; }
|
|
|
|
// Root or trash
|
|
|
|
if (filesOp.isPathIn(path, [ROOT, TRASH])) { return path[path.length - 1]; }
|
|
|
|
// Unsorted or template
|
|
|
|
if (filesOp.isPathIn(path, ['hrefArray'])) {
|
|
|
|
var file = filesOp.find(path);
|
|
|
|
if (filesOp.isFile(file) && filesOp.getTitle(file)) {
|
|
|
|
return filesOp.getTitle(file);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// default
|
|
|
|
return "???";
|
|
|
|
};
|
|
|
|
// filesOp.moveElements is able to move several paths to a new location, including
|
|
|
|
// the Trash or the "Unsorted files" folder
|
|
|
|
var moveElements = function (paths, newPath, force, cb) {
|
|
|
|
if (!APP.editable) { return; }
|
|
|
|
var andThen = function () {
|
|
|
|
filesOp.move(paths, newPath, cb);
|
|
|
|
};
|
|
|
|
// Cancel drag&drop from TRASH to TRASH
|
|
|
|
if (filesOp.isPathIn(newPath, [TRASH]) && paths.length && paths[0][0] === TRASH) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// "force" is currently unused but may be configurable by user
|
|
|
|
if (newPath[0] !== TRASH || force) {
|
|
|
|
andThen();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var msg = Messages._getKey('fm_removeSeveralDialog', [paths.length]);
|
|
|
|
if (paths.length === 1) {
|
|
|
|
var path = paths[0];
|
|
|
|
var name = path[0] === UNSORTED ? filesOp.getTitle(filesOp.find(path)) : path[path.length - 1];
|
|
|
|
msg = Messages._getKey('fm_removeDialog', [name]);
|
|
|
|
}
|
|
|
|
Cryptpad.confirm(msg, function (res) {
|
|
|
|
$(ifrw).focus();
|
|
|
|
if (!res) { return; }
|
|
|
|
andThen();
|
|
|
|
});
|
|
|
|
};
|
|
|
|
// 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 = findDataHolder($(ev.target));
|
|
|
|
if ($element.hasClass('selected')) {
|
|
|
|
var $selected = $iframe.find('.selected');
|
|
|
|
$selected.each(function (idx, elmt) {
|
|
|
|
var ePath = $(elmt).data('path');
|
|
|
|
if (ePath) {
|
|
|
|
var val = filesOp.find(ePath);
|
|
|
|
if (!val) { return; } // Error? A ".selected" element in not in the object
|
|
|
|
paths.push({
|
|
|
|
path: ePath,
|
|
|
|
value: {
|
|
|
|
name: getElementName(ePath),
|
|
|
|
el: val
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
removeSelected();
|
|
|
|
$element.addClass('selected');
|
|
|
|
var val = filesOp.find(path);
|
|
|
|
if (!val) { return; } // The element in not in the object
|
|
|
|
paths = [{
|
|
|
|
path: path,
|
|
|
|
value: {
|
|
|
|
name: getElementName(path),
|
|
|
|
el: val
|
|
|
|
}
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
var data = {
|
|
|
|
'path': paths
|
|
|
|
};
|
|
|
|
ev.dataTransfer.setData("text", stringify(data));
|
|
|
|
};
|
|
|
|
|
|
|
|
var onDrop = function (ev) {
|
|
|
|
ev.preventDefault();
|
|
|
|
$iframe.find('.droppable').removeClass('droppable');
|
|
|
|
var data = ev.dataTransfer.getData("text");
|
|
|
|
var oldPaths = JSON.parse(data).path;
|
|
|
|
if (!oldPaths) { return; }
|
|
|
|
|
|
|
|
// Dropped elements can be moved from the same file manager or imported from another one.
|
|
|
|
// A moved element should be removed from its previous location
|
|
|
|
var movedPaths = [];
|
|
|
|
var importedElements = [];
|
|
|
|
oldPaths.forEach(function (p) {
|
|
|
|
var el = filesOp.find(p.path);
|
|
|
|
if (el && (stringify(el) === stringify(p.value.el) || !p.value || !p.value.el)) {
|
|
|
|
movedPaths.push(p.path);
|
|
|
|
} else {
|
|
|
|
importedElements.push(p.value);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
var $el = findDataHolder($(ev.target));
|
|
|
|
var newPath = $el.data('path');
|
|
|
|
if (!newPath) { return; }
|
|
|
|
if (movedPaths && movedPaths.length) {
|
|
|
|
moveElements(movedPaths, newPath, null, refresh);
|
|
|
|
}
|
|
|
|
if (importedElements && importedElements.length) {
|
|
|
|
// TODO workgroup
|
|
|
|
//filesOp.importElements(importedElements, newPath, refresh);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
var addDragAndDropHandlers = function ($element, path, isFolder, droppable) {
|
|
|
|
if (!APP.editable) { return; }
|
|
|
|
// "dragenter" is fired for an element and all its children
|
|
|
|
// "dragleave" may be fired when entering a child
|
|
|
|
// --> we use pointer-events: none in CSS, but we still need a counter to avoid some issues
|
|
|
|
// --> 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;
|
|
|
|
$element.on('dragstart', function (e) {
|
|
|
|
e.stopPropagation();
|
|
|
|
counter = 0;
|
|
|
|
onDrag(e.originalEvent, path);
|
|
|
|
});
|
|
|
|
|
|
|
|
$element.on('mousedown', function (e) {
|
|
|
|
e.stopPropagation();
|
|
|
|
});
|
|
|
|
|
|
|
|
// 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();
|
|
|
|
counter++;
|
|
|
|
$element.addClass('droppable');
|
|
|
|
});
|
|
|
|
$element.on('dragleave', function (e) {
|
|
|
|
e.preventDefault();
|
|
|
|
e.stopPropagation();
|
|
|
|
counter--;
|
|
|
|
if (counter <= 0) {
|
|
|
|
counter = 0;
|
|
|
|
$element.removeClass('droppable');
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
// In list mode, display metadata from the filesData object
|
|
|
|
// _WORKGROUP_ : Do not display title, atime and ctime columns since we don't have files data
|
|
|
|
var addFileData = function (element, key, $span, displayTitle) {
|
|
|
|
if (!filesOp.isFile(element)) { return; }
|
|
|
|
|
|
|
|
// The element with the class '.name' is underlined when the 'li' is hovered
|
|
|
|
var $name = $('<span>', {'class': 'name', title: key}).text(key);
|
|
|
|
$span.html('');
|
|
|
|
$span.append($name);
|
|
|
|
|
|
|
|
if (!filesOp.getFileData(element)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var hrefData = Cryptpad.parsePadUrl(element);
|
|
|
|
var data = filesOp.getFileData(element);
|
|
|
|
var type = Messages.type[hrefData.type] || hrefData.type;
|
|
|
|
var $title = $('<span>', {'class': 'title listElement', title: data.title}).text(data.title);
|
|
|
|
var $type = $('<span>', {'class': 'type listElement', title: type}).text(type);
|
|
|
|
if (hrefData.hash && Cryptpad.parseHash(hrefData.hash) && Cryptpad.parseHash(hrefData.hash).mode === 'view') {
|
|
|
|
$type.append(' (' + Messages.readonly+ ')');
|
|
|
|
}
|
|
|
|
var $adate = $('<span>', {'class': 'atime listElement', title: getDate(data.atime)}).text(getDate(data.atime));
|
|
|
|
var $cdate = $('<span>', {'class': 'ctime listElement', title: getDate(data.ctime)}).text(getDate(data.ctime));
|
|
|
|
if (displayTitle && !isWorkgroup()) {
|
|
|
|
$span.append($title);
|
|
|
|
}
|
|
|
|
$span.append($type);
|
|
|
|
if (!isWorkgroup()) {
|
|
|
|
$span.append($adate).append($cdate);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
var addFolderData = function (element, key, $span) {
|
|
|
|
if (!element || !filesOp.isFolder(element)) { return; }
|
|
|
|
$span.html('');
|
|
|
|
// The element with the class '.name' is underlined when the 'li' is hovered
|
|
|
|
var sf = filesOp.hasSubfolder(element);
|
|
|
|
var files = filesOp.hasFile(element);
|
|
|
|
var $name = $('<span>', {'class': 'name', title: key}).text(key);
|
|
|
|
var $subfolders = $('<span>', {'class': 'folders listElement', title: sf}).text(sf);
|
|
|
|
var $files = $('<span>', {'class': 'files listElement', title: files}).text(files);
|
|
|
|
$span.append($name).append($subfolders).append($files);
|
|
|
|
};
|
|
|
|
|
|
|
|
var getFileIcon = function (href) {
|
|
|
|
var $icon = $fileIcon.clone();
|
|
|
|
|
|
|
|
if (href.indexOf('/pad/') !== -1) { $icon = $padIcon.clone(); }
|
|
|
|
else if (href.indexOf('/code/') !== -1) { $icon = $codeIcon.clone(); }
|
|
|
|
else if (href.indexOf('/slide/') !== -1) { $icon = $slideIcon.clone(); }
|
|
|
|
else if (href.indexOf('/poll/') !== -1) { $icon = $pollIcon.clone(); }
|
|
|
|
else if (href.indexOf('/whiteboard/') !== -1) { $icon = $whiteboardIcon.clone(); }
|
|
|
|
|
|
|
|
return $icon;
|
|
|
|
};
|
|
|
|
|
|
|
|
// 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 {
|
|
|
|
key = elPath;
|
|
|
|
newPath.push(key);
|
|
|
|
}
|
|
|
|
|
|
|
|
var element = filesOp.find(newPath);
|
|
|
|
var $icon = !isFolder ? getFileIcon(element) : undefined;
|
|
|
|
var ro = filesOp.isReadOnlyFile(element);
|
|
|
|
// ro undefined mens it's an old hash which doesn't support read-only
|
|
|
|
var roClass = typeof(ro) === 'undefined' ? ' noreadonly' : ro ? ' readonly' : '';
|
|
|
|
var liClass = 'file-item file-element element' + roClass;
|
|
|
|
if (isFolder) {
|
|
|
|
liClass = 'folder-item folder-element element';
|
|
|
|
$icon = filesOp.isFolderEmpty(root[key]) ? $folderEmptyIcon.clone() : $folderIcon.clone();
|
|
|
|
}
|
|
|
|
var $element = $('<li>', {
|
|
|
|
draggable: true,
|
|
|
|
'class': 'element-row'
|
|
|
|
});
|
|
|
|
if (isFolder) {
|
|
|
|
addFolderData(element, key, $element);
|
|
|
|
} else {
|
|
|
|
addFileData(element, key, $element, true);
|
|
|
|
}
|
|
|
|
$element.prepend($icon).dblclick(function () {
|
|
|
|
if (isFolder) {
|
|
|
|
module.displayDirectory(newPath);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (isTrash) { return; }
|
|
|
|
openFile(root[key], key);
|
|
|
|
});
|
|
|
|
$element.addClass(liClass);
|
|
|
|
$element.data('path', newPath);
|
|
|
|
addDragAndDropHandlers($element, newPath, isFolder, !isTrash);
|
|
|
|
$element.click(function(e) {
|
|
|
|
e.stopPropagation();
|
|
|
|
onElementClick(e, $element, newPath);
|
|
|
|
});
|
|
|
|
if (!isTrash) {
|
|
|
|
$element.contextmenu(openDirectoryContextMenu);
|
|
|
|
$element.data('context', $contextMenu);
|
|
|
|
} else {
|
|
|
|
$element.contextmenu(openTrashContextMenu);
|
|
|
|
$element.data('context', $trashContextMenu);
|
|
|
|
}
|
|
|
|
var isNewFolder = module.newFolder && filesOp.comparePath(newPath, module.newFolder);
|
|
|
|
if (isNewFolder) {
|
|
|
|
appStatus.onReady(function () {
|
|
|
|
window.setTimeout(function () { displayRenameInput($element, newPath); }, 0);
|
|
|
|
});
|
|
|
|
delete module.newFolder;
|
|
|
|
}
|
|
|
|
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<path.length; i++) {
|
|
|
|
if (i === 3 && path[i] === 'element') {}
|
|
|
|
else if (i === 2 && parseInt(path[i]) === path[i]) {
|
|
|
|
if (path[i] !== 0) {
|
|
|
|
title += " [" + path[i] + "]";
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
title += " / " + path[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return title;
|
|
|
|
};
|
|
|
|
|
|
|
|
var getPrettyName = function (name) {
|
|
|
|
var pName;
|
|
|
|
switch (name) {
|
|
|
|
case ROOT: pName = ROOT_NAME; break;
|
|
|
|
case TRASH: pName = TRASH_NAME; break;
|
|
|
|
case UNSORTED: pName = UNSORTED_NAME; break;
|
|
|
|
case TEMPLATE: pName = TEMPLATE_NAME; break;
|
|
|
|
case FILES_DATA: pName = FILES_DATA_NAME; break;
|
|
|
|
case SEARCH: pName = SEARCH_NAME; break;
|
|
|
|
default: pName = name;
|
|
|
|
}
|
|
|
|
return pName;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Create the title block with the "parent folder" button
|
|
|
|
var createTitle = function (path, noStyle) {
|
|
|
|
if (!path || path.length === 0) { return; }
|
|
|
|
var isTrash = filesOp.isPathIn(path, [TRASH]);
|
|
|
|
var $title = $('<span>', {'class': 'path unselectable'});
|
|
|
|
if (APP.mobile()) {
|
|
|
|
return $title;
|
|
|
|
}
|
|
|
|
path = path[0] === SEARCH ? path.slice(0,1) : path;
|
|
|
|
path.forEach(function (p, idx) {
|
|
|
|
if (isTrash && [2,3].indexOf(idx) !== -1) { return; }
|
|
|
|
|
|
|
|
var $span = $('<span>', {'class': 'element'});
|
|
|
|
if (idx < path.length - 1) {
|
|
|
|
if (!noStyle) {
|
|
|
|
$span.addClass('clickable');
|
|
|
|
$span.click(function (e) {
|
|
|
|
var sliceEnd = idx + 1;
|
|
|
|
if (isTrash && idx === 1) { sliceEnd = 4; } // Make sure we don't show the index or 'element' and 'path'
|
|
|
|
module.displayDirectory(path.slice(0, sliceEnd));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var name = p;
|
|
|
|
if (idx === 0) { name = getPrettyName(p); }
|
|
|
|
else { $title.append(' > '); }
|
|
|
|
|
|
|
|
$span.text(name).appendTo($title);
|
|
|
|
});
|
|
|
|
return $title;
|
|
|
|
};
|
|
|
|
|
|
|
|
var createInfoBox = function (path) {
|
|
|
|
var $box = $('<div>', {'class': 'info-box'});
|
|
|
|
var msg;
|
|
|
|
switch (path[0]) {
|
|
|
|
case ROOT:
|
|
|
|
msg = Messages.fm_info_root;
|
|
|
|
break;
|
|
|
|
case UNSORTED:
|
|
|
|
msg = Messages.fm_info_unsorted;
|
|
|
|
break;
|
|
|
|
case TEMPLATE:
|
|
|
|
msg = Messages.fm_info_template;
|
|
|
|
break;
|
|
|
|
case TRASH:
|
|
|
|
msg = Messages.fm_info_trash;
|
|
|
|
break;
|
|
|
|
case FILES_DATA:
|
|
|
|
msg = Messages.fm_info_allFiles;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
msg = undefined;
|
|
|
|
}
|
|
|
|
if (!msg || Cryptpad.getLSAttribute('hide-info-' + path[0]) === '1') {
|
|
|
|
$box.hide();
|
|
|
|
} else {
|
|
|
|
$box.text(msg);
|
|
|
|
var $close = $closeIcon.clone().css({
|
|
|
|
'cursor': 'pointer',
|
|
|
|
'margin-left': '10px',
|
|
|
|
title: Messages.fm_closeInfoBox
|
|
|
|
}).on('click', function () {
|
|
|
|
$box.hide();
|
|
|
|
Cryptpad.setLSAttribute('hide-info-' + path[0], '1');
|
|
|
|
});
|
|
|
|
$box.prepend($close);
|
|
|
|
}
|
|
|
|
return $box;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Create the button allowing the user to switch from list to icons modes
|
|
|
|
var createViewModeButton = function () {
|
|
|
|
var $block = $('<div>', {
|
|
|
|
'class': 'dropdown-bar right changeViewModeContainer'
|
|
|
|
});
|
|
|
|
|
|
|
|
var $listButton = $('<button>', {
|
|
|
|
'class': 'element'
|
|
|
|
}).append($listIcon.clone());
|
|
|
|
var $gridButton = $('<button>', {
|
|
|
|
'class': 'element'
|
|
|
|
}).append($gridIcon.clone());
|
|
|
|
|
|
|
|
$listButton.click(function () {
|
|
|
|
$gridButton.removeClass('active');
|
|
|
|
$listButton.addClass('active');
|
|
|
|
setViewMode('list');
|
|
|
|
$iframe.find('#' + FOLDER_CONTENT_ID).removeClass('grid');
|
|
|
|
$iframe.find('#' + FOLDER_CONTENT_ID).addClass('list');
|
|
|
|
});
|
|
|
|
$gridButton.click(function () {
|
|
|
|
$listButton.removeClass('active');
|
|
|
|
$gridButton.addClass('active');
|
|
|
|
setViewMode('grid');
|
|
|
|
$iframe.find('#' + FOLDER_CONTENT_ID).addClass('grid');
|
|
|
|
$iframe.find('#' + FOLDER_CONTENT_ID).removeClass('list');
|
|
|
|
});
|
|
|
|
|
|
|
|
if (getViewMode() === 'list') {
|
|
|
|
$listButton.addClass('active');
|
|
|
|
} else {
|
|
|
|
$gridButton.addClass('active');
|
|
|
|
}
|
|
|
|
$block.append($listButton).append($gridButton);
|
|
|
|
return $block;
|
|
|
|
};
|
|
|
|
|
|
|
|
var createNewButton = function (isInRoot) {
|
|
|
|
if (!APP.editable) { return; }
|
|
|
|
|
|
|
|
// Create dropdown
|
|
|
|
var options = [];
|
|
|
|
if (isInRoot) {
|
|
|
|
options.push({
|
|
|
|
tag: 'a',
|
|
|
|
attributes: {'class': 'newFolder'},
|
|
|
|
content: Messages.fm_folder
|
|
|
|
});
|
|
|
|
options.push({tag: 'hr'});
|
|
|
|
}
|
|
|
|
AppConfig.availablePadTypes.forEach(function (type) {
|
|
|
|
if (type === 'drive') { return; }
|
|
|
|
var attributes = {
|
|
|
|
'class': 'newdoc',
|
|
|
|
'data-type': type,
|
|
|
|
'href': '#'
|
|
|
|
};
|
|
|
|
options.push({
|
|
|
|
tag: 'a',
|
|
|
|
attributes: attributes,
|
|
|
|
content: Messages.type[type]
|
|
|
|
});
|
|
|
|
});
|
|
|
|
var $plusIcon = $('<div>').append($('<span>', {'class': 'fa fa-plus'}));
|
|
|
|
|
|
|
|
|
|
|
|
var dropdownConfig = {
|
|
|
|
text: $plusIcon.html() + Messages.fm_newButton,
|
|
|
|
options: options
|
|
|
|
};
|
|
|
|
var $block = Cryptpad.createDropdown(dropdownConfig);
|
|
|
|
|
|
|
|
// Custom style:
|
|
|
|
$block.find('button').addClass('btn').addClass('btn-primary').addClass('new');
|
|
|
|
$block.find('button').attr('title', Messages.fm_newButtonTitle);
|
|
|
|
|
|
|
|
// Handlers
|
|
|
|
if (isInRoot) {
|
|
|
|
var onCreated = function (info) {
|
|
|
|
module.newFolder = info.newPath;
|
|
|
|
refresh();
|
|
|
|
};
|
|
|
|
$block.find('a.newFolder').click(function () {
|
|
|
|
filesOp.addFolder(currentPath, null, onCreated);
|
|
|
|
});
|
|
|
|
$block.find('a.newdoc').click(function (e) {
|
|
|
|
var type = $(this).attr('data-type') || 'pad';
|
|
|
|
var name = Cryptpad.getDefaultName({type: type});
|
|
|
|
filesOp.addFile(currentPath, name, type, onCreated);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
$block.find('a.newdoc').click(function (e) {
|
|
|
|
var type = $(this).attr('data-type') || 'pad';
|
|
|
|
sessionStorage[Cryptpad.newPadPathKey] = filesOp.isPathIn(currentPath, [TRASH]) ? '' : currentPath;
|
|
|
|
window.open('/' + type + '/');
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return $block;
|
|
|
|
};
|
|
|
|
|
|
|
|
var hideNewButton = function () {
|
|
|
|
$iframe.find('.dropdown-bar-content').hide();
|
|
|
|
};
|
|
|
|
|
|
|
|
var SORT_FOLDER_DESC = 'sortFoldersDesc';
|
|
|
|
var SORT_FILE_BY = 'sortFilesBy';
|
|
|
|
var SORT_FILE_DESC = 'sortFilesDesc';
|
|
|
|
|
|
|
|
var getSortFileDesc = function () {
|
|
|
|
return Cryptpad.getLSAttribute(SORT_FILE_DESC) === "true";
|
|
|
|
};
|
|
|
|
var getSortFolderDesc = function () {
|
|
|
|
return Cryptpad.getLSAttribute(SORT_FOLDER_DESC) === "true";
|
|
|
|
};
|
|
|
|
|
|
|
|
var onSortByClick = function (e) {
|
|
|
|
var $span = $(this);
|
|
|
|
var value;
|
|
|
|
if ($span.hasClass('foldername')) {
|
|
|
|
value = getSortFolderDesc();
|
|
|
|
Cryptpad.setLSAttribute(SORT_FOLDER_DESC, value ? false : true);
|
|
|
|
refresh();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
value = Cryptpad.getLSAttribute(SORT_FILE_BY);
|
|
|
|
var descValue = getSortFileDesc();
|
|
|
|
if ($span.hasClass('filename')) {
|
|
|
|
if (value === '') {
|
|
|
|
descValue = descValue ? false : true;
|
|
|
|
} else {
|
|
|
|
descValue = false;
|
|
|
|
value = '';
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
var found = false;
|
|
|
|
['title', 'type', 'atime', 'ctime'].forEach(function (c) {
|
|
|
|
if (!found && $span.hasClass(c)) {
|
|
|
|
found = true;
|
|
|
|
if (value === c) { descValue = descValue ? false : true; }
|
|
|
|
else {
|
|
|
|
// atime and ctime should be ordered in a desc order at the first click
|
|
|
|
descValue = c !== 'title';
|
|
|
|
value = c;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
Cryptpad.setLSAttribute(SORT_FILE_BY, value);
|
|
|
|
Cryptpad.setLSAttribute(SORT_FILE_DESC, descValue);
|
|
|
|
refresh();
|
|
|
|
};
|
|
|
|
|
|
|
|
var addFolderSortIcon = function ($list) {
|
|
|
|
var $icon = $sortAscIcon.clone();
|
|
|
|
if (getSortFolderDesc()) {
|
|
|
|
$icon = $sortDescIcon.clone();
|
|
|
|
}
|
|
|
|
if (typeof(Cryptpad.getLSAttribute(SORT_FOLDER_DESC)) !== "undefined") {
|
|
|
|
$list.find('.foldername').addClass('active').prepend($icon);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
var getFolderListHeader = function () {
|
|
|
|
var $fohElement = $('<li>', {'class': 'header listElement'});
|
|
|
|
//var $fohElement = $('<span>', {'class': 'element'}).appendTo($folderHeader);
|
|
|
|
var $fhIcon = $('<span>', {'class': 'icon'});
|
|
|
|
var $name = $('<span>', {'class': 'name foldername clickable'}).text(Messages.fm_folderName).click(onSortByClick);
|
|
|
|
var $subfolders = $('<span>', {'class': 'folders listElement'}).text(Messages.fm_numberOfFolders);
|
|
|
|
var $files = $('<span>', {'class': 'files listElement'}).text(Messages.fm_numberOfFiles);
|
|
|
|
$fohElement.append($fhIcon).append($name).append($subfolders).append($files);
|
|
|
|
addFolderSortIcon($fohElement);
|
|
|
|
return $fohElement;
|
|
|
|
};
|
|
|
|
var addFileSortIcon = function ($list) {
|
|
|
|
var $icon = $sortAscIcon.clone();
|
|
|
|
if (getSortFileDesc()) {
|
|
|
|
$icon = $sortDescIcon.clone();
|
|
|
|
}
|
|
|
|
var classSorted;
|
|
|
|
if (Cryptpad.getLSAttribute(SORT_FILE_BY) === '') { classSorted = 'filename'; }
|
|
|
|
else if (Cryptpad.getLSAttribute(SORT_FILE_BY)) { classSorted = Cryptpad.getLSAttribute(SORT_FILE_BY); }
|
|
|
|
if (classSorted) {
|
|
|
|
$list.find('.' + classSorted).addClass('active').prepend($icon);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
// _WORKGROUP_ : do not display title, atime and ctime in workgroups since we don't have files data
|
|
|
|
var getFileListHeader = function (displayTitle) {
|
|
|
|
var $fihElement = $('<li>', {'class': 'file-header header listElement element'});
|
|
|
|
//var $fihElement = $('<span>', {'class': 'element'}).appendTo($fileHeader);
|
|
|
|
var $fhIcon = $('<span>', {'class': 'icon'});
|
|
|
|
var $fhName = $('<span>', {'class': 'name filename clickable'}).text(Messages.fm_fileName).click(onSortByClick);
|
|
|
|
var $fhTitle = $('<span>', {'class': 'title clickable'}).text(Messages.fm_title).click(onSortByClick);
|
|
|
|
var $fhType = $('<span>', {'class': 'type clickable'}).text(Messages.fm_type).click(onSortByClick);
|
|
|
|
var $fhAdate = $('<span>', {'class': 'atime clickable'}).text(Messages.fm_lastAccess).click(onSortByClick);
|
|
|
|
var $fhCdate = $('<span>', {'class': 'ctime clickable'}).text(Messages.fm_creation).click(onSortByClick);
|
|
|
|
// If displayTitle is false, it means the "name" is the title, so do not display the "name" header
|
|
|
|
$fihElement.append($fhIcon);
|
|
|
|
if (displayTitle || isWorkgroup()) {
|
|
|
|
$fihElement.append($fhName);
|
|
|
|
} else {
|
|
|
|
$fhTitle.width('auto');
|
|
|
|
}
|
|
|
|
if (!isWorkgroup()) {
|
|
|
|
$fihElement.append($fhTitle);
|
|
|
|
}
|
|
|
|
$fihElement.append($fhType);
|
|
|
|
if (!isWorkgroup()) {
|
|
|
|
$fihElement.append($fhAdate).append($fhCdate);
|
|
|
|
}
|
|
|
|
addFileSortIcon($fihElement);
|
|
|
|
return $fihElement;
|
|
|
|
//return $fileHeader;
|
|
|
|
};
|
|
|
|
|
|
|
|
var allFilesSorted = function () {
|
|
|
|
return filesOp.getFiles([UNSORTED]).length === 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
var sortElements = function (folder, path, oldkeys, prop, asc, useHref, useData) {
|
|
|
|
var root = filesOp.find(path);
|
|
|
|
var test = folder ? filesOp.isFolder : filesOp.isFile;
|
|
|
|
var keys;
|
|
|
|
if (!useData) {
|
|
|
|
keys = oldkeys.filter(function (e) {
|
|
|
|
return useHref ? test(e) : test(root[e]);
|
|
|
|
});
|
|
|
|
} else { keys = oldkeys.slice(); }
|
|
|
|
if (keys.length < 2) { return keys; }
|
|
|
|
var mult = asc ? 1 : -1;
|
|
|
|
var getProp = function (el, prop) {
|
|
|
|
if (prop) {
|
|
|
|
var element = useHref || useData ? el : root[el];
|
|
|
|
var e = useData ? element : filesOp.getFileData(element);
|
|
|
|
if (!e) {
|
|
|
|
e = {
|
|
|
|
href : element,
|
|
|
|
title : Messages.fm_noname,
|
|
|
|
atime : 0,
|
|
|
|
ctime : 0
|
|
|
|
};
|
|
|
|
}
|
|
|
|
if (prop === 'type') {
|
|
|
|
var hrefData = Cryptpad.parsePadUrl(e.href);
|
|
|
|
return hrefData.type;
|
|
|
|
}
|
|
|
|
if (prop === 'atime' || prop === 'ctime') {
|
|
|
|
return new Date(e[prop]);
|
|
|
|
}
|
|
|
|
return e && e.title ? e.title.toLowerCase() : '';
|
|
|
|
}
|
|
|
|
return useData ? el.title.toLowerCase() : el.toLowerCase();
|
|
|
|
};
|
|
|
|
keys.sort(function(a, b) {
|
|
|
|
if (getProp(a, prop) < getProp(b, prop)) { return mult * -1; }
|
|
|
|
if (getProp(a, prop) > getProp(b, prop)) { return mult * 1; }
|
|
|
|
return 0;
|
|
|
|
});
|
|
|
|
return keys;
|
|
|
|
};
|
|
|
|
var sortTrashElements = function (folder, oldkeys, prop, asc) {
|
|
|
|
var root = files[TRASH];
|
|
|
|
var test = folder ? filesOp.isFolder : filesOp.isFile;
|
|
|
|
var keys = oldkeys.filter(function (e) {
|
|
|
|
return test(e.element);
|
|
|
|
});
|
|
|
|
if (keys.length < 2) { return keys; }
|
|
|
|
var mult = asc ? 1 : -1;
|
|
|
|
var getProp = function (el, prop) {
|
|
|
|
if (prop && !folder) {
|
|
|
|
var element = el.element;
|
|
|
|
var e = filesOp.getFileData(element);
|
|
|
|
if (!e) {
|
|
|
|
e = {
|
|
|
|
href : el,
|
|
|
|
title : Messages.fm_noname,
|
|
|
|
atime : 0,
|
|
|
|
ctime : 0
|
|
|
|
};
|
|
|
|
}
|
|
|
|
if (prop === 'type') {
|
|
|
|
var hrefData = Cryptpad.parsePadUrl(e.href);
|
|
|
|
return hrefData.type;
|
|
|
|
}
|
|
|
|
if (prop === 'atime' || prop === 'ctime') {
|
|
|
|
return new Date(e[prop]);
|
|
|
|
}
|
|
|
|
return e.title.toLowerCase();
|
|
|
|
}
|
|
|
|
return el.name.toLowerCase();
|
|
|
|
};
|
|
|
|
keys.sort(function(a, b) {
|
|
|
|
if (getProp(a, prop) < getProp(b, prop)) { return mult * -1; }
|
|
|
|
if (getProp(a, prop) > getProp(b, prop)) { return mult * 1; }
|
|
|
|
return 0;
|
|
|
|
});
|
|
|
|
return keys;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Drive content toolbar
|
|
|
|
var createToolbar = function (path) {
|
|
|
|
var $toolbar = $driveToolbar;
|
|
|
|
$toolbar.html('');
|
|
|
|
var $leftside = $('<div>', {'class': 'leftside'}).appendTo($toolbar);
|
|
|
|
if (!APP.mobile()) {
|
|
|
|
$leftside.width($tree.width());
|
|
|
|
}
|
|
|
|
var $rightside = $('<div>', {'class': 'rightside'}).appendTo($toolbar);
|
|
|
|
return $toolbar;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Unsorted element are represented by "href" in an array: they don't have a filename
|
|
|
|
// and they don't hav a hierarchical structure (folder/subfolders)
|
|
|
|
var displayHrefArray = function ($container, rootName, draggable) {
|
|
|
|
var unsorted = files[rootName];
|
|
|
|
if (rootName === UNSORTED && allFilesSorted()) { return; }
|
|
|
|
var $fileHeader = getFileListHeader(false);
|
|
|
|
$container.append($fileHeader);
|
|
|
|
var keys = unsorted;
|
|
|
|
var sortBy = Cryptpad.getLSAttribute(SORT_FILE_BY);
|
|
|
|
sortBy = sortBy === "" ? sortBy = 'title' : sortBy;
|
|
|
|
var sortedFiles = sortElements(false, [rootName], keys, sortBy, !getSortFileDesc(), true);
|
|
|
|
sortedFiles.forEach(function (href) {
|
|
|
|
var file = filesOp.getFileData(href);
|
|
|
|
if (!file) {
|
|
|
|
//debug("Unsorted or template returns an element not present in filesData: ", href);
|
|
|
|
file = { title: Messages.fm_noname };
|
|
|
|
//return;
|
|
|
|
}
|
|
|
|
var idx = files[rootName].indexOf(href);
|
|
|
|
var $icon = getFileIcon(href);
|
|
|
|
var ro = filesOp.isReadOnlyFile(href);
|
|
|
|
// ro undefined mens it's an old hash which doesn't support read-only
|
|
|
|
var roClass = typeof(ro) === 'undefined' ? ' noreadonly' : ro ? ' readonly' : '';
|
|
|
|
var $element = $('<li>', {
|
|
|
|
'class': 'file-element element element-row' + roClass,
|
|
|
|
draggable: draggable
|
|
|
|
});
|
|
|
|
addFileData(href, file.title, $element, false);
|
|
|
|
$element.prepend($icon).dblclick(function () {
|
|
|
|
openFile(href);
|
|
|
|
});
|
|
|
|
var path = [rootName, idx];
|
|
|
|
$element.data('path', path);
|
|
|
|
$element.click(function(e) {
|
|
|
|
e.stopPropagation();
|
|
|
|
onElementClick(e, $element, path);
|
|
|
|
});
|
|
|
|
$element.contextmenu(openDefaultContextMenu);
|
|
|
|
$element.data('context', $defaultContextMenu);
|
|
|
|
if (draggable) {
|
|
|
|
addDragAndDropHandlers($element, path, false, false);
|
|
|
|
}
|
|
|
|
$container.append($element);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
var displayAllFiles = function ($container) {
|
|
|
|
var allfiles = files[FILES_DATA];
|
|
|
|
if (allfiles.length === 0) { return; }
|
|
|
|
var $fileHeader = getFileListHeader(false);
|
|
|
|
$container.append($fileHeader);
|
|
|
|
var keys = allfiles;
|
|
|
|
|
|
|
|
var sortedFiles = sortElements(false, [FILES_DATA], keys, Cryptpad.getLSAttribute(SORT_FILE_BY), !getSortFileDesc(), false, true);
|
|
|
|
sortedFiles.forEach(function (file) {
|
|
|
|
var $icon = getFileIcon(file.href);
|
|
|
|
var ro = filesOp.isReadOnlyFile(file.href);
|
|
|
|
// ro undefined mens it's an old hash which doesn't support read-only
|
|
|
|
var roClass = typeof(ro) === 'undefined' ? ' noreadonly' : ro ? ' readonly' : '';
|
|
|
|
var $element = $('<li>', { 'class': 'file-element element element-row' + roClass });
|
|
|
|
addFileData(file.href, file.title, $element, false);
|
|
|
|
$element.data('path', [FILES_DATA, allfiles.indexOf(file)]);
|
|
|
|
$element.data('element', file.href);
|
|
|
|
$element.prepend($icon).dblclick(function () {
|
|
|
|
openFile(file.href);
|
|
|
|
});
|
|
|
|
$element.click(function(e) {
|
|
|
|
e.stopPropagation();
|
|
|
|
onElementClick(e, $element);
|
|
|
|
});
|
|
|
|
$element.contextmenu(openDefaultContextMenu);
|
|
|
|
$element.data('context', $defaultContextMenu);
|
|
|
|
$container.append($element);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
var displayTrashRoot = function ($list, $folderHeader, $fileHeader) {
|
|
|
|
var filesList = [];
|
|
|
|
var root = files[TRASH];
|
|
|
|
// Elements in the trash are JS arrays (several elements can have the same name)
|
|
|
|
[true,false].forEach(function (folder) {
|
|
|
|
var testElement = filesOp.isFile;
|
|
|
|
if (!folder) {
|
|
|
|
testElement = filesOp.isFolder;
|
|
|
|
}
|
|
|
|
Object.keys(root).forEach(function (key) {
|
|
|
|
if (!$.isArray(root[key])) {
|
|
|
|
logError("Trash element has a wrong type", root[key]);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
root[key].forEach(function (el, idx) {
|
|
|
|
if (testElement(el.element)) { return; }
|
|
|
|
var spath = [key, idx, 'element'];
|
|
|
|
filesList.push({
|
|
|
|
element: el.element,
|
|
|
|
spath: spath,
|
|
|
|
name: key
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
var sortedFolders = sortTrashElements(true, filesList, null, !getSortFolderDesc());
|
|
|
|
var sortedFiles = sortTrashElements(false, filesList, Cryptpad.getLSAttribute(SORT_FILE_BY), !getSortFileDesc());
|
|
|
|
if (filesOp.hasSubfolder(root, true)) { $list.append($folderHeader); }
|
|
|
|
sortedFolders.forEach(function (f) {
|
|
|
|
var $element = createElement([TRASH], f.spath, root, true);
|
|
|
|
$list.append($element);
|
|
|
|
});
|
|
|
|
if (filesOp.hasFile(root, true)) { $list.append($fileHeader); }
|
|
|
|
sortedFiles.forEach(function (f) {
|
|
|
|
var $element = createElement([TRASH], f.spath, root, false);
|
|
|
|
$list.append($element);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
var displaySearch = function ($list, value) {
|
|
|
|
var filesList = filesOp.search(value);
|
|
|
|
filesList.forEach(function (r) {
|
|
|
|
r.paths.forEach(function (path) {
|
|
|
|
var href = r.data.href;
|
|
|
|
var parsed = Cryptpad.parsePadUrl(href);
|
|
|
|
var $table = $('<table>');
|
|
|
|
var $icon = $('<td>', {'rowspan': '3', 'class': 'icon'}).append(getFileIcon(href));
|
|
|
|
var $title = $('<td>', {'class': 'col1 title'}).text(r.data.title).click(function (e) {
|
|
|
|
openFile(r.data.href);
|
|
|
|
});
|
|
|
|
var $typeName = $('<td>', {'class': 'label2'}).text(Messages.fm_type);
|
|
|
|
var $type = $('<td>', {'class': 'col2'}).text(Messages.type[parsed.type] || parsed.type);
|
|
|
|
var $atimeName = $('<td>', {'class': 'label2'}).text(Messages.fm_lastAccess);
|
|
|
|
var $atime = $('<td>', {'class': 'col2'}).text(new Date(r.data.atime).toLocaleString());
|
|
|
|
var $ctimeName = $('<td>', {'class': 'label2'}).text(Messages.fm_creation);
|
|
|
|
var $ctime = $('<td>', {'class': 'col2'}).text(new Date(r.data.ctime).toLocaleString());
|
|
|
|
if (filesOp.isPathIn(path, ['hrefArray'])) {
|
|
|
|
path.pop();
|
|
|
|
path.push(r.data.title);
|
|
|
|
}
|
|
|
|
var $path = $('<td>', {'class': 'col1 path'}).html(createTitle(path, true).html());
|
|
|
|
var parentPath = path.slice();
|
|
|
|
var $a;
|
|
|
|
if (parentPath) {
|
|
|
|
$a = $('<a>').text(Messages.fm_openParent).click(function (e) {
|
|
|
|
e.preventDefault();
|
|
|
|
parentPath.pop();
|
|
|
|
module.displayDirectory(parentPath);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
var $openDir = $('<td>', {'class': 'openDir'}).append($a);
|
|
|
|
var $row1 = $('<tr>').append($icon).append($title).append($typeName).append($type).appendTo($table);
|
|
|
|
var $row2 = $('<tr>').append($path).append($atimeName).append($atime).appendTo($table);
|
|
|
|
var $row3 = $('<tr>').append($openDir).append($ctimeName).append($ctime).appendTo($table);
|
|
|
|
$('<li>', {'class':'searchResult'}).append($table).appendTo($list);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
// Display the selected directory into the content part (rightside)
|
|
|
|
// NOTE: Elements in the trash are not using the same storage structure as the others
|
|
|
|
// _WORKGROUP_ : do not change the lastOpenedFolder value in localStorage
|
|
|
|
var displayDirectory = module.displayDirectory = function (path, force) {
|
|
|
|
module.hideMenu();
|
|
|
|
if (!APP.editable) { debug("Read-only mode"); }
|
|
|
|
if (!appStatus.isReady && !force) { return; }
|
|
|
|
// Only Trash and Root are available in not-owned files manager
|
|
|
|
if (displayedCategories.indexOf(path[0]) === -1) {
|
|
|
|
log(Messages.categoryError);
|
|
|
|
currentPath = [ROOT];
|
|
|
|
displayDirectory(currentPath);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
appStatus.ready(false);
|
|
|
|
currentPath = path;
|
|
|
|
var s = $content.scrollTop() || 0;
|
|
|
|
$content.html("");
|
|
|
|
if (!path || path.length === 0) {
|
|
|
|
path = [ROOT];
|
|
|
|
}
|
|
|
|
var isInRoot = filesOp.isPathIn(path, [ROOT]);
|
|
|
|
var isTrashRoot = filesOp.comparePath(path, [TRASH]);
|
|
|
|
var isUnsorted = filesOp.comparePath(path, [UNSORTED]);
|
|
|
|
var isTemplate = filesOp.comparePath(path, [TEMPLATE]);
|
|
|
|
var isAllFiles = filesOp.comparePath(path, [FILES_DATA]);
|
|
|
|
var isSearch = path[0] === SEARCH;
|
|
|
|
|
|
|
|
var root = isSearch ? undefined : filesOp.find(path);
|
|
|
|
if (!isSearch && typeof(root) === "undefined") {
|
|
|
|
log(Messages.fm_unknownFolderError);
|
|
|
|
debug("Unable to locate the selected directory: ", path);
|
|
|
|
var parentPath = path.slice();
|
|
|
|
parentPath.pop();
|
|
|
|
displayDirectory(parentPath, true);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
module.resetTree();
|
|
|
|
|
|
|
|
$tree.find('#searchInput').focus();
|
|
|
|
$tree.find('#searchInput')[0].selectionStart = getSearchCursor();
|
|
|
|
$tree.find('#searchInput')[0].selectionEnd = getSearchCursor();
|
|
|
|
|
|
|
|
if (!isWorkgroup()) {
|
|
|
|
setLastOpenedFolder(path);
|
|
|
|
}
|
|
|
|
|
|
|
|
var $toolbar = createToolbar(path);
|
|
|
|
var $info = createInfoBox(path);
|
|
|
|
|
|
|
|
var $dirContent = $('<div>', {id: FOLDER_CONTENT_ID});
|
|
|
|
$dirContent.data('path', path);
|
|
|
|
if (!isSearch) {
|
|
|
|
var mode = getViewMode();
|
|
|
|
if (mode) {
|
|
|
|
$dirContent.addClass(getViewModeClass());
|
|
|
|
}
|
|
|
|
var $modeButton = createViewModeButton().appendTo($toolbar.find('.rightside'));
|
|
|
|
}
|
|
|
|
var $list = $('<ul>').appendTo($dirContent);
|
|
|
|
|
|
|
|
var $title = createTitle(path).appendTo($toolbar.find('.rightside'));
|
|
|
|
updatePathSize();
|
|
|
|
|
|
|
|
if (APP.mobile()) {
|
|
|
|
var $context = $('<button>', {'class': 'element right dropdown-bar', id: 'contextButton'});
|
|
|
|
$context.append($('<span>', {'class': 'fa fa-caret-down'}));
|
|
|
|
$context.appendTo($toolbar.find('.rightside'));
|
|
|
|
$context.click(function (e) {
|
|
|
|
e.preventDefault();
|
|
|
|
e.stopPropagation();
|
|
|
|
var $li = $content.find('.selected');
|
|
|
|
if ($li.length !== 1) {
|
|
|
|
$li = findDataHolder($tree.find('.active'));
|
|
|
|
}
|
|
|
|
// Close if already opened
|
|
|
|
if ($iframe.find('.contextMenu:visible').length) {
|
|
|
|
module.hideMenu();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Open the menu
|
|
|
|
$iframe.find('.contextMenu').css({
|
|
|
|
top: ($context.offset().top + 32) + 'px',
|
|
|
|
right: '0px',
|
|
|
|
left: ''
|
|
|
|
});
|
|
|
|
$li.contextmenu();
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
var $contextButtons = $('<span>', {'id' : 'contextButtonsContainer'});
|
|
|
|
$contextButtons.appendTo($toolbar.find('.rightside'));
|
|
|
|
}
|
|
|
|
updateContextButton();
|
|
|
|
|
|
|
|
// NewButton can be undefined if we're in read only mode
|
|
|
|
$toolbar.find('.leftside').append(createNewButton(isInRoot));
|
|
|
|
|
|
|
|
|
|
|
|
var $folderHeader = getFolderListHeader();
|
|
|
|
var $fileHeader = getFileListHeader(true);
|
|
|
|
|
|
|
|
if (isUnsorted || isTemplate) {
|
|
|
|
displayHrefArray($list, path[0], true);
|
|
|
|
} else if (isAllFiles) {
|
|
|
|
displayAllFiles($list);
|
|
|
|
} else if (isTrashRoot) {
|
|
|
|
displayTrashRoot($list, $folderHeader, $fileHeader);
|
|
|
|
} else if (isSearch) {
|
|
|
|
displaySearch($list, path[1]);
|
|
|
|
} else {
|
|
|
|
$dirContent.contextmenu(openContentContextMenu);
|
|
|
|
if (filesOp.hasSubfolder(root)) { $list.append($folderHeader); }
|
|
|
|
// display sub directories
|
|
|
|
var keys = Object.keys(root);
|
|
|
|
var sortedFolders = sortElements(true, path, keys, null, !getSortFolderDesc());
|
|
|
|
var sortedFiles = sortElements(false, path, keys, Cryptpad.getLSAttribute(SORT_FILE_BY), !getSortFileDesc());
|
|
|
|
sortedFolders.forEach(function (key) {
|
|
|
|
if (filesOp.isFile(root[key])) { return; }
|
|
|
|
var $element = createElement(path, key, root, true);
|
|
|
|
$element.appendTo($list);
|
|
|
|
});
|
|
|
|
if (filesOp.hasFile(root)) { $list.append($fileHeader); }
|
|
|
|
// display files
|
|
|
|
sortedFiles.forEach(function (key) {
|
|
|
|
if (filesOp.isFolder(root[key])) { return; }
|
|
|
|
var $element = createElement(path, key, root, false);
|
|
|
|
$element.appendTo($list);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
//$content.append($toolbar).append($title).append($info).append($dirContent);
|
|
|
|
$content.append($info).append($dirContent);
|
|
|
|
$content.scrollTop(s);
|
|
|
|
appStatus.ready(true);
|
|
|
|
};
|
|
|
|
|
|
|
|
var refreshFilesData = function () {
|
|
|
|
$content.find('.element-row').each(function (i, e) {
|
|
|
|
var $el = $(e);
|
|
|
|
if ($el.data('path')) {
|
|
|
|
var path = $el.data('path');
|
|
|
|
var element = filesOp.find(path);
|
|
|
|
if (!filesOp.isFile(element)) { return; }
|
|
|
|
var data = filesOp.getFileData(element);
|
|
|
|
if (!data) { return; }
|
|
|
|
if (filesOp.isPathIn(path, ['hrefArray'])) { $el.find('.name').attr('title', data.title).text(data.title); }
|
|
|
|
$el.find('.title').attr('title', data.title).text(data.title);
|
|
|
|
$el.find('.atime').attr('title', getDate(data.atime)).text(getDate(data.atime));
|
|
|
|
$el.find('.ctime').attr('title', getDate(data.ctime)).text(getDate(data.ctime));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
var createTreeElement = function (name, $icon, path, draggable, droppable, collapsable, active) {
|
|
|
|
var $name = $('<span>', { 'class': 'folder-element element' }).text(name);
|
|
|
|
var $collapse;
|
|
|
|
if (collapsable) {
|
|
|
|
$collapse = $expandIcon.clone();
|
|
|
|
}
|
|
|
|
var $elementRow = $('<span>', {'class': 'element-row'}).append($collapse).append($icon).append($name).click(function (e) {
|
|
|
|
e.stopPropagation();
|
|
|
|
module.displayDirectory(path);
|
|
|
|
});
|
|
|
|
var $element = $('<li>').append($elementRow);
|
|
|
|
if (draggable) { $elementRow.attr('draggable', true); }
|
|
|
|
if (collapsable) {
|
|
|
|
$element.addClass('collapsed');
|
|
|
|
$collapse.click(function(e) {
|
|
|
|
e.stopPropagation();
|
|
|
|
if ($element.hasClass('collapsed')) {
|
|
|
|
// It is closed, open it
|
|
|
|
$element.removeClass('collapsed');
|
|
|
|
setFolderOpened(path, true);
|
|
|
|
$collapse.removeClass('fa-plus-square-o');
|
|
|
|
$collapse.addClass('fa-minus-square-o');
|
|
|
|
} else {
|
|
|
|
// Collapse the folder
|
|
|
|
$element.addClass('collapsed');
|
|
|
|
setFolderOpened(path, false);
|
|
|
|
$collapse.removeClass('fa-minus-square-o');
|
|
|
|
$collapse.addClass('fa-plus-square-o');
|
|
|
|
// Change the current opened folder if it was collapsed
|
|
|
|
if (filesOp.isSubpath(currentPath, path)) {
|
|
|
|
displayDirectory(path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
if (wasFolderOpened(path) ||
|
|
|
|
(filesOp.isSubpath(currentPath, path) && path.length < currentPath.length)) {
|
|
|
|
$collapse.click();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$elementRow.data('path', path);
|
|
|
|
addDragAndDropHandlers($elementRow, path, true, droppable);
|
|
|
|
if (active) { $elementRow.addClass('active'); }
|
|
|
|
return $element;
|
|
|
|
};
|
|
|
|
|
|
|
|
var createTree = function ($container, path) {
|
|
|
|
var root = filesOp.find(path);
|
|
|
|
|
|
|
|
// don't try to display what doesn't exist
|
|
|
|
if (!root) { return; }
|
|
|
|
|
|
|
|
// Display the root element in the tree
|
|
|
|
var displayingRoot = filesOp.comparePath([ROOT], path);
|
|
|
|
if (displayingRoot) {
|
|
|
|
var isRootOpened = filesOp.comparePath([ROOT], currentPath);
|
|
|
|
var $rootIcon = filesOp.isFolderEmpty(files[ROOT]) ?
|
|
|
|
(isRootOpened ? $folderOpenedEmptyIcon : $folderEmptyIcon) :
|
|
|
|
(isRootOpened ? $folderOpenedIcon : $folderIcon);
|
|
|
|
var $rootElement = createTreeElement(ROOT_NAME, $rootIcon.clone(), [ROOT], false, true, false, isRootOpened);
|
|
|
|
$rootElement.addClass('root');
|
|
|
|
$rootElement.find('>.element-row').contextmenu(openDirectoryContextMenu);
|
|
|
|
var $root = $('<ul>').append($rootElement).appendTo($container);
|
|
|
|
$container = $rootElement;
|
|
|
|
} else if (filesOp.isFolderEmpty(root)) { return; }
|
|
|
|
|
|
|
|
// Display root content
|
|
|
|
var $list = $('<ul>').appendTo($container);
|
|
|
|
var keys = Object.keys(root).sort();
|
|
|
|
keys.forEach(function (key) {
|
|
|
|
// Do not display files in the menu
|
|
|
|
if (filesOp.isFile(root[key])) { return; }
|
|
|
|
var newPath = path.slice();
|
|
|
|
newPath.push(key);
|
|
|
|
var isCurrentFolder = filesOp.comparePath(newPath, currentPath);
|
|
|
|
var isEmpty = filesOp.isFolderEmpty(root[key]);
|
|
|
|
var subfolder = filesOp.hasSubfolder(root[key]);
|
|
|
|
var $icon = isEmpty ?
|
|
|
|
(isCurrentFolder ? $folderOpenedEmptyIcon : $folderEmptyIcon) :
|
|
|
|
(isCurrentFolder ? $folderOpenedIcon : $folderIcon);
|
|
|
|
var $element = createTreeElement(key, $icon.clone(), newPath, true, true, subfolder, isCurrentFolder);
|
|
|
|
$element.appendTo($list);
|
|
|
|
$element.find('>.element-row').contextmenu(openDirectoryContextMenu);
|
|
|
|
createTree($element, newPath);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
var createUnsorted = function ($container, path) {
|
|
|
|
var $icon = $unsortedIcon.clone();
|
|
|
|
var isOpened = filesOp.comparePath(path, currentPath);
|
|
|
|
var $unsortedElement = createTreeElement(UNSORTED_NAME, $icon, [UNSORTED], false, true, false, isOpened);
|
|
|
|
$unsortedElement.addClass('root');
|
|
|
|
var $unsortedList = $('<ul>', { id: 'unsortedTree', 'class': 'category2' }).append($unsortedElement);
|
|
|
|
$container.append($unsortedList);
|
|
|
|
};
|
|
|
|
|
|
|
|
var createTemplate = function ($container, path) {
|
|
|
|
var $icon = $templateIcon.clone();
|
|
|
|
var isOpened = filesOp.comparePath(path, currentPath);
|
|
|
|
var $element = createTreeElement(TEMPLATE_NAME, $icon, [TEMPLATE], false, true, false, isOpened);
|
|
|
|
$element.addClass('root');
|
|
|
|
var $list = $('<ul>', { id: 'templateTree', 'class': 'category2' }).append($element);
|
|
|
|
$container.append($list);
|
|
|
|
};
|
|
|
|
|
|
|
|
var createAllFiles = function ($container, path) {
|
|
|
|
var $icon = $unsortedIcon.clone();
|
|
|
|
var isOpened = filesOp.comparePath(path, currentPath);
|
|
|
|
var $allfilesElement = createTreeElement(FILES_DATA_NAME, $icon, [FILES_DATA], false, false, false, isOpened);
|
|
|
|
$allfilesElement.addClass('root');
|
|
|
|
var $allfilesList = $('<ul>', { id: 'allfilesTree', 'class': 'category2' }).append($allfilesElement);
|
|
|
|
$container.append($allfilesList);
|
|
|
|
};
|
|
|
|
|
|
|
|
var createTrash = function ($container, path) {
|
|
|
|
var $icon = filesOp.isFolderEmpty(files[TRASH]) ? $trashEmptyIcon.clone() : $trashIcon.clone();
|
|
|
|
var isOpened = filesOp.comparePath(path, currentPath);
|
|
|
|
var $trashElement = createTreeElement(TRASH_NAME, $icon, [TRASH], false, true, false, isOpened);
|
|
|
|
$trashElement.addClass('root');
|
|
|
|
$trashElement.contextmenu(openTrashTreeContextMenu);
|
|
|
|
var $trashList = $('<ul>', { id: 'trashTree', 'class': 'category2' }).append($trashElement);
|
|
|
|
$container.append($trashList);
|
|
|
|
};
|
|
|
|
|
|
|
|
var search = APP.Search = {};
|
|
|
|
var createSearch = function ($container) {
|
|
|
|
var isInSearch = currentPath[0] === SEARCH;
|
|
|
|
var $div = $('<div>', {'id': 'searchContainer'});
|
|
|
|
var $input = $('<input>', {
|
|
|
|
id: 'searchInput',
|
|
|
|
type: 'text',
|
|
|
|
placeholder: Messages.fm_searchPlaceholder
|
|
|
|
}).keyup(function (e) {
|
|
|
|
if (search.to) { window.clearTimeout(search.to); }
|
|
|
|
var isInSearchTmp = currentPath[0] === SEARCH;
|
|
|
|
if ($input.val().trim() === "") {
|
|
|
|
setSearchCursor(0);
|
|
|
|
if (search.oldLocation && search.oldLocation.length) { displayDirectory(search.oldLocation); }
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (e.which === 13) {
|
|
|
|
if (!isInSearchTmp) { search.oldLocation = currentPath.slice(); }
|
|
|
|
var newLocation = [SEARCH, $input.val()];
|
|
|
|
setSearchCursor();
|
|
|
|
if (!filesOp.comparePath(newLocation, currentPath.slice())) { displayDirectory(newLocation); }
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
search.to = window.setTimeout(function () {
|
|
|
|
if (!isInSearchTmp) { search.oldLocation = currentPath.slice(); }
|
|
|
|
var newLocation = [SEARCH, $input.val()];
|
|
|
|
setSearchCursor();
|
|
|
|
if (!filesOp.comparePath(newLocation, currentPath.slice())) { displayDirectory(newLocation); }
|
|
|
|
}, 500);
|
|
|
|
}).appendTo($div);
|
|
|
|
if (isInSearch) { $input.val(currentPath[1] || ''); }
|
|
|
|
$container.append($div);
|
|
|
|
};
|
|
|
|
|
|
|
|
var resetTree = module.resetTree = function () {
|
|
|
|
$tree.html('');
|
|
|
|
if (displayedCategories.indexOf(SEARCH) !== -1) { createSearch($tree); }
|
|
|
|
if (displayedCategories.indexOf(ROOT) !== -1) { createTree($tree, [ROOT]); }
|
|
|
|
if (displayedCategories.indexOf(UNSORTED) !== -1) { createUnsorted($tree, [UNSORTED]); }
|
|
|
|
if (displayedCategories.indexOf(TEMPLATE) !== -1) { createTemplate($tree, [TEMPLATE]); }
|
|
|
|
if (displayedCategories.indexOf(FILES_DATA) !== -1) { createAllFiles($tree, [FILES_DATA]); }
|
|
|
|
if (displayedCategories.indexOf(TRASH) !== -1) { createTrash($tree, [TRASH]); }
|
|
|
|
};
|
|
|
|
|
|
|
|
var hideMenu = module.hideMenu = function () {
|
|
|
|
$contextMenu.hide();
|
|
|
|
$trashTreeContextMenu.hide();
|
|
|
|
$trashContextMenu.hide();
|
|
|
|
$contentContextMenu.hide();
|
|
|
|
$defaultContextMenu.hide();
|
|
|
|
};
|
|
|
|
|
|
|
|
var stringifyPath = function (path) {
|
|
|
|
if (!$.isArray(path)) { return; }
|
|
|
|
var rootName = function (s) {
|
|
|
|
var prettyName;
|
|
|
|
switch (s) {
|
|
|
|
case ROOT:
|
|
|
|
prettyName = ROOT_NAME;
|
|
|
|
break;
|
|
|
|
case UNSORTED:
|
|
|
|
prettyName = UNSORTED_NAME;
|
|
|
|
break;
|
|
|
|
case FILES_DATA:
|
|
|
|
prettyName = FILES_DATA_NAME;
|
|
|
|
break;
|
|
|
|
case TRASH:
|
|
|
|
prettyName = TRASH_NAME;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
prettyName = s;
|
|
|
|
}
|
|
|
|
return prettyName;
|
|
|
|
};
|
|
|
|
var $div = $('<div>');
|
|
|
|
var i = 0;
|
|
|
|
var space = 10;
|
|
|
|
path.forEach(function (s) {
|
|
|
|
if (i === 0) { s = rootName(s); }
|
|
|
|
$div.append($('<span>', {'style': 'margin: 0 0 0 ' + i * space + 'px;'}).text(s));
|
|
|
|
$div.append($('<br>'));
|
|
|
|
i++;
|
|
|
|
});
|
|
|
|
return $div.html();
|
|
|
|
};
|
|
|
|
|
|
|
|
var getReadOnlyUrl = APP.getRO = function (href) {
|
|
|
|
if (!filesOp.isFile(href)) { return; }
|
|
|
|
var i = href.indexOf('#') + 1;
|
|
|
|
var hash = href.slice(i);
|
|
|
|
var base = href.slice(0, i);
|
|
|
|
var hrefsecret = Cryptpad.getSecrets(hash);
|
|
|
|
if (!hrefsecret.keys) { return; }
|
|
|
|
var viewHash = Cryptpad.getViewHashFromKeys(hrefsecret.channel, hrefsecret.keys);
|
|
|
|
return base + viewHash;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Disable middle click in the context menu to avoid opening /drive/inner.html# in new tabs
|
|
|
|
$(ifrw).click(function (e) {
|
|
|
|
if (!e.target || !$(e.target).parents('.cryptpad-dropdown').length) { return; }
|
|
|
|
if (e.which !== 1) {
|
|
|
|
e.stopPropagation();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
var getProperties = function (el) {
|
|
|
|
if (!filesOp.isFile(el)) { return; }
|
|
|
|
var ro = filesOp.isReadOnlyFile(el);
|
|
|
|
var base = window.location.origin;
|
|
|
|
var $d = $('<div>');
|
|
|
|
$('<strong>').text(Messages.fc_prop).appendTo($d);
|
|
|
|
$('<br>').appendTo($d);
|
|
|
|
if (!ro) {
|
|
|
|
$('<label>', {'for': 'propLink'}).text(Messages.editShare).appendTo($d);
|
|
|
|
$('<input>', {'id': 'propLink', 'readonly': 'readonly', 'value': base + el}).appendTo($d);
|
|
|
|
}
|
|
|
|
var roLink = ro ? base + el : getReadOnlyUrl(base + el);
|
|
|
|
if (roLink) {
|
|
|
|
$('<label>', {'for': 'propROLink'}).text(Messages.viewShare).appendTo($d);
|
|
|
|
$('<input>', {'id': 'propROLink', 'readonly': 'readonly', 'value': roLink}).appendTo($d);
|
|
|
|
}
|
|
|
|
return $d.html();
|
|
|
|
};
|
|
|
|
|
|
|
|
$contextMenu.on("click", "a", function(e) {
|
|
|
|
e.stopPropagation();
|
|
|
|
var paths = $(this).data('paths');
|
|
|
|
//var path = $(this).data('path');
|
|
|
|
//var $element = $(this).data('element');
|
|
|
|
if (paths.length === 0) {
|
|
|
|
log(Messages.fm_forbidden);
|
|
|
|
debug("Directory context menu on a forbidden or unexisting element. ", paths);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if ($(this).hasClass("rename")) {
|
|
|
|
if (paths.length !== 1) { return; }
|
|
|
|
displayRenameInput(paths[0].element, paths[0].path);
|
|
|
|
}
|
|
|
|
else if($(this).hasClass("delete")) {
|
|
|
|
var pathsList = [];
|
|
|
|
paths.forEach(function (p) { pathsList.push(p.path); });
|
|
|
|
moveElements(pathsList, [TRASH], false, refresh);
|
|
|
|
}
|
|
|
|
else if ($(this).hasClass('open')) {
|
|
|
|
paths.forEach(function (p) {
|
|
|
|
var $element = p.element;
|
|
|
|
$element.click();
|
|
|
|
$element.dblclick();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
else if ($(this).hasClass('open_ro')) {
|
|
|
|
paths.forEach(function (p) {
|
|
|
|
var el = filesOp.find(p.path);
|
|
|
|
if (filesOp.isFolder(el)) { return; }
|
|
|
|
var roUrl = getReadOnlyUrl(el);
|
|
|
|
openFile(roUrl, false);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
else if ($(this).hasClass('newfolder')) {
|
|
|
|
if (paths.length !== 1) { return; }
|
|
|
|
var onCreated = function (info) {
|
|
|
|
module.newFolder = info.newPath;
|
|
|
|
module.displayDirectory(paths[0].path);
|
|
|
|
};
|
|
|
|
filesOp.addFolder(paths[0].path, null, onCreated);
|
|
|
|
}
|
|
|
|
else if ($(this).hasClass("properties")) {
|
|
|
|
if (paths.length !== 1) { return; }
|
|
|
|
var el = filesOp.find(paths[0].path);
|
|
|
|
var prop = getProperties(el);
|
|
|
|
Cryptpad.alert('', undefined, true);
|
|
|
|
$('.alertify .msg').html(prop);
|
|
|
|
$('#propLink').click(function () { $(this).select(); });
|
|
|
|
$('#propROLink').click(function () { $(this).select(); });
|
|
|
|
}
|
|
|
|
module.hideMenu();
|
|
|
|
});
|
|
|
|
|
|
|
|
$defaultContextMenu.on("click", "a", function(e) {
|
|
|
|
e.stopPropagation();
|
|
|
|
var paths = $(this).data('paths');
|
|
|
|
if (paths.length === 0) {
|
|
|
|
log(Messages.fm_forbidden);
|
|
|
|
debug("Context menu on a forbidden or unexisting element. ", paths);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if ($(this).hasClass('open')) {
|
|
|
|
paths.forEach(function (p) {
|
|
|
|
var $element = p.element;
|
|
|
|
$element.dblclick();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
else if ($(this).hasClass('open_ro')) {
|
|
|
|
paths.forEach(function (p) {
|
|
|
|
var el = filesOp.find(p.path);
|
|
|
|
if (filesOp.isPathIn(p.path, [FILES_DATA])) { el = el.href; }
|
|
|
|
if (!el || filesOp.isFolder(el)) { return; }
|
|
|
|
var roUrl = getReadOnlyUrl(el);
|
|
|
|
openFile(roUrl, false);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
else if ($(this).hasClass('delete')) {
|
|
|
|
var pathsList = [];
|
|
|
|
paths.forEach(function (p) { pathsList.push(p.path); });
|
|
|
|
moveElements(pathsList, [TRASH], false, refresh);
|
|
|
|
}
|
|
|
|
else if ($(this).hasClass("properties")) {
|
|
|
|
if (paths.length !== 1) { return; }
|
|
|
|
var el = filesOp.find(paths[0].path);
|
|
|
|
var prop = getProperties(el);
|
|
|
|
Cryptpad.alert('', undefined, true);
|
|
|
|
$('.alertify .msg').html(prop);
|
|
|
|
$('#propLink').click(function () { $(this).select(); });
|
|
|
|
$('#propROLink').click(function () { $(this).select(); });
|
|
|
|
}
|
|
|
|
module.hideMenu();
|
|
|
|
});
|
|
|
|
|
|
|
|
$contentContextMenu.on('click', 'a', function (e) {
|
|
|
|
e.stopPropagation();
|
|
|
|
var path = $(this).data('path');
|
|
|
|
var onCreated = function (info) {
|
|
|
|
module.newFolder = info.newPath;
|
|
|
|
refresh();
|
|
|
|
};
|
|
|
|
if ($(this).hasClass("newfolder")) {
|
|
|
|
filesOp.addFolder(path, null, onCreated);
|
|
|
|
}
|
|
|
|
else if ($(this).hasClass("newdoc")) {
|
|
|
|
var type = $(this).data('type') || 'pad';
|
|
|
|
var name = Cryptpad.getDefaultName({type: type});
|
|
|
|
filesOp.addFile(path, name, type, onCreated);
|
|
|
|
}
|
|
|
|
module.hideMenu();
|
|
|
|
});
|
|
|
|
|
|
|
|
$trashTreeContextMenu.on('click', 'a', function (e) {
|
|
|
|
e.stopPropagation();
|
|
|
|
var paths = $(this).data('paths');
|
|
|
|
if (paths.length !== 1 || !paths[0].element || !filesOp.comparePath(paths[0].path, [TRASH])) {
|
|
|
|
log(Messages.fm_forbidden);
|
|
|
|
debug("Trash tree context menu on a forbidden or unexisting element. ", paths);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if ($(this).hasClass("empty")) {
|
|
|
|
Cryptpad.confirm(Messages.fm_emptyTrashDialog, function(res) {
|
|
|
|
if (!res) { return; }
|
|
|
|
filesOp.emptyTrash(refresh);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
module.hideMenu();
|
|
|
|
});
|
|
|
|
|
|
|
|
$trashContextMenu.on('click', 'a', function (e) {
|
|
|
|
e.stopPropagation();
|
|
|
|
var paths = $(this).data('paths');
|
|
|
|
if (paths.length === 0) {
|
|
|
|
log(Messages.fm_forbidden);
|
|
|
|
debug("Trash context menu on a forbidden or unexisting element. ", paths);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var path = paths[0].path;
|
|
|
|
var name = paths[0].path[paths[0].path.length - 1];
|
|
|
|
if ($(this).hasClass("remove")) {
|
|
|
|
if (paths.length === 1) {
|
|
|
|
if (path.length === 4) { name = path[1]; }
|
|
|
|
Cryptpad.confirm(Messages._getKey("fm_removePermanentlyDialog", [name]), function(res) {
|
|
|
|
if (!res) { return; }
|
|
|
|
filesOp.delete([path], refresh);
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var pathsList = [];
|
|
|
|
paths.forEach(function (p) { pathsList.push(p.path); });
|
|
|
|
var msg = Messages._getKey("fm_removeSeveralPermanentlyDialog", [paths.length]);
|
|
|
|
Cryptpad.confirm(msg, function(res) {
|
|
|
|
if (!res) { return; }
|
|
|
|
filesOp.delete(pathsList, refresh);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
else if ($(this).hasClass("restore")) {
|
|
|
|
if (paths.length !== 1) { return; }
|
|
|
|
if (path.length === 4) { name = path[1]; }
|
|
|
|
Cryptpad.confirm(Messages._getKey("fm_restoreDialog", [name]), function(res) {
|
|
|
|
if (!res) { return; }
|
|
|
|
filesOp.restore(path, refresh);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
else if ($(this).hasClass("properties")) {
|
|
|
|
if (paths.length !== 1 || path.length !== 4) { return; }
|
|
|
|
var element = filesOp.find(path.slice(0,3)); // element containing the oldpath
|
|
|
|
var sPath = stringifyPath(element.path);
|
|
|
|
Cryptpad.alert('<strong>' + Messages.fm_originalPath + "</strong>:<br>" + sPath, undefined, true);
|
|
|
|
}
|
|
|
|
module.hideMenu();
|
|
|
|
});
|
|
|
|
|
|
|
|
$appContainer.on('click', function (e) {
|
|
|
|
if (e.which !== 1) { return ; }
|
|
|
|
removeSelected(e);
|
|
|
|
removeInput();
|
|
|
|
module.hideMenu(e);
|
|
|
|
hideNewButton();
|
|
|
|
});
|
|
|
|
$appContainer.on('drag drop', function (e) {
|
|
|
|
removeInput();
|
|
|
|
module.hideMenu(e);
|
|
|
|
});
|
|
|
|
$appContainer.on('mouseup drop', function (e) {
|
|
|
|
$iframe.find('.droppable').removeClass('droppable');
|
|
|
|
});
|
|
|
|
$appContainer.on('keydown', function (e) {
|
|
|
|
// "Del"
|
|
|
|
if (e.which === 46) {
|
|
|
|
if (filesOp.isPathIn(currentPath, [FILES_DATA])) { return; } // We can't remove elements directly from filesData
|
|
|
|
var $selected = $iframe.find('.selected');
|
|
|
|
if (!$selected.length) { return; }
|
|
|
|
var paths = [];
|
|
|
|
var isTrash = filesOp.isPathIn(currentPath, [TRASH]);
|
|
|
|
$selected.each(function (idx, elmt) {
|
|
|
|
if (!$(elmt).data('path')) { return; }
|
|
|
|
paths.push($(elmt).data('path'));
|
|
|
|
});
|
|
|
|
// If we are in the trash or anon pad or if we are holding the "shift" key, delete permanently,
|
|
|
|
if (isTrash || e.shiftKey) {
|
|
|
|
var msg = Messages._getKey("fm_removeSeveralPermanentlyDialog", [paths.length]);
|
|
|
|
if (paths.length === 1) {
|
|
|
|
msg = Messages.fm_removePermanentlyDialog;
|
|
|
|
}
|
|
|
|
|
|
|
|
Cryptpad.confirm(msg, function(res) {
|
|
|
|
$(ifrw).focus();
|
|
|
|
if (!res) { return; }
|
|
|
|
filesOp.delete(paths, refresh);
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// else move to trash
|
|
|
|
moveElements(paths, [TRASH], false, refresh);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
$appContainer.contextmenu(function () {
|
|
|
|
module.hideMenu();
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
|
|
|
|
var onRefresh = {
|
|
|
|
refresh: function() {
|
|
|
|
if (onRefresh.to) {
|
|
|
|
window.clearTimeout(onRefresh.to);
|
|
|
|
}
|
|
|
|
onRefresh.to = window.setTimeout(refresh, 500);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
proxy.on('change', [], function (o, n, p) {
|
|
|
|
var path = arguments[2];
|
|
|
|
if (path[0] !== 'drive') { return false; }
|
|
|
|
path = path.slice(1);
|
|
|
|
var cPath = currentPath.slice();
|
|
|
|
if ((filesOp.isPathIn(cPath, ['hrefArray', TRASH]) && cPath[0] === path[0]) ||
|
|
|
|
(path.length >= cPath.length && filesOp.isSubpath(path, cPath))) {
|
|
|
|
// Reload after a few ms to make sure all the change events have been received
|
|
|
|
onRefresh.refresh();
|
|
|
|
} else if (path.length && path[0] === FILES_DATA) {
|
|
|
|
onRefresh.refresh();
|
|
|
|
}
|
|
|
|
module.resetTree();
|
|
|
|
return false;
|
|
|
|
}).on('remove', [], function (o, p) {
|
|
|
|
var path = arguments[1];
|
|
|
|
if (path[0] !== 'drive') { return false; }
|
|
|
|
path = path.slice(1);
|
|
|
|
var cPath = currentPath.slice();
|
|
|
|
if ((filesOp.isPathIn(cPath, ['hrefArray', TRASH]) && cPath[0] === path[0]) ||
|
|
|
|
(path.length >= cPath.length && filesOp.isSubpath(path, cPath))) {
|
|
|
|
// Reload after a few to make sure all the change events have been received
|
|
|
|
onRefresh.to = window.setTimeout(refresh, 500);
|
|
|
|
}
|
|
|
|
module.resetTree();
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
|
|
|
|
$iframe.find('#tree').mousedown(function (e) {
|
|
|
|
if (APP.mobile()) { return; }
|
|
|
|
if (APP.resizeTree) { return; }
|
|
|
|
APP.resizeTree = window.setInterval(function () {
|
|
|
|
$driveToolbar.find('.leftside').width($tree.width());
|
|
|
|
updatePathSize();
|
|
|
|
}, 100);
|
|
|
|
});
|
|
|
|
$appContainer.mouseup(function (e) {
|
|
|
|
window.clearInterval(APP.resizeTree);
|
|
|
|
APP.resizeTree = undefined;
|
|
|
|
});
|
|
|
|
|
|
|
|
var createReadme = function (proxy, cb) {
|
|
|
|
if (sessionStorage.createReadme) {
|
|
|
|
var hash = Cryptpad.createRandomHash();
|
|
|
|
Get.put(hash, Messages.driveReadme, function (e) {
|
|
|
|
if (e) { logError(e); }
|
|
|
|
var href = '/pad/#' + hash;
|
|
|
|
proxy.drive[UNSORTED].push(href);
|
|
|
|
proxy.drive[FILES_DATA].push({
|
|
|
|
href: href,
|
|
|
|
title: Messages.driveReadmeTitle,
|
|
|
|
atime: new Date().toISOString(),
|
|
|
|
ctime: new Date().toISOString()
|
|
|
|
});
|
|
|
|
if (typeof(cb) === "function") { cb(); }
|
|
|
|
});
|
|
|
|
delete sessionStorage.createReadme;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (typeof(cb) === "function") { cb(); }
|
|
|
|
};
|
|
|
|
|
|
|
|
createReadme(proxy, function () {
|
|
|
|
refresh();
|
|
|
|
APP.userList.onChange();
|
|
|
|
Cryptpad.removeLoadingScreen();
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
var setName = APP.setName = function (newName) {
|
|
|
|
if (typeof(newName) !== 'string') { return; }
|
|
|
|
var myUserNameTemp = newName.trim();
|
|
|
|
if(myUserNameTemp.length > 32) {
|
|
|
|
myUserNameTemp = myUserNameTemp.substr(0, 32);
|
|
|
|
}
|
|
|
|
var myUserName = myUserNameTemp;
|
|
|
|
Cryptpad.setAttribute('username', myUserName, function (err, data) {
|
|
|
|
if (err) {
|
|
|
|
logError("Couldn't set username", err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (myUserName === "") {
|
|
|
|
myUserName = Messages.anonymous;
|
|
|
|
}
|
|
|
|
APP.$displayName.text(myUserName);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
var migrateAnonDrive = function (proxy, cb) {
|
|
|
|
if (sessionStorage.migrateAnonDrive) {
|
|
|
|
Merge.anonDriveIntoUser(proxy, function () {
|
|
|
|
delete sessionStorage.migrateAnonDrive;
|
|
|
|
if (typeof(cb) === "function") { cb(); }
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
if (typeof(cb) === "function") { cb(); }
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// don't initialize until the store is ready.
|
|
|
|
Cryptpad.ready(function () {
|
|
|
|
Cryptpad.reportAppUsage();
|
|
|
|
APP.$bar = $iframe.find('#toolbar');
|
|
|
|
|
|
|
|
var storeObj = Cryptpad.getStore().getProxy && Cryptpad.getStore().getProxy().proxy ? Cryptpad.getStore().getProxy() : undefined;
|
|
|
|
|
|
|
|
if (window.location.hash && window.location.hash === "#iframe") {
|
|
|
|
$iframe.find('body').addClass('iframe');
|
|
|
|
window.location.hash = "";
|
|
|
|
APP.homePageIframe = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
var hash = window.location.hash.slice(1) || Cryptpad.getUserHash() || localStorage.FS_hash;
|
|
|
|
var secret = Cryptpad.getSecrets(hash);
|
|
|
|
var readOnly = APP.readOnly = secret.keys && !secret.keys.editKeyStr;
|
|
|
|
|
|
|
|
var listmapConfig = module.config = {
|
|
|
|
data: {},
|
|
|
|
websocketURL: Cryptpad.getWebsocketURL(),
|
|
|
|
channel: secret.channel,
|
|
|
|
readOnly: readOnly,
|
|
|
|
validateKey: secret.keys.validateKey || undefined,
|
|
|
|
crypto: Crypto.createEncryptor(secret.keys),
|
|
|
|
logging: false
|
|
|
|
};
|
|
|
|
|
|
|
|
var proxy;
|
|
|
|
if (storeObj && !window.location.hash.slice(1)) { proxy = storeObj.proxy; }
|
|
|
|
else {
|
|
|
|
var rt = window.rt = module.rt = Listmap.create(listmapConfig);
|
|
|
|
proxy = rt.proxy;
|
|
|
|
}
|
|
|
|
var onCreate = function (info) {
|
|
|
|
var realtime = module.realtime = info.realtime;
|
|
|
|
|
|
|
|
var editHash = APP.editHash = !readOnly ? Cryptpad.getEditHashFromKeys(info.channel, secret.keys) : undefined;
|
|
|
|
var viewHash = APP.viewHash = Cryptpad.getViewHashFromKeys(info.channel, secret.keys);
|
|
|
|
|
|
|
|
APP.hash = readOnly ? viewHash : editHash;
|
|
|
|
if (!readOnly && !localStorage.FS_hash && !Cryptpad.getUserHash() && !window.location.hash) {
|
|
|
|
localStorage.FS_hash = editHash;
|
|
|
|
}
|
|
|
|
|
|
|
|
module.patchText = TextPatcher.create({
|
|
|
|
realtime: realtime,
|
|
|
|
logging: true,
|
|
|
|
});
|
|
|
|
|
|
|
|
var userList = APP.userList = info.userList;
|
|
|
|
var config = {
|
|
|
|
displayed: ['useradmin', 'spinner', 'lag', 'state'],
|
|
|
|
readOnly: readOnly,
|
|
|
|
ifrw: window,
|
|
|
|
common: Cryptpad,
|
|
|
|
hideShare: true
|
|
|
|
};
|
|
|
|
var toolbar = APP.toolbar = info.realtime.toolbar = Toolbar.create(APP.$bar, info.myID, info.realtime, info.getLag, userList, config);
|
|
|
|
|
|
|
|
var $bar = APP.$bar;
|
|
|
|
var $rightside = $bar.find('.' + Toolbar.constants.rightside);
|
|
|
|
var $userBlock = $bar.find('.' + Toolbar.constants.userAdmin);
|
|
|
|
APP.$displayName = $bar.find('.' + Toolbar.constants.username);
|
|
|
|
|
|
|
|
if (APP.homePageIframe) {
|
|
|
|
var $linkToMain = $bar.find('.cryptpad-link a');
|
|
|
|
$linkToMain.attr('href', '#');
|
|
|
|
$linkToMain.attr('title', '');
|
|
|
|
$linkToMain.css('cursor', 'default');
|
|
|
|
$linkToMain.off('click');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!readOnly && !APP.loggedIn) {
|
|
|
|
var $backupButton = Cryptpad.createButton('', true).removeClass('fa').removeClass('fa-question');
|
|
|
|
$backupButton.append($backupIcon.clone().css('marginRight', '0px'));
|
|
|
|
$backupButton.attr('title', Messages.fm_backup_title);
|
|
|
|
$backupButton.on('click', function() {
|
|
|
|
var url = window.location.origin + window.location.pathname + '#' + editHash;
|
|
|
|
var msg = Messages.fm_alert_backupUrl + '<input type="text" readonly="readonly" id="fm_backupUrl" value="'+url+'">';
|
|
|
|
Cryptpad.alert(msg, undefined, true);
|
|
|
|
$('#fm_backupUrl').val(url);
|
|
|
|
$('#fm_backupUrl').click(function () {
|
|
|
|
$(this).select();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
$userBlock.append($backupButton);
|
|
|
|
}
|
|
|
|
|
|
|
|
Cryptpad.onDisplayNameChanged(setName);
|
|
|
|
};
|
|
|
|
var onReady = function () {
|
|
|
|
module.files = proxy;
|
|
|
|
if (!proxy.drive || typeof(proxy.drive) !== 'object') { proxy.drive = {}; }
|
|
|
|
migrateAnonDrive(proxy, function () {
|
|
|
|
initLocalStorage();
|
|
|
|
init(proxy);
|
|
|
|
APP.userList.onChange();
|
|
|
|
Cryptpad.removeLoadingScreen();
|
|
|
|
});
|
|
|
|
};
|
|
|
|
var onDisconnect = function (info) {
|
|
|
|
setEditable(false);
|
|
|
|
if (APP.refresh) { APP.refresh(); }
|
|
|
|
APP.toolbar.failed();
|
|
|
|
Cryptpad.alert(Messages.common_connectionLost, undefined, true);
|
|
|
|
};
|
|
|
|
var onReconnect = function (info) {
|
|
|
|
setEditable(true);
|
|
|
|
if (APP.refresh) { APP.refresh(); }
|
|
|
|
APP.toolbar.reconnecting(info.myId);
|
|
|
|
Cryptpad.findOKButton().click();
|
|
|
|
};
|
|
|
|
|
|
|
|
if (storeObj && !window.location.hash) {
|
|
|
|
onCreate(storeObj.info);
|
|
|
|
onReady();
|
|
|
|
} else {
|
|
|
|
proxy.on('create', function (info) {
|
|
|
|
onCreate(info);
|
|
|
|
}).on('ready', function () {
|
|
|
|
onReady();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
proxy.on('disconnect', function () {
|
|
|
|
onDisconnect();
|
|
|
|
});
|
|
|
|
proxy.on('reconnect', function (info) {
|
|
|
|
onReconnect(info);
|
|
|
|
});
|
|
|
|
|
|
|
|
Cryptpad.onLogout(function () { setEditable(false); });
|
|
|
|
});
|
|
|
|
Cryptpad.onError(function (info) {
|
|
|
|
if (info) {
|
|
|
|
onConnectError();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|