diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index a79cb382f..c254c69ee 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -350,6 +350,7 @@ define(function () { out.fm_templateName = "Modèles"; out.fm_searchName = "Recherche"; out.fm_recentPadsName = "Pads récents"; + out.fm_ownedPadsName = "Possédés"; out.fm_searchPlaceholder = "Rechercher..."; out.fm_newButton = "Nouveau"; out.fm_newButtonTitle = "Créer un nouveau pad ou un dossier, importer un fichier dans le dossier courant"; diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index 1d7fd8b15..5cee6cb97 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -353,6 +353,7 @@ define(function () { out.fm_templateName = "Templates"; out.fm_searchName = "Search"; out.fm_recentPadsName = "Recent pads"; + out.fm_ownedPadsName = "Owned"; out.fm_searchPlaceholder = "Search..."; out.fm_newButton = "New"; out.fm_newButtonTitle = "Create a new pad or folder, import a file in the current folder"; diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index c426a0272..992f98352 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -1241,8 +1241,8 @@ define([ } // XXX TODO remove these lines - ownedVal = undefined; - expire = undefined; + //ownedVal = undefined; + //expire = undefined; sframeChan.query("Q_CREATE_PAD", { owned: ownedVal, diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index fca25b3b1..9e72194d9 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -361,6 +361,7 @@ define([ Store.addPad = function (data, cb) { if (!data.href) { return void cb({error:'NO_HREF'}); } var pad = makePad(data.href, data.title); + if (data.owners) { pad.owners = data.owners; } store.userObject.pushData(pad, function (e, id) { if (e) { return void cb({error: "Error while adding a template:"+ e}); } var path = data.path || ['root']; @@ -522,6 +523,11 @@ define([ var p = Hash.parsePadUrl(href); var h = p.hashData; + var owners; + if (Store.channel && Util.base64ToHex(h.channel) === Store.channel.wc.id) { + owners = Store.channel.data.owners || undefined; + } + var allPads = Util.find(store.proxy, ['drive', 'filesData']) || {}; var isStronger; @@ -583,6 +589,7 @@ define([ Store.addPad({ href: href, title: title, + owners: owners, path: data.path || (store.data && store.data.initialPath) }, cb); return; @@ -735,12 +742,14 @@ define([ // TODO with sharedworker // channel will be an object storing the webchannel associated to each browser tab - var channel = { - queue: [] + var channel = Store.channel = { + queue: [], + data: {} }; Store.joinPad = function (data, cb) { var conf = { - onReady: function () { + onReady: function (padData) { + channel.data = padData || {}; postMessage("PAD_READY"); }, // post EV_PAD_READY onMessage: function (m) { diff --git a/www/common/outer/chainpad-netflux-worker.js b/www/common/outer/chainpad-netflux-worker.js index a60d73b3e..ffd3f9078 100644 --- a/www/common/outer/chainpad-netflux-worker.js +++ b/www/common/outer/chainpad-netflux-worker.js @@ -36,6 +36,7 @@ define([], function () { var owners = conf.owners; var password = conf.password; var expire = conf.expire; + var padData; conf = undefined; var initializing = true; @@ -43,11 +44,11 @@ define([], function () { var messageFromOuter = function () {}; - var onRdy = function () { + var onRdy = function (padData) { // Trigger onReady only if not ready yet. This is important because the history keeper sends a direct // message through "network" when it is synced, and it triggers onReady for each channel joined. if (!initializing) { return; } - onReady(); + onReady(padData); //sframeChan.event('EV_RT_READY', null); // we're fully synced initializing = false; @@ -92,13 +93,14 @@ define([], function () { if (parsed.channel === wc.id && !validateKey) { validateKey = parsed.validateKey; } + padData = parsed; // We have to return even if it is not the current channel: // we don't want to continue with other channels messages here return; } if (parsed.state && parsed.state === 1 && parsed.channel) { if (parsed.channel === wc.id) { - onRdy(); + onRdy(padData); } // We have to return even if it is not the current channel: // we don't want to continue with other channels messages here @@ -180,7 +182,7 @@ define([], function () { }); network.historyKeeper = hk; - var cfg = { + var cfg = padData = { validateKey: validateKey, lastKnownHash: lastKnownHash, owners: owners, diff --git a/www/common/sframe-app-outer.js b/www/common/sframe-app-outer.js index d0dcb631c..b65f3f98b 100644 --- a/www/common/sframe-app-outer.js +++ b/www/common/sframe-app-outer.js @@ -35,6 +35,8 @@ define([ }; window.addEventListener('message', onMsg); }).nThen(function (/*waitFor*/) { - SFCommonO.start(); + SFCommonO.start({ + useCreationScreen: true + }); }); }); diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index e31143e80..34d929522 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -567,7 +567,8 @@ define([ // Join the netflux channel var rtStarted = false; - var startRealtime = function () { + var startRealtime = function (rtConfig) { + rtConfig = rtConfig || {}; rtStarted = true; var replaceHash = function (hash) { if (window.history && window.history.replaceState) { @@ -581,7 +582,7 @@ define([ window.location.hash = hash; }; - CpNfOuter.start({ + var cfg = { sframeChan: sframeChan, channel: secret.channel, padRpc: Cryptpad.padRpc, @@ -600,7 +601,11 @@ define([ if (readOnly || cfg.noHash) { return; } replaceHash(Utils.Hash.getEditHashFromKeys(wc, secret.keys)); } + }; + Object.keys(rtConfig).forEach(function (k) { + cfg[k] = rtConfig[k]; }); + CpNfOuter.start(cfg); }; sframeChan.on('Q_CREATE_PAD', function (data, cb) { @@ -624,12 +629,11 @@ define([ var rtConfig = {}; if (data.owned) { - //rtConfig.owners = [edPublic]; + rtConfig.owners = [edPublic]; } if (data.expire) { - //rtConfig.expire = data.expire; + rtConfig.expire = data.expire; } - if (data.template) { // Pass rtConfig to useTemplate because Cryptput will create the file and // we need to have the owners and expiration time in the first line on the @@ -651,7 +655,7 @@ define([ if (!realtime) { return; } if (isNewFile && Utils.LocalStore.isLoggedIn() - && AppConfig.displayCreationScreen) { return; } + && AppConfig.displayCreationScreen && cfg.useCreationScreen) { return; } startRealtime(); }); diff --git a/www/common/userObject.js b/www/common/userObject.js index 2b9b47d38..1f2993542 100644 --- a/www/common/userObject.js +++ b/www/common/userObject.js @@ -453,6 +453,12 @@ define([ .map(function (str) { return Number(str); }); return sorted; }; + exp.getOwnedPads = function (edPub) { + var allFiles = files[FILES_DATA]; + return Object.keys(allFiles).filter(function (id) { + return allFiles[id].owners && allFiles[id].owners.indexOf(edPub) !== -1; + }).map(function (k) { return Number(k); });; + }; /** * OPERATIONS diff --git a/www/drive/inner.html b/www/drive/inner.html index 8f5a884ae..d2f7a2f55 100644 --- a/www/drive/inner.html +++ b/www/drive/inner.html @@ -44,6 +44,7 @@
  • Open
  • Open (read-only)
  • Delete
  • +
  • Delete permanently
  • Properties
  • Tags
  • diff --git a/www/drive/inner.js b/www/drive/inner.js index aa8034f23..51e17a931 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -60,6 +60,8 @@ define([ var TRASH_NAME = Messages.fm_trashName; var RECENT = "recent"; var RECENT_NAME = Messages.fm_recentPadsName; + var OWNED = "owned"; + var OWNED_NAME = Messages.fm_ownedPadsName; var LS_LAST = "app-drive-lastOpened"; var LS_OPENED = "app-drive-openedFolders"; @@ -180,6 +182,8 @@ define([ var $addIcon = $('', {"class": "fa fa-plus"}); var $renamedIcon = $('', {"class": "fa fa-flag"}); var $readonlyIcon = $('', {"class": "fa fa-eye"}); + var $ownedIcon = $('', {"class": "fa fa-id-card-o"}); + var $ownerIcon = $('', {"class": "fa fa-id-card"}); var history = { isHistoryMode: false, @@ -202,6 +206,7 @@ define([ var sframeChan = common.getSframeChannel(); var priv = metadataMgr.getPrivateData(); var user = metadataMgr.getUserData(); + var edPublic = priv.edPublic; APP.origin = priv.origin; var isOwnDrive = function () { @@ -255,9 +260,10 @@ define([ // Categories dislayed in the menu // _WORKGROUP_ : do not display unsorted var displayedCategories = [ROOT, TRASH, SEARCH, RECENT]; + if (AppConfig.displayCreationScreen) { displayedCategories.push(OWNED); } if (AppConfig.enableTemplates) { displayedCategories.push(TEMPLATE); } if (isWorkgroup()) { displayedCategories = [ROOT, TRASH, SEARCH]; } - var virtualCategories = [SEARCH, RECENT]; + var virtualCategories = [SEARCH, RECENT, OWNED]; if (!APP.loggedIn) { displayedCategories = [FILES_DATA]; @@ -652,13 +658,18 @@ define([ if (!isOwnDrive()) { hide.push($menu.find('a.cp-app-drive-context-own')); } + if ($element.is('.cp-app-drive-element-owned')) { + hide.push($menu.find('a.cp-app-drive-context-delete')); + } else { + hide.push($menu.find('a.cp-app-drive-context-deleteowned')); + } if ($element.is('.cp-app-drive-element-file')) { // No folder in files hide.push($menu.find('a.cp-app-drive-context-newfolder')); - if ($element.is('.cp-app-drive-readonly')) { + if ($element.is('.cp-app-drive-element-readonly')) { // Keep only open readonly hide.push($menu.find('a.cp-app-drive-context-open')); - } else if ($element.is('.cp-app-drive-noreadonly')) { + } else if ($element.is('.cp-app-drive-element-noreadonly')) { // Keep only open readonly hide.push($menu.find('a.cp-app-drive-context-openro')); } @@ -1169,6 +1180,13 @@ define([ var $renamed = $renamedIcon.clone().appendTo($state); $renamed.attr('title', Messages._getKey('fm_renamedPad', [data.title])); } + if (data.owners && data.owners.indexOf(edPublic) !== -1) { + var $owned = $ownedIcon.clone().appendTo($state); + $owned.attr('title', Messages.fm_padIsOwned || 'TODO: owned pad'); // XXX + } else if (data.owners && data.owners.length) { + var $owner = $ownerIcon.clone().appendTo($state); + $owner.attr('title', Messages.fm_padIsOwned || 'TODO: owned pad'); // XXX + } var name = filesOp.getTitle(element); @@ -1391,6 +1409,9 @@ define([ case RECENT: msg = Messages.fm_info_recent; break; + case OWNED: + msg = 'TODO: files deleted here are actually removed from the server...'; // XXX + break; default: msg = undefined; } @@ -1702,10 +1723,10 @@ define([ }; var sortElements = function (folder, path, oldkeys, prop, asc, useId) { - var root = filesOp.find(path); + var root = path && filesOp.find(path); var test = folder ? filesOp.isFolder : filesOp.isFile; var keys = oldkeys.filter(function (e) { - return useId ? test(e) : test(root[e]); + return useId ? test(e) : (path && test(root[e])); }); if (keys.length < 2) { return keys; } var mult = asc ? 1 : -1; @@ -2065,6 +2086,41 @@ define([ }); }; + // Owned pads category + var displayOwned = function ($container) { + var list = filesOp.getOwnedPads(edPublic); + if (list.length === 0) { return; } + var $fileHeader = getFileListHeader(false); + $container.append($fileHeader); + var sortedFiles = sortElements(false, false, list, APP.store[SORT_FILE_BY], !getSortFileDesc(), true); + sortedFiles.forEach(function (id) { + var paths = filesOp.findFile(id); + if (!paths.length) { return; } + var path = paths[0]; + var $icon = getFileIcon(id); + var ro = filesOp.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 = $('
  • ', { + 'class': 'cp-app-drive-element cp-app-drive-element-owned 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(openDefaultContextMenu); + $element.data('context', $defaultContextMenu); + $container.append($element); + }); + }; + // Display the selected directory into the content part (rightside) // NOTE: Elements in the trash are not using the same storage structure as the others // _WORKGROUP_ : do not change the lastOpenedFolder value in localStorage @@ -2096,6 +2152,7 @@ define([ var isAllFiles = filesOp.comparePath(path, [FILES_DATA]); var isSearch = path[0] === SEARCH; var isRecent = path[0] === RECENT; + var isOwned = path[0] === OWNED; var isVirtual = virtualCategories.indexOf(path[0]) !== -1; var root = isVirtual ? undefined : filesOp.find(path); @@ -2194,6 +2251,8 @@ define([ displaySearch($list, path[1]); } else if (isRecent) { displayRecent($list); + } else if (isOwned) { + displayOwned($list); } else { $dirContent.contextmenu(openContentContextMenu); if (filesOp.hasSubfolder(root)) { $list.append($folderHeader); } @@ -2374,6 +2433,15 @@ define([ $container.append($list); }; + var createOwned = function ($container, path) { + var $icon = $ownedIcon.clone(); // TODO + var isOpened = filesOp.comparePath(path, currentPath); + var $element = createTreeElement(OWNED_NAME, $icon, [OWNED], false, false, false, isOpened); + $element.addClass('root'); + var $list = $('
      ', { 'class': 'cp-app-drive-tree-category' }).append($element); + $container.append($list); + }; + var search = APP.Search = {}; var createSearch = function ($container) { var isInSearch = currentPath[0] === SEARCH; @@ -2437,6 +2505,7 @@ define([ var $div = $('
      ', {'class': 'cp-app-drive-tree-categories-container'}) .appendTo($tree); if (displayedCategories.indexOf(RECENT) !== -1) { createRecent($div, [RECENT]); } + if (displayedCategories.indexOf(OWNED) !== -1) { createOwned($div, [OWNED]); } if (displayedCategories.indexOf(ROOT) !== -1) { createTree($div, [ROOT]); } if (displayedCategories.indexOf(TEMPLATE) !== -1) { createTemplate($div, [TEMPLATE]); } if (displayedCategories.indexOf(FILES_DATA) !== -1) { createAllFiles($div, [FILES_DATA]); } @@ -2683,6 +2752,21 @@ define([ } moveElements(pathsList, [TRASH], false, refresh); } + else if ($(this).hasClass('cp-app-drive-context-deleteowned')) { + // TODO + // Remove owned pad from drive and remove from server + var pathsList = []; + paths.forEach(function (p) { pathsList.push(p.path); }); + var msg = Messages._getKey("fm_deleteOwnedPads"); // XXX + UI.confirm(msg, function(res) { + $(window).focus(); + if (!res) { return; } + filesOp.delete(pathsList, refresh); + // TODO HERE + // RPC to delete from server? + }); + return; + } else if ($(this).hasClass("cp-app-drive-context-properties")) { if (paths.length !== 1) { return; } el = filesOp.find(paths[0].path);