cryptpad/www/common/drive-ui.js

5822 lines
246 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later
define([
'jquery',
'/api/config',
'/common/toolbar.js',
'json.sortify',
'/common/common-util.js',
'/common/common-hash.js',
'/common/common-ui-elements.js',
'/common/common-interface.js',
'/common/common-constants.js',
'/common/common-feedback.js',
'/common/inner/share.js',
'/common/inner/access.js',
'/common/inner/properties.js',
'/components/nthen/index.js',
'/common/hyperscript.js',
'/common/proxy-manager.js',
'/customize/application_config.js',
'/customize/messages.js',
'/customize/pages.js',
'/common/pad-types.js',
'/common/onlyoffice/broken-formats.js',
'/common/common-icons.js'
], function (
$,
ApiConfig,
Toolbar,
JSONSortify,
Util,
Hash,
UIElements,
UI,
Constants,
Feedback,
Share,
Access,
Properties,
nThen,
h,
ProxyManager,
AppConfig,
Messages,
Pages,
PadTypes,
BrokenFormats,
Icons)
{
var APP = window.APP = {
editable: false,
online: false,
mobile: function () {
if (window.matchMedia) { return !window.matchMedia('(any-pointer:fine)').matches; }
else { return $('body').width() <= 600; }
},
isMac: navigator.platform === "MacIntel",
allowFolderUpload: File.prototype.hasOwnProperty("webkitRelativePath"),
};
var onConnectEvt = Util.mkEvent(true);
var stringify = function (obj) {
return JSONSortify(obj);
};
var E_OVER_LIMIT = 'E_OVER_LIMIT';
var ROOT = "root";
var ROOT_NAME = Messages.fm_rootName;
var SEARCH = "search";
var SEARCH_NAME = Messages.fm_searchName;
var TRASH = "trash";
var TRASH_NAME = Messages.fm_trashName;
var FILES_DATA = Constants.storageKey;
var FILES_DATA_NAME = Messages.fm_filesDataName;
var TEMPLATE = "template";
var TEMPLATE_NAME = Messages.fm_templateName;
var RECENT = "recent";
var RECENT_NAME = Messages.fm_recentPadsName;
var OWNED = "owned";
var OWNED_NAME = Messages.fm_ownedPadsName;
var TAGS = "tags";
var TAGS_NAME = Messages.fm_tagsName;
var SHARED_FOLDER = 'sf';
var SHARED_FOLDER_NAME = Messages.fm_sharedFolderName;
var FILTER = "filter";
// Icons
var folder = 'folder';
var folderOpen = 'folder-open';
var sharedFolder = 'drive-shared-folder';
var expandAll = 'expand';
var collapseAll = 'collapse';
var readOnly = 'read-only';
var preview = 'preview';
var rename = 'rename';
var palette = 'color-palette';
var trashFull = 'trash-full';
var copy = 'copy';
var destroy = 'destroy';
var access = 'access';
var properties = 'properties';
var tags = 'tag';
var uploadFile = 'drive-upload-file';
var uploadFolder = 'drive-upload-folder';
var trashEmpty = 'trash-empty';
var restore = 'restore';
var showParent = 'location';
var download = 'download';
var $folderIcon = $(Icons.get(folder,{"class": "cp-app-drive-icon-folder cp-app-drive-content-icon"}));
var $fileMenuIcon = $(Icons.get('ellipsis-horizontal'));
var $folderEmptyIcon = $folderIcon.clone();
var $folderOpenedIcon = $(Icons.get('folder-open', {"class": "cp-app-drive-icon-folder cp-app-drive-content-icon"}));
var $folderOpenedEmptyIcon = $folderOpenedIcon.clone();
var $sharedFolderIcon = $(Icons.get('drive-shared-folder', {"class": "cp-app-drive-icon-folder"}));
var $sharedFolderOpenedIcon = $(Icons.get('folder-open', {"class": "cp-app-drive-icon-folder"}));
var $unsortedIcon = $(Icons.get('copy'));
var $templateIcon = $(Icons.get('file-template'));
var $recentIcon = $(Icons.get('drive-recent'));
var $trashIcon = $(Icons.get('trash-full'));
var $trashEmptyIcon = $(Icons.get('trash-empty'));
var $sortAscIcon = $(Icons.get('sort-asc', {'class': 'sortasc'}));
var $sortDescIcon = $(Icons.get('sort-desc', {'class': 'sortdesc'}));
var $closeIcon = $(Icons.get('close'));
var $searchIcon = $($(Icons.get('search', {'class': 'cp-app-drive-tree-search-icon'})));
var $addIcon = $(Icons.get('add'));
var $renamedIcon = $(Icons.get('renamed'));
var $readonlyIcon = $(Icons.get(readOnly));
var $ownedIcon = $(Icons.get('document-owner'));
var $sharedIcon = $(Icons.get('share'));
var $tagsIcon = $(Icons.get(tags));
var $passwordIcon = $(Icons.get('drive-password-document'));
var $restrictedIcon = $(Icons.get('restricted'));
var $expirableIcon = $(Icons.get('expire'));
var $separator = $('<div>', {"class": "dropdown-divider"});
var LS_VIEWMODE = "app-drive-viewMode";
var FOLDER_CONTENT_ID = "cp-app-drive-content-folder";
var config = {};
var DEBUG = config.DEBUG = false;
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 = UI.log;
var localStore = window.cryptpadStore;
APP.store = {};
$(window).keydown(function (e) {
if (e.which === 70 && e.ctrlKey) {
e.preventDefault();
e.stopPropagation();
if (APP.displayDirectory) {
APP.displayDirectory([SEARCH]);
}
return;
}
});
var makeLS = function (teamId) {
var suffix = teamId ? ('-' + teamId) : '';
var LS_LAST = "app-drive-lastOpened" + suffix;
var LS_OPENED = "app-drive-openedFolders" + suffix;
var LS = {};
LS.getLastOpenedFolder = function () {
var path;
try {
path = APP.store[LS_LAST] ? JSON.parse(APP.store[LS_LAST]) : [ROOT];
} catch (e) {
path = [ROOT];
}
return path;
};
LS.setLastOpenedFolder = function (path) {
if (path[0] === SEARCH) { return; }
APP.store[LS_LAST] = JSON.stringify(path);
localStore.put(LS_LAST, JSON.stringify(path));
};
LS.wasFolderOpened = function (path) {
var stored = JSON.parse(APP.store[LS_OPENED] || '[]');
return stored.indexOf(JSON.stringify(path)) !== -1;
};
LS.getOpenedFolders = function () {
var stored = JSON.parse(APP.store[LS_OPENED] || '[]');
return stored.map(function(s) {
try {
return JSON.parse(s);
} catch (e) {
return null;
}
}).filter(function(p) {
return p !== null;
});
};
LS.setFolderOpened = function (path, opened) {
var s = JSON.stringify(path);
var stored = JSON.parse(APP.store[LS_OPENED] || '[]');
if (opened && stored.indexOf(s) === -1) {
stored.push(s);
}
if (!opened) {
var idx = stored.indexOf(s);
if (idx !== -1) {
stored.splice(idx, 1);
}
}
APP.store[LS_OPENED] = JSON.stringify(stored);
localStore.put(LS_OPENED, JSON.stringify(stored));
};
LS.removeFoldersOpened = function (parentPath) {
var stored = JSON.parse(APP.store[LS_OPENED] || '[]');
var s = JSON.stringify(parentPath).slice(0, -1);
for (var i = stored.length - 1 ; i >= 0 ; i--) {
if (stored[i].indexOf(s) === 0) {
stored.splice(i, 1);
}
}
APP.store[LS_OPENED] = JSON.stringify(stored);
localStore.put(LS_OPENED, JSON.stringify(stored));
};
LS.renameFoldersOpened = function (parentPath, newName) {
var stored = JSON.parse(APP.store[LS_OPENED] || '[]');
var s = JSON.stringify(parentPath).slice(0, -1);
var newParentPath = parentPath.slice();
newParentPath[newParentPath.length - 1] = newName;
var sNew = JSON.stringify(newParentPath).slice(0, -1);
for (var i = 0 ; i < stored.length ; i++) {
if (stored[i].indexOf(s) === 0) {
stored[i] = stored[i].replace(s, sNew);
}
}
APP.store[LS_OPENED] = JSON.stringify(stored);
localStore.put(LS_OPENED, JSON.stringify(stored));
};
LS.moveFoldersOpened = function (previousPath, newPath) {
var stored = JSON.parse(APP.store[LS_OPENED] || '[]');
var s = JSON.stringify(previousPath).slice(0, -1);
var sNew = JSON.stringify(newPath).slice(0, -1);
if (s === sNew ) { return; } // move to itself
if (sNew.indexOf(s) === 0) { return; } // move to subfolder
sNew = JSON.stringify(newPath.concat(previousPath[previousPath.length - 1])).slice(0, -1);
for (var i = 0 ; i < stored.length ; i++) {
if (stored[i].indexOf(s) === 0) {
stored[i] = stored[i].replace(s, sNew);
}
}
APP.store[LS_OPENED] = JSON.stringify(stored);
localStore.put(LS_OPENED, JSON.stringify(stored));
};
return LS;
};
var getViewModeClass = function (forceList) {
var mode = APP.store[LS_VIEWMODE];
if (mode === 'list' || forceList) { return 'cp-app-drive-content-list'; }
return 'cp-app-drive-content-grid';
};
var getViewMode = function () {
return APP.store[LS_VIEWMODE] || 'grid';
};
var setViewMode = function (mode) {
if (typeof(mode) !== "string") {
logError("Incorrect view mode: ", mode);
return;
}
APP.store[LS_VIEWMODE] = mode;
localStore.put(LS_VIEWMODE, mode);
};
// Handle disconnect/reconnect
// If isHistory and isSf are both false, update the "APP.online" flag
// If isHistory is true, update the "APP.history" flag
// isSf is used to detect offline shared folders: setEditable is called on displayDirectory
var setEditable = function (state, isHistory, isSf) {
if (APP.closed || !APP.$content || !$.contains(document.documentElement, APP.$content[0])) { return; }
if (isHistory) {
APP.history = !state;
} else if (!isSf) {
APP.online = state;
}
state = APP.online && !APP.history && state;
APP.editable = !APP.readOnly && state;
if (APP.editable) { onConnectEvt.fire(); }
if (!state) {
APP.$content.addClass('cp-app-drive-readonly');
if (!APP.history || !APP.online) {
$('#cp-app-drive-connection-state').show();
} else {
$('#cp-app-drive-connection-state').hide();
}
$('[draggable="true"]').attr('draggable', false);
}
else {
APP.$content.removeClass('cp-app-drive-readonly');
$('#cp-app-drive-connection-state').hide();
$('[draggable="false"]').attr('draggable', true);
}
};
var copyObjectValue = function (objRef, objToCopy) {
for (var k in objRef) { delete objRef[k]; }
$.extend(true, objRef, objToCopy);
};
APP.selectedFiles = [];
var findElementId = function ($element) {
var isTrashed = $element.data("path")[0] === TRASH;
let elementId;
if (isTrashed) {
elementId = $element.data("path").join(',');
} else {
elementId = $element.data("path").slice(-1)[0];
}
return elementId;
};
var isElementSelected = function ($element) {
var elementId = findElementId($element);
return APP.selectedFiles.indexOf(elementId) !== -1;
};
var selectElement = function ($element) {
var elementId = findElementId($element);
if (APP.selectedFiles.indexOf(elementId) === -1) {
APP.selectedFiles.push(elementId);
}
$element.addClass("cp-app-drive-element-selected");
};
var unselectElement = function ($element) {
var elementId = findElementId($element);
var index = APP.selectedFiles.indexOf(elementId);
if (index !== -1) {
APP.selectedFiles.splice(index, 1);
}
$element.removeClass("cp-app-drive-element-selected");
};
var findSelectedElements = function () {
return $(".cp-app-drive-element-selected");
};
var getNewPadTypes = function () {
var arr = [];
PadTypes.availableTypes.forEach(function (type) {
if (AppConfig.hiddenTypes.indexOf(type) !== -1) { return; }
if (!APP.loggedIn && AppConfig.registeredOnlyTypes &&
AppConfig.registeredOnlyTypes.indexOf(type) !== -1) {
return;
}
arr.push(type);
});
return arr;
};
var createContextMenu = function (common) {
var metadataMgr = common.getMetadataMgr();
var priv = metadataMgr.getPrivateData();
APP.premiumPlan = priv.plan;
var getOpenIn = function (app) {
var icon = AppConfig.applicationsIcon[app] || app;
var iconHtml = Icons.get(icon).outerHTML;
var html = iconHtml + Messages.type[app];
return Messages._getKey('fc_openIn', [html]);
};
var restricted = {};
var enabled = [];
var isAppEnabled = function (app) {
return enabled.includes(app);
};
getNewPadTypes().forEach(function (app) {
if (!Array.isArray(AppConfig.availablePadTypes)) { return void enabled.push(app); }
var registered = common.isLoggedIn() || !(AppConfig.registeredOnlyTypes || []).includes(app);
restricted[app] = common.checkRestrictedApp(app);
var e = PadTypes.isAvailable(app) && registered && restricted[app] >= 0;
if (e) { enabled.push(app); }
});
var menu = h('div.cp-contextmenu.dropdown.cp-unselectable', [
h('ul.dropdown-menu', {
'role': 'menu',
'aria-labelledby': 'dropdownMenu',
'style': 'display:block;position:static;margin-bottom:5px;'
}, [
h('span.cp-app-drive-context-noAction.dropdown-item.disabled', Messages.fc_noAction || "No action possible"),
h('li', h('a.cp-app-drive-context-preview.dropdown-item', {
'tabindex': '-1',
}, [
Icons.get(preview),
Messages.pad_mediatagPreview
])),
h('li', h('a.cp-app-drive-context-open.dropdown-item', {
'tabindex': '-1',
}, [
Icons.get(folderOpen),
Messages.fc_open
]
)),
h('li', h('a.cp-app-drive-context-openfolder.dropdown-item', {
'tabindex': '-1',
}, [
Icons.get(folderOpen),
Messages.fc_open
])),
h('li', h('a.cp-app-drive-context-openro.dropdown-item', {
'tabindex': '-1',
}, [
Icons.get(readOnly),
h('span.cp-text', Messages.fc_open_ro)
])),
isAppEnabled('code') ? h('li', UI.setHTML(h('a.cp-app-drive-context-openincode.dropdown-item', {
'tabindex': '-1',
}, [Icons.get('select')]), getOpenIn('code'))) : undefined,
isAppEnabled('sheet') ? h('li', UI.setHTML(h('a.cp-app-drive-context-openinsheet.dropdown-item', {
'tabindex': '-1',
}, [Icons.get('select')]), getOpenIn('sheet'))) : undefined,
isAppEnabled('doc') ? h('li', UI.setHTML(h('a.cp-app-drive-context-openindoc.dropdown-item' + (restricted.doc === 0 ? '.cp-app-disabled' : ''), {
'tabindex': '-1',
}, [Icons.get('select')]), getOpenIn('doc'))) : undefined,
isAppEnabled('presentation') ? h('li', UI.setHTML(h('a.cp-app-drive-context-openinpresentation.dropdown-item' + (restricted.presentation === 0 ? '.cp-app-disabled' : ''), {
'tabindex': '-1',
}, [Icons.get('select')]), getOpenIn('presentation'))) : undefined,
h('li', h('a.cp-app-drive-context-savelocal.dropdown-item', {
'tabindex': '-1',
}, [
Icons.get('cloud-upload'),
Messages.pad_mediatagImport
])), // Save in your CryptDrive
$separator.clone()[0],
h('li', h('a.cp-app-drive-context-expandall.dropdown-item', {
'tabindex': '-1',
}, [
Icons.get(expandAll),
Messages.fc_expandAll
])),
h('li', h('a.cp-app-drive-context-collapseall.dropdown-item', {
'tabindex': '-1',
}, [
Icons.get(collapseAll),
Messages.fc_collapseAll
])),
$separator.clone()[0],
h('li', h('a.cp-app-drive-context-openparent.dropdown-item', {
'tabindex': '-1',
}, [
Icons.get(showParent),
Messages.fm_openParent
])),
$separator.clone()[0],
h('li', h('a.cp-app-drive-context-share.dropdown-item', {
'tabindex': '-1',
}, [
Icons.get('share'),
Messages.shareButton
])),
h('li', h('a.cp-app-drive-context-access.dropdown-item', {
'tabindex': '-1',
}, [
Icons.get(access),
Messages.accessButton
])),
$separator.clone()[0],
h('li', h('a.cp-app-drive-context-newfolder.dropdown-item.cp-app-drive-context-editable', {
'tabindex': '-1',
},[
Icons.get(folder),
Messages.fc_newfolder
])),
h('li', h('a.cp-app-drive-context-newsharedfolder.dropdown-item.cp-app-drive-context-editable', {
'tabindex': '-1',
},[
Icons.get(sharedFolder),
Messages.fc_newsharedfolder
])),
$separator.clone()[0],
h('li', h('a.cp-app-drive-context-uploadfiles.dropdown-item.cp-app-drive-context-editable', {
'tabindex': '-1',
},[
Icons.get(uploadFile),
Messages.uploadButton
])),
h('li', h('a.cp-app-drive-context-uploadfolder.dropdown-item.cp-app-drive-context-editable', {
'tabindex': '-1',
}, [
Icons.get(uploadFolder),
Messages.uploadFolderButton
])),
$separator.clone()[0],
h('li', h('a.cp-app-drive-context-newdoc.dropdown-item.cp-app-drive-context-editable', {
'tabindex': '-1',
},[
Icons.get('link'),
Messages.fm_link_new
])),
h('li.dropdown-submenu', [
h('a.cp-app-drive-context-newdocmenu.dropdown-item', {
'tabindex': '-1',
},[
Icons.get('add'),
Messages.fm_newFile
]),
h("ul.dropdown-menu", getNewPadTypes().map(function (app) {
return isAppEnabled(app) ? h('li', h('a.cp-app-drive-context-newdoc.dropdown-item.cp-app-drive-context-editable' + (restricted[app] === 0 ? '.cp-app-disabled' : ''), {
'tabindex': '-1',
'data-type': app,
},[
Icons.get(AppConfig.applicationsIcon[app] || 'file'),
Messages.type[app]
])) : undefined;
})),
]),
$separator.clone()[0],
h('li', h('a.cp-app-drive-context-empty.dropdown-item.cp-app-drive-context-editable', {
'tabindex': '-1',
}, [
Icons.get(trashEmpty),
Messages.fc_empty
])),
h('li', h('a.cp-app-drive-context-restore.dropdown-item.cp-app-drive-context-editable', {
'tabindex': '-1',
'data-icon': restore,
}, [
Icons.get(restore),
Messages.fc_restore
])),
$separator.clone()[0],
h('li', h('a.cp-app-drive-context-rename.dropdown-item.cp-app-drive-context-editable', {
'tabindex': '-1',
}, [
Icons.get(rename),
Messages.fc_rename
])),
h('li', h('a.cp-app-drive-context-color.dropdown-item.cp-app-drive-context-editable', {
'tabindex': '-1',
}, [
Icons.get(palette),
Messages.fc_color
])),
h('li', h('a.cp-app-drive-context-hashtag.dropdown-item.cp-app-drive-context-editable', {
'tabindex': '-1',
}, [
Icons.get(tags),
Messages.fc_hashtag
])),
$separator.clone()[0],
h('li', h('a.cp-app-drive-context-makeacopy.dropdown-item.cp-app-drive-context-editable', {
'tabindex': '-1',
}, [
Icons.get(copy),
Messages.makeACopy
])),
h('li', h('a.cp-app-drive-context-download.dropdown-item', {
'tabindex': '-1',
}, [
Icons.get(download),
Messages.download_mt_button
])),
h('li', h('a.cp-app-drive-context-delete.dropdown-item.cp-app-drive-context-editable', {
'tabindex': '-1',
}, [
Icons.get(trashFull),
Messages.fc_delete
])), // "Move to trash"
h('li', h('a.cp-app-drive-context-deleteowned.dropdown-item.cp-app-drive-context-editable', {
'tabindex': '-1',
}, [
Icons.get(destroy),
Messages.fc_delete_owned
])),
h('li', h('a.cp-app-drive-context-remove.dropdown-item.cp-app-drive-context-editable', {
'tabindex': '-1',
}, [
Icons.get(trashFull),
Messages.fc_remove
] )),
h('li', h('a.cp-app-drive-context-removesf.dropdown-item.cp-app-drive-context-editable', {
'tabindex': '-1',
}, [
Icons.get(trashFull),
Messages.fc_remove_sharedfolder
])),
$separator.clone()[0],
h('li', h('a.cp-app-drive-context-properties.dropdown-item', {
'tabindex': '-1',
},[
Icons.get(properties),
Messages.fc_prop
]))
])
]);
// add events handlers for the contextmenu submenus
$(menu).find(".dropdown-submenu").each(function (i, el) {
var $el = $(el);
var $a = $el.children().filter("a");
var $sub = $el.find(".dropdown-menu").first();
var left, bottomOffset;
var timeoutId;
var showSubmenu = function () {
clearTimeout(timeoutId);
left = $el.offset().left + $el.outerWidth() + $sub.outerWidth() > $(window).width();
bottomOffset = $el.offset().top + $el.outerHeight() + $sub.outerHeight() - $(window).height();
$sub.css("left", left ? "0%": "100%");
$sub.css("transform", "translate("
+ (left ? "-100%" : "0")
+ "," + (bottomOffset > 0 ? -(bottomOffset - $el.outerHeight()) : 0) + "px)");
$el.siblings().find(".dropdown-menu").hide();
$sub.show();
};
var hideSubmenu = function () {
$sub.hide();
$sub.removeClass("left");
};
var mouseOutSubmenu = function () {
// don't hide immediately the submenu
timeoutId = setTimeout(hideSubmenu, 100);
};
// Add submenu expand icon
$a.append(h("span", [Icons.get('expand-menu')]));
// Show / hide submenu
$el.hover(function () {
showSubmenu();
}, function () {
mouseOutSubmenu();
});
// handle click event
$el.click(function (e) {
var targetItem = $(e.target).closest(".dropdown-item")[0]; // don't close contextmenu if open submenu
var elTarget = $el.children(".dropdown-item")[0];
if (targetItem === elTarget) { e.stopPropagation(); }
if ($el.children().filter(".dropdown-menu:visible").length !== 0) {
$el.find(".dropdown-menu").hide();
hideSubmenu();
}
else {
showSubmenu();
}
});
});
return $(menu);
};
var create = function (common, driveConfig) { //proxy, folders) {
var metadataMgr = common.getMetadataMgr();
var sframeChan = common.getSframeChannel();
var priv = metadataMgr.getPrivateData();
// Initialization
Util.extend(APP, driveConfig.APP);
APP.$limit = driveConfig.$limit;
var proxy = driveConfig.proxy;
var folders = driveConfig.folders;
var files = proxy.drive;
var history = driveConfig.history || {};
var edPublic = driveConfig.edPublic || priv.edPublic;
config.editKey = driveConfig.editKey;
APP.origin = priv.origin;
APP.hideDuplicateOwned = Util.find(priv, ['settings', 'drive', 'hideDuplicate']);
APP.closed = false;
APP.toolbar = driveConfig.toolbar;
var $readOnly = $(h('div.cp-banner.cp-banner-info.cp-app-drive-content-info-box', Messages.readonly));
var updateObject = driveConfig.updateObject;
var updateSharedFolders = driveConfig.updateSharedFolders;
// manager
config.loggedIn = APP.loggedIn;
config.sframeChan = sframeChan;
var manager = ProxyManager.createInner(files, sframeChan, edPublic, config);
var LS = makeLS(APP.team);
Object.keys(folders).forEach(function (id) {
var f = folders[id];
var sfData = files.sharedFolders[id] || {};
var href = manager.user.userObject.getHref(sfData);
var parsed = Hash.parsePadUrl(href);
var secret = Hash.getSecrets('drive', parsed.hash, sfData.password);
manager.addProxy(id, {proxy: f}, null, secret.keys.secondaryKey);
});
// UI containers
var $tree = APP.$tree = $("#cp-app-drive-tree");
var $content = APP.$content = $("#cp-app-drive-content");
var $contentContainer = $("#cp-app-drive-content-container");
var $appContainer = $(".cp-app-drive-container");
var $driveToolbar = APP.toolbar.$bottom;
var $contextMenu = createContextMenu(common).appendTo($appContainer);
var $contentContextMenu = $("#cp-app-drive-context-content");
var $defaultContextMenu = $("#cp-app-drive-context-default");
var $trashTreeContextMenu = $("#cp-app-drive-context-trashtree");
var $trashContextMenu = $("#cp-app-drive-context-trash");
$content.attr("tabindex", "0");
if (APP.loggedIn) {
var splitter = h('div.cp-splitter', [
Icons.get('ellipsis-vertical')
]);
$contentContainer.append(splitter);
APP.$splitter = $(splitter).on('mousedown touchstart', function (e) {
e.preventDefault();
var x = e.type === 'touchstart' ? e.originalEvent.touches[0].pageX : e.pageX;
var w = $tree.width();
var handler = function (evt) {
if (evt.type === 'mouseup' || evt.type === 'touchend') {
$(window).off('mouseup mousemove touchend touchmove', handler);
return;
}
var pageX = evt.type === 'touchmove'
? evt.originalEvent.touches[0].pageX
: evt.pageX;
$tree.css('width', (w - x + pageX) + 'px');
};
$(window).off('mouseup mousemove touchend touchmove', handler, { passive: false });
$(window).on('mouseup mousemove touchend touchmove', handler);
});
}
// TOOLBAR
// DRIVE
var currentPath = APP.currentPath = LS.getLastOpenedFolder();
if (APP.newSharedFolder) {
var newSFPaths = manager.findFile(Number(APP.newSharedFolder));
if (newSFPaths.length) {
currentPath = newSFPaths[0];
}
}
// Categories dislayed in the menu
var displayedCategories = [ROOT, TRASH, SEARCH, RECENT];
// PCS enabled: display owned pads
//if (AppConfig.displayCreationScreen) { displayedCategories.push(OWNED); }
// Templates enabled: display template category
if (AppConfig.enableTemplates) { displayedCategories.push(TEMPLATE); }
// Tags used: display Tags category
if (Object.keys(manager.getTagsList()).length) { displayedCategories.push(TAGS); }
var virtualCategories = [SEARCH, RECENT, OWNED, TAGS];
if (!APP.loggedIn) {
$tree.hide();
if (APP.newSharedFolder) {
// ANON_SHARED_FOLDER
displayedCategories = [SHARED_FOLDER];
virtualCategories.push(SHARED_FOLDER);
currentPath = [SHARED_FOLDER, ROOT];
} else {
displayedCategories = [FILES_DATA];
currentPath = [FILES_DATA];
}
} else if (priv.isEmbed && APP.newSharedFolder) {
displayedCategories = [ROOT, TRASH];
}
APP.editable = !APP.readOnly;
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('.cp-app-drive-element-row') ? $el : $el.closest('.cp-app-drive-element-row');
};
// Selection
var sel = {};
var removeSelected = function (keepObj) {
APP.selectedFiles = [];
findSelectedElements().removeClass("cp-app-drive-element-selected");
if (!keepObj) {
delete sel.startSelected;
delete sel.endSelected;
delete sel.oldSelection;
}
};
sel.refresh = 50;
sel.$selectBox = $('<div>', {'class': 'cp-app-drive-content-select-box'}).appendTo($content);
var checkSelected = function () {
if (!sel.down) { return; }
var pos = sel.pos;
var l = $content[0].querySelectorAll('.cp-app-drive-element:not(.cp-app-drive-element-selected):not(.cp-app-drive-element-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('cp-app-drive-element-selected-tmp');
} else {
$(el).addClass('cp-app-drive-element-selected-tmp');
}
}
};
$content.on('mousedown', function (e) {
if (e.which !== 1) { return; }
$content.focus();
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'
});
APP.hideMenu(e);
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);
});
var onWindowMouseUp = function (e) {
if (!sel.down) { return; }
if (e.which !== 1) { return; }
sel.down = false;
sel.$selectBox.hide();
$content.off('mousemove', sel.move);
delete sel.move;
$content.find('.cp-app-drive-element-selected-tmp')
.removeClass('cp-app-drive-element-selected-tmp')
.each(function (idx, element) {
selectElement($(element));
});
e.stopPropagation();
};
var getSelectedPaths = function ($element) {
var paths = [];
if (!$element || $element.length === 0) { return paths; }
if (findSelectedElements().length > 1) {
var $selected = findSelectedElements();
$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 removeInput = function (cancel) {
if (!cancel && $('.cp-app-drive-element-row > input').length === 1) {
var $input = $('.cp-app-drive-element-row > input');
manager.rename($input.data('path'), $input.val(), function (err) {
if (err && err.error === 'E_DUPLICATE_FOLDER_NAME') {
UI.alert(err.message || Messages.fo_unavailableName);
return;
}
APP.refresh();
});
}
$('.cp-app-drive-element-row > input').remove();
$('.cp-app-drive-element-row > span:hidden').removeAttr('style');
};
var getFileNameExtension = function (name) {
var matched = /\.[^\. ]+$/.exec(name);
if (matched && matched.length) { return matched[matched.length -1]; }
return '';
};
// 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
var renameInput = function () {
if (!APP.editable) { return; }
if (!path || path.length < 2) {
logError("Renaming a top level element (root, trash or filesData) is forbidden.");
return;
}
removeInput();
var $name = $element.find('.cp-app-drive-element-name');
if (!$name.length) {
$name = $element.find('> .cp-app-drive-element');
}
$name.hide();
var isFolder = $element.is(".cp-app-drive-element-folder:not(.cp-app-drive-element-sharedf)");
var el = manager.find(path);
var name = manager.isFile(el) ? manager.getTitle(el) : path[path.length - 1];
if (manager.isSharedFolder(el)) {
name = manager.getSharedFolderData(el).title;
}
var $input = $('<input>', {
placeholder: name,
value: name
}).data('path', path);
// Stop propagation on keydown to avoid issues with arrow keys
$input.on('keydown', function (e) { e.stopPropagation(); });
$input.on('keyup', function (e) {
e.stopPropagation();
if (e.which === 13) {
removeInput(true);
var newName = $input.val();
if (JSON.stringify(path) === JSON.stringify(currentPath)) {
manager.rename(path, $input.val(), function (err) {
if (err && err.error === 'E_DUPLICATE_FOLDER_NAME') {
UI.alert(err.message || Messages.fo_unavailableName);
return;
}
if (isFolder) {
LS.renameFoldersOpened(path, newName);
path[path.length - 1] = newName;
}
APP.displayDirectory(path);
});
}
else {
manager.rename(path, $input.val(), function (err) {
if (err && err.error === 'E_DUPLICATE_FOLDER_NAME') {
UI.alert(err.message || Messages.fo_unavailableName);
return;
}
if (isFolder) {
LS.renameFoldersOpened(path, newName);
unselectElement($element);
$element.data("path", $element.data("path").slice(0, -1).concat(newName));
selectElement($element);
}
APP.refresh();
});
}
return;
}
if (e.which === 27) {
removeInput(true);
}
}).on('keypress', function (e) { e.stopPropagation(); });
//$element.parent().append($input);
$name.after($input);
$input.focus();
var extension = getFileNameExtension(name);
var input = $input[0];
input.selectionStart = 0;
input.selectionEnd = name.length - extension.length;
// We don't want to open the file/folder when clicking on the input
$input.on('click dblclick', function (e) {
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('.cp-app-drive-element-row').attr("draggable", false);
});
$input.on('mouseup', function (e) {
e.stopPropagation();
$input.parents('.cp-app-drive-element-row').attr("draggable", true);
});
};
if (APP.mobile()) {
renameInput();
} else {
window.setTimeout(renameInput, 0);
}
};
// Arrow keys to modify the selection
var onWindowKeydown = function (e) {
if (!$content.is(':visible')) { return; }
if ($('.cp-modal').is(':visible')) {
return;
}
var $searchBar = $tree.find('#cp-app-drive-tree-search-input');
if (document.activeElement && document.activeElement.nodeName === 'INPUT') { return; }
if ($searchBar.is(':focus') && $searchBar.val()) { return; }
var $elements = $content.find('.cp-app-drive-element:not(.cp-app-drive-element-header)');
var ev = {};
if (e.ctrlKey) { ev.ctrlKey = true; }
if (e.shiftKey) { ev.shiftKey = true; }
// ESC
if (e.which === 27) {
return void APP.hideMenu();
}
// Enter
if (e.which === 13) {
var $allSelected = $content.find('.cp-app-drive-element.cp-app-drive-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('.cp-app-drive-element-file.cp-app-drive-element-selected');
$select.each(function (idx, el) {
$(el).dblclick();
});
return;
}
// Ctrl+A select all
if (e.which === 65 && (e.ctrlKey || (e.metaKey && APP.isMac))) {
e.preventDefault();
$content.find('.cp-app-drive-element:not(.cp-app-drive-element-selected)')
.each(function (idx, element) {
selectElement($(element));
});
return;
}
// F2: rename selected element
if (e.which === 113) {
var paths = getSelectedPaths(findSelectedElements().first());
if (paths.length !== 1) { return; }
displayRenameInput(paths[0].element, paths[0].path);
}
// [Left, Up, Right, Down]
if ([37, 38, 39, 40].indexOf(e.which) === -1) { return; }
e.preventDefault();
// If the arrow keys aren't caught by another listener before, it means we can
// use them to select content in the drive. If that's the case, we'll also
// focus the drive container to avoid conflicts with other focused elements
$content.focus();
var click = function (el) {
if (!el) { return; }
APP.onElementClick(ev, $(el));
};
var $selection = findSelectedElements();
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 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) {
if (!sDate) { return ''; }
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) {
console.error("Unable to format that string to a date with .toLocaleString", sDate, e);
}
return ret;
};
var previewMediaTag = function (data) {
var mts = [];
$content.find('.cp-app-drive-element.cp-border-color-file').each(function (i, el) {
var path = $(el).data('path');
var id = manager.find(path);
if (!id) { return; }
var _data = manager.getFileData(id);
if (!_data || _data.channel < 48) { return; }
mts.push({
channel: _data.channel,
href: _data.href,
password: _data.password
});
});
// Find initial position
var idx = -1;
mts.some(function (obj, i) {
if (obj.channel === data.channel) {
idx = i;
return true;
}
});
if (idx === -1) {
mts.unshift({
href: data.href,
password: data.password
});
idx = 0;
}
common.getMediaTagPreview(mts, idx);
};
var FILTER_BY = "filterBy";
var refreshDeprecated = function () {
if (!APP.passwordModal) { return; }
var deprecated = files.sharedFoldersTemp;
if (JSONSortify(deprecated) === APP.deprecatedSF) { return; }
APP.deprecatedSF = JSONSortify(deprecated);
if (typeof (deprecated) === "object" && Object.keys(deprecated).length) {
var nt = nThen;
Object.keys(deprecated).forEach(function (fId) {
var data = deprecated[fId];
var sfId = manager.user.userObject.getSFIdFromHref(data.href);
if (folders[fId] || sfId) { // This shared folder is already stored in the drive...
return void manager.delete([['sharedFoldersTemp', fId]], function () { });
}
nt = nt(function (waitFor) {
UI.openCustomModal(APP.passwordModal(fId, data, waitFor()));
}).nThen;
});
nt(function () {
APP.refresh();
});
}
};
var refresh = APP.refresh = function (cb, opt) {
var type = APP.store[FILTER_BY];
var path = type ? [FILTER, type, currentPath] : currentPath;
APP.displayDirectory(path, undefined, () => {
refreshDeprecated();
if (typeof(cb) === "function") { cb(); }
}, opt);
};
// `app`: true (force open with the app), false (force open in preview),
// falsy (open in preview if default is not using the app)
var defaultInApp = ['application/pdf'];
var openFile = function (el, isRo, app) {
// In anonymous drives, `el` already contains file data
var data = el.channel ? el : manager.getFileData(el);
if (data.static) {
if (data.href) {
common.openUnsafeURL(data.href);
manager.updateStaticAccess(el, refresh);
}
return;
}
if (!data || (!data.href && !data.roHref)) {
return void logError("Missing data for the file", el, data);
}
var href = isRo ? data.roHref : (data.href || data.roHref);
var parsed = Hash.parsePadUrl(href);
if (parsed.hashData && parsed.hashData.type === 'file' && !app
&& (defaultInApp.indexOf(data.fileType) === -1 || app === false)) {
return void previewMediaTag(data);
}
var obj = { t: APP.team };
var priv = metadataMgr.getPrivateData();
var useUnsafe = Util.find(priv, ['settings', 'security', 'unsafeLinks']);
if (useUnsafe === true || APP.newSharedFolder) {
return void common.openURL(Hash.getNewPadURL(href, obj));
}
// Get hidden hash
var secret = Hash.getSecrets(parsed.type, parsed.hash, data.password);
var opts = {};
if (isRo) { opts.view = true; }
var hash = Hash.getHiddenHashFromKeys(parsed.type, secret, opts);
var hiddenHref = Hash.hashToHref(hash, parsed.type);
common.openURL(Hash.getNewPadURL(hiddenHref, obj));
};
var openIn = function (type, path, team, fData) {
var obj = {
p: path,
t: team,
d: fData
};
var href = Hash.hashToHref('', type);
common.openURL(Hash.getNewPadURL(href, obj));
};
var pickFolderColor = function ($element, currentColor, cb) {
var colors = ["", "#f23c38", "#ff0073", "#da0eba", "#9d00ac", "#6c19b3", "#4a42b1", "#3d8af0", "#30a0f1", "#1fb9d1", "#009686", "#45b354", "#84c750", "#c6e144", "#faf147", "#fbc423", "#fc9819", "#fd5227", "#775549", "#9c9c9c", "#607a89"];
var colorsElements = [];
var currentElement = null;
const icon_classes = 'cp-app-drive-icon-folder cp-app-drive-content-icon';
colors.forEach(function (color, i) {
var element = h("span.cp-app-drive-color-picker-color", [
(i === 0 ? Icons.get('folder-nocolor', {'class': icon_classes + ' folder-no-color'}): Icons.get('folder', {'class': icon_classes})),
]);
$(element).css("color", colors[i]);
if (colors[i] === currentColor) {
currentElement = element;
$(element).find('.cp-app-drive-icon-folder').replaceWith(
Icons.get('folder-check', {'class': icon_classes})
);
if (i === 0) {
$(element).addClass("folder-no-color");
}
$(element).addClass("cp-app-drive-current-color");
}
$(element).on("click", function () {
if (currentElement) {
$(currentElement).removeClass("cp-app-drive-current-color");
var currentIndex = colorsElements.indexOf(currentElement);
var originalIcon = currentIndex === 0 ?
Icons.get('folder-nocolor', {'class': icon_classes + ' folder-no-color'}) :
Icons.get('folder', {'class': icon_classes});
$(currentElement).find('.cp-app-drive-icon-folder').replaceWith(originalIcon);
}
currentElement = element;
$(element).addClass("cp-app-drive-current-color");
$(element).find('.cp-app-drive-icon-folder').replaceWith(
Icons.get('folder-check', {'class': icon_classes})
);
cb(color);
});
colorsElements.push(element);
});
var content = h("div.cp-app-drive-color-picker", colorsElements);
UI.alert(content);
};
var getFolderColor = function (path) {
if (path.length === 0) { return; }
return manager.getFolderData(path).color || "";
};
var setFolderColor = function ($element, path, color) {
if ($element.length === 0) { return; }
$element.find(".cp-app-drive-icon-folder").css("color", color);
manager.setFolderData({
path: path,
key: "color",
value: color
}, function () {});
};
var filterContextMenu = function (type, paths) {
if (!paths || paths.length === 0) { logError('no paths'); }
$contextMenu.find('li').hide();
var show = [];
var filter;
var editable = true;
if (type === "content") {
if (APP.$content.data('readOnlyFolder')) { editable = false; }
// Return true in filter to hide
filter = function ($el, className) {
if (className === 'newfolder') { return; }
if (className === 'newsharedfolder') {
// Hide the new shared folder menu if we're already in a shared folder
return manager.isInSharedFolder(currentPath) || APP.disableSF;
}
if (className === 'uploadfiles') { return; }
if (className === 'uploadfolder') { return !APP.allowFolderUpload; }
if (className === 'newdoc') {
return;
}
};
} else {
// In case of multiple selection, we must hide the option if at least one element
// is not compatible
var containsFolder = false;
var hide = [];
if (!APP.team) {
hide.push('savelocal');
}
paths.forEach(function (p) {
var path = p.path;
var $element = p.element;
if (APP.$content.data('readOnlyFolder') &&
manager.isSubpath(path, currentPath)) { editable = false; }
if (!$element.closest("#cp-app-drive-tree").length) {
hide.push('expandall');
hide.push('collapseall');
}
if (path.length === 1) {
// Can't rename, share, delete, or change the color of categories
hide.push('delete');
hide.push('rename');
hide.push('share');
hide.push('savelocal');
hide.push('color');
}
if ($element.is('.cp-app-drive-element-folder')) {
hide.push('open');
}
if (!$element.is('.cp-app-drive-element-owned')) {
hide.push('deleteowned');
}
if ($element.is('.cp-app-drive-element-restricted')) {
hide.push('rename', 'download', 'share', 'access', 'color');
}
if ($element.is('.cp-app-drive-element-notrash')) {
// We can't delete elements in virtual categories
hide.push('delete');
}
if (!$element.is('.cp-border-color-file')) {
//hide.push('download');
hide.push('openincode');
hide.push('preview');
}
if ($element.is('.cp-app-drive-static')) {
hide.push('access', 'hashtag', 'properties', 'download');
}
if ($element.is('.cp-app-drive-element-file')) {
// No folder in files
hide.push('openfolder');
hide.push('color');
hide.push('newfolder');
if ($element.is('.cp-app-drive-element-readonly')) {
hide.push('open'); // Remove open 'edit' mode
} else if ($element.is('.cp-app-drive-element-noreadonly')) {
hide.push('openro'); // Remove open 'view' mode
}
var metadata = manager.getFileData(manager.find(path));
if (!metadata || !Util.isPlainTextFile(metadata.fileType, metadata.title)) {
hide.push('openincode');
}
if (metadata && /\/(doc|presentation|sheet)\//.test(metadata.href)) {
hide.push('openinsheet');
hide.push('openindoc');
hide.push('openinpresentation');
}
if (!metadata || !Util.isSpreadsheet(metadata.fileType, metadata.title)
|| !priv.supportsWasm) {
hide.push('openinsheet');
}
if (!metadata || !Util.isOfficeDoc(metadata.fileType, metadata.title)
|| !priv.supportsWasm) {
hide.push('openindoc');
}
if (!metadata || !Util.isPresentation(metadata.fileType, metadata.title)
|| !priv.supportsWasm) {
hide.push('openinpresentation');
}
if (metadata.channel && metadata.channel.length < 48) {
hide.push('preview');
}
if (!metadata.channel || metadata.channel.length > 32) {
hide.push('makeacopy'); // Not for blobs
}
} else if ($element.is('.cp-app-drive-element-sharedf')) {
if (containsFolder) {
// More than 1 folder selected: cannot create a new subfolder
hide.push('newfolder');
hide.push('expandall');
hide.push('collapseall');
}
containsFolder = true;
hide.push('openro');
hide.push('openincode');
hide.push('openinsheet');
hide.push('openindoc');
hide.push('openinpresentation');
hide.push('hashtag');
//hide.push('delete');
hide.push('makeacopy');
//hide.push('deleteowned');
} else { // it's a folder or a file in a guest drive
if (containsFolder) {
// More than 1 folder selected: cannot create a new subfolder
hide.push('newfolder');
hide.push('expandall');
hide.push('collapseall');
}
containsFolder = true;
hide.push('savelocal');
if ($element.is('.cp-app-drive-element-folder')) {
hide.push('openro');
}
hide.push('openincode');
hide.push('openinsheet');
hide.push('openindoc');
hide.push('openinpresentation');
hide.push('properties', 'access');
hide.push('hashtag');
hide.push('makeacopy');
}
// If we're in the trash, hide restore and properties for non-root elements
if (type === "trash" && path && path.length > 4) {
hide.push('restore');
hide.push('properties');
}
// If we're not in the trash nor in a shared folder, hide "remove"
if (!manager.isInSharedFolder(path)
&& !$element.is('.cp-app-drive-element-sharedf')) {
// This isn't a shared folder: can't delete shared folder
hide.push('removesf');
} else if (type === "tree") {
// This is a shared folder or an element inside a shsared folder
// ==> can't move to trash
hide.push('delete');
}
if ($element.closest('[data-ro]').length) {
editable = false;
}
});
if (paths.length > 1) {
hide.push('properties', 'access');
hide.push('rename');
hide.push('openparent');
hide.push('download');
hide.push('share');
hide.push('savelocal');
//hide.push('openincode'); // can't because of race condition
//hide.push('openinsheet'); // can't because of race condition
hide.push('makeacopy');
hide.push('preview');
}
if (containsFolder && paths.length > 1) {
// Cannot open multiple folders
hide.push('open');
}
if (!APP.loggedIn) {
hide.push('openparent');
hide.push('rename');
hide.push('openinsheet');
hide.push('openindoc');
hide.push('openinpresentation');
}
filter = function ($el, className) {
if (hide.indexOf(className) !== -1) { return true; }
};
}
switch(type) {
case 'content':
show = ['newfolder', 'newsharedfolder', 'uploadfiles', 'uploadfolder', 'newdoc'];
break;
case 'tree':
show = ['open', 'openfolder', 'openro', 'preview', 'openincode', 'expandall', 'collapseall',
'color', 'download', 'share', 'savelocal', 'rename', 'delete',
'makeacopy', 'openinsheet', 'openindoc', 'openinpresentation',
'deleteowned', 'removesf', 'access', 'properties', 'hashtag'];
break;
case 'default':
show = ['open', 'openro', 'preview', 'openincode', 'openinsheet', 'openindoc', 'openinpresentation', 'share', 'download', 'openparent', 'delete', 'deleteowned', 'properties', 'access', 'hashtag', 'makeacopy', 'savelocal', 'rename'];
break;
case 'trashtree': {
show = ['empty'];
break;
}
case 'trash': {
show = ['remove', 'deleteowned', 'restore', 'properties'];
}
}
var filtered = [];
show.forEach(function (className) {
var $el = $contextMenu.find('.cp-app-drive-context-' + className);
if ((!APP.editable || !editable) && $el.is('.cp-app-drive-context-editable')) { return; }
if ((!APP.editable || !editable) && $el.is('.cp-app-drive-context-editable')) { return; }
if (filter($el, className)) { return; }
$el.parent('li').show();
filtered.push('.cp-app-drive-context-' + className);
});
return filtered;
};
var scrollTo = function ($element) {
// Current scroll position
var st = $content.scrollTop();
// Block height
var h = $content.height();
// Current top position of the element relative to the scroll position
var pos = Math.round($element.offset().top - $content.position().top);
// Element height
var eh = $element.outerHeight();
// New scroll value
var v = st + pos + eh - h;
// If the element is completely visile, don't change the scroll position
if (pos+eh <= h && pos >= 0) { return; }
$content.scrollTop(v);
};
// Add the "selected" class to the "li" corresponding to the clicked element
var onElementClick = APP.onElementClick = function (e, $element) {
// 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) { APP.hideMenu(); }
// Remove the selection if we don't hold ctrl key or if we are right-clicking
if (!e || !e.ctrlKey) {
removeSelected(e && e.shiftKey);
}
if (!$element.length) {
log(Messages.fm_selectError);
return;
}
scrollTo($element);
// Add the selected class to the clicked / right-clicked element
// Remove the class if it already has it
// If ctrlKey, add to the selection
// If shiftKey, select a range of elements
var $elements = $content.find('.cp-app-drive-element:not(.cp-app-drive-element-header)');
var $selection = $elements.filter('.cp-app-drive-element-selected');
if (typeof sel.startSelected !== "number" || !e || (e.ctrlKey && !e.shiftKey)) {
sel.startSelected = $elements.index($element[0]);
sel.oldSelection = [];
$selection.each(function (idx, el) {
sel.oldSelection.push(el);
});
delete sel.endSelected;
}
if (e && e.shiftKey) {
var end = $elements.index($element[0]);
sel.endSelected = end;
var $el;
removeSelected(true);
sel.oldSelection.forEach(function (el) {
if (!isElementSelected($(el))) {
selectElement($(el));
}
});
for (var i = Math.min(sel.startSelected, sel.endSelected);
i <= Math.max(sel.startSelected, sel.endSelected);
i++) {
$el = $($elements.get(i));
if (!isElementSelected($el)) {
selectElement($el);
}
}
} else {
if (!isElementSelected($element)) {
selectElement($element);
} else {
unselectElement($element);
}
}
};
// show / hide dropdown separators
var hideSeparators = function ($menu) {
var showSep = false;
var $lastVisibleSep = null;
$menu.children().each(function (i, el) {
var $el = $(el);
if ($el.is(".dropdown-divider")) {
$el.css("display", showSep ? "list-item" : "none");
if (showSep) { $lastVisibleSep = $el; }
showSep = false;
}
else if ($el.is("li") && $el.css("display") !== "none") {
showSep = true;
}
});
if (!showSep && $lastVisibleSep) { $lastVisibleSep.css("display", "none"); } // remove last divider if no options after
};
// prepare and display contextmenu
var displayMenu = function (e) {
var $menu = $contextMenu;
// show / hide submenus
$menu.find(".dropdown-submenu").each(function (i, el) {
var $el = $(el);
$el.children(".dropdown-menu").css("display", "none");
$el.find("li").each(function (i, li) {
if ($(li).css("display") !== "none") {
$el.css("display", "block");
return;
}
});
});
// show / hide separators
$menu.find(".dropdown-menu").each(function (i, menu) {
hideSeparators($(menu));
});
// show contextmenu at cursor position
$menu.css({ display: "block" });
if (APP.mobile()) {
let menuPositionTop;
let menuPositionLeft;
if (($(e.target).offset().top + $(e.target).height()) > ($(window).height()-$menu.height())) {
if ( $(e.target).offset().top < $menu.height()) {
menuPositionTop = 0;
} else {
menuPositionTop = ($(e.target).offset().top - $menu.height());
}
} else {
menuPositionTop = $(e.target).offset().top + $(e.target).outerHeight();
}
if (($(e.target).offset().left + $(e.target).width()) < $menu.width()) {
menuPositionLeft = $(e.target).offset().left;
} else {
menuPositionLeft = $(e.target).offset().left - ($menu.width() - $(e.target).width()) + 10;
}
$menu.css({
top: menuPositionTop + 'px',
left: menuPositionLeft + 'px',
});
return;
}
var h = $menu.outerHeight();
var w = $menu.outerWidth();
var wH = window.innerHeight;
var wW = window.innerWidth;
if (h > wH) {
$menu.css({
top: '0px',
bottom: ''
});
} else if (e.pageY + h <= wH) {
$menu.css({
top: e.pageY+'px',
bottom: ''
});
} else {
$menu.css({
bottom: '0px',
top: ''
});
}
if(w > wW) {
$menu.css({
left: '0px',
right: ''
});
} else if (e.pageX + w <= wW) {
$menu.css({
left: e.pageX+'px',
right: ''
});
} else {
$menu.css({
left: '',
right: '0px',
});
}
};
// Open the selected context menu on the closest "li" element
var openContextMenu = function (type) {
return function (e) {
APP.hideMenu();
e.stopPropagation();
var paths;
if (type === 'content') {
paths = [{path: $(e.target).closest('#' + FOLDER_CONTENT_ID).data('path')}];
if (!paths) { return; }
removeSelected();
} else {
var $element = findDataHolder($(e.target));
// if clicked from tree
var fromTree = $element.closest("#cp-app-drive-tree").length;
if (fromTree) {
removeSelected();
}
// if clicked on non selected element
if (!isElementSelected($element)) {
removeSelected();
}
if (type === 'trash' && !$element.data('path')) { return; }
if (!$element.length) {
logError("Unable to locate the .element tag", e.target);
log(Messages.fm_contextMenuError);
return false;
}
if (!isElementSelected($element)) {
selectElement($element);
}
paths = getSelectedPaths($element);
$('.cp-app-drive-context-openro .cp-text').text(Messages.fc_open_ro);
if (paths.length === 1) {
var metadata = manager.getFileData(manager.find(paths[0].path));
if (metadata.roHref || manager.find(paths[0].path).roHref) {
var parsed = metadata.roHref ? Hash.parsePadUrl(metadata.roHref) : Hash.parsePadUrl(manager.find(paths[0].path).roHref);
if (parsed.type === "form") {
if (parsed.hashData.auditorKey) {
$('.cp-app-drive-context-openro .cp-text').text(Messages.fc_open_formaud);
} else {
$('.cp-app-drive-context-openro .cp-text').text(Messages.fc_open_formro);
}
}
}
}
}
$contextMenu.attr('data-menu-type', type);
filterContextMenu(type, paths);
displayMenu(e);
$(".cp-app-drive-context-noAction").toggle($contextMenu.find('li:visible').length === 0);
$contextMenu.data('paths', paths);
return false;
};
};
var getElementName = function (path) {
var file = manager.find(path);
if (!file) { return; }
if (manager.isSharedFolder(file)) {
return manager.getSharedFolderData(file).title;
}
return manager.getTitle(file);
};
// moveElements is able to move several paths to a new location
var moveElements = function (paths, newPath, copy, cb) {
if (!APP.editable) { return; }
// Cancel drag&drop from TRASH to TRASH
if (manager.isPathIn(newPath, [TRASH]) && paths.length && paths[0][0] === TRASH) {
return;
}
var newCb = function (err) {
if (err && err.error === 'E_DUPLICATE_FOLDER_NAME') {
UI.alert(err.message || Messages.fo_unavailableName);
paths.forEach(function (path) {
LS.moveFoldersOpened(path, newPath);
});
cb();
return;
}
paths.forEach(function (path) {
LS.moveFoldersOpened(path, newPath);
});
cb();
};
if (paths.some(function (p) { return manager.comparePath(newPath, p); })) { return void cb(); }
manager.move(paths, newPath, newCb, copy);
};
// Delete paths from the drive and/or shared folders (without moving them to the trash)
var deletePaths = function (paths, pathsList) {
pathsList = pathsList || [];
if (paths) {
paths.forEach(function (p) { pathsList.push(p.path); });
}
var msg = Messages._getKey("fm_removeSeveralPermanentlyDialog", [pathsList.length]);
if (pathsList.length === 1) {
msg = Messages.fm_removePermanentlyDialog;
}
UI.confirm(msg, function(res) {
$(window).focus();
if (!res) { return; }
manager.delete(pathsList, function () {
pathsList.forEach(LS.removeFoldersOpened);
removeSelected();
refresh();
});
});
};
// 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));
var getRefPath = function(navPath) {
if (navPath && navPath.length > 1 && navPath[navPath.length - 1] === ROOT) {
return navPath.slice(0, -1);
}
return navPath;
};
if ($element.hasClass('cp-app-drive-element-selected')) {
var $selected = findSelectedElements();
$selected.each(function (idx, elmt) {
var navPath = $(elmt).data('path');
var ePath = getRefPath(navPath);
if (ePath) {
var val = manager.find(ePath);
if (!val) { return; } // Error? A ".selected" element is not in the object
paths.push({
path: ePath,
value: {
name: getElementName(ePath),
el: val
}
});
}
});
} else {
removeSelected();
selectElement($element);
var dragPath = getRefPath(path);
var val = manager.find(dragPath);
if (!val) { return; } // The element is not in the object
paths = [{
path: dragPath,
value: {
name: getElementName(dragPath),
el: val
}
}];
}
var data = {
'path': paths
};
ev.dataTransfer.setData("text", stringify(data));
};
var findDropPath = function (target) {
var $target = $(target);
var $el;
if ($target.is(".cp-app-drive-path-element")) {
$el = $target;
}
else {
$el = findDataHolder($target);
}
var newPath = $el.data('path');
var dropEl = newPath && manager.find(newPath);
if (newPath && manager.isSharedFolder(dropEl)) {
newPath.push(manager.user.userObject.ROOT);
} else if ((!newPath || manager.isFile(dropEl))
&& $target.parents('#cp-app-drive-content')) {
newPath = currentPath;
}
return newPath;
};
var onFileDrop = APP.onFileDrop = function (file, e) {
var ev = {
target: e.target,
path: findDropPath(e.target)
};
// Make sure we can only upload files in the Documents tree
var p = ev.path;
if (!Array.isArray(p) || !p.length || p[0] !== ROOT) {
return void UI.warn(Messages.fm_cantUploadHere);
}
APP.FM.onFileDrop(file, ev);
};
var traverseFileTree = function (item, path, w, files) {
path = path || "";
if (item.isFile) {
// Get file
item.file(w(function(file) {
file.fix_path = path + file.name;
files.push(file);
}));
} else if (item.isDirectory) {
// Get folder contents
var dirReader = item.createReader();
// this API is not supported in Opera or IE
// https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem/webkitGetAsEntry
// all other browsers will recurse over subfolders and upload everything
dirReader.readEntries(w(function(entries) {
for (var i=0; i<entries.length; i++) {
traverseFileTree(entries[i], path + item.name + "/", w, files);
}
}));
// FIXME readEntries takes a function (error handler) as an optional second argument
// what kind of errors can be thrown? what will happen?
}
};
// create the folder structure before to upload files from folder
var uploadFolder = function (fileList, name) {
var currentFolder = currentPath;
// create an array of all the files relative path
var files = Array.prototype.map.call(fileList, function (file) {
return {
file: file,
path: (file.webkitRelativePath || file.fix_path).split("/"),
};
});
// if folder name already exist in drive, rename it
var uploadedFolderName = files.length ? files[0].path[0] : (name || '');
var availableName = manager.user.userObject.getAvailableName(manager.find(currentFolder), uploadedFolderName);
// ask for folder name and files options, then upload all the files!
APP.FM.showFolderUploadModal(availableName, function (folderUploadOptions) {
if (!folderUploadOptions) { return; }
// verfify folder name is possible, and update files path
availableName = manager.user.userObject.getAvailableName(manager.find(currentFolder), folderUploadOptions.folderName);
if (!files.length) {
return manager.addFolder(currentFolder, availableName, refresh);
}
if (uploadedFolderName !== availableName) {
files.forEach(function (file) {
file.path[0] = availableName;
});
}
// uploadSteps is an array of objects {folders: [], files: []}, containing all the folders and files to create safely
// at the index i + 1, the files and folders are children of the folders at the index i
var maxSteps = files.reduce(function (max, file) { return Math.max(max, file.path.length); }, 0);
var uploadSteps = [];
for (var i = 0 ; i < maxSteps ; i++) {
uploadSteps[i] = {
folders: [],
files: [],
};
}
files.forEach(function (file) {
// add steps to create subfolders containing file
for (var depth = 0 ; depth < file.path.length - 1 ; depth++) {
var subfolderStr = file.path.slice(0, depth + 1).join("/");
if (uploadSteps[depth].folders.indexOf(subfolderStr) === -1) {
uploadSteps[depth].folders.push(subfolderStr);
}
}
// add step to upload file (one step later than the step of its direct parent folder)
uploadSteps[file.path.length - 1].files.push(file);
});
// add folders, then add files when theirs folders have been created
// wait for the folders to be created to go to the next step (don't wait for the files)
var stepByStep = function (uploadSteps, i) {
if (i >= uploadSteps.length) { return; }
nThen(function (waitFor) {
// add folders
uploadSteps[i].folders.forEach(function (folder) {
var folderPath = folder.split("/");
var parentFolder = currentFolder.concat(folderPath.slice(0, -1));
var folderName = folderPath.slice(-1);
manager.addFolder(parentFolder, folderName, waitFor(refresh));
});
// upload files
uploadSteps[i].files.forEach(function (file) {
var ev = {
target: $content[0],
path: currentFolder.concat(file.path.slice(0, -1)),
};
APP.FM.handleFile(file.file, ev, folderUploadOptions);
});
}).nThen(function () {
stepByStep(uploadSteps, i + 1);
});
};
stepByStep(uploadSteps, 0);
});
};
var onDrop = function (ev) {
ev.preventDefault();
$('.cp-app-drive-element-droppable').removeClass('cp-app-drive-element-droppable');
var data = ev.dataTransfer.getData("text");
var newPath = findDropPath(ev.target);
if (!newPath) { return; }
var sfId = manager.isInSharedFolder(newPath);
if (sfId && folders[sfId] && folders[sfId].readOnly) {
return void UI.warn(Messages.fm_forbidden);
}
if (newPath[0] === TEMPLATE) {
var oldPaths = data && JSON.parse(data).path;
if (oldPaths && oldPaths.length) {
var hasFolder = oldPaths.some(function(p) {
var el = manager.find(p.path);
return manager.isFolder(el);
});
if (hasFolder) {
return void UI.warn(Messages.fo_moveUnsortedError);
}
}
}
var fileDrop = ev.dataTransfer.items;
if (fileDrop.length) {
// Filter out all the folders and use the correct function to upload them
fileDrop = Array.prototype.slice.call(fileDrop).map(function (file) {
if (file.kind !== "file") { return; }
var f = file.getAsFile();
if (file.webkitGetAsEntry) {
let entry = file.webkitGetAsEntry();
if (entry.isFile) { return f; }
// It's a folder
var files = [];
nThen(function (w) {
traverseFileTree(entry, "", w, files);
}).nThen(function () {
uploadFolder(files, f.name);
});
return;
} else {
// Old browsers: unreliable hack to detect folders
if (!file.type && file.size%4096 === 0) {
return; // Folder upload not supported
}
}
return f;
}).filter(Boolean);
// Continue only with the files
// if there are no files, fall through to other handlers
if (fileDrop.length) {
return void onFileDrop(fileDrop, ev);
}
}
oldPaths = data && JSON.parse(data).path;
if (!oldPaths) { return; }
// A moved element should be removed from its previous location
var movedPaths = [];
var sharedF = false;
var draggedSharedFolder = false;
var folderContainsSharedFolder = false;
oldPaths.forEach(function (p) {
movedPaths.push(p.path);
if (!sharedF && manager.isInSharedFolder(p.path)) {
sharedF = true;
}
if (!draggedSharedFolder && p.value && p.value.el && manager.isSharedFolder(p.value.el)) {
draggedSharedFolder = true;
}
if (!folderContainsSharedFolder && p.value && p.value.el && manager.hasSubSharedFolder(p.value.el)) {
folderContainsSharedFolder = true;
}
});
if (draggedSharedFolder && manager.isInSharedFolder(newPath)) {
return void UI.warn(Messages.fo_sharedFolder_move_error);
}
if (manager.isPathIn(newPath, [SHARED_FOLDER])) {
return void UI.warn(Messages.fo_sharedFolder_move_error);
}
if (sharedF && manager.isPathIn(newPath, [TRASH])) {
// TODO create a key here?
// You can't move to YOUR trash documents stored in a shared folder
// TODO or keep deletePaths: trigger the "Remove from cryptdrive" modal
return void UI.warn(Messages.fo_trash_move_error);
//return void deletePaths(null, movedPaths);
}
if (folderContainsSharedFolder) {
var destEl = manager.find(newPath);
if (manager.isInSharedFolder(newPath) || (destEl && manager.hasSubSharedFolder(destEl))) {
return void UI.warn(Messages.fo_sharedFolder_move_error);
}
}
var copy = false;
if (ev.ctrlKey || (ev.metaKey && APP.isMac)) {
copy = true;
}
if (movedPaths && movedPaths.length) {
moveElements(movedPaths, newPath, copy, 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) {
e.preventDefault();
e.stopPropagation();
onDrop(e.originalEvent);
});
$element.on('dragenter', function (e) {
e.preventDefault();
e.stopPropagation();
counter++;
$element.addClass('cp-app-drive-element-droppable');
});
$element.on('dragleave', function (e) {
e.preventDefault();
e.stopPropagation();
counter--;
if (counter <= 0) {
counter = 0;
$element.removeClass('cp-app-drive-element-droppable');
}
});
};
addDragAndDropHandlers($content, null, true, true);
$tree.on('drop dragover', function (e) {
e.preventDefault();
e.stopPropagation();
});
$driveToolbar.on('drop dragover', function (e) {
e.preventDefault();
e.stopPropagation();
});
// In list mode, display metadata from the filesData object
var addStaticData = function (element, $element, data) {
$element.addClass('cp-border-color-drive');
var name = data.name;
var $name = $('<span>', {'class': 'cp-app-drive-element-name'}).text(name);
$element.append($name);
if (getViewMode() === 'grid') {
//console.error(name, Util.fixHTML(name));
// this is only safe because our build of tippy sets titles as
// 'textContent' instead of innerHTML, otherwise
// we would need to use Util.fixHTML
$element.attr('title', name);
}
var type = Messages.fm_link_type;
var $type = $('<span>', {
'class': 'cp-app-drive-element-type cp-app-drive-element-list'
}).text(type);
var $adate = $('<span>', {
'class': 'cp-app-drive-element-atime cp-app-drive-element-list'
}).text(getDate(data.atime));
var $cdate = $('<span>', {
'class': 'cp-app-drive-element-ctime cp-app-drive-element-list'
}).text(getDate(data.ctime));
$element.append($type).append($adate).append($cdate);
};
var _addOwnership = function ($span, $state, data) {
if (data && Array.isArray(data.owners) && data.owners.indexOf(edPublic) !== -1) {
var $owned = $ownedIcon.clone().appendTo($state);
$owned.attr('title', Messages.fm_padIsOwned);
$span.addClass('cp-app-drive-element-owned');
} /* else if (data.owners && data.owners.length) {
var $owner = $ownerIcon.clone().appendTo($state);
$owner.attr('title', Messages.fm_padIsOwnedOther);
} */
};
var thumbsUrls = {};
// This is duplicated in cryptpad-common, it should be unified
var getFileIcon = function (id) {
var data = manager.getFileData(id);
return UI.getFileIcon(data);
};
var getIcon = UI.getIcon;
var addTitleIcon = function (element, $name) {
var icon = getFileIcon(element);
$(icon).addClass('cp-app-drive-element-icon');
$name.addClass('cp-app-drive-element-name-icon');
$name.prepend($(icon));
// Clean up
setTimeout(function() {
$name.closest('li').children('svg').remove();
}, 0);
};
var addFileData = function (element, $element) {
if (!manager.isFile(element)) { return; }
var data = manager.getFileData(element);
if (data.static) {
return addStaticData(element, $element, data);
}
if (!Object.keys(data).length) {
return true;
}
var href = data.href || data.roHref;
if (!data) { return void logError("No data for the file", element); }
var hrefData = Hash.parsePadUrl(href);
if (hrefData.type) {
$element.addClass('cp-border-color-'+hrefData.type);
}
var $state = $('<span>', {'class': 'cp-app-drive-element-state'});
if (hrefData.hashData && hrefData.hashData.mode === 'view') {
var $ro = $readonlyIcon.clone().appendTo($state);
$ro.attr('title', Messages.readonly);
}
if (data.filename && data.filename !== data.title) {
var $renamed = $renamedIcon.clone().appendTo($state);
$renamed.attr('data-cptippy-html', 'true');
$renamed.attr('title', Messages._getKey('fm_renamedPad', [Util.fixHTML(data.title)]));
}
if (hrefData.hashData && hrefData.hashData.password) {
var $password = $passwordIcon.clone().appendTo($state);
$password.attr('title', Messages.fm_passwordProtected || '');
}
if (data.expire) {
var $expire = $expirableIcon.clone().appendTo($state);
$expire.attr('title', Messages._getKey('fm_expirablePad', [new Date(data.expire).toLocaleString()]));
}
_addOwnership($element, $state, data);
var $menu = $('<span>', {'class': 'cp-app-drive-element-menu'});
$menu.click(function (e) {
e.preventDefault();
e.stopPropagation();
$element.contextmenu();
});
$fileMenuIcon.clone().appendTo($menu);
var name = manager.getTitle(element);
// The element with the class '.name' is underlined when the 'li' is hovered
var $name = $(h('span.cp-app-drive-element-name', [
h('span.cp-app-drive-element-name-text', name)
]));
$element.append($name);
$element.append($state);
if (APP.mobile()) {
$element.append($menu);
}
if (getViewMode() === 'grid') {
$element.attr('title', name);
}
// display the thumbnail
// if the thumbnail has already been displayed once, do not reload it, keep the same url
if (thumbsUrls[element]) {
var img = new Image();
img.src = thumbsUrls[element];
$element.prepend(img);
$(img).addClass('cp-app-drive-element-grid cp-app-drive-element-thumbnail');
$(img).attr("draggable", false);
$(img).attr("role", "presentation");
addTitleIcon(element, $name);
} else {
common.displayThumbnail(href || data.roHref, data.channel, data.password, $element, function ($thumb) {
// Called only if the thumbnail exists
// Remove the .hide() added by displayThumnail() because it hides the icon in list mode too
$element.find('.cp-icon').removeAttr('style');
$thumb.addClass('cp-app-drive-element-grid cp-app-drive-element-thumbnail');
$thumb.attr("draggable", false);
thumbsUrls[element] = $thumb[0].src;
addTitleIcon(element, $name);
});
}
var type = Messages.type[hrefData.type] || hrefData.type;
var $type = $('<span>', {
'class': 'cp-app-drive-element-type cp-app-drive-element-list'
}).text(type);
var $adate = $('<span>', {
'class': 'cp-app-drive-element-atime cp-app-drive-element-list'
}).text(getDate(data.atime));
var $cdate = $('<span>', {
'class': 'cp-app-drive-element-ctime cp-app-drive-element-list'
}).text(getDate(data.ctime));
$element.append($type).append($adate).append($cdate);
};
var addFolderData = function (element, key, $span) {
if (!element || !manager.isFolder(element)) { return; }
// The element with the class '.name' is underlined when the 'li' is hovered
var $state = $('<span>', {'class': 'cp-app-drive-element-state'});
var $ro;
var $menu = $('<span>', {'class': 'cp-app-drive-element-menu'});
$menu.click(function (e) {
e.preventDefault();
e.stopPropagation();
$span.contextmenu();
});
$fileMenuIcon.clone().appendTo($menu);
if (manager.isSharedFolder(element)) {
var data = manager.getSharedFolderData(element);
var fId = element;
key = data.title || data.lastTitle || Messages.fm_deletedFolder;
element = Util.find(manager, ['folders', element, 'proxy', manager.user.userObject.ROOT]) || {};
$span.addClass('cp-app-drive-element-sharedf');
_addOwnership($span, $state, data);
var hrefData = Hash.parsePadUrl(data.href || data.roHref);
if (hrefData.hashData && hrefData.hashData.password) {
var $password = $passwordIcon.clone().appendTo($state);
$password.attr('title', Messages.fm_passwordProtected || '');
}
if (hrefData.hashData && hrefData.hashData.mode === 'view') {
$ro = $readonlyIcon.clone().appendTo($state);
$ro.attr('title', Messages.readonly);
}
if (files.restrictedFolders[fId]) {
var $restricted = $restrictedIcon.clone().appendTo($state);
$restricted.attr('title', Messages.fm_restricted);
}
var $shared = $sharedIcon.clone().appendTo($state);
$shared.attr('title', Messages.fm_canBeShared);
} else if ($content.data('readOnlyFolder') || APP.readOnly) {
$ro = $readonlyIcon.clone().appendTo($state);
$ro.attr('title', Messages.readonly);
}
var sf = manager.hasSubfolder(element);
var hasFiles = manager.hasFile(element);
var $name = $(h('span.cp-app-drive-element-name', [
h('span.cp-app-drive-element-name-text', key)
]));
var $subfolders = $('<span>', {
'class': 'cp-app-drive-element-folders cp-app-drive-element-list'
}).text(sf);
var $files = $('<span>', {
'class': 'cp-app-drive-element-files cp-app-drive-element-list'
}).text(hasFiles);
var $filler = $('<span>', {
'class': 'cp-app-drive-element-filler cp-app-drive-element-list'
});
if (getViewMode() === 'grid') {
$span.attr('title', key);
}
$span.append($name).append($state).append($subfolders).append($files).append($filler);
if (APP.mobile()) {
$span.append($menu);
}
};
var createShareButton = function (id, $container) {
var $shareBlock = $('<button>', {
'class': 'cp-toolbar-share-button',
title: Messages.shareButton
});
$sharedIcon.clone().appendTo($shareBlock);
$('<span>').text(Messages.shareButton).appendTo($shareBlock);
var data = manager.getSharedFolderData(id);
var parsed = (data.href && data.href.indexOf('#') !== -1) ? Hash.parsePadUrl(data.href) : {};
var roParsed = Hash.parsePadUrl(data.roHref) || {};
if (!parsed.hash && !roParsed.hash) { return void console.error("Invalid href: "+(data.href || data.roHref)); }
var friends = common.getFriends();
var ro = folders[id] && folders[id].version >= 2;
// If we're a viewer and this is an old shared folder (no read-only mode), we
// can't share the read-only URL and we don't have access to the edit one.
// We should hide the share button.
if (!data.href && !ro) { return; }
$shareBlock.click(function () {
Share.getShareModal(common, {
teamId: APP.team,
origin: APP.origin,
pathname: "/drive/",
friends: friends,
title: data.title,
password: data.password,
sharedFolder: true,
common: common,
hashes: {
editHash: parsed.hash,
viewHash: ro && roParsed.hash,
}
});
});
$container.append($shareBlock);
return $shareBlock;
};
// 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;
var element;
if (isTrash && Array.isArray(elPath)) {
key = elPath[0];
elPath.forEach(function (k) { newPath.push(k); });
element = manager.find(newPath);
} else {
key = elPath;
newPath.push(key);
element = root[key];
}
var restricted = files.restrictedFolders[element];
var isSharedFolder = manager.isSharedFolder(element);
var $icon = !isFolder ? getFileIcon(element) : undefined;
var ro = manager.isReadOnlyFile(element);
// ro undefined means it's an old hash which doesn't support read-only
var roClass = typeof(ro) === 'undefined' ? '.cp-app-drive-element-noreadonly' :
ro ? '.cp-app-drive-element-readonly' : '';
var liClass = '.cp-app-drive-element-file';
var restrictedClass = restricted ? '.cp-app-drive-element-restricted' : '';
if (isSharedFolder) {
liClass = '.cp-app-drive-element-folder';
$icon = $sharedFolderIcon.clone();
$icon.css("color", getFolderColor(path.concat(elPath)));
} else if (isFolder) {
liClass = '.cp-app-drive-element-folder';
$icon = manager.isFolderEmpty(root[key]) ? $folderEmptyIcon.clone() : $folderIcon.clone();
$icon.css("color", getFolderColor(path.concat(elPath)));
}
var staticClass = manager.isStaticFile(element) ? '.cp-app-drive-static' : '';
var classes = restrictedClass + roClass + liClass + staticClass;
var $element = $(h('li.cp-app-drive-element.cp-app-drive-element-row' + classes, {
draggable: true
}));
$element.data('path', newPath);
if (isElementSelected($element)) {
selectElement($element);
}
$element.prepend($icon).dblclick(function () {
if (restricted) {
UI.warn(Messages.fm_restricted);
return;
}
if (isSharedFolder && !manager.folders[element]) {
UI.warn(Messages.fm_deletedFolder);
return;
}
if (isFolder) {
APP.displayDirectory(newPath);
return;
}
if (isTrash) { return; }
openFile(root[key]);
});
var invalid;
if (isFolder) {
invalid = addFolderData(element, key, $element);
} else {
invalid = addFileData(element, $element);
}
if (invalid) {
return;
}
$element.find('.lucide').on('mouseenter', function (e) {
if ($element[0] && $element[0]._tippy) {
$element[0]._tippy.destroy();
}
e.stopPropagation();
});
var droppable = !isTrash && !APP.$content.data('readOnlyFolder') && !restricted;
addDragAndDropHandlers($element, newPath, isFolder, droppable);
$element.click(function(e) {
e.stopPropagation();
onElementClick(e, $element);
});
if (!isTrash) {
$element.on('contextmenu', openContextMenu('tree'));
$element.data('context', 'tree');
} else {
$element.contextmenu(openContextMenu('trash'));
$element.data('context', 'trash');
}
var isNewFolder = APP.newFolder && manager.comparePath(newPath, APP.newFolder);
if (isNewFolder) {
appStatus.onReady(function () {
window.setTimeout(function () { displayRenameInput($element, newPath); }, 0);
});
delete APP.newFolder;
}
if (isSharedFolder && APP.convertedFolder === element) {
setTimeout(function () {
var $fakeButton = createShareButton(element, $('<div>'));
if (!$fakeButton) { return; }
$fakeButton.click();
}, 100);
}
if ($element.is('.cp-app-drive-static') && APP.mobile()) {
var $menu = $('<span>', {'class': 'cp-app-drive-element-menu'});
$menu.click(function (e) {
e.preventDefault();
e.stopPropagation();
$element.contextmenu();
});
$fileMenuIcon.clone().appendTo($menu);
$element.append($menu);
}
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 TEMPLATE: pName = TEMPLATE_NAME; break;
case FILES_DATA: pName = FILES_DATA_NAME; break;
case SEARCH: pName = SEARCH_NAME; break;
case RECENT: pName = RECENT_NAME; break;
case OWNED: pName = OWNED_NAME; break;
case TAGS: pName = TAGS_NAME; break;
case SHARED_FOLDER: pName = SHARED_FOLDER_NAME; break;
default: pName = name;
}
return pName;
};
var drivePathOverflowing = function () {
var $container = $(".cp-app-drive-path");
if ($container.length) {
$container.css("overflow", "hidden");
var overflown = $container[0].scrollWidth > $container[0].clientWidth;
$container.css("overflow", "");
return overflown;
}
};
var collapseDrivePath = function () {
var $container = $(".cp-app-drive-path-inner");
var $spanCollapse = $(".cp-app-drive-path-collapse");
$spanCollapse.css("display", "none");
var $pathElements = $container.find(".cp-app-drive-path-element");
$pathElements.not($spanCollapse).css("display", "");
var oneFolder = currentPath.length > 1 + (currentPath[0] === SHARED_FOLDER);
if (oneFolder && drivePathOverflowing()) {
var collapseLevel = 0;
var removeOverflowElement = function () {
if (drivePathOverflowing()) {
if ($pathElements.length <= 3) {
return false;
}
collapseLevel++;
if ($($pathElements.get(-2)).is(".cp-app-drive-path-separator")) {
$($pathElements.get(-2)).css("display", "none");
$pathElements = $pathElements.not($pathElements.get(-2));
}
$($pathElements.get(-2)).css("display", "none");
$pathElements = $pathElements.not($pathElements.get(-2));
return true;
}
};
currentPath.every(removeOverflowElement);
$spanCollapse.css("display", "");
removeOverflowElement();
var tipPath = currentPath.slice(0, collapseLevel);
tipPath[0] = getPrettyName(tipPath[0]);
$spanCollapse.attr("title", tipPath.join(" / "));
$spanCollapse[0].onclick = function () {
APP.displayDirectory(LS.getLastOpenedFolder().slice(0, collapseLevel));
};
}
};
window.addEventListener("resize", collapseDrivePath);
var treeResizeObserver = new MutationObserver(collapseDrivePath);
treeResizeObserver.observe($("#cp-app-drive-tree")[0], {"attributes": true});
// Create the title block with the "parent folder" button
var createTitle = function ($container, path, noStyle) {
if (!path || path.length === 0) { return; }
var isTrash = manager.isPathIn(path, [TRASH]);
// we assume that users viewing shared folders may want to see the "bread-crumb"
if (!APP.newSharedFolder && APP.mobile() && !noStyle) { // noStyle means title in search result
return $container;
}
var isVirtual = virtualCategories.indexOf(path[0]) !== -1;
var el = isVirtual ? undefined : manager.find(path);
path = path[0] === SEARCH ? path.slice(0,1) : path;
var isInTrashRoot = manager.isInTrashRoot(path);
var $outer = $('<div>', {'class': 'cp-app-drive-path'});
var $inner = $('<div>', {'class': 'cp-app-drive-path-inner'});
$outer.append($inner);
$container.prepend($outer);
var skipNext = false; // When encountering a shared folder, skip a key in the path
path.forEach(function (p, idx) {
if (isTrash && [2,3].indexOf(idx) !== -1) { return; }
if (skipNext) { skipNext = false; return; }
if (APP.newSharedFolder && priv.isEmbed && p === ROOT && !idx) { return; }
var name = p;
if (manager.isFile(el) && isInTrashRoot && idx === 1) {
idx = 3;
}
// Check if the current element is a shared folder. If it is, get its
// name and skip the next itme in the path (it will be "root")
var currentEl = isVirtual ? undefined : manager.find(path.slice(0, idx+1));
// If we're in trash root, check if the "element" is a shared folder
if (isTrash && idx === 1) {
currentEl = manager.find(path.slice(0, idx+3));
}
// Name and skip next...
// "p === SHARED_FOLDER" for anonymous shared folders
if (p === SHARED_FOLDER || (currentEl && manager.isSharedFolder(currentEl))) {
name = manager.getSharedFolderData(currentEl || APP.newSharedFolder).title;
skipNext = true;
}
var $span = $('<span>', {'class': 'cp-app-drive-path-element'});
if (idx < path.length - 1) {
if (!noStyle) {
$span.addClass('cp-app-drive-path-clickable');
$span.click(function (e) {
e.stopPropagation();
var sliceEnd = idx + 1;
if (isTrash && idx === 1) { sliceEnd = 4; } // Make sure we don't show the index or 'element' and 'path'
APP.displayDirectory(path.slice(0, sliceEnd));
});
}
} else if (idx > 0 && manager.isFile(el)) {
name = getElementName(path);
}
$span.data("path", path.slice(0, idx + 1));
addDragAndDropHandlers($span, path.slice(0, idx), true, true);
if (idx === 0) { name = p === SHARED_FOLDER ? name : getPrettyName(p); }
else if (!(APP.newSharedFolder && priv.isEmbed && idx === 1)) {
var $span2 = $('<span>', {
'class': 'cp-app-drive-path-element cp-app-drive-path-separator'
}).text(' / ');
$inner.prepend($span2);
}
$span.text(name).prependTo($inner);
});
var $spanCollapse = $('<span>', {
'class': 'cp-app-drive-path-element cp-app-drive-path-collapse'
}).text(' ... ');
$inner.append($spanCollapse);
collapseDrivePath();
};
var createInfoBox = function (path) {
if (APP.readOnly || $content.data('readOnlyFolder')) { return; }
var $box = $('<div>', {'class': 'cp-app-drive-content-info-box'});
var msg;
switch (path[0]) {
case ROOT:
msg = Messages.fm_info_root;
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;
case RECENT:
msg = Messages.fm_info_recent;
break;
case OWNED:
msg = Messages.fm_info_owned;
break;
case TAGS:
break;
default:
msg = undefined;
}
if (history.isHistoryMode && history.sfId) {
// Shared folder history: always display the warning
var sfName = (manager.getSharedFolderData(history.sfId) || {}).title || Messages.fm_sharedFolderName;
msg = Messages._getKey('fm_info_sharedFolderHistory', [sfName]);
return $(common.fixLinks($box.html(msg)));
}
if (!APP.loggedIn) {
msg = APP.newSharedFolder ? Messages.fm_info_sharedFolder : Messages._getKey('fm_info_anonymous', [ApiConfig.inactiveTime || 90]);
var docsLink = 'https://docs.cryptpad.org/en/user_guide/user_account.html#account-types';
$box.html(msg).find('a[href="#docs"]').each(function () {
$(this).attr({
href: Pages.localizeDocsLink(docsLink),
target: '_blank',
});
});
return $(common.fixLinks($box));
}
if (!msg || APP.store['hide-info-' + path[0]] === '1') {
$box.hide();
} else {
$box.text(msg);
var $close = $('<span>').css({
'cursor': 'pointer',
'margin-left': '10px'
})
.attr('title', Messages.fm_closeInfoBox)
.attr('role', 'button')
.append($closeIcon.clone())
.on('click', function () {
$box.hide();
APP.store['hide-info-' + path[0]] = '1';
localStore.put('hide-info-' + path[0], '1');
});
$box.prepend($close);
}
return $box;
};
var getOppositeViewMode = function (viewMode) {
viewMode = viewMode || getViewMode();
var newViewMode = viewMode === 'grid'? 'list': 'grid';
return newViewMode;
};
// Create the button allowing the user to switch from list to icons modes
var createViewModeButton = function ($container) {
var viewMode = getViewMode();
var gridIcon = Icons.get('grid', {title: Messages.fm_viewGridButton});
var listIcon = Icons.get('list', {title: Messages.fm_viewListButton});
var $button = $(h('button.cp-app-drive-viewmode-button', [
gridIcon,
listIcon
]));
$button.attr('aria-label', Messages.label_viewMode);
var showMode = function (mode) {
var $gridIcon = $button.find('svg.lucide-layout-grid');
var $listIcon = $button.find('svg.lucide-list');
if (!$gridIcon.length && !$listIcon.length) {
$gridIcon = $button.find('[data-lucide=layout-grid]');
$listIcon = $button.find('[data-lucide=list]');
}
if (mode === 'grid') {
$gridIcon.hide();
$listIcon.show();
} else {
$listIcon.hide();
$gridIcon.show();
}
};
setViewMode(viewMode || 'grid');
showMode(viewMode);
$button.click(function () {
var viewMode = getViewMode();
var newViewMode = getOppositeViewMode(viewMode);
setViewMode(newViewMode);
showMode(newViewMode);
var $folder = $('#' + FOLDER_CONTENT_ID);
if (newViewMode === 'list') {
$folder.removeClass('cp-app-drive-content-grid').addClass('cp-app-drive-content-list');
Feedback.send('DRIVE_LIST_MODE');
} else {
$folder.addClass('cp-app-drive-content-grid').removeClass('cp-app-drive-content-list');
Feedback.send('DRIVE_GRID_MODE');
}
});
$container.append($button);
};
var emptyTrashModal = function () {
var ownedInTrash = manager.ownedInTrash();
var hasOwned = Array.isArray(ownedInTrash) && ownedInTrash.length;
var content = h('p', [
Messages.fm_emptyTrashDialog,
hasOwned ? h('br') : undefined,
hasOwned ? UI.setHTML(h('span'), Messages.fm_emptyTrashOwned) : undefined
]);
var buttons = [{
className: 'cancel',
name: Messages.cancelButton,
onClick: function () {},
keys: [27]
}];
if (hasOwned) {
buttons.push({
className: 'danger',
iconClass: 'destroy',
name: Messages.fc_delete_owned,
onClick: function () {
manager.emptyTrash(true, refresh);
},
keys: []
});
}
buttons.push({
className: 'primary',
// We may want to use a new key here
iconClass: 'trash-full',
name: hasOwned ? Messages.fc_remove : Messages.okButton,
onClick: function () {
manager.emptyTrash(false, refresh);
},
keys: [13]
});
var m = UI.dialog.customModal(content, {
buttons: buttons
});
UI.openCustomModal(m);
};
var createEmptyTrashButton = function () {
var button = h('button.btn.btn-danger', [
Icons.get(trashFull),
h('span', Messages.fc_empty)
]);
$(button).click(function () {
emptyTrashModal();
});
return $(h('div.cp-app-drive-button', button));
};
// Get the upload options
var addSharedFolderModal = function (cb) {
var docsHref = common.getBounceURL(Pages.localizeDocsLink("https://docs.cryptpad.org/en/user_guide/share_and_access.html#owners"));
// Ask for name, password and owner
var content = h('div', [
h('h4', Messages.sharedFolders_create),
h('label', {for: 'cp-app-drive-sf-name'}, Messages.sharedFolders_create_name),
h('input#cp-app-drive-sf-name', {type: 'text', placeholder: Messages.fm_newFolder,tabindex:'1'}),
h('label', {for: 'cp-app-drive-sf-password'}, Messages.fm_shareFolderPassword),
UI.passwordInput({id: 'cp-app-drive-sf-password'}),
h('span', {
style: 'display:flex;align-items:center;justify-content:space-between'
}, [
UI.createCheckbox('cp-app-drive-sf-owned', Messages.sharedFolders_create_owned, true),
UI.createHelper(docsHref, Messages.creation_owned1)
]),
]);
$(content).find('#cp-app-drive-sf-name').keydown(function (e) {
if (e.which === 13) {
UI.findOKButton().click();
}
});
UI.confirm(content, function (yes) {
if (!yes) { return void cb(); }
// Get the values
var newName = $(content).find('#cp-app-drive-sf-name').val();
var password = $(content).find('#cp-app-drive-sf-password').val() || undefined;
var owned = $(content).find('#cp-app-drive-sf-owned').is(':checked');
cb({
name: newName,
password: password,
owned: owned
});
});
};
var showUploadFilesModal = function () {
var $input = $('<input>', {
'type': 'file',
'style': 'display: none;',
'multiple': 'multiple'
}).on('change', function (e) {
var files = Util.slice(e.target.files);
files.forEach(function (file) {
var ev = {
target: $content[0],
path: findDropPath($content[0])
};
APP.FM.handleFile(file, ev);
});
});
$input.click();
};
var showUploadFolderModal = function () {
var $input = $('<input>', {
'type': 'file',
'style': 'display: none;',
'multiple': 'multiple',
'webkitdirectory': true,
}).on('change', function (e) {
uploadFolder(e.target.files);
});
$input.click();
};
var showLinkModal = function () {
var name, url;
var warning = h('div.alert.alert-warning', [
Icons.get('alert'),
h('span', Messages.fm_link_warning)
]);
var content = h('p', [
h('label', {for: 'cp-app-drive-link-name'}, Messages.fm_link_name),
name = h('input#cp-app-drive-link-name', { autocomplete: 'off', placeholder: Messages.fm_link_name_placeholder}),
h('label', {for: 'cp-app-drive-link-url'}, Messages.fm_link_url),
url = h('input#cp-app-drive-link-url', { type: 'url', autocomplete: 'off', placeholder: Messages.form_input_ph_url}),
warning,
]);
var protocolPattern = /https*:\/\//;
var fragmentPattern = /#.*$/;
var setNamePlaceholder = function (val) {
var temp = val.replace(protocolPattern, '').replace(fragmentPattern, '').trim().slice(0, 48);
if (!protocolPattern.test(val) || !temp) {
temp = Messages.fm_link_name_placeholder;
}
name.setAttribute('placeholder', temp);
};
var $warning = $(warning).hide();
var $url = $(url).on('change keypress keydown', function () {
var v = $url.val().trim();
if (Util.isValidURL(v)) {
$url.removeClass('cp-input-invalid');
} else {
$url.addClass('cp-input-invalid');
}
if (v.length > 200) {
$warning.show();
return;
}
setNamePlaceholder(v);
$warning.hide();
});
var buttons = [{
className: 'cancel',
name: Messages.cancelButton,
onClick: function () {},
keys: [27]
}];
buttons.push({
className: 'primary',
// We may want to use a new key here
iconClass: 'add',
name: Messages.tag_add,
onClick: function () {
var $name = $(name);
var n = $name.val().trim() || $name.attr('placeholder');
var u = $url.val().trim();
if (!n || !u || !Util.isValidURL(u)) {
UI.warn(Messages.fm_link_invalid);
return true;
}
manager.addLink(currentPath, {
name: n,
url: u
}, refresh);
Feedback.send("LINK_CREATED");
},
keys: [13]
});
var m = UI.dialog.customModal(content, {
buttons: buttons
});
UI.openCustomModal(m);
};
var triggerEnter = function (selector, $context) {
$context.find(selector).on('keypress', function (event) {
if (event.which === 13) { // enter
event.preventDefault();
$(this).trigger('click'); // the keypress event triggers the click event
}
});
};
var addNewPadHandlers = function ($block, isInRoot) {
// Handlers
if (isInRoot) {
var onCreated = function (err, info) {
if (err) {
if (err === E_OVER_LIMIT) {
var content = h('span', UIElements.fixInlineBRs(Messages.pinLimitDrive));
return void UI.alert(content);
}
return void UI.alert(Messages.fm_error_cantPin);
}
APP.newFolder = info.newPath;
refresh();
};
$block.find('a.cp-app-drive-new-folder, li.cp-app-drive-new-folder')
.on('click', function (event) {
event.preventDefault();
manager.addFolder(currentPath, null, onCreated);
});
triggerEnter('a.cp-app-drive-new-folder, li.cp-app-drive-new-folder', $block);
if (!APP.disableSF && !manager.isInSharedFolder(currentPath)) {
$block.find('a.cp-app-drive-new-shared-folder, li.cp-app-drive-new-shared-folder')
.on('click', function (event) {
event.preventDefault();
addSharedFolderModal(function (obj) {
if (!obj) { return; }
manager.addSharedFolder(currentPath, obj, refresh);
});
});
triggerEnter('a.cp-app-drive-new-shared-folder, li.cp-app-drive-new-shared-folder', $block);
}
$block.find('a.cp-app-drive-new-fileupload, li.cp-app-drive-new-fileupload')
.on('click', function (event) {
event.preventDefault();
showUploadFilesModal();
});
triggerEnter('a.cp-app-drive-new-fileupload, li.cp-app-drive-new-fileupload', $block);
$block.find('a.cp-app-drive-new-folderupload, li.cp-app-drive-new-folderupload')
.on('click', function (event) {
event.preventDefault();
showUploadFolderModal();
});
triggerEnter('a.cp-app-drive-new-folderupload, li.cp-app-drive-new-folderupload', $block);
$block.find('a.cp-app-drive-new-link, li.cp-app-drive-new-link')
.on('click', function (event) {
event.preventDefault();
showLinkModal();
});
triggerEnter('a.cp-app-drive-new-link, li.cp-app-drive-new-link', $block);
}
$block.find('a.cp-app-drive-new-doc, li.cp-app-drive-new-doc')
.on('click auxclick', function (event) {
if (event.type === 'click' || event.type === 'auxclick') {
event.preventDefault();
var type = $(this).attr('data-type') || 'pad';
var path = manager.isPathIn(currentPath, [TRASH]) ? '' : currentPath;
openIn(type, path, APP.team);
}
});
triggerEnter('a.cp-app-drive-new-doc, li.cp-app-drive-new-doc', $block);
};
var getNewPadOptions = function (isInRoot) {
var options = [];
if (isInRoot) {
options.push({
class: 'cp-app-drive-new-folder',
icon: $folderIcon.clone()[0],
name: Messages.fm_folder,
});
if (!APP.disableSF && !manager.isInSharedFolder(currentPath)) {
options.push({
class: 'cp-app-drive-new-shared-folder',
icon: $sharedFolderIcon.clone()[0],
name: Messages.fm_sharedFolder,
});
}
options.push({ separator: true });
options.push({
class: 'cp-app-drive-new-fileupload',
icon: Icons.get('drive-upload-file',{'class': 'cp-icon-color-file'}),
name: Messages.uploadButton,
});
if (APP.allowFolderUpload) {
options.push({
class: 'cp-app-drive-new-folderupload',
icon: Icons.get('drive-upload-folder',{'class': 'cp-icon-color-file'}),
name: Messages.uploadFolderButton,
});
}
options.push({ separator: true });
options.push({
class: 'cp-app-drive-new-link',
icon: Icons.get('link', {class:'cp-icon-color-drive'}),
name: Messages.fm_link_new,
});
options.push({ separator: true });
}
getNewPadTypes().forEach(function (type) {
var typeClass = 'cp-app-drive-new-doc';
var premium = common.checkRestrictedApp(type);
if (premium < 0) {
typeClass += ' cp-app-hidden cp-app-disabled';
} else if (premium === 0) {
typeClass += ' cp-app-disabled';
}
options.push({
class: typeClass,
type: type,
icon: getIcon(type),
name: Messages.type[type],
});
});
if (APP.store[FILTER_BY]) {
var typeFilter = APP.store[FILTER_BY];
options = options.filter((obj) => {
if (obj.separator) { return false; }
if (typeFilter === 'link') {
return obj.class.includes('cp-app-drive-new-link');
}
if (typeFilter === 'file') {
return obj.class.includes('cp-app-drive-new-fileupload');
}
if (getNewPadTypes().indexOf(typeFilter) !== -1) {
return typeFilter === obj.type;
}
});
}
return options;
};
var createNewButton = function (isInRoot, $container) {
if (!APP.editable) { return; }
if (!APP.loggedIn) { return; } // Anonymous users can use the + menu in the toolbar
if (!manager.isPathIn(currentPath, [ROOT, 'hrefArray'])) { return; }
// Create dropdown
var options = getNewPadOptions(isInRoot).map(function (obj) {
if (obj.separator) {
return { tag: 'hr', };
}
var newObj = {
tag: 'a',
attributes: { 'class': obj.class },
content: [obj.icon, obj.name]
};
if (obj.type) {
newObj.attributes['data-type'] = obj.type;
newObj.attributes['href'] = APP.origin + Hash.hashToHref('', obj.type);
}
return newObj;
});
var dropdownConfig = {
buttonContent: [
Icons.get('add'),
h('span.cp-button-name', Messages.fm_newButton),
],
buttonCls: 'cp-toolbar-dropdown-nowrap',
options: options,
feedback: 'DRIVE_NEWPAD_LOCALFOLDER',
common: common
};
var $block = UIElements.createDropdown(dropdownConfig);
// Custom style:
// actions for +New menu button
var menuButton = $block.find('button');
menuButton.addClass('cp-app-drive-toolbar-new');
addNewPadHandlers($block, isInRoot);
$container.append($block);
};
var createFilterButton = function (isTemplate, $container) {
if (!APP.loggedIn) { return; }
// Create dropdown
var options = [];
if (APP.store[FILTER_BY]) {
options.push({
tag: 'a',
attributes: {
'class': 'cp-app-drive-rm-filter',
},
content: [
Icons.get('close'),
Messages.fm_rmFilter,
],
});
options.push({tag: 'hr'});
}
getNewPadTypes().forEach(function (type) {
var attributes = {
'class': 'cp-app-drive-filter-doc',
'data-type': type,
};
var premium = common.checkRestrictedApp(type);
if (premium < 0) {
attributes.class += ' cp-app-hidden cp-app-disabled';
} else if (premium === 0) {
attributes.class += ' cp-app-disabled';
}
options.push({
tag: 'a',
attributes: attributes,
content: [
getIcon(type),
Messages.type[type]
],
});
});
if (!isTemplate) {
options.push({tag: 'hr'});
options.push({
tag: 'a',
attributes: {
'class': 'cp-app-drive-filter-doc',
'data-type': 'link',
},
content: [
Icons.get('link', {'class': 'cp-icon-color-drive'}),
Messages.fm_link_type
],
});
options.push({
tag: 'a',
attributes: {
'class': 'cp-app-drive-filter-doc',
'data-type': 'file',
},
content: [
Icons.get('file', {'class': 'cp-icon-color-file'}),
Messages.type['file']
],
});
}
var dropdownConfig = {
buttonContent: [
Icons.get('filter'),
h('span.cp-button-name', Messages.fm_filterBy),
],
buttonCls: 'cp-toolbar-dropdown-nowrap',
options: options,
feedback: 'DRIVE_FILTERBY',
common: common
};
if (APP.store[FILTER_BY]) {
var type = APP.store[FILTER_BY];
var message = type === 'link' ? Messages.fm_link_type : Messages.type[type];
dropdownConfig.buttonContent.push(
h('span.cp-button-name', ':'),
getIcon(type),
h('span.cp-button-name', message)
);
}
var $block = UIElements.createDropdown(dropdownConfig);
// Add style
if (APP.store[FILTER_BY]) {
$block.find('button').addClass('cp-toolbar-button-active');
}
// Add handlers
if (APP.store[FILTER_BY]) {
$block.find('a.cp-app-drive-rm-filter')
.click(function () {
APP.store[FILTER_BY] = undefined;
APP.displayDirectory(currentPath);
});
}
$block.find('a.cp-app-drive-filter-doc')
.click(function () {
var type = $(this).attr('data-type') || 'invalid-filter';
APP.store[FILTER_BY] = type;
APP.displayDirectory([FILTER, type, currentPath]);
});
$container.append($block);
};
var SORT_FOLDER_DESC = 'sortFoldersDesc';
var SORT_FILE_BY = 'sortFilesBy';
var SORT_FILE_DESC = 'sortFilesDesc';
var getSortFileDesc = function () {
return APP.store[SORT_FILE_DESC]+"" === "true";
};
var getSortFolderDesc = function () {
return APP.store[SORT_FOLDER_DESC]+"" === "true";
};
var onSortByClick = function () {
var $span = $(this);
var value;
if ($span.hasClass('cp-app-drive-sort-foldername')) {
value = getSortFolderDesc();
APP.store[SORT_FOLDER_DESC] = value ? false : true;
localStore.put(SORT_FOLDER_DESC, value ? false : true);
refresh();
return;
}
value = APP.store[SORT_FILE_BY];
var descValue = getSortFileDesc();
if ($span.hasClass('cp-app-drive-sort-filename')) {
if (value === '') {
descValue = descValue ? false : true;
} else {
descValue = false;
value = '';
}
} else {
['cp-app-drive-element-type',
'cp-app-drive-element-atime', 'cp-app-drive-element-ctime'].some(function (c) {
if ($span.hasClass(c)) {
var nValue = c.replace(/cp-app-drive-element-/, '');
if (value === nValue) { descValue = descValue ? false : true; }
else {
// atime and ctime should be ordered in a desc order at the first click
value = nValue;
descValue = value !== 'title';
}
return true;
}
});
}
APP.store[SORT_FILE_BY] = value;
APP.store[SORT_FILE_DESC] = descValue;
localStore.put(SORT_FILE_BY, value);
localStore.put(SORT_FILE_DESC, descValue);
refresh();
};
var addFolderSortIcon = function ($list) {
var $icon = $sortAscIcon.clone();
if (getSortFolderDesc()) {
$icon = $sortDescIcon.clone();
}
if (typeof(APP.store[SORT_FOLDER_DESC]) !== "undefined") {
$list.find('.cp-app-drive-sort-foldername').addClass('cp-app-drive-sort-active').prepend($icon);
}
};
var getSortDropdown = function () {
var $fhSort = $(h('span.cp-dropdown-container.cp-app-drive-element-sort.cp-app-drive-sort-clickable'));
var options = [{
tag: 'a',
attributes: {'class': 'cp-app-drive-element-type'},
content: [
Icons.get('minus'),
Messages.fm_type,
],
action: function (e) { onSortByClick.call($(e.target).find('a')[0]); }
},{
tag: 'a',
attributes: {'class': 'cp-app-drive-element-atime'},
content: [
Icons.get('minus'),
Messages.fm_lastAccess,
],
action: function (e) { onSortByClick.call($(e.target).find('a')[0]); }
},{
tag: 'a',
attributes: {'class': 'cp-app-drive-element-ctime'},
content: [
Icons.get('minus'),
Messages.fm_creation,
],
action: function (e) { onSortByClick.call($(e.target).find('a')[0]); }
}];
var dropdownConfig = {
text: '', // Button initial text
options: options, // Entries displayed in the menu
container: $fhSort,
left: true,
noscroll: true,
common: common
};
var $sortBlock = UIElements.createDropdown(dropdownConfig);
$sortBlock.find('button').append(Icons.get('sort-amount-desc')).append(h('span', Messages.fm_sort));
return $fhSort;
};
var getFolderListHeader = function (clickable, small) {
var $fohElement = $('<li>', {
'class': 'cp-app-drive-element-header cp-app-drive-element-list'
});
var clickCls = clickable ? 'cp-app-drive-sort-clickable ' : '';
var onClick = clickable ? onSortByClick : function () {};
//var $fohElement = $('<span>', {'class': 'element'}).appendTo($folderHeader);
var $name = $('<span>', {
'class': 'cp-app-drive-element-name cp-app-drive-sort-foldername ' + clickCls
}).text(Messages.fm_folderName).click(onClick);
var $state = $('<span>', {'class': 'cp-app-drive-element-state'});
var $subfolders, $files;
if (!small) {
$subfolders = $('<span>', {
'class': 'cp-app-drive-element-folders cp-app-drive-element-list'
}).text(Messages.fm_numberOfFolders);
$files = $('<span>', {
'class': 'cp-app-drive-element-files cp-app-drive-element-list'
}).text(Messages.fm_numberOfFiles);
}
var $filler = $('<span>', {
'class': 'cp-app-drive-element-filler cp-app-drive-element-list'
});
$fohElement.append($name).append($state)
.append($subfolders).append($files).append($filler);
if (clickable) { addFolderSortIcon($fohElement); }
return $fohElement;
};
var addFileSortIcon = function ($list) {
var $icon = $sortAscIcon.clone();
if (getSortFileDesc()) {
$icon = $sortDescIcon.clone();
}
var classSorted;
if (APP.store[SORT_FILE_BY] === '') { classSorted = 'cp-app-drive-sort-filename'; }
else if (APP.store[SORT_FILE_BY]) { classSorted = 'cp-app-drive-element-' + APP.store[SORT_FILE_BY]; }
if (classSorted) {
$list.find('.' + classSorted).addClass('cp-app-drive-sort-active').prepend($icon).find('i').eq(1).hide();
}
};
var getFileListHeader = function (clickable) {
var $fihElement = $('<li>', {
'class': 'cp-app-drive-element-header cp-app-drive-element-list'
});
var clickCls = clickable ? 'cp-app-drive-sort-clickable ' : '';
var onClick = clickable ? onSortByClick : function () {};
//var $fihElement = $('<span>', {'class': 'element'}).appendTo($fileHeader);
var $fhIcon = $('<span>', {'class': 'cp-app-drive-content-icon'});
var $fhName = $('<span>', {
'class': 'cp-app-drive-element-name cp-app-drive-sort-filename ' + clickCls
}).text(Messages.fm_fileName).click(onClick);
var $fhSort = clickable ? getSortDropdown() : undefined;
var $fhState = $('<span>', {'class': 'cp-app-drive-element-state'});
var $fhType = $('<span>', {
'class': 'cp-app-drive-element-type cp-app-drive-element-list ' + clickCls
}).text(Messages.fm_type).click(onClick);
var $fhAdate = $('<span>', {
'class': 'cp-app-drive-element-atime cp-app-drive-element-list ' + clickCls
}).text(Messages.fm_lastAccess).click(onClick);
var $fhCdate = $('<span>', {
'class': 'cp-app-drive-element-ctime cp-app-drive-element-list ' + clickCls
}).text(Messages.fm_creation).click(onClick);
// If displayTitle is false, it means the "name" is the title, so do not display the "name" header
$fihElement.append($fhIcon).append($fhName).append($fhSort).append($fhState).append($fhType);
$fihElement.append($fhAdate).append($fhCdate);
if (clickable) { addFileSortIcon($fihElement); }
return $fihElement;
};
var lexicographicCompare = function(a, b) {
if (!Array.isArray(a)) {
a = [a];
}
if (!Array.isArray(b)) {
b = [b];
}
if (a.length === 0 && b.length === 0) {
return 0;
} else if (a.length === 0) {
return -1;
} else if (b.length === 0) {
return 1;
} else if (typeof (a[0]) !== typeof (b[0])) {
return String(a[0]) < String(b[0]) ? -1 : 1;
} else {
if (a[0] < b[0]) {
return -1;
} else if (a[0] > b[0]) {
return 1;
} else {
// This means `a[0] == b[0]`. Chop off the first elements and compare the rest.
return lexicographicCompare(a.slice(1), b.slice(1));
}
}
};
var splitStringToTextAndNumbers = function(s) {
var textOrDigitsRe = /(?<text>\D+)?(?<digits>\d+)?/g;
var split = [];
for (var match of s.matchAll(textOrDigitsRe)) {
if (match.groups.text !== undefined) {
split.push(match.groups.text);
}
if (match.groups.digits !== undefined) {
split.push(parseInt(match.groups.digits));
}
}
return split;
};
var naturalSort = function(a, b) {
if (typeof(a) === "string") {
a = splitStringToTextAndNumbers(a);
}
if (typeof(b) === "string") {
b = splitStringToTextAndNumbers(b);
}
var comp = lexicographicCompare(a, b);
return comp;
};
var sortElements = function (folder, path, oldkeys, prop, asc, useId) {
var root = path && manager.find(path);
if (path[0] === SHARED_FOLDER) {
path = path.slice(1);
root = Util.find(folders[APP.newSharedFolder], path);
}
var test = folder ? manager.isFolder : manager.isFile;
var keys = oldkeys.filter(function (e) {
return useId ? test(e) : (path && test(root[e]));
});
if (keys.length < 2) { return keys; }
var mult = asc ? 1 : -1;
var getProp = function (_el) {
var el = useId ? _el : root[_el];
var sfId = (el && el.root && el.key) ? el.root[el.key] : el;
if (folder && el && manager.isSharedFolder(sfId)) {
var sfData = manager.getSharedFolderData(sfId);
var title = sfData.title || sfData.lastTitle || el;
return String(title).toLowerCase();
} else if (folder) {
return String((el && el.key) || _el).toLowerCase();
}
var data = manager.getFileData(el);
if (!data) { return ''; }
if (prop === 'type') {
var hrefData = Hash.parsePadUrl(data.href || data.roHref);
return hrefData.type;
}
if (prop === 'atime' || prop === 'ctime') {
return typeof(data[prop]) === "number" ? data[prop] : new Date(data[prop]);
}
return (manager.getTitle(el) || "").toLowerCase();
};
var props = {};
keys.forEach(function (k) {
var uid = k;
if (typeof(k) === "object") {
uid = k.uid = Util.uid();
}
props[uid] = getProp(k);
});
keys.sort(function(a, b) {
var _a = props[(a && a.uid) || a];
var _b = props[(b && b.uid) || b];
return mult * naturalSort(_a, _b);
});
return keys;
};
var sortTrashElements = function (folder, oldkeys, prop, asc) {
var test = folder ? manager.isFolder : manager.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 = manager.getFileData(element);
if (!e) {
e = {
href : el,
title : Messages.fm_noname,
atime : 0,
ctime : 0
};
}
if (prop === 'type') {
var hrefData = Hash.parsePadUrl(e.href || e.roHref);
return hrefData.type;
}
if (prop === 'atime' || prop === 'ctime') {
return new Date(e[prop]);
}
}
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;
};
var filterPads = function (files, type, path, useId) {
var root = path && manager.find(path);
return files
.filter(function (e) {
return useId ? manager.isFile(e) : (path && manager.isFile(root[e]));
})
.filter(function (e) {
var id = useId ? e : root[e];
var data = manager.getFileData(id);
if (type === 'link') { return data.static; }
var href = data.href || data.roHref;
return href ? (href.split('/')[1] === type) : true;
// if types are unreachable, display files to avoid misleading the user
});
};
// Create the ghost icon to add pads/folders
var createNewPadIcons = function ($block, isInRoot) {
var $container = $('<ul>');
getNewPadOptions(isInRoot).forEach(function (obj) {
if (obj.separator) { return; }
var $element = $('<li>', {
'class': obj.class + ' cp-app-drive-element-row ' +
'cp-app-drive-element-grid',
'tabindex': 1
}).prepend(obj.icon).appendTo($container);
$element.append($('<span>', { 'class': 'cp-app-drive-new-name' }).text(obj.name));
if (obj.type) {
$element.attr('data-type', obj.type);
}
});
$container.find('.cp-app-drive-element-row').click(function () {
$block.hide();
});
return $container;
};
var createGhostIcon = function ($list) {
if (APP.$content.data('readOnlyFolder') || !APP.editable) { return; }
var isInRoot = currentPath[0] === ROOT;
var $element = $('<li>', {
'class': 'cp-app-drive-element-row cp-app-drive-new-ghost',
'tabindex': 0
}).prepend($addIcon.clone()).appendTo($list);
$element.append($('<span>', {'class': 'cp-app-drive-element-name'})
.text(Messages.fm_newButton));
$element.click(function () {
var modal = UI.createModal({
id: 'cp-app-drive-new-ghost-dialog',
$body: $('body')
});
var $modal = modal.$modal;
var $title = $(h('h3', [ Icons.get('add'), ' ', Messages.fm_newButton ]));
var $description = $('<p>').text(Messages.fm_newButtonTitle);
$modal.find('.cp-modal').append($title);
$modal.find('.cp-modal').append($description);
var $content = createNewPadIcons($modal, isInRoot);
$modal.find('.cp-modal').append($content);
window.setTimeout(function () { modal.show(); });
addNewPadHandlers($modal, isInRoot);
});
$element.keydown(function(){
if (event.which === 13) {
event.stopPropagation();
$element.click();
}
});
};
// Drive content toolbar
var checkCollapseButton = function () {
APP.$collapseButton.removeClass('cp-toolbar-button-active');
if (APP.$tree.is(':visible')) {
APP.$collapseButton.addClass('cp-toolbar-button-active');
}
};
var collapseTreeButton = function () {
APP.$collapseButton = APP.$collapseButton || common.createButton('', true, {
text: Messages.drive_treeButton,
name: 'files',
icon: 'drive',
drawer: false,
});
checkCollapseButton();
APP.toolbar.$bottomL.append(APP.$collapseButton);
APP.$collapseButton.off('click').on('click', function () {
APP.$tree.toggle();
if (!APP.mobile()) {
APP.$splitter.toggle(APP.$tree.is(':visible'));
}
checkCollapseButton();
});
};
var createToolbar = function () {
var $toolbar = APP.toolbar.$bottom;
APP.toolbar.$bottomL.html('');
APP.toolbar.$bottomR.html('');
if (APP.histConfig && (APP.loggedIn || !APP.newSharedFolder)) {
// ANON_SHARED_FOLDER
var $hist = common.createButton('history', true, {histConfig: APP.histConfig});
APP.toolbar.$bottomR.append($hist);
}
if (APP.$burnThisDrive) {
APP.toolbar.$bottomR.append(APP.$burnThisDrive);
}
// this button is not useful for unregistered users who do not have a tree worth looking at
if (APP.loggedIn) { collapseTreeButton(); }
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, typeFilter) {
var unsorted = files[rootName];
if (unsorted.length) {
var $fileHeader = getFileListHeader(true);
$container.append($fileHeader);
}
var keys = unsorted;
var sortBy = APP.store[SORT_FILE_BY];
sortBy = sortBy === "" ? sortBy = 'name' : sortBy;
var sortedFiles = sortElements(false, [rootName], keys, sortBy, !getSortFileDesc(), true);
sortedFiles = typeFilter ? filterPads(sortedFiles, typeFilter, false, true) : sortedFiles;
sortedFiles.forEach(function (id) {
var file = manager.getFileData(id);
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(id);
var $icon = getFileIcon(id);
var ro = manager.isReadOnlyFile(id);
// ro undefined mens it's an old hash which doesn't support read-only
var roClass = typeof(ro) === 'undefined' ? ' cp-app-drive-element-noreadonly' :
ro ? ' cp-app-drive-element-readonly' : '';
var $element = $('<li>', {
'class': 'cp-app-drive-element cp-app-drive-element-file cp-app-drive-element-row' + roClass,
draggable: draggable
});
var path = [rootName, idx];
$element.data('path', path);
if (isElementSelected($element)) {
selectElement($element);
}
$element.prepend($icon).dblclick(function () {
openFile(id);
});
addFileData(id, $element);
$element.click(function(e) {
e.stopPropagation();
onElementClick(e, $element);
});
$element.contextmenu(openContextMenu('default'));
$element.data('context', 'default');
if (draggable) {
addDragAndDropHandlers($element, path, false, false);
}
$container.append($element);
});
createGhostIcon($container);
};
var displayAllFiles = function ($container) {
if (AppConfig.disableAnonymousStore && !APP.loggedIn) {
$container.append(Messages.anonymousStoreDisabled);
return;
}
var allfiles = files[FILES_DATA];
if (Object.keys(allfiles || {}).length === 0) {
createGhostIcon($container);
return;
}
var $fileHeader = getFileListHeader(true);
$container.append($fileHeader);
var keys = manager.getFiles([FILES_DATA]);
var sortedFiles = sortElements(false, [FILES_DATA], keys, APP.store[SORT_FILE_BY], !getSortFileDesc(), true);
sortedFiles.forEach(function (id) {
var $icon = getFileIcon(id);
var ro = manager.isReadOnlyFile(id);
// ro undefined maens it's an old hash which doesn't support read-only
var roClass = typeof(ro) === 'undefined' ? ' cp-app-drive-element-noreadonly' :
ro ? ' cp-app-drive-element-readonly' : '';
var $element = $('<li>', {
'class': 'cp-app-drive-element cp-app-drive-element-row' + roClass
});
$element.prepend($icon).dblclick(function () {
openFile(id);
});
addFileData(id, $element);
$element.data('path', [FILES_DATA, id]);
$element.data('element', id);
$element.click(function(e) {
e.stopPropagation();
onElementClick(e, $element);
});
$element.contextmenu(openContextMenu('default'));
$element.data('context', 'default');
$container.append($element);
});
createGhostIcon($container);
};
var displayTrashRoot = function ($list, $folderHeader, $fileHeader, typeFilter) {
var filesList = [];
var root = files[TRASH];
var isEmpty = true;
// Elements in the trash are JS arrays (several elements can have the same name)
Object.keys(root).forEach(function (key) {
if (!Array.isArray(root[key])) {
logError("Trash element has a wrong type", root[key]);
return;
}
root[key].forEach(function (el, idx) {
if (!manager.isFile(el.element) && !manager.isFolder(el.element)) { return; }
var spath = [key, idx, 'element'];
filesList.push({
element: el.element,
spath: spath,
name: key
});
});
isEmpty = false;
});
var sortedFolders = typeFilter ? [] : sortTrashElements(true, filesList, null, !getSortFolderDesc());
var sortedFiles = sortTrashElements(false, filesList, APP.store[SORT_FILE_BY], !getSortFileDesc);
if (typeFilter) {
var ids = sortedFiles.map(function (obj) { return obj.element; });
var idsFilter = filterPads(ids, typeFilter, false, true);
sortedFiles = sortedFiles.filter(function (obj) {
return (idsFilter.indexOf(obj.element) !== -1);
});
// prevent trash emptying while filter is active
isEmpty = true;
}
if (!isEmpty) {
var $empty = createEmptyTrashButton();
$content.append($empty);
}
if (!typeFilter && manager.hasSubfolder(root, true)) { $list.append($folderHeader); }
sortedFolders.forEach(function (f) {
var $element = createElement([TRASH], f.spath, root, true);
$list.append($element);
});
if (manager.hasFile(root, true)) { $list.append($fileHeader); }
sortedFiles.forEach(function (f) {
var $element = createElement([TRASH], f.spath, root, false);
$list.append($element);
});
};
APP.Search = {};
var displaySearch = function ($list, value) {
var search = APP.Search;
var $div = $('<div>', {'id': 'cp-app-drive-search', 'class': 'cp-unselectable'});
$searchIcon.clone().appendTo($div);
var $spinnerContainer = $(h('div.cp-app-drive-search-spinner'));
var spinner = UI.makeSpinner($spinnerContainer);
var searching = true;
var $input = APP.Search.$input = $('<input>', {
id: 'cp-app-drive-search-input',
placeholder: Messages.fm_searchName,
type: 'text',
draggable: false,
tabindex: 1,
}).keyup(function (e) {
if (searching) {
e.preventDefault();
e.stopPropagation();
return;
}
var currentValue = $input.val().trim();
if (search.to) { window.clearTimeout(search.to); }
if (e.which === 13) {
spinner.spin();
var newLocation = [SEARCH, $input.val()];
search.cursor = $input[0].selectionStart;
if (!manager.comparePath(newLocation, currentPath.slice())) {
searching = true;
APP.displayDirectory(newLocation);
}
return;
}
if (e.which === 27) {
$input.val('');
search.cursor = 0;
searching = true;
APP.displayDirectory([SEARCH]);
return;
}
if (currentValue === "") {
search.cursor = 0;
APP.displayDirectory([SEARCH]);
return;
}
if (currentValue.length < 2) { return; } // Don't autosearch 1 character
search.to = window.setTimeout(function () {
var newLocation = [SEARCH, $input.val()];
search.cursor = $input[0].selectionStart;
if (currentValue === search.value) { return; }
if (!manager.comparePath(newLocation, currentPath.slice())) {
searching = true;
APP.displayDirectory(newLocation);
}
}, 500);
}).on('click mousedown mouseup', function (e) {
e.stopPropagation();
}).val(value || '').appendTo($div);
$input[0].selectionStart = search.cursor || 0;
$input[0].selectionEnd = search.cursor || 0;
var cancel = Icons.get('close', {class: 'cp-app-drive-search-cancel',title:Messages.cancel}); // not visible
cancel.addEventListener('click', function () {
$input.val('');
search.cursor = 0;
APP.displayDirectory([SEARCH]);
});
$div.append(cancel);
$div.append($spinnerContainer);
$list.append($div);
setTimeout(function () {
$input.focus();
});
if (typeof(value) === "string" && value.trim()) {
spinner.spin();
} else {
searching = false;
return;
}
setTimeout(function () {
//$list.closest('#cp-app-drive-content-folder').addClass('cp-app-drive-content-list');
var filesList = manager.search(value);
if (!filesList.length) {
$list.append(h('div.cp-app-drive-search-noresult', Messages.fm_noResult));
spinner.hide();
searching = false;
return;
}
var sortable = {};
var sortableFolders = [];
filesList.forEach(function (r) {
// if r.id === null, then it's a folder, not a file
r.paths.forEach(function (path) {
if (!r.inSharedFolder &&
APP.hideDuplicateOwned && manager.isDuplicateOwned(path)) { return; }
var _path = path.slice();
var key = path.pop();
var root = manager.find(path);
var obj = {
path: path,
_path: _path,
key: key,
root: root,
data: r.data
};
if (manager.isFolder(root[key])) {
sortableFolders.push(obj);
return;
}
sortable[root[key]] = obj;
});
});
var _folders = sortElements(true, [ROOT], sortableFolders, null, !getSortFolderDesc(), true);
var sortableKeys = Object.keys(sortable).map(Number);
var _files = sortElements(false, [ROOT], sortableKeys, APP.store[SORT_FILE_BY], !getSortFileDesc(), true);
var addEl = function (obj, folder) {
var $element = createElement(obj.path, obj.key, obj.root, folder);
$element.addClass('cp-app-drive-element-notrash cp-app-drive-search-result');
$element.off('contextmenu');
$element.contextmenu(openContextMenu('default'));
$element.data('context', 'default');
if (folder) {
$element.find('.cp-app-drive-element-list').css({
visibility: 'hidden'
}).text('');
}
if (manager.isPathIn(obj._path, ['hrefArray'])) {
obj._path.pop();
obj._path.push(obj.data.title);
}
var $path = $('<span>', {
'class': 'cp-app-drive-search-path'
}).appendTo($element.find('.cp-app-drive-element-name'));
createTitle($path, obj._path);
$list.append($element);
};
if (_folders.length) { getFolderListHeader(true, true).appendTo($list); }
_folders.forEach(function (el) {
var obj = el;
addEl(obj, true);
});
if (_files.length) { getFileListHeader(true).appendTo($list); }
_files.forEach(function (el) {
var obj = sortable[el];
addEl(obj, false);
});
setTimeout(collapseDrivePath);
spinner.hide();
searching = false;
});
};
var displayRecent = function ($list, typeFilter) {
var filesList = manager.getRecentPads();
var limit = 20;
var now = new Date();
var last1 = new Date(now);
last1.setDate(last1.getDate()-1);
var last7 = new Date(now);
last7.setDate(last7.getDate()-7);
var last28 = new Date(now);
last28.setDate(last28.getDate()-28);
var header7, header28, headerOld;
var i = 0;
var channels = [];
var toShow = [];
if (typeFilter) {
let ids = filesList.map(function (arr) { return arr[0]; });
var idsFilter = filterPads(ids, typeFilter, false, true);
filesList = filesList.filter(function (arr) {
return (idsFilter.indexOf(arr[0]) !== -1);
});
}
var $fileHeader = getFileListHeader(false);
$list.append($fileHeader);
$list.append(h('li.cp-app-drive-element-separator', h('span', Messages.drive_active1Day)));
filesList.some(function (arr) {
var id = arr[0];
var file = arr[1];
if (!file || !file.atime) { return; }
if (file.atime <= last28 && i >= limit) {
return true;
}
if (!file.channel) { file.channel = id; }
if (channels.indexOf(file.channel) !== -1) { return; }
channels.push(file.channel);
toShow.push({id, file});
i++;
});
let ids = toShow.map(obj => { return obj.id; });
var allPaths = manager.findFiles(ids);
toShow.forEach(obj => {
let file = obj.file;
let id = obj.id;
if (!header7 && file.atime < last1) {
$list.append(h('li.cp-app-drive-element-separator', h('span', Messages.drive_active7Days)));
header7 = true;
}
if (!header28 && file.atime < last7) {
$list.append(h('li.cp-app-drive-element-separator', h('span', Messages.drive_active28Days)));
header28 = true;
}
if (!headerOld && file.atime < last28) {
$list.append(h('li.cp-app-drive-element-separator', h('span', Messages.drive_activeOld)));
headerOld = true;
}
// Display the pad
/*
var $icon = getFileIcon(id);
var ro = manager.isReadOnlyFile(id);
// ro undefined means it's an old hash which doesn't support read-only
var roClass = typeof(ro) === 'undefined' ? ' cp-app-drive-element-noreadonly' :
ro ? ' cp-app-drive-element-readonly' : '';
var $element = $('<li>', {
'class': 'cp-app-drive-element cp-app-drive-element-notrash cp-app-drive-element-file cp-app-drive-element-row' + roClass,
});*/
var paths = allPaths[id];
if (paths.every(path => {
return manager.isPathIn(path, [TRASH]);
})) { return; }
var path = paths[0];
var parentPath = path.slice();
var key = parentPath.pop();
var root = manager.find(parentPath);
var $element = createElement(parentPath, key, root);
$element.off('contextmenu').contextmenu(openContextMenu('default'));
$element.data('context', 'default');
$list.append($element);
});
};
// Owned pads category
var displayOwned = function ($container) {
var list = manager.getOwnedPads();
if (list.length === 0) { return; }
var $fileHeader = getFileListHeader(true);
$container.append($fileHeader);
var sortedFiles = sortElements(false, false, list, APP.store[SORT_FILE_BY], !getSortFileDesc(), true);
sortedFiles.forEach(function (id) {
var paths = manager.findFile(id);
if (!paths.length) { return; }
var path = paths[0];
var $icon = getFileIcon(id);
var ro = manager.isReadOnlyFile(id);
// ro undefined maens it's an old hash which doesn't support read-only
var roClass = typeof(ro) === 'undefined' ? ' cp-app-drive-element-noreadonly' :
ro ? ' cp-app-drive-element-readonly' : '';
var $element = $('<li>', {
'class': 'cp-app-drive-element cp-app-drive-element-notrash ' +
'cp-app-drive-element-file cp-app-drive-element-row' + roClass
});
$element.prepend($icon).dblclick(function () {
openFile(id);
});
addFileData(id, $element);
$element.data('path', path);
$element.data('element', id);
$element.click(function(e) {
e.stopPropagation();
onElementClick(e, $element);
});
$element.contextmenu(openContextMenu('default'));
$element.data('context', 'default');
$container.append($element);
});
};
// Tags category
var displayTags = function ($container) {
var list = manager.getTagsList();
if (Object.keys(list).length === 0) { return; }
var sortedTags = Object.keys(list);
sortedTags.sort(function (a, b) {
return list[b] - list[a];
});
var lines = [
h('tr', [
h('th', Messages.fm_tags_name),
h('th', Messages.fm_tags_used)
])
];
sortedTags.forEach(function (tag) {
var tagLink = h('a', { href: '#' }, '#' + tag);
$(tagLink).click(function (e) {
e.preventDefault();
if (displayedCategories.indexOf(SEARCH) !== -1) {
APP.displayDirectory([SEARCH, '#' + tag]);
}
});
lines.push(h('tr', [
h('td', tagLink),
h('td.cp-app-drive-tags-used', list[tag])
]));
});
$(h('li.cp-app-drive-tags-list', h('table', lines))).appendTo($container);
};
// ANON_SHARED_FOLDER
// Display a shared folder for anon users (read-only)
var displaySharedFolder = function ($list) {
if (currentPath.length === 1) {
currentPath.push(ROOT);
}
var fId = APP.newSharedFolder;
var data = folders[fId];
var $folderHeader = getFolderListHeader(true);
var $fileHeader = getFileListHeader(true);
var path = currentPath.slice(1);
var root = Util.find(data, path);
var realPath = [ROOT, SHARED_FOLDER].concat(path);
if (manager.hasSubfolder(root)) { $list.append($folderHeader); }
// display sub directories
var keys = Object.keys(root);
var sortedFolders = sortElements(true, realPath, keys, null, !getSortFolderDesc());
var sortedFiles = sortElements(false, realPath, keys, APP.store[SORT_FILE_BY], !getSortFileDesc());
sortedFolders.forEach(function (key) {
if (manager.isFile(root[key])) { return; }
var $element = createElement(realPath, key, root, true);
$element.appendTo($list);
});
if (manager.hasFile(root)) { $list.append($fileHeader); }
// display files
sortedFiles.forEach(function (key) {
if (manager.isFolder(root[key])) { return; }
var $element = createElement(realPath, key, root, false);
if (!$element) { return; }
$element.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
var _displayDirectory = function (path, force) {
if (APP.closed || (APP.$content && !$.contains(document.documentElement, APP.$content[0]))) { return; }
APP.hideMenu();
if (!APP.editable) { debug("Read-only mode"); }
if (!appStatus.isReady && !force) { return; }
// Fix path obvious issues
if (!path || path.length === 0) {
// Only Trash and Root are available in not-owned files manager
if (!path || displayedCategories.indexOf(path[0]) === -1) {
log(Messages.fm_categoryError);
}
if (!APP.loggedIn && APP.newSharedFolder) {
// ANON_SHARED_FOLDER
path = [SHARED_FOLDER, ROOT];
} else {
path = [ROOT];
}
}
if (APP.loggedIn && path[0] === FILES_DATA) {
path = [ROOT];
}
// Get path data
appStatus.ready(false);
currentPath = path;
var s = $content.scrollTop() || 0;
$content.html("");
sel.$selectBox = $('<div>', {'class': 'cp-app-drive-content-select-box'})
.appendTo($content);
var typeFilter;
var isFilter = path[0] === FILTER;
if (isFilter) {
if (path.length < 3) { return; }
typeFilter = path[1];
path = path[2];
currentPath = path;
} else {
APP.store[FILTER_BY] = undefined;
}
var isInRoot = manager.isPathIn(path, [ROOT]);
var inTrash = manager.isPathIn(path, [TRASH]);
var isTrashRoot = manager.comparePath(path, [TRASH]);
var isTemplate = manager.comparePath(path, [TEMPLATE]);
var isAllFiles = manager.comparePath(path, [FILES_DATA]);
var isVirtual = virtualCategories.indexOf(path[0]) !== -1;
var isSearch = path[0] === SEARCH;
var isRecent = path[0] === RECENT;
var isOwned = path[0] === OWNED;
var isTags = path[0] === TAGS;
// ANON_SHARED_FOLDER
var isSharedFolder = path[0] === SHARED_FOLDER && APP.newSharedFolder;
if (isSharedFolder && path.length < 2) {
path = [SHARED_FOLDER, 'root'];
currentPath = path;
}
// Make sure the path is valid
var root = isVirtual ? undefined : manager.find(path);
if (manager.isSharedFolder(root)) {
// ANON_SHARED_FOLDER
path.push(manager.user.userObject.ROOT);
root = manager.find(path);
if (!root) { return; }
}
if (!isVirtual && typeof(root) === "undefined") {
if (!force) {
log(Messages.fm_unknownFolderError);
}
debug("Unable to locate the selected directory: ", path);
if (path.length === 1 && path[0] === ROOT) {
// Somehow we can't display ROOT. We should abort now because we'll
// end up in an infinite loop
return void UI.warn(Messages.fm_error_cantPin); // Internal server error, please reload...
}
var parentPath = path.slice();
parentPath.pop();
_displayDirectory(parentPath, true);
return;
}
if (!isSearch) { delete APP.Search.oldLocation; }
// Display the tree and build the content
APP.resetTree();
LS.setLastOpenedFolder(path);
createToolbar(path);
if (!isSearch) { createTitle($content, path); }
var $info = createInfoBox(path);
var $dirContent = $('<div>', {id: FOLDER_CONTENT_ID});
$dirContent.data('path', path);
if (!isTags) {
$dirContent.addClass(getViewModeClass(isSearch));
if (!isSearch) {
createViewModeButton(APP.toolbar.$bottomR);
}
}
var $list = $('<ul>').appendTo($dirContent);
var sfId = manager.isInSharedFolder(currentPath);
// Restricted folder? display ROOT instead
if (sfId && files.restrictedFolders[sfId]) {
_displayDirectory([ROOT], true);
return;
}
var readOnlyFolder = false;
// If the shared folder is offline, add the "DISCONNECTED" banner, otherwise
// use the normal "editable" behavior (based on drive offline or history mode)
if (sfId && manager.folders[sfId].offline) {
setEditable(false, false, true);
} else {
setEditable(true, false, true);
}
if (APP.readOnly && !APP.loggedIn) {
(function () {
// show 'READ-ONLY' when a guest only has view rights
if (/\/view\//.test(APP.anonSFHref)) {
// !common.getMetadataMgr().getPrivateData().canEdit
// would accomplish the same thing if this breaks
$content.prepend($readOnly.clone());
return;
}
// otherwise prompt them to log in or register to take advantage of their edit rights
var $banner = $(Pages.setHTML(h('div.cp-app-drive-content-info-box'), Messages.fm_info_sharedFolder));
$banner.find('[href="/login/"], [href="/register/"]').click(function (ev) {
ev.preventDefault();
var page = this.getAttribute('href').replace(/\//g, '');
common.setLoginRedirect(page);
});
$content.prepend($banner);
}());
} else if (APP.readOnly) {
// Read-only drive (team?)
$content.prepend($readOnly.clone());
} else if (sfId && folders[sfId] && folders[sfId].readOnly) {
// If readonly shared folder...
$content.prepend($readOnly.clone());
readOnlyFolder = true;
}
$content.data('readOnlyFolder', readOnlyFolder);
if (!readOnlyFolder) {
createNewButton(isInRoot, APP.toolbar.$bottomL);
}
if (!isTags && !isSearch) {
createFilterButton(isTemplate, APP.toolbar.$bottomL);
}
var $folderHeader = getFolderListHeader(true);
var $fileHeader = getFileListHeader(true);
if (isTemplate) {
displayHrefArray($list, path[0], true, typeFilter);
} else if (isAllFiles) {
displayAllFiles($list);
} else if (isTrashRoot) {
displayTrashRoot($list, $folderHeader, $fileHeader, typeFilter);
} else if (isSearch) {
displaySearch($list, path[1]);
} else if (isRecent) {
displayRecent($list, typeFilter);
} else if (isOwned) {
displayOwned($list);
} else if (isTags) {
displayTags($list);
} else if (isSharedFolder) {
// ANON_SHARED_FOLDER
displaySharedFolder($list);
} else {
if (!inTrash) { $dirContent.contextmenu(openContextMenu('content')); }
if (!isFilter && manager.hasSubfolder(root)) { $list.append($folderHeader); }
// display sub directories
var keys = Object.keys(root);
var sortedFolders = isFilter ? [] : sortElements(true, path, keys, null, !getSortFolderDesc());
var sortedFiles = sortElements(false, path, keys, APP.store[SORT_FILE_BY], !getSortFileDesc());
sortedFiles = isFilter ? filterPads(sortedFiles, typeFilter, path) : sortedFiles;
sortedFolders.forEach(function (key) {
if (manager.isFile(root[key])) { return; }
var $element = createElement(path, key, root, true);
$element.appendTo($list);
});
if (manager.hasFile(root)) { $list.append($fileHeader); }
// display files
sortedFiles.forEach(function (key) {
if (manager.isFolder(root[key])) { return; }
var p = path.slice();
p.push(key);
if (APP.hideDuplicateOwned && manager.isDuplicateOwned(p)) { return; }
var $element = createElement(path, key, root, false);
if (!$element) { return; }
$element.appendTo($list);
});
if (!inTrash) { createGhostIcon($list); }
}
$content.append($info).append($dirContent);
/*var $truncated = $('<span>', {'class': 'cp-app-drive-element-truncated'}).text('...');
$content.find('.cp-app-drive-element').each(function (idx, el) {
var $name = $(el).find('.cp-app-drive-element-name');
if ($name.length === 0) { return; }
if ($name[0].scrollHeight > $name[0].clientHeight) {
var $tr = $truncated.clone();
$tr.attr('title', $name.text());
$(el).append($tr);
}
});*/
// If the selected element is not visible, scroll to make it visible, otherwise scroll to
// the previous scroll position
var $sel = findSelectedElements();
if ($sel.length) {
var _top = $sel[0].getBoundingClientRect().top;
var _topContent = $content[0].getBoundingClientRect().top;
if ((_topContent + s + $content.height() - 20) < _top) {
$sel[0].scrollIntoView();
} else {
$content.scrollTop(s);
}
} else {
$content.scrollTop(s);
}
delete APP.convertedFolder;
appStatus.ready(true);
};
APP.displayDirectory = function (path, force, cb, opt) {
cb = Util.once(cb || function () {});
if (APP.closed || (APP.$content && !$.contains(document.documentElement, APP.$content[0]))) { return; }
if (history.isHistoryMode) {
_displayDirectory(path, force);
return void cb();
}
if (!manager.comparePath(currentPath, path)) {
removeSelected();
}
const fixTags = () => {
if (!displayedCategories.includes(TAGS) &&
Object.keys(manager.getTagsList()).length) { displayedCategories.push(TAGS); }
};
updateObject(sframeChan, proxy, function () {
copyObjectValue(files, proxy.drive);
files.restrictedFolders ||= {};
files.root ||= {};
files.filesData ||= {};
let fpath = currentPath;
if (opt?.init && !manager.isInSharedFolder(fpath) && !priv.anonSFHref) {
fixTags();
// Wait for shared folder metadata
// _displayDirectory(path, force);
// cb();
}
updateSharedFolders(sframeChan, manager, files, folders, function () {
_displayDirectory(path, force);
fixTags();
cb();
});
});
};
// Build tree data recursively
var buildTreeData = function (root, path, currentPath) {
var data = { content: {} };
var keys = Object.keys(root);
keys.forEach(function (key) {
// Do not display files in the menu
if (!manager.isFolder(root[key])) { return; }
var newPath = path.slice();
newPath.push(key);
var isSharedFolder = manager.isSharedFolder(root[key]) && root[key];
var sfId = manager.isInSharedFolder(newPath) || (isSharedFolder && root[key]);
var $icon, isCurrentFolder, subfolder, folderName = key;
var randomKey = Util.uid();
var navPath = newPath.slice();
if (isSharedFolder) {
// Fix path
newPath.push(manager.user.userObject.ROOT);
isCurrentFolder = manager.comparePath(newPath, currentPath);
// Subfolders?
var newRoot = Util.find(manager, ['folders', sfId, 'proxy', manager.user.userObject.ROOT]) || {};
subfolder = manager.hasSubfolder(newRoot);
// Fix name
var sfData = manager.getSharedFolderData(sfId);
folderName = sfData.title || sfData.lastTitle || Messages.fm_deletedFolder;
// Fix icon
$icon = isCurrentFolder ? $sharedFolderOpenedIcon : $sharedFolderIcon;
isSharedFolder = sfId;
} else {
var isEmpty = manager.isFolderEmpty(root[key]);
subfolder = manager.hasSubfolder(root[key]);
isCurrentFolder = manager.comparePath(newPath, currentPath);
$icon = isEmpty ?
(isCurrentFolder ? $folderOpenedEmptyIcon : $folderEmptyIcon) :
(isCurrentFolder ? $folderOpenedIcon : $folderIcon);
}
data.content[randomKey] = {
name: folderName,
icon: $icon,
navPath: navPath,
isActive: isCurrentFolder,
content: {}
};
if (subfolder) {
var subRoot = isSharedFolder ?
Util.find(manager, ['folders', sfId, 'proxy', ROOT]) :
root[key];
if (subRoot) {
data.content[randomKey].content = buildTreeData(subRoot, newPath, currentPath).content;
}
}
});
return data;
};
var createTree = function ($container, path) {
var root = manager.find(path);
var isRoot = manager.comparePath([ROOT], path);
var rootName = ROOT_NAME;
if (APP.newSharedFolder && priv.isEmbed && isRoot) {
var newSFPaths = manager.findFile(Number(APP.newSharedFolder));
if (newSFPaths.length) {
path = newSFPaths[0];
path.push(ROOT);
root = manager.find(path);
rootName = manager.getSharedFolderData(APP.newSharedFolder).title;
}
}
// don't try to display what doesn't exist
if (!root) { return; }
// build tree data structure
var treeData = buildTreeData(root, path, currentPath);
var data = {
root: {
name: rootName,
icon: $(Icons.get('drive')),
content: treeData.content
}
};
var openFolders = LS.getOpenedFolders();
var treeElement = UIElements.getTree(data, {
currentPath: currentPath,
cb: {
addDragAndDropHandlers: addDragAndDropHandlers
},
events: {
onFolderClicked: function (clickedPath) {
// Check for shared folder restrictions
var sfId = manager.isInSharedFolder(clickedPath);
if (sfId) {
if (!manager.folders[sfId]) {
UI.warn(Messages.fm_deletedFolder);
return;
}
if (files.restrictedFolders[sfId]) {
UI.warn(Messages.fm_restricted);
return;
}
}
APP.displayDirectory(clickedPath);
},
onContextMenu: function (clickedPath, event) {
openContextMenu('tree')(event, clickedPath);
},
onFolderExpanded: function (clickedPath, isOpen) {
LS.setFolderOpened(clickedPath, isOpen);
}
},
openFolders: openFolders
});
var $treeElement = $(treeElement);
// Drive specific features
$treeElement.find('.cp-app-drive-element-row').each(function() {
var $row = $(this);
var elPath = $row.data('path');
if (!elPath || elPath.length === 0) { return; }
var pathToCheck = elPath[0] === ROOT ? elPath : [ROOT].concat(elPath);
var isSharedFolderPath = pathToCheck.length > 1 && pathToCheck[pathToCheck.length - 1] === ROOT;
$row.find('.cp-app-drive-icon-folder').css("color", isSharedFolderPath ? getFolderColor(pathToCheck.slice(0, -1)) : getFolderColor(pathToCheck));
var element = manager.find(elPath);
var isSharedFolder = element && manager.isSharedFolder(element);
var sfId = manager.isInSharedFolder(elPath);
var f = null;
var droppable = true;
if (isSharedFolder) {
sfId = element;
f = folders[sfId];
var editable = !(f && f.readOnly);
droppable = editable;
$row.addClass('cp-app-drive-element-sharedf');
if (!editable) {
$row.closest('li').attr('data-ro', true);
}
var sfData = manager.getSharedFolderData(sfId);
_addOwnership($row, $(), sfData);
if (files.restrictedFolders[sfId]) {
$row.addClass('cp-app-drive-element-restricted');
}
// Update drag/drop handlers if droppable changed for shared folder
$row.off('dragover drop dragenter dragleave');
addDragAndDropHandlers($row, elPath, true, droppable);
} else if (sfId) {
f = folders[sfId];
editable = !(f && f.readOnly);
droppable = editable;
if (!editable) {
$row.closest('li').attr('data-ro', true);
}
$row.off('dragover drop dragenter dragleave');
addDragAndDropHandlers($row, elPath, true, droppable);
}
});
$treeElement.find('.cp-app-drive-element-row').first().removeAttr('draggable');
$container.append($treeElement);
};
var createTrash = function ($container, path) {
var $icon = manager.isFolderEmpty(files[TRASH]) ? $trashEmptyIcon.clone() : $trashIcon.clone();
var isOpened = manager.comparePath(path, currentPath);
var $trashElement = UIElements.createTreeElement(TRASH_NAME, $icon, [TRASH], false, true, false, isOpened, {
onFolderClicked: function (clickedPath) {
APP.displayDirectory(clickedPath);
},
onContextMenu: function (clickedPath, event) {
openContextMenu('trashtree')(event, clickedPath);
}
}, [], currentPath, { addDragAndDropHandlers: addDragAndDropHandlers });
$trashElement.addClass('cp-app-drive-tree-root');
var $trashList = $('<ul>', { 'class': 'cp-app-drive-tree-category' }).append($trashElement);
$container.append($trashList);
};
var categories = {};
categories[FILES_DATA] = {
name: FILES_DATA_NAME,
$icon: $unsortedIcon
};
categories[TEMPLATE] = {
name: TEMPLATE_NAME,
droppable: true,
$icon: $templateIcon
};
categories[RECENT] = {
name: RECENT_NAME,
$icon: $recentIcon
};
categories[OWNED] = {
name: OWNED_NAME,
$icon: $ownedIcon
};
categories[SEARCH] = {
name: Messages.fm_searchPlaceholder,
$icon: $searchIcon
};
categories[TAGS] = {
name: TAGS_NAME,
$icon: $tagsIcon
};
var createCategory = function ($container, cat) {
var options = categories[cat];
var $icon = options.$icon.clone();
var isOpened = manager.comparePath([cat], currentPath);
var $element = UIElements.createTreeElement(options.name, $icon, [cat], false, options.droppable, false, isOpened, {
onFolderClicked: function (clickedPath) {
APP.displayDirectory(clickedPath);
}
}, [], currentPath, { addDragAndDropHandlers: addDragAndDropHandlers });
$element.addClass('cp-app-drive-tree-root');
var $list = $('<ul>', { 'class': 'cp-app-drive-tree-category' }).append($element);
$container.append($list);
};
APP.resetTree = function () {
var $categories = $tree.find('.cp-app-drive-tree-categories-container');
var s = $categories.scrollTop() || 0;
$tree.html('');
var $div = $('<div>', {'class': 'cp-app-drive-tree-categories-container'})
.appendTo($tree);
if (displayedCategories.indexOf(SEARCH) !== -1) { createCategory($div, SEARCH); }
if (displayedCategories.indexOf(TAGS) !== -1) { createCategory($div, TAGS); }
if (displayedCategories.indexOf(RECENT) !== -1) { createCategory($div, RECENT); }
if (displayedCategories.indexOf(OWNED) !== -1) { createCategory($div, OWNED); }
if (displayedCategories.indexOf(ROOT) !== -1) { createTree($div, [ROOT]); }
if (displayedCategories.indexOf(TEMPLATE) !== -1) { createCategory($div, TEMPLATE); }
if (displayedCategories.indexOf(FILES_DATA) !== -1) { createCategory($div, FILES_DATA); }
if (displayedCategories.indexOf(TRASH) !== -1) { createTrash($div, [TRASH]); }
$tree.append(APP.$limit);
$categories = $tree.find('.cp-app-drive-tree-categories-container');
$categories.scrollTop(s);
};
APP.hideMenu = function (e) {
$contextMenu.hide();
$trashTreeContextMenu.hide();
$trashContextMenu.hide();
$contentContextMenu.hide();
$defaultContextMenu.hide();
if (!e || !$(e.target).parents('.cp-dropdown')) {
$('.cp-dropdown-content').hide();
}
};
var stringifyPath = function (path) {
if (!Array.isArray(path)) { return; }
var div = h('div');
var space = 10;
path.forEach(function (s, i) {
if (i === 0) { s = getPrettyName(s); }
div.appendChild(h('span', { style: 'margin: 0 0 0 ' + i * space + 'px', }, s));
div.appendChild(h(('br')));
});
return div;
};
// Disable middle click in the context menu to avoid opening /drive/inner.html# in new tabs
var onWindowClick = function (e) {
if (!e.target || !$(e.target).parents('.cp-dropdown-content').length) { return; }
if (e.which !== 1) {
e.stopPropagation();
return false;
}
};
APP.getProperties = function (el, cb) {
if (!manager.isFile(el) && !manager.isSharedFolder(el)) {
return void cb('NOT_FILE');
}
//var ro = manager.isReadOnlyFile(el);
var data;
if (manager.isSharedFolder(el)) {
data = JSON.parse(JSON.stringify(manager.getSharedFolderData(el)));
} else {
data = JSON.parse(JSON.stringify(manager.getFileData(el)));
}
if (!data || !(data.href || data.roHref)) { return void cb('INVALID_FILE'); }
var opts = {};
opts.href = Hash.getRelativeHref(data.href || data.roHref);
opts.channel = data.channel;
if (manager.isSharedFolder(el)) {
var ro = folders[el] && folders[el].version >= 2;
if (!ro) { opts.noReadOnly = true; }
}
Properties.getPropertiesModal(common, opts, cb);
};
APP.getAccess = function (el, cb) {
if (!manager.isFile(el) && !manager.isSharedFolder(el)) {
return void cb('NOT_FILE');
}
var data;
if (manager.isSharedFolder(el)) {
data = JSON.parse(JSON.stringify(manager.getSharedFolderData(el)));
} else {
data = JSON.parse(JSON.stringify(manager.getFileData(el)));
}
if (!data || !(data.href || data.roHref)) { return void cb('INVALID_FILE'); }
var opts = {};
opts.href = Hash.getRelativeHref(data.href || data.roHref);
opts.channel = data.channel;
// Transfer ownership: templates are stored as templates for other users/teams
if (currentPath[0] === TEMPLATE) {
opts.isTemplate = true;
}
// Shared folders: no expiration date
if (manager.isSharedFolder(el)) {
opts.noExpiration = true;
}
let attr = opts.attributes = {};
if (data.rtChannel) { attr.rtChannel = data.rtChannel; }
if (data.lastVersion) { attr.lastVersion = data.lastVersion; }
if (data.lastCpHash) { attr.lastCpHash = data.lastCpHash; }
if (data.answersChannel) { attr.answersChannel = data.answersChannel; }
Access.getAccessModal(common, opts, cb);
};
var deleteOwnedPaths = function (paths, pathsList) {
pathsList = pathsList || [];
if (paths) {
paths.forEach(function (p) { pathsList.push(p.path); });
}
var msgD = pathsList.length === 1 ? Messages.fm_deleteOwnedPad :
Messages.fm_deleteOwnedPads;
UI.confirm(msgD, function(res) {
$(window).focus();
if (!res) { return; }
manager.deleteOwned(pathsList, function () {
pathsList.forEach(LS.removeFoldersOpened);
removeSelected();
refresh();
});
});
};
var downloadFolder = function (folderElement, folderName, sfId) {
var todo = function (data) {
data.folder = folderElement;
data.sharedFolderId = sfId;
data.name = Util.fixFileName(folderName);
data.folderName = Util.fixFileName(folderName) + '.zip';
data.common = common;
var uo = manager.user.userObject;
if (sfId && manager.folders[sfId]) {
uo = manager.folders[sfId].userObject;
}
if (uo.getFilesRecursively) {
data.list = uo.getFilesRecursively(folderElement).map(function (el) {
var d = uo.getFileData(el);
return d.channel;
});
}
APP.FM.downloadFolder(data, function (err, obj) {
console.log(err, obj);
console.log('DONE');
});
};
todo({
uo: proxy,
sf: folders,
});
};
var openInApp = async function (paths, app) {
var p = paths[0];
var el = manager.find(p.path);
var path = currentPath;
if (path[0] !== ROOT) { path = [ROOT]; }
var _metadata = manager.getFileData(el);
var _simpleData = {
title: _metadata.filename || _metadata.title,
href: _metadata.href || _metadata.roHref,
fileType: _metadata.fileType,
password: _metadata.password,
channel: _metadata.channel,
};
const ext = _simpleData.title.split('.').pop();
if (BrokenFormats.brokenImportFormats.includes(ext)) {
await UI.alertPromise(Messages.oo_unstableMigrationWarning);
}
openIn(app, path, APP.team, _simpleData);
};
var addContextEvent = function () { $contextMenu.on("click", "a", function(e) {
e.stopPropagation();
var paths = $contextMenu.data('paths');
var pathsList = [];
var type = $contextMenu.attr('data-menu-type');
var $this = $(this);
var prefix = /cp\-app\-drive\-context\-/;
var command = Util.slice(this.classList)
.map(c => {
if (!prefix.test(c)) { return; }
return c.replace(prefix, '');
}).filter(Boolean);
console.log(command);
var el, data;
if (paths.length === 0) {
log(Messages.fm_forbidden);
debug("Context menu on a forbidden or unexisting element. ", paths);
return;
}
if ($this.hasClass("cp-app-drive-context-rename")) {
if (paths.length !== 1) { return; }
displayRenameInput(paths[0].element, paths[0].path);
}
else if ($this.hasClass('cp-app-drive-context-openfolder')) {
APP.displayDirectory(paths[0].path);
return;
}
else if ($this.hasClass("cp-app-drive-context-color")) {
var currentColor = getFolderColor(paths[0].path);
pickFolderColor(paths[0].element, currentColor, function (color) {
paths.forEach(function (p) {
setFolderColor(p.element, p.path, color);
});
refresh();
});
}
else if($this.hasClass("cp-app-drive-context-delete")) {
if (!APP.loggedIn) {
return void deletePaths(paths);
}
paths.forEach(function (p) { pathsList.push(p.path); });
moveElements(pathsList, [TRASH], false, refresh);
}
else if ($this.hasClass('cp-app-drive-context-deleteowned')) {
deleteOwnedPaths(paths);
}
else if ($this.hasClass('cp-app-drive-context-preview')) {
if (paths.length !== 1) { return; }
el = manager.find(paths[0].path);
openFile(el, null, false);
}
else if ($this.hasClass('cp-app-drive-context-open')) {
paths.forEach(function (p) {
var el = manager.find(p.path);
if (files.restrictedFolders[el]) {
UI.warn(Messages.fm_restricted);
return;
}
openFile(el, false, true);
});
}
else if ($this.hasClass('cp-app-drive-context-openro')) {
paths.forEach(function (p) {
var el = manager.find(p.path);
if (paths[0].path[0] === SHARED_FOLDER && APP.newSharedFolder) {
// ANON_SHARED_FOLDER
el = manager.find(paths[0].path.slice(1), APP.newSharedFolder);
}
if (manager.isPathIn(p.path, [FILES_DATA])) {
el = p.path[1];
} else {
if (!el || manager.isFolder(el)) { return; }
}
openFile(el, true, true);
});
}
else if ($this.hasClass('cp-app-drive-context-makeacopy')) {
if (paths.length !== 1) { return; }
el = manager.find(paths[0].path);
(function () {
var path = currentPath;
if (path[0] !== ROOT) { path = [ROOT]; }
var _metadata = manager.getFileData(el);
var _simpleData = {
title: _metadata.filename || _metadata.title,
href: _metadata.href || _metadata.roHref,
password: _metadata.password,
channel: _metadata.channel,
};
var parsed = Hash.parsePadUrl(_metadata.href || _metadata.roHref);
openIn(parsed.type, path, APP.team, _simpleData);
})();
}
else if ($this.hasClass('cp-app-drive-context-openincode')) {
if (paths.length !== 1) { return; }
openInApp(paths, 'code');
}
else if ($this.hasClass('cp-app-drive-context-openinsheet')) {
if (paths.length !== 1) { return; }
openInApp(paths, 'sheet');
}
else if ($this.hasClass('cp-app-drive-context-openindoc')) {
if (paths.length !== 1) { return; }
openInApp(paths, 'doc');
}
else if ($this.hasClass('cp-app-drive-context-openinpresentation')) {
if (paths.length !== 1) { return; }
openInApp(paths, 'presentation');
}
else if ($this.hasClass('cp-app-drive-context-expandall') ||
$this.hasClass('cp-app-drive-context-collapseall')) {
if (paths.length !== 1) { return; }
var opened = $this.hasClass('cp-app-drive-context-expandall');
var collapsedPath = paths[0].path;
var openRecursive = function (path) {
LS.setFolderOpened(path, opened);
var folderContent = manager.find(path);
if (folderContent && manager.isSharedFolder(folderContent)) {
path = path.concat(manager.user.userObject.ROOT);
folderContent = manager.find(path);
}
if (!folderContent) { return; }
var subfolders = [];
for (var k in folderContent) {
if (manager.isFolder(folderContent[k])) {
if (manager.isSharedFolder(folderContent[k])) {
subfolders.push([k].concat(manager.user.userObject.ROOT));
}
else {
subfolders.push(k);
}
}
}
subfolders.forEach(function (p) {
var subPath = path.concat(p);
if (Array.isArray(p) && p.length > 1 && p[p.length - 1] === ROOT) {
var sharedFolderPath = subPath.slice(0, -1);
LS.setFolderOpened(sharedFolderPath, opened);
}
openRecursive(subPath);
});
};
openRecursive(collapsedPath);
if (!opened && currentPath && manager.isSubpath(currentPath, collapsedPath)) {
APP.displayDirectory(collapsedPath);
} else {
refresh();
}
}
else if ($this.hasClass('cp-app-drive-context-download')) {
if (paths.length !== 1) { return; }
var path = paths[0];
el = $(path.element).data('element') || manager.find(path.path);
// folder
if (manager.isFolder(el)) {
// folder
var name, folderEl;
if (!manager.isSharedFolder(el)) {
name = path.path[path.path.length - 1];
folderEl = el;
var sfId = manager.isInSharedFolder(path.path);
downloadFolder(folderEl, name, sfId);
}
// shared folder
else {
data = manager.getSharedFolderData(el);
name = data.title;
folderEl = manager.find(path.path.concat("root"));
downloadFolder(folderEl, name, el);
}
}
// file
else if (manager.isFile(el)) {
// imported file
if (path.element.is(".cp-border-color-file")) {
data = manager.getFileData(el);
APP.FM.downloadFile(data, function (err, obj) {
console.log(err, obj);
console.log('DONE');
});
}
// pad
else {
data = manager.getFileData(el);
APP.FM.downloadPad(data, function (err, obj) {
console.log(err, obj);
console.log('DONE');
});
}
}
}
else if ($this.hasClass('cp-app-drive-context-share')) {
if (paths.length !== 1) { return; }
el = manager.find(paths[0].path);
var parsed;
var friends = common.getFriends();
var anonDrive = manager.isPathIn(currentPath, [FILES_DATA]) && !APP.loggedIn;
if (manager.isFolder(el) && !manager.isSharedFolder(el) && !anonDrive) { // Folder
// disconnected
if (!APP.editable) {
return void UI.warn(Messages.error);
}
// if folder is inside SF
else if (manager.isInSharedFolder(paths[0].path)) {
return void UI.alert(Messages.convertFolderToSF_SFParent);
}
// if folder already contains SF
else if (manager.hasSubSharedFolder(el)) {
return void UI.alert(Messages.convertFolderToSF_SFChildren);
}
// if root
else if (paths[0].path.length <= 1) {
return void UI.warn(Messages.error);
}
// if folder does not contains SF
else {
var convertContent = h('div', [
h('p', Messages.convertFolderToSF_confirm),
h('label', {for: 'cp-upload-password'}, Messages.fm_shareFolderPassword),
UI.passwordInput({
id: 'cp-upload-password',
placeholder: Messages.creation_passwordValue
}),
h('span', {
style: 'display:flex;align-items:center;justify-content:space-between'
}, [
UI.createCheckbox('cp-upload-owned', Messages.sharedFolders_create_owned, true),
UI.createHelper(Pages.localizeDocsLink('https://docs.cryptpad.org/en/user_guide/share_and_access.html#owners'), Messages.creation_owned1)
]),
]);
return void UI.confirm(convertContent, function(res) {
if (!res) { return; }
var password = $(convertContent).find('#cp-upload-password').val() || undefined;
var owned = Util.isChecked($(convertContent).find('#cp-upload-owned'));
manager.convertFolderToSharedFolder(paths[0].path, owned, password, function (err, obj) {
if (err || obj && obj.error) { return void console.error(err || obj.error); }
if (obj && obj.fId) { APP.convertedFolder = obj.fId; }
refresh();
});
});
}
} else { // File or shared folder
var sf = !anonDrive && manager.isSharedFolder(el);
if (anonDrive) {
data = el;
} else {
data = sf ? manager.getSharedFolderData(el) : manager.getFileData(el);
}
parsed = (data.href && data.href.indexOf('#') !== -1) ? Hash.parsePadUrl(data.href) : {};
// Form: get auditor hash
var auditorHash;
if (parsed.hash && parsed.type === "form") {
var formData = Hash.getFormData(null, parsed.hash, data.password);
console.log(formData);
if (formData) {
auditorHash = formData.form_auditorHash;
}
}
var roParsed = Hash.parsePadUrl(data.roHref);
if (!auditorHash && roParsed.type === "form" &&
roParsed?.hashData?.auditorKey) {
// If we have stored the auditor hash, mark it as such
auditorHash = roParsed.hash;
}
var padType = parsed.type || roParsed.type;
var ro = !sf || (folders[el] && folders[el].version >= 2);
var padData = {
teamId: APP.team,
origin: APP.origin,
pathname: "/" + padType + "/",
friends: friends,
password: data.password,
hashes: {
editHash: parsed.hash,
viewHash: ro && roParsed.hash,
fileHash: parsed.hash
},
auditorHash: auditorHash,
fileData: {
hash: parsed.hash,
password: data.password
},
isTemplate: paths[0].path[0] === 'template',
title: data.title || data.name,
sharedFolder: sf,
static: data.static ? data.href : undefined,
common: common
};
if (padType === 'file') {
return void Share.getFileShareModal(common, padData, function (err) {
if (err) { UI.warn(Messages.error); }
});
}
Share.getShareModal(common, padData, function (err) {
if (err) { UI.warn(Messages.error); }
});
}
}
else if ($this.hasClass('cp-app-drive-context-savelocal')) {
if (paths.length !== 1) { return; }
el = manager.find(paths[0].path);
if (manager.isFile(el)) {
data = manager.getFileData(el);
} else if (manager.isSharedFolder(el)) {
data = manager.getSharedFolderData(el);
}
if (!data) { return; }
if (data.static) {
sframeChan.query("Q_DRIVE_USEROBJECT", {
cmd: "addLink",
teamId: -1,
data: {
name: data.name,
href: data.href,
path: ['root']
}
}, function () {
UI.log(Messages.saved);
});
return;
}
sframeChan.query('Q_STORE_IN_TEAM', {
href: data.href || data.rohref,
password: data.password,
path: paths[0].path[0] === 'template' ? ['template'] : undefined,
title: data.title || '',
teamId: -1
}, function (err) {
if (err) { return void console.error(err); }
});
}
else if ($this.hasClass('cp-app-drive-context-newfolder')) {
if (paths.length !== 1) { return; }
var onFolderCreated = function (err, info) {
if (err) { return void logError(err); }
APP.newFolder = info.newPath;
APP.displayDirectory(paths[0].path);
};
el = manager.find(paths[0].path);
if (manager.isSharedFolder(el)) {
paths[0].path.push(ROOT);
}
manager.addFolder(paths[0].path, null, onFolderCreated);
}
else if ($this.hasClass('cp-app-drive-context-newsharedfolder')) {
if (paths.length !== 1) { return; }
addSharedFolderModal(function (obj) {
if (!obj) { return; }
manager.addSharedFolder(paths[0].path, obj, refresh);
});
}
else if ($this.hasClass("cp-app-drive-context-uploadfiles")) {
showUploadFilesModal();
}
else if ($this.hasClass("cp-app-drive-context-uploadfolder")) {
showUploadFolderModal();
}
else if ($this.hasClass("cp-app-drive-context-newdoc")) {
var ntype = $this.data('type') || 'pad';
if (ntype === 'link') {
return void showLinkModal();
}
var path2 = manager.isPathIn(currentPath, [TRASH]) ? '' : currentPath;
openIn(ntype, path2, APP.team);
}
else if ($this.hasClass("cp-app-drive-context-properties")) {
if (type === 'trash') {
var pPath = paths[0].path;
if (paths.length !== 1 || pPath.length !== 4) { return; }
var element = manager.find(pPath.slice(0,3)); // element containing the oldpath
var sPath = stringifyPath(element.path);
return void UI.alert(h('span', [
h('strong', Messages.fm_originalPath),
h('br'),
sPath,
]));
}
if (paths.length !== 1) { return; }
el = manager.find(paths[0].path);
if (paths[0].path[0] === SHARED_FOLDER && APP.newSharedFolder) {
// ANON_SHARED_FOLDER
el = manager.find(paths[0].path.slice(1), APP.newSharedFolder);
}
APP.getProperties(el, function (e) {
if (e) {
UI.warn(Messages.error);
return void logError(e, el);
}
});
}
else if ($this.hasClass("cp-app-drive-context-access")) {
if (paths.length !== 1) { return; }
el = manager.find(paths[0].path);
if (paths[0].path[0] === SHARED_FOLDER && APP.newSharedFolder) {
// ANON_SHARED_FOLDER
el = manager.find(paths[0].path.slice(1), APP.newSharedFolder);
}
APP.getAccess(el, function (e) {
if (e) {
UI.warn(Messages.error);
return void logError(e);
}
});
}
else if ($this.hasClass("cp-app-drive-context-hashtag")) {
var hrefs = paths.map(function (p) {
var el = manager.find(p.path);
var data = manager.getFileData(el);
return data.href || data.roHref;
}).filter(Boolean);
common.updateTags(hrefs);
}
else if ($this.hasClass("cp-app-drive-context-empty")) {
if (paths.length !== 1 || !paths[0].element
|| !manager.comparePath(paths[0].path, [TRASH])) {
log(Messages.fm_forbidden);
return;
}
emptyTrashModal();
}
else if ($this.hasClass("cp-app-drive-context-remove")) {
return void deletePaths(paths);
}
else if ($this.hasClass("cp-app-drive-context-removesf")) {
return void deletePaths(paths);
}
else if ($this.hasClass("cp-app-drive-context-restore")) {
let getRestoreProperties = (path) => {
let restorePath = path;
let restoreName = path.at(-1);
if (restorePath.length === 4) {
let rEl = manager.find(restorePath);
if (manager.isFile(rEl)) {
restoreName = manager.getTitle(rEl);
} else if (manager.isSharedFolder(rEl)) {
let sfData = manager.getSharedFolderData(rEl);
restoreName = sfData.title || sfData.lastTitle || Messages.fm_deletedFolder;
} else {
restoreName = restorePath[1];
}
}
return [restorePath, restoreName];
};
let restoreNumber = paths.length;
if (restoreNumber === 0) { return; }
if (restoreNumber === 1) { // single file restoration
let [restorePath, restoreName] = getRestoreProperties(paths[0].path);
UI.confirm(Messages._getKey("fm_restoreDialog", [restoreName]), res => {
if (!res) { return; }
manager.restore(restorePath, refresh);
});
} else { // multiple files restoration
UI.confirm(Messages._getKey("fm_restoreMultipleDialog", [restoreNumber]), res => {
if (!res) { return; }
nThen(waitFor => {
paths.forEach(path => {
if (!path) { // We met an error
console.error("Error while restoring files: no path");
return;
}
let restorePath = getRestoreProperties(path.path)[0];
setTimeout(manager.restore(restorePath, waitFor()), 10);
});
}).nThen(refresh);
});
}
}
else if ($this.hasClass("cp-app-drive-context-openparent")) {
if (paths.length !== 1) { return; }
var parentPath = paths[0].path.slice();
if (manager.isInTrashRoot(parentPath)) { parentPath = [TRASH]; }
else { parentPath.pop(); }
APP.displayDirectory(parentPath);
APP.selectedFiles = paths[0].path.slice(-1);
}
APP.hideMenu();
}); };
addContextEvent();
metadataMgr.onChange(function () {
var priv = metadataMgr.getPrivateData();
if (priv.plan !== APP.premiumPlan) {
$contextMenu.remove();
$contextMenu = createContextMenu(common).appendTo($appContainer);
if (!APP.loggedIn) {
$contextMenu.find('.cp-app-drive-context-delete').attr('data-icon', destroy)
.html($contextMenu.find('.cp-app-drive-context-remove').html());
}
addContextEvent();
}
});
// Chrome considers the double-click means "select all" in the window
$content.on('mousedown', function (e) {
$content.focus();
e.preventDefault();
});
$appContainer.on('mouseup', function (e) {
if (e.which !== 1) { return ; }
if ($(e.target).is(".dropdown-submenu a, .dropdown-submenu a span")) { return; } // if we click on dropdown-submenu, don't close menu
APP.hideMenu(e);
});
$appContainer.on('click', function (e) {
if (e.which !== 1) { return ; }
removeInput();
});
$appContainer.on('drag drop', function (e) {
removeInput();
APP.hideMenu(e);
});
$appContainer.on('mouseup drop', function () {
$('.cp-app-drive-element-droppable').removeClass('cp-app-drive-element-droppable');
});
$appContainer.on('keydown', function (e) {
// "Del"
if (e.which === 46) {
if (manager.isPathIn(currentPath, [FILES_DATA]) && APP.loggedIn) {
return; // We can't remove elements directly from filesData
}
var $selected = findSelectedElements();
if (!$selected.length) { return; }
var paths = [];
var isTrash = manager.isPathIn(currentPath, [TRASH]);
$selected.each(function (idx, elmt) {
if (!$(elmt).data('path')) { return; }
paths.push($(elmt).data('path'));
});
if (!paths.length) { return; }
// If we are in the trash or anon USER or if we are holding the "shift" key,
// delete permanently
// Or if we are in a shared folder
// Or if the selection is only shared folders
if (!APP.loggedIn || isTrash || manager.isInSharedFolder(currentPath)
|| e.shiftKey) {
deletePaths(null, paths);
return;
}
// else move to trash
moveElements(paths, [TRASH], false, refresh);
return;
}
});
$appContainer.contextmenu(function () {
APP.hideMenu();
return false;
});
var onRefresh = {
refresh: function() {
if (onRefresh.to) {
window.clearTimeout(onRefresh.to);
}
onRefresh.to = window.setTimeout(refresh, 500);
}
};
var onEvDriveChange = sframeChan.on('EV_DRIVE_CHANGE', function (data) {
if (history.isHistoryMode) { return; }
var path = data.path.slice();
var originalPath = data.path.slice();
if (!APP.loggedIn && APP.newSharedFolder && data.id === APP.newSharedFolder) {
// ANON_SHARED_FOLDER
return void onRefresh.refresh();
}
// Fix the path if this is about a shared folder
if (data.id && manager.folders[data.id]) {
var uoPath = manager.getUserObjectPath(manager.folders[data.id].userObject);
if (uoPath) {
Array.prototype.unshift.apply(path, uoPath);
path.unshift('drive');
}
}
if (path[0] !== 'drive') { return false; }
path = path.slice(1);
if (originalPath[0] === 'drive') { originalPath = originalPath.slice(1); }
var cPath = currentPath.slice();
if (originalPath.length && originalPath[0] === FILES_DATA) {
onRefresh.refresh();
} else if ((manager.isPathIn(cPath, ['hrefArray', TRASH]) && cPath[0] === path[0]) ||
(path.length >= cPath.length && manager.isSubpath(path, cPath))) {
// Reload after a few ms to make sure all the change events have been received
onRefresh.refresh();
} else {
APP.resetTree();
}
return false;
});
var onEvDriveRemove = sframeChan.on('EV_DRIVE_REMOVE', function (data) {
if (history.isHistoryMode) { return; }
var path = data.path.slice();
if (!APP.loggedIn && APP.newSharedFolder && data.id === APP.newSharedFolder) {
// ANON_SHARED_FOLDER
return void onRefresh.refresh();
}
// Fix the path if this is about a shared folder
if (data.id && manager.folders[data.id]) {
var uoPath = manager.getUserObjectPath(manager.folders[data.id].userObject);
if (uoPath) {
Array.prototype.unshift.apply(path, uoPath);
path.unshift('drive');
}
}
if (path[0] !== 'drive') { return false; }
path = path.slice(1);
var cPath = currentPath.slice();
if ((manager.isPathIn(cPath, ['hrefArray', TRASH]) && cPath[0] === path[0]) ||
(path.length >= cPath.length && manager.isSubpath(path, cPath))) {
// Reload after a few to make sure all the change events have been received
onRefresh.refresh();
} else {
APP.resetTree();
}
return false;
});
$(window).on('mouseup', onWindowMouseUp);
$(window).on('keydown', onWindowKeydown);
$(window).on('click', onWindowClick);
var removeWindowListeners = function () {
$(window).off('mouseup', onWindowMouseUp);
$(window).off('keydown', onWindowKeydown);
$(window).off('click', onWindowClick);
try {
onEvDriveChange.stop();
onEvDriveRemove.stop();
} catch (e) {}
};
if (APP.histConfig) {
APP.histConfig.onOpen = function () {
// If we're in a shared folder history, store its id in memory
// so that we remember that this isn't the drive history if
// we browse through the drive
var sfId = manager.isInSharedFolder(currentPath);
if (!sfId) {
delete history.sfId;
delete APP.histConfig.sharedFolder;
return;
}
history.sfId = sfId;
var data = manager.getSharedFolderData(sfId);
var parsed = Hash.parsePadUrl(data.href || data.roHref);
APP.histConfig.sharedFolder = {
hash: parsed.hash,
password: data.password
};
};
history.onEnterHistory = function (obj) {
if (history.sfId) {
if (!obj || typeof(obj) !== "object" || Object.keys(obj).length === 0) {
return;
}
manager.setHistoryMode(true);
copyObjectValue(folders[history.sfId], obj);
refresh();
return;
}
history.sfId = false;
var ok = manager.isValidDrive(obj.drive);
if (!ok) { return; }
manager.setHistoryMode(true);
var restricted = files.restrictedFolders;
copyObjectValue(files, obj.drive);
files.restrictedFolders = restricted;
appStatus.isReady = true;
refresh();
};
history.onLeaveHistory = function () {
manager.setHistoryMode(false);
copyObjectValue(files, proxy.drive);
refresh();
};
}
var fmConfig = {
teamId: APP.team,
noHandlers: true,
onUploaded: function () {
refresh();
},
body: $('body')
};
APP.FM = common.createFileManager(fmConfig);
refresh(function () {
UI.removeLoadingScreen();
}, {init:true});
APP.passwordModal = function (fId, data, cb) {
var content = [];
var legacy = data.legacy; // Legacy mode: we don't know if the sf has been destroyed or its password has changed
var folderName = '<b>'+ (Util.fixHTML(data.lastTitle) || Messages.fm_newFolder) +'</b>';
var pwMsg = legacy ? Messages._getKey('drive_sfPassword', [folderName]) : Messages._getKey('dph_sf_pw', [folderName]);
content.push(UI.setHTML(h('p'), pwMsg));
var newPassword = UI.passwordInput({
id: 'cp-app-prop-change-password',
placeholder: Messages.settings_changePasswordNew,
style: 'flex: 1;'
});
var passwordOk = h('button.btn.btn-secondary', Messages.properties_changePasswordButton);
var changePass = h('span.cp-password-container', [
newPassword,
passwordOk
]);
content.push(changePass);
var div = h('div', content);
var locked = false;
$(passwordOk).click(function () {
if (locked) { return; }
var pw = $(newPassword).find('.cp-password-input').val();
locked = true;
$(div).find('.alert').remove();
$(passwordOk).html('').append(Icons.get('loading'));
manager.restoreSharedFolder(fId, pw, function (err, obj) {
if (obj && obj.error) {
var wrong = h('div.alert.alert-danger', Messages.drive_sfPasswordError);
$(div).prepend(wrong);
$(passwordOk).text(Messages.properties_changePasswordButton);
locked = false;
return;
}
UI.findCancelButton($(div).closest('.alertify')).click();
cb();
});
});
var buttons = [{
className: 'primary',
name: Messages.forgetButton,
onClick: function () {
manager.delete([['sharedFoldersTemp', fId]], function () { });
},
keys: []
}, {
className: 'cancel',
name: Messages.later,
onClick: function () {},
keys: [27]
}];
return UI.dialog.customModal(div, {
buttons: buttons,
onClose: cb
});
};
onConnectEvt.reg(refreshDeprecated);
return {
refresh: refresh,
close: function () {
APP.closed = true;
removeWindowListeners();
}
};
};
return {
create: create,
setEditable: setEditable,
logError: logError
};
});