diff --git a/www/common/proxy-manager.js b/www/common/proxy-manager.js index 7f20ae5b6..d7c943893 100644 --- a/www/common/proxy-manager.js +++ b/www/common/proxy-manager.js @@ -461,6 +461,102 @@ define([ cb(id); }); }; + + // convert a folder to a Shared Folder + var _convertFolderToSharedFolder = function (Env, data, cb) { + var path = data.path; + var folderElement = Env.user.userObject.find(path); + // don't try to convert top-level elements (trash, root, etc) to shared-folders + // TODO also validate that you're in root (not templates, etc) + if (data.path.length <= 1) { + return void cb({ + error: 'E_INVAL_PATH', + }); + } + if (_isInSharedFolder(Env, path)) { + return void cb({ + error: 'E_INVAL_NESTING', + }); + } + if (Env.user.userObject.hasSubSharedFolder(folderElement)) { + return void cb({ + error: 'E_INVAL_NESTING', + }); + } + var parentPath = path.slice(0, -1); + var parentFolder = Env.user.userObject.find(parentPath); + var folderName = path[path.length - 1]; + var SFId; + nThen(function (waitFor) { + // create shared folder + _addSharedFolder(Env, { + path: parentPath, + name: folderName, + owned: true, // XXX FIXME hardcoded preference + password: '', // XXX FIXME hardcoded preference + }, waitFor(function (id) { + // _addSharedFolder can be an id or an error + if (typeof(id) === 'object' && id && id.error) { + waitFor.abort(); + return void cb(id); + } else { + SFId = id; + } + })); + }).nThen(function (waitFor) { + // move everything from folder to SF + if (!SFId) { + waitFor.abort(); + return void cb({ + error: 'E_NO_ID' + }); + } + var paths = []; + for (var el in folderElement) { + if (Env.user.userObject.isFolder(folderElement[el]) || Env.user.userObject.isFile(folderElement[el])) { + paths.push(path.concat(el)); + } + } + var SFKey; + // this is basically Array.find, except it works in IE + Object.keys(parentFolder).some(function (el) { + if (parentFolder[el] === SFId) { + SFKey = el; + return true; + } + }); + + if (!SFKey) { + waitFor.abort(); + return void cb({ + error: 'E_NO_KEY' + }); + } + var newPath = parentPath.concat(SFKey).concat(UserObject.ROOT); + _move(Env, { + paths: paths, + newPath: newPath, + copy: false, + }, waitFor()); + }).nThen(function () { + // migrate metadata + var sharedFolderElement = Env.user.proxy[UserObject.SHARED_FOLDERS][SFId]; + var metadata = Env.user.userObject.getFolderData(folderElement); + for (var key in metadata) { + // it shouldn't be possible to have nested metadata + // but this is a reasonable sanity check + if (key === "metadata") { continue; } + // copy the metadata from the original folder to the new shared folder + sharedFolderElement[key] = metadata[key]; + } + + // remove folder + Env.user.userObject.delete([path], function () { + cb(); + }); + }); + }; + // Delete permanently some pads or folders var _delete = function (Env, data, cb) { data = data || {}; @@ -598,6 +694,8 @@ define([ _addFolder(Env, data, cb); break; case 'addSharedFolder': _addSharedFolder(Env, data, cb); break; + case 'convertFolderToSharedFolder': + _convertFolderToSharedFolder(Env, data, cb); break; case 'delete': _delete(Env, data, cb); break; case 'emptyTrash': @@ -914,6 +1012,14 @@ define([ } }, cb); }; + var convertFolderToSharedFolderInner = function (Env, path, cb) { + return void Env.sframeChan.query("Q_DRIVE_USEROBJECT", { + cmd: "convertFolderToSharedFolder", + data: { + path: path + } + }, cb); + }; var deleteInner = function (Env, paths, cb) { return void Env.sframeChan.query("Q_DRIVE_USEROBJECT", { cmd: "delete", @@ -1074,6 +1180,9 @@ define([ } return Env.user.userObject.hasSubfolder(el, trashRoot); }; + var hasSubSharedFolder = function (Env, el) { + return Env.user.userObject.hasSubSharedFolder(el); + }; var hasFile = function (Env, el, trashRoot) { if (Env.folders[el]) { var uo = Env.folders[el].userObject; @@ -1113,6 +1222,7 @@ define([ emptyTrash: callWithEnv(emptyTrashInner), addFolder: callWithEnv(addFolderInner), addSharedFolder: callWithEnv(addSharedFolderInner), + convertFolderToSharedFolder: callWithEnv(convertFolderToSharedFolderInner), delete: callWithEnv(deleteInner), restore: callWithEnv(restoreInner), setFolderData: callWithEnv(setFolderDataInner), @@ -1144,6 +1254,7 @@ define([ isInTrashRoot: callWithEnv(isInTrashRoot), comparePath: callWithEnv(comparePath), hasSubfolder: callWithEnv(hasSubfolder), + hasSubSharedFolder: callWithEnv(hasSubSharedFolder), hasFile: callWithEnv(hasFile), // Data user: Env.user, diff --git a/www/common/userObject.js b/www/common/userObject.js index fe99f9935..38372e880 100644 --- a/www/common/userObject.js +++ b/www/common/userObject.js @@ -123,7 +123,11 @@ define([ }; exp.isFolderEmpty = function (element) { if (!isFolder(element)) { return false; } - return Object.keys(element).length === 0; + // if the folder contains nothing, it's empty + if (Object.keys(element).length === 0) { return true; } + // or if it contains one thing and that thing is metadata + if (Object.keys(element).length === 1 && isFolderData(element[Object.keys(element)[0]])) { return true; } + return false; }; exp.hasSubfolder = function (element, trashRoot) { @@ -170,6 +174,20 @@ define([ } }; + var hasSubSharedFolder = exp.hasSubSharedFolder = function (folder) { + for (var el in folder) { + if (isSharedFolder(folder[el])) { + return true; + } + else if (isFolder(folder[el])) { + if (hasSubSharedFolder(folder[el])) { + return true; + } + } + } + return false; + }; + // Get data from AllFiles (Cryptpad_RECENTPADS) var getFileData = exp.getFileData = function (file) { if (!file) { return; } diff --git a/www/drive/inner.js b/www/drive/inner.js index 3e593f209..ac63b1a33 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -1101,9 +1101,10 @@ define([ hide.push('collapseall'); } if (path.length === 1) { - // Can't rename or delete root elements + // Can't rename, share, delete, or change the color of root elements hide.push('delete'); hide.push('rename'); + hide.push('share'); hide.push('color'); } if (!$element.is('.cp-app-drive-element-owned')) { @@ -1158,7 +1159,6 @@ define([ hide.push('openro'); hide.push('openincode'); hide.push('properties'); - hide.push('share'); hide.push('hashtag'); } // If we're in the trash, hide restore and properties for non-root elements @@ -3922,7 +3922,27 @@ define([ editHash: parsed.hash } }); - } else { + return void UI.openCustomModal(modal, { + wide: Object.keys(friends).length !== 0 + }); + } else if (manager.isFolder(el)) { // Folder + // if folder is inside SF + 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 folder does not contains SF + else { + return void UI.confirm(Messages.convertFolderToSF_confirm, function(res) { + if (!res) { return; } + if (paths[0].path.length <= 1) { return; } // if root + manager.convertFolderToSharedFolder(paths[0].path, refresh); + }); + } + } else { // File data = manager.getFileData(el); parsed = Hash.parsePadUrl(data.href); var roParsed = Hash.parsePadUrl(data.roHref); @@ -3948,10 +3968,10 @@ define([ modal = padType === 'file' ? UIElements.createFileShareModal(padData) : UIElements.createShareModal(padData); modal = UI.dialog.tabs(modal); + UI.openCustomModal(modal, { + wide: Object.keys(friends).length !== 0 + }); } - UI.openCustomModal(modal, { - wide: Object.keys(friends).length !== 0 - }); } else if ($this.hasClass('cp-app-drive-context-newfolder')) { if (paths.length !== 1) { return; }