diff --git a/customize.dist/application_config.js b/customize.dist/application_config.js index 8a0e4caa4..0e067331e 100644 --- a/customize.dist/application_config.js +++ b/customize.dist/application_config.js @@ -12,8 +12,8 @@ define(function() { */ config.notificationTimeout = 5000; - config.USE_FS_STORE = false; - config.USE_HOMEPAGE_TABLE = true; + config.USE_FS_STORE = true; + config.USE_HOMEPAGE_TABLE = false; return config; }); diff --git a/customize.dist/fsStore.js b/customize.dist/fsStore.js index 6b68f831d..db94393a9 100644 --- a/customize.dist/fsStore.js +++ b/customize.dist/fsStore.js @@ -133,8 +133,11 @@ define([ } }; + var initialized = false; + var init = function (f, Cryptpad) { - if (!Cryptpad) { return; } + if (!Cryptpad || initialized) { return; } + initialized = true; var hash = Cryptpad.getUserHash() || localStorage.FS_hash; var secret = Cryptpad.getSecrets(hash); var listmapConfig = { diff --git a/customize.dist/messages.js b/customize.dist/messages.js index 1807d5c82..59be80317 100644 --- a/customize.dist/messages.js +++ b/customize.dist/messages.js @@ -95,7 +95,7 @@ define(['/customize/languageSelector.js', $('[data-localization]').each(translateText); $('#pad-iframe').contents().find('[data-localization]').each(translateText); $('[data-localization-title]').each(translateTitle); - $('[data-localization-placeholder').each(translatePlaceholder); + $('[data-localization-placeholder]').each(translatePlaceholder); $('#pad-iframe').contents().find('[data-localization-title]').each(translateTitle); }; diff --git a/www/code/main.js b/www/code/main.js index 87298cbf5..ebdb90167 100644 --- a/www/code/main.js +++ b/www/code/main.js @@ -155,6 +155,7 @@ define([ crypto: Crypto.createEncryptor(secret.keys), setMyID: setMyID, transformFunction: JsonOT.transform || JsonOT.validate, + network: Cryptpad.getNetwork() }; var canonicalize = function (t) { return t.replace(/\r\n/g, '\n'); }; diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 0bf419a24..2378b70f2 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -43,6 +43,14 @@ define([ if (USE_FS_STORE && !legacy && fsStore) { return fsStore; } throw new Error("Store is not ready!"); }; + var getNetwork = common.getNetwork = function () { + if (USE_FS_STORE && fsStore) { + if (fsStore.getProxy() && fsStore.getProxy().info) { + return fsStore.getProxy().info.network; + } + } + return; + }; var getWebsocketURL = common.getWebsocketURL = function () { if (!Config.websocketPath) { return Config.websocketURL; } diff --git a/www/file/fileObject.js b/www/file/fileObject.js index 1f98171c9..e29adfb20 100644 --- a/www/file/fileObject.js +++ b/www/file/fileObject.js @@ -21,6 +21,7 @@ define([ var log = config.log || logging; var logError = config.logError || logging; var debug = config.debug || logging; + var workgroup = config.workgroup; var exp = {}; @@ -241,6 +242,9 @@ define([ }; var checkDeletedFiles = function () { + // Nothing in FILES_DATA for workgroups + if (workgroup) { return; } + var rootFiles = getRootFiles(); var unsortedFiles = getUnsortedFiles(); var templateFiles = getTemplateFiles(); @@ -324,6 +328,7 @@ define([ // Data from filesData var getTitle = exp.getTitle = function (href) { + if (workgroup) { debug("No titles in workgroups"); return; } var data = getFileData(href); if (!href || !data) { error("getTitle called with a non-existing href: ", href); @@ -458,6 +463,21 @@ define([ if(cb) { cb(); } }; + // Import elements in the file manager + var importElements = exp.importElements = function (elements, path, cb) { + if (!elements || elements.length === 0) { return; } + console.log(elements); + var newParent = findElement(files, path); + if (!newParent) { debug("Trying to import elements into a non-existing folder"); return; } + elements.forEach(function (e) { + var el = e.el; + var key = e.name; + if (!key) { key = "???"; } // Should not happen... + newParent[key] = el; + }); + if(cb) { cb(); } + }; + var createNewFolder = exp.createNewFolder = function (folderPath, name, cb) { var parentEl = findElement(files, folderPath); var folderName = getAvailableName(parentEl, name || NEW_FOLDER_NAME); @@ -579,6 +599,7 @@ define([ var forgetPad = exp.forgetPad = function (href) { + if (workgroup) { return; } var rootFiles = getRootFiles().slice(); if (rootFiles.indexOf(href) !== -1) { removeFileFromRoot(files[ROOT], href); @@ -592,6 +613,7 @@ define([ }; var addUnsortedPad = exp.addPad = function (href, path, name) { + if (workgroup) { return; } var unsortedFiles = getUnsortedFiles(); var rootFiles = getRootFiles(); var trashFiles = getTrashFiles(); @@ -620,6 +642,7 @@ define([ // addTemplate is called when we want to add a new pad, never visited, to the templates list // first, we must add it to FILES_DATA, so the input has to be an fileDAta object var addTemplate = exp.addTemplate = function (fileData) { + if (workgroup) { return; } if (typeof fileData !== "object" || !fileData.href || !fileData.title) { return; } var href = fileData.href; @@ -635,6 +658,7 @@ define([ }; var listTemplates = exp.listTemplates = function (type) { + if (workgroup) { return; } var templateFiles = getTemplateFiles(); var res = []; templateFiles.forEach(function (f) { @@ -664,13 +688,9 @@ define([ var before = JSON.stringify(files); - if (typeof(files[ROOT]) !== "object") { debug("ROOT was not an object"); files[ROOT] = {}; } - if (typeof(files[TRASH]) !== "object") { debug("TRASH was not an object"); files[TRASH] = {}; } - if (!$.isArray(files[FILES_DATA])) { debug("FILES_DATA was not an array"); files[FILES_DATA] = []; } - if (!$.isArray(files[UNSORTED])) { debug("UNSORTED was not an array"); files[UNSORTED] = []; } - if (!$.isArray(files[TEMPLATE])) { debug("TEMPLATE was not an array"); files[TEMPLATE] = []; } - - var fixRoot = function (element) { + var fixRoot = function (elem) { + if (typeof(files[ROOT]) !== "object") { debug("ROOT was not an object"); files[ROOT] = {}; } + var element = elem || files[ROOT]; for (var el in element) { if (!isFile(element[el]) && !isFolder(element[el])) { debug("An element in ROOT was not a folder nor a file. ", element[el]); @@ -681,9 +701,9 @@ define([ } } }; - fixRoot(files[ROOT]); - - var fixTrashRoot = function (tr) { + var fixTrashRoot = function () { + if (typeof(files[TRASH]) !== "object") { debug("TRASH was not an object"); files[TRASH] = {}; } + var tr = files[TRASH]; var toClean; var addToClean = function (obj, idx) { if (typeof(obj) !== "object") { toClean.push(idx); return; } @@ -704,9 +724,10 @@ define([ } } }; - fixTrashRoot(files[TRASH]); - - var fixUnsorted = function (us) { + var fixUnsorted = function () { + if (!$.isArray(files[UNSORTED])) { debug("UNSORTED was not an array"); files[UNSORTED] = []; } + files[UNSORTED] = uniq(files[UNSORTED]); + var us = files[UNSORTED]; var rootFiles = getRootFiles().slice(); var templateFiles = getTemplateFiles(); var toClean = []; @@ -722,10 +743,10 @@ define([ } }); }; - files[UNSORTED] = uniq(files[UNSORTED]); - fixUnsorted(files[UNSORTED]); - - var fixTemplate = function (us) { + var fixTemplate = function () { + if (!$.isArray(files[TEMPLATE])) { debug("TEMPLATE was not an array"); files[TEMPLATE] = []; } + files[TEMPLATE] = uniq(files[TEMPLATE]); + var us = files[TEMPLATE]; var rootFiles = getRootFiles().slice(); var unsortedFiles = getUnsortedFiles(); var toClean = []; @@ -741,10 +762,9 @@ define([ } }); }; - files[TEMPLATE] = uniq(files[TEMPLATE]); - fixUnsorted(files[TEMPLATE]); - var fixFilesData = function (fd) { + if (!$.isArray(files[FILES_DATA])) { debug("FILES_DATA was not an array"); files[FILES_DATA] = []; } + var fd = files[FILES_DATA]; var rootFiles = getRootFiles(); var unsortedFiles = getUnsortedFiles(); var trashFiles = getTrashFiles(); @@ -769,7 +789,14 @@ define([ } }); }; - fixFilesData(files[FILES_DATA]); + + fixRoot(); + fixTrashRoot(); + if (!workgroup) { + fixUnsorted(); + fixTemplate(); + fixFilesData(); + } if (JSON.stringify(files) !== before) { debug("Your file system was corrupted. It has been cleaned so that the pads you visit can be stored safely"); diff --git a/www/file/main.js b/www/file/main.js index 8308b58f9..938e21e8c 100644 --- a/www/file/main.js +++ b/www/file/main.js @@ -28,6 +28,10 @@ define([ Cryptpad: Cryptpad }; + var stringify = APP.stringify = function (obj) { + return JSONSortify(obj); + }; + var ROOT = "root"; var ROOT_NAME = Messages.fm_rootName; var UNSORTED = "unsorted"; @@ -121,7 +125,7 @@ define([ var setEditable = function (state) { APP.editable = state; - if (state) { $iframe.find('[draggable="true"]').attr('draggable', false); } + if (!state) { $iframe.find('[draggable="true"]').attr('draggable', false); } else { $iframe.find('[draggable="false"]').attr('draggable', true); } }; @@ -140,6 +144,11 @@ define([ }; var init = function (files) { + var ownFileManager = function () { + return Cryptpad.getUserHash() === APP.hash || localStorage.FS_hash === APP.hash; + }; + config.workgroup = !ownFileManager(); + var filesOp = FO.init(files, config); filesOp.fixFiles(); @@ -242,10 +251,6 @@ define([ } }; - var ownFileManager = function () { - return Cryptpad.getUserHash() === APP.hash || localStorage.FS_hash === APP.hash; - }; - var removeSelected = function () { $iframe.find('.selected').removeClass("selected"); }; @@ -454,6 +459,25 @@ define([ return false; }; + var getElementName = function (path) { + // Trash root + if (filesOp.isInTrashRoot(path)) { + return path[0]; + } + // Root or trash + if (filesOp.isPathInRoot(path) || filesOp.isPathInTrash(path)) { + return path[path.length - 1]; + } + // Unsorted or template + if (filesOp.isPathInUnsorted(path) || filesOp.isPathInTemplate(path)) { + var file = filesOp.findElement(files, path); + if (filesOp.isFile(file) && filesOp.getTitle(file)) { + return filesOp.getTitle(file); + } + } + // default + return "???"; + }; // filesOp.moveElements is able to move several paths to a new location, including // the Trash or the "Unsorted files" folder var moveElements = function (paths, newPath, force, cb) { @@ -485,19 +509,36 @@ define([ if ($element.hasClass('selected')) { var $selected = $iframe.find('.selected'); $selected.each(function (idx, elmt) { - if ($(elmt).data('path')) { - paths.push($(elmt).data('path')); + var ePath = $(elmt).data('path'); + if (ePath) { + var val = filesOp.findElement(files, ePath); + if (!val) { return; } // Error? A ".selected" element in not in the object + paths.push({ + path: ePath, + value: { + name: getElementName(ePath), + el: val + } + }); } }); } else { removeSelected(); $element.addClass('selected'); - paths = [path]; + var val = filesOp.findElement(files, path); + if (!val) { return; } // The element in not in the object + paths = [{ + path: path, + value: { + name: getElementName(path), + el: val + } + }]; } var data = { 'path': paths }; - ev.dataTransfer.setData("text", JSON.stringify(data)); + ev.dataTransfer.setData("text", stringify(data)); }; var onDrop = function (ev) { @@ -505,9 +546,30 @@ define([ $iframe.find('.droppable').removeClass('droppable'); var data = ev.dataTransfer.getData("text"); var oldPaths = JSON.parse(data).path; + if (!oldPaths) { return; } + + // Dropped elements can be moved from the same file manager or imported from another one. + // A moved element should be removed from its previous location + var movedPaths = []; + var importedElements = []; + oldPaths.forEach(function (p) { + var el = filesOp.findElement(files, p.path); + if (el && (stringify(el) === stringify(p.value.el) || !p.value || !p.value.el)) { + movedPaths.push(p.path); + } else { + importedElements.push(p.value); + } + }); + var newPath = $(ev.target).data('path') || $(ev.target).parent('li').data('path'); - if (!oldPaths || !oldPaths.length || !newPath) { return; } - moveElements(oldPaths, newPath, null, refresh); + console.log(newPath); + if (!newPath) { return; } + if (movedPaths && movedPaths.length) { + moveElements(movedPaths, newPath, null, refresh); + } + if (importedElements && importedElements.length) { + filesOp.importElements(importedElements, newPath, refresh); + } }; var addDragAndDropHandlers = function ($element, path, isFolder, droppable) { @@ -569,10 +631,13 @@ define([ var $type = $('', {'class': 'type listElement', title: type}).text(type); var $adate = $('', {'class': 'atime listElement', title: getDate(data.atime)}).text(getDate(data.atime)); var $cdate = $('', {'class': 'ctime listElement', title: getDate(data.ctime)}).text(getDate(data.ctime)); - if (displayTitle) { + if (displayTitle && ownFileManager()) { $span.append($title); } - $span.append($type).append($adate).append($cdate); + $span.append($type); + if (ownFileManager()) { + $span.append($adate).append($cdate); + } }; var addFolderData = function (element, key, $span) { @@ -876,10 +941,13 @@ define([ var $fhAdate = $('', {'class': 'atime'}).text(Messages.fm_lastAccess).click(onSortByClick); var $fhCdate = $('', {'class': 'ctime'}).text(Messages.fm_creation).click(onSortByClick); $fihElement.append($fhName); - if (displayTitle) { + if (displayTitle && ownFileManager()) { $fihElement.append($fhTitle); } - $fihElement.append($fhType).append($fhAdate).append($fhCdate); + $fihElement.append($fhType); + if (ownFileManager()) { + $fihElement.append($fhAdate).append($fhCdate); + } addFileSortIcon($fihElement); return $fileHeader; }; @@ -1049,6 +1117,13 @@ define([ // NOTE: Elements in the trash are not using the same storage structure as the others var displayDirectory = module.displayDirectory = function (path, force) { if (!appStatus.isReady && !force) { return; } + // Only Trash and Root are available in not-owned files manager + if (!ownFileManager() && !filesOp.isPathInTrash(path) && !filesOp.isPathInRoot(path)) { + log("TRANSLATE or REMOVE: Unable to open the selected category, displaying root"); //TODO translate + currentPath = [ROOT]; + displayDirectory(currentPath); + return; + } appStatus.ready(false); currentPath = path; $content.html(""); @@ -1274,9 +1349,11 @@ define([ var resetTree = module.resetTree = function () { $tree.html(''); createTree($tree, [ROOT]); - createUnsorted($tree, [UNSORTED]); - createTemplate($tree, [TEMPLATE]); - createAllFiles($tree, [FILES_DATA]); + if (ownFileManager()) { + createUnsorted($tree, [UNSORTED]); + createTemplate($tree, [TEMPLATE]); + createAllFiles($tree, [FILES_DATA]); + } createTrash($tree, [TRASH]); }; @@ -1518,7 +1595,7 @@ define([ // don't initialize until the store is ready. Cryptpad.ready(function () { - var storeObj = Cryptpad.getStore().getProxy && Cryptpad.getStore().getProxy().proxy ? Cryptpad.getStore().getProxy() : undefined; + var storeObj = Cryptpad.getStore().getProxy && Cryptpad.getStore().getProxy().proxy ? Cryptpad.getStore().getProxy() : undefined; Cryptpad.styleAlerts(); @@ -1528,7 +1605,7 @@ define([ APP.homePageIframe = true; } - var hash = Cryptpad.getUserHash() || window.location.hash.slice(1) || localStorage.FS_hash; + var hash = window.location.hash.slice(1) || Cryptpad.getUserHash() || localStorage.FS_hash; var secret = Cryptpad.getSecrets(hash); var readOnly = APP.readOnly = secret.keys && !secret.keys.editKeyStr; @@ -1539,12 +1616,11 @@ define([ readOnly: readOnly, validateKey: secret.keys.validateKey || undefined, crypto: Crypto.createEncryptor(secret.keys), - logging: false, - logLevel: 1, + logging: false }; var proxy; - if (storeObj) { proxy = storeObj.proxy; } + if (storeObj && !window.location.hash.slice(1)) { proxy = storeObj.proxy; } else { var rt = window.rt = module.rt = Listmap.create(listmapConfig); proxy = rt.proxy; @@ -1620,8 +1696,12 @@ define([ console.error('err'); Cryptpad.alert(Messages.common_connectionLost); }; + var onReconnect = function (info) { + setEditable(true); + Cryptpad.findOKButton().click(); + }; - if (storeObj) { + if (storeObj && !window.location.hash) { onCreate(storeObj.info); onReady(); } else { @@ -1634,6 +1714,9 @@ define([ proxy.on('disconnect', function () { onDisconnect(); }); + proxy.on('reconnect', function () { + onReconnect(); + }); }); Cryptpad.onError(function (info) { if (info) { diff --git a/www/pad/main.js b/www/pad/main.js index 09c2be0f4..6fbf46428 100644 --- a/www/pad/main.js +++ b/www/pad/main.js @@ -376,6 +376,9 @@ define([ // the channel we will communicate over channel: secret.channel, + // the nework used for the file store if it exists + network: Cryptpad.getNetwork(), + // our public key validateKey: secret.keys.validateKey || undefined, readOnly: readOnly, @@ -629,6 +632,11 @@ define([ var onReady = realtimeOptions.onReady = function (info) { if (!APP.isMaximized) { editor.execCommand('maximize'); + // We have to call it 3 times in Safari in order to have the editor fully maximized -_- + if ((''+window.navigator.vendor).indexOf('Apple') !== -1) { + editor.execCommand('maximize'); + editor.execCommand('maximize'); + } APP.isMaximized = true; } diff --git a/www/poll/main.js b/www/poll/main.js index e689d33a4..a0b9e5273 100644 --- a/www/poll/main.js +++ b/www/poll/main.js @@ -698,20 +698,21 @@ define([ Cryptpad.alert(Messages.common_connectionLost); }; - var config = { - websocketURL: Cryptpad.getWebsocketURL(), - channel: secret.channel, - readOnly: readOnly, - data: {}, - // our public key - validateKey: secret.keys.validateKey || undefined, - //readOnly: readOnly, - crypto: Crypto.createEncryptor(secret.keys), - userName: 'poll', - }; - // don't initialize until the store is ready. Cryptpad.ready(function () { + var config = { + websocketURL: Cryptpad.getWebsocketURL(), + channel: secret.channel, + readOnly: readOnly, + data: {}, + // our public key + validateKey: secret.keys.validateKey || undefined, + //readOnly: readOnly, + crypto: Crypto.createEncryptor(secret.keys), + userName: 'poll', + network: Cryptpad.getNetwork() + }; + if (readOnly) { $('#commit, #create-user, #create-option, #publish, #admin').remove(); } diff --git a/www/slide/main.js b/www/slide/main.js index adf27c3f1..cd8180b0f 100644 --- a/www/slide/main.js +++ b/www/slide/main.js @@ -195,7 +195,8 @@ define([ readOnly: readOnly, crypto: Crypto.createEncryptor(secret.keys), setMyID: setMyID, - transformFunction: JsonOT.validate + transformFunction: JsonOT.validate, + network: Cryptpad.getNetwork() }; var canonicalize = function (t) { return t.replace(/\r\n/g, '\n'); };