From 80df45f257b0ac8d3258efe6e431e3510aa1e8c6 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 1 Feb 2018 10:09:08 +0100 Subject: [PATCH 01/45] Get text from ckeditor --- www/common/sframe-app-framework.js | 9 +++++++++ www/pad/inner.js | 11 +++++++++++ 2 files changed, 20 insertions(+) diff --git a/www/common/sframe-app-framework.js b/www/common/sframe-app-framework.js index 29845697e..960fce5f7 100644 --- a/www/common/sframe-app-framework.js +++ b/www/common/sframe-app-framework.js @@ -84,6 +84,7 @@ define([ }); }); + var textContentGetter; var titleRecommender = function () { return false; }; var contentGetter = function () { return UNINITIALIZED; }; var normalize0 = function (x) { return x; }; @@ -284,6 +285,10 @@ define([ if (!readOnly) { onLocal(); } evOnReady.fire(newPad); + if (AppConfig.textAnalyzer && textContentGetter) { + AppConfig.textAnalyzer(textContentGetter); + } + UI.removeLoadingScreen(emitResize); var privateDat = cpNfInner.metadataMgr.getPrivateData(); @@ -567,6 +572,10 @@ define([ // in the pad when requested by the framework. setContentGetter: function (cg) { contentGetter = cg; }, + // Set a text content supplier, this is a function which will give a text + // representation of the pad content if a text analyzer is configured + setTextContentGetter: function (tcg) { textContentGetter = tcg; }, + // Inform the framework that the content of the pad has been changed locally. localChange: onLocal, diff --git a/www/pad/inner.js b/www/pad/inner.js index 6945f6d45..22ec21e40 100644 --- a/www/pad/inner.js +++ b/www/pad/inner.js @@ -402,6 +402,17 @@ define([ } }); + framework.setTextContentGetter(function () { + var innerCopy = inner.cloneNode(true); + displayMediaTags(framework, innerCopy, mediaTagMap); + innerCopy.normalize(); + $(innerCopy).find('*').each(function (i, el) { + $(el).append(' '); + }); + var str = $(innerCopy).text(); + str = str.replace(/\s\s+/g, ' '); + return str; + }); framework.setContentGetter(function () { displayMediaTags(framework, inner, mediaTagMap); inner.normalize(); From 970122b41d398d7b65d2ef5890ade9735e655b28 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 1 Feb 2018 11:08:04 +0100 Subject: [PATCH 02/45] Send the channel id to the text analyzer --- www/common/sframe-app-framework.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/www/common/sframe-app-framework.js b/www/common/sframe-app-framework.js index 960fce5f7..181c45a94 100644 --- a/www/common/sframe-app-framework.js +++ b/www/common/sframe-app-framework.js @@ -7,6 +7,7 @@ define([ '/common/sframe-common.js', '/customize/messages.js', '/common/common-util.js', + '/common/common-hash.js', '/common/common-interface.js', '/common/common-thumbnail.js', '/common/common-feedback.js', @@ -27,6 +28,7 @@ define([ SFCommon, Messages, Util, + Hash, UI, Thumb, Feedback, @@ -286,7 +288,12 @@ define([ evOnReady.fire(newPad); if (AppConfig.textAnalyzer && textContentGetter) { - AppConfig.textAnalyzer(textContentGetter); + var privateData = common.getMetadataMgr().getPrivateData(); + var channelHashes = privateData.availableHashes; + var hash = channelHashes.editHash || channelHashes.viewHash; + var href = privateData.pathname + '#' + hash; + var channelId = Hash.hrefToHexChannelId(href); + AppConfig.textAnalyzer(textContentGetter, channelId); } UI.removeLoadingScreen(emitResize); From 0672c2f41a394fb945fb2de193ddfc58e287117c Mon Sep 17 00:00:00 2001 From: ansuz Date: Thu, 1 Feb 2018 11:09:24 +0100 Subject: [PATCH 03/45] implement a running diff --- customize.dist/delta-words.js | 61 +++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 customize.dist/delta-words.js diff --git a/customize.dist/delta-words.js b/customize.dist/delta-words.js new file mode 100644 index 000000000..74f93b918 --- /dev/null +++ b/customize.dist/delta-words.js @@ -0,0 +1,61 @@ +define([ + '/bower_components/chainpad/chainpad.dist.js', +], function (ChainPad) { + var Diff = ChainPad.Diff; + + var isSpace = function (S, i) { + return /^\s$/.test(S.charAt(i)); + }; + + var leadingBoundary = function (S, offset) { + if (/\s/.test(S.charAt(offset))) { return offset; } + while (offset > 0) { + offset--; + if (isSpace(S, offset)) { offset++; break; } + } + return offset; + }; + + var trailingBoundary = function (S, offset) { + if (isSpace(S, offset)) { return offset; } + while (offset < S.length && !/\s/.test(S.charAt(offset))) { + offset++; + } + return offset; + }; + + var opsToWords = function (last, current) { + var output = []; + Diff.diff(A, B).forEach(function (op) { + // ignore deleted sections... + var offset = op.offset; + var toInsert = op.toInsert; + + // given an operation, check whether it is a word fragment, + // if it is, expand it to its word boundaries + var first = B.slice(leadingBoundary(B, offset), offset); + var last = B.slice(offset + toInsert.length, trailingBoundary(B, offset + toInsert.length)); + + var result = first + toInsert + last; + // concat-in-place + Array.prototype.push.apply(output, result.split(/\s+/)); + }); + return output; + }; + + var runningDiff = function (getter, f, time) { + var last = getter(); + // first time through, send all the words :D + f(opsToWords("", last)); + return setInterval(function () { + var current = getter(); + + // find inserted words... + var words = opsToWords(last, current); + last = current; + f(words); + }, time); + }; + + return runningDiff; +}); From 83eb3047168aa36051de409eadcad5a542e65b6e Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 1 Feb 2018 11:16:34 +0100 Subject: [PATCH 04/45] lint compliance --- www/common/sframe-app-framework.js | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/www/common/sframe-app-framework.js b/www/common/sframe-app-framework.js index 181c45a94..9c9c1b786 100644 --- a/www/common/sframe-app-framework.js +++ b/www/common/sframe-app-framework.js @@ -287,23 +287,20 @@ define([ if (!readOnly) { onLocal(); } evOnReady.fire(newPad); + UI.removeLoadingScreen(emitResize); + + var privateDat = cpNfInner.metadataMgr.getPrivateData(); + var hash = privateDat.availableHashes.editHash || + privateDat.availableHashes.viewHash; + var href = privateDat.pathname + '#' + hash; if (AppConfig.textAnalyzer && textContentGetter) { - var privateData = common.getMetadataMgr().getPrivateData(); - var channelHashes = privateData.availableHashes; - var hash = channelHashes.editHash || channelHashes.viewHash; - var href = privateData.pathname + '#' + hash; var channelId = Hash.hrefToHexChannelId(href); AppConfig.textAnalyzer(textContentGetter, channelId); } - UI.removeLoadingScreen(emitResize); - - var privateDat = cpNfInner.metadataMgr.getPrivateData(); if (options.thumbnail && privateDat.thumbnails) { - var hash = privateDat.availableHashes.editHash || - privateDat.availableHashes.viewHash; if (hash) { - options.thumbnail.href = privateDat.pathname + '#' + hash; + options.thumbnail.href = href; options.thumbnail.getContent = function () { if (!cpNfInner.chainpad) { return; } return cpNfInner.chainpad.getUserDoc(); From a281869bba0e3f52fa774ec431edccaba40d8a33 Mon Sep 17 00:00:00 2001 From: ansuz Date: Thu, 1 Feb 2018 11:43:29 +0100 Subject: [PATCH 05/45] make things actually work --- customize.dist/delta-words.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/customize.dist/delta-words.js b/customize.dist/delta-words.js index 74f93b918..4eec0d0b3 100644 --- a/customize.dist/delta-words.js +++ b/customize.dist/delta-words.js @@ -24,23 +24,23 @@ define([ return offset; }; - var opsToWords = function (last, current) { + var opsToWords = function (previous, current) { var output = []; - Diff.diff(A, B).forEach(function (op) { + Diff.diff(previous, current).forEach(function (op) { // ignore deleted sections... var offset = op.offset; var toInsert = op.toInsert; // given an operation, check whether it is a word fragment, // if it is, expand it to its word boundaries - var first = B.slice(leadingBoundary(B, offset), offset); - var last = B.slice(offset + toInsert.length, trailingBoundary(B, offset + toInsert.length)); + var first = current.slice(leadingBoundary(current, offset), offset); + var last = current.slice(offset + toInsert.length, trailingBoundary(current, offset + toInsert.length)); var result = first + toInsert + last; // concat-in-place Array.prototype.push.apply(output, result.split(/\s+/)); }); - return output; + return output.filter(Boolean); }; var runningDiff = function (getter, f, time) { From 8213d0d9265ec26468f2e316963bf07f70789c62 Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 5 Feb 2018 15:24:30 +0100 Subject: [PATCH 06/45] guard against null pointer exception --- www/todo/inner.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/www/todo/inner.js b/www/todo/inner.js index 8d5de7778..6e6bcf297 100644 --- a/www/todo/inner.js +++ b/www/todo/inner.js @@ -70,6 +70,10 @@ define([ var makeCheckbox = function (id, cb) { var entry = APP.lm.proxy.data[id]; + if (!entry || typeof(entry) !== 'object') { + return void console.log('entry undefined'); + } + var checked = entry.state === 1 ? 'cp-app-todo-task-checkbox-checked fa-check-square-o': 'cp-app-todo-task-checkbox-unchecked fa-square-o'; @@ -108,6 +112,9 @@ define([ .appendTo($taskDiv); var entry = APP.lm.proxy.data[el]; + if (!entry || typeof(entry) !== 'object') { + return void console.log('entry undefined'); + } if (entry.state) { $taskDiv.addClass('cp-app-todo-task-complete'); From cac5e75a99f52217eeb8eac61723602a742ddce8 Mon Sep 17 00:00:00 2001 From: Caleb James DeLisle Date: Tue, 6 Feb 2018 11:35:24 +0100 Subject: [PATCH 07/45] Add a repl to improve debugging on the prod server --- config.example.js | 8 ++++++++ package.json | 1 + rpc.js | 25 +++++++++++++++---------- server.js | 23 +++++++++++++++++++++-- 4 files changed, 45 insertions(+), 12 deletions(-) diff --git a/config.example.js b/config.example.js index 8fb4acb00..49ba68541 100644 --- a/config.example.js +++ b/config.example.js @@ -313,4 +313,12 @@ module.exports = { // '/etc/apache2/ssl/my_public_cert.crt', // '/etc/apache2/ssl/my_certificate_authorities_cert_chain.ca' //], + + /* You can get a repl for debugging the server if you want it. + * to enable this, specify the debugReplName and then you can + * connect to it with `nc -U /tmp/repl/.sock` + * If you run multiple cryptpad servers, you need to use different + * repl names. + */ + //debugReplName: "cryptpad" }; diff --git a/package.json b/package.json index b9cac6c82..eb6149506 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "express": "~4.10.1", "nthen": "~0.1.0", "pull-stream": "^3.6.1", + "replify": "^1.2.0", "saferphore": "0.0.1", "stream-to-pull-stream": "^1.7.2", "tweetnacl": "~0.12.2", diff --git a/rpc.js b/rpc.js index ebc6ae96d..16360544b 100644 --- a/rpc.js +++ b/rpc.js @@ -395,8 +395,7 @@ var getHash = function (Env, publicKey, cb) { // The limits object contains storage limits for all the publicKey that have paid // To each key is associated an object containing the 'limit' value and a 'note' explaining that limit -var limits = {}; -var updateLimits = function (config, publicKey, cb /*:(?string, ?any[])=>void*/) { +var updateLimits = function (Env, config, publicKey, cb /*:(?string, ?any[])=>void*/) { if (config.adminEmail === false) { if (config.allowSubscriptions === false) { return; } throw new Error("allowSubscriptions must be false if adminEmail is false"); @@ -461,15 +460,15 @@ var updateLimits = function (config, publicKey, cb /*:(?string, ?any[])=>void*/) response.on('end', function () { try { var json = JSON.parse(str); - limits = json; + Env.limits = json; Object.keys(customLimits).forEach(function (k) { if (!isLimit(customLimits[k])) { return; } - limits[k] = customLimits[k]; + Env.limits[k] = customLimits[k]; }); var l; if (userId) { - var limit = limits[userId]; + var limit = Env.limits[userId]; l = limit && typeof limit.limit === "number" ? [limit.limit, limit.plan, limit.note] : [defaultLimit, '', '']; } @@ -490,7 +489,7 @@ var updateLimits = function (config, publicKey, cb /*:(?string, ?any[])=>void*/) var getLimit = function (Env, publicKey, cb) { var unescapedKey = unescapeKeyCharacters(publicKey); - var limit = limits[unescapedKey]; + var limit = Env.limits[unescapedKey]; var defaultLimit = typeof(Env.defaultStorageLimit) === 'number'? Env.defaultStorageLimit: DEFAULT_LIMIT; @@ -1063,7 +1062,11 @@ type NetfluxWebsocketSrvContext_t = { )=>void }; */ -RPC.create = function (config /*:Config_t*/, cb /*:(?Error, ?Function)=>void*/) { +RPC.create = function ( + config /*:Config_t*/, + debuggable /*:(string, T)=>T*/, + cb /*:(?Error, ?Function)=>void*/ +) { // load pin-store... console.log('loading rpc module...'); @@ -1081,8 +1084,10 @@ RPC.create = function (config /*:Config_t*/, cb /*:(?Error, ?Function)=>void*/) msgStore: (undefined /*:any*/), pinStore: (undefined /*:any*/), pinnedPads: {}, - evPinnedPadsReady: mkEvent(true) + evPinnedPadsReady: mkEvent(true), + limits: {} }; + debuggable('rpc_env', Env); var Sessions = Env.Sessions; var paths = Env.paths; @@ -1264,7 +1269,7 @@ RPC.create = function (config /*:Config_t*/, cb /*:(?Error, ?Function)=>void*/) Respond(e, size); }); case 'UPDATE_LIMITS': - return void updateLimits(config, safeKey, function (e, limit) { + return void updateLimits(Env, config, safeKey, function (e, limit) { if (e) { WARN(e, limit); return void Respond(e); @@ -1376,7 +1381,7 @@ RPC.create = function (config /*:Config_t*/, cb /*:(?Error, ?Function)=>void*/) }; var updateLimitDaily = function () { - updateLimits(config, undefined, function (e) { + updateLimits(Env, config, undefined, function (e) { if (e) { WARN('limitUpdate', e); } diff --git a/server.js b/server.js index a51c94343..0a39888f5 100644 --- a/server.js +++ b/server.js @@ -20,10 +20,25 @@ try { var websocketPort = config.websocketPort || config.httpPort; var useSecureWebsockets = config.useSecureWebsockets || false; +// This is stuff which will become available to replify +const debuggableStore = new WeakMap(); +const debuggable = function (name, x) { + if (name in debuggableStore) { + try { throw new Error(); } catch (e) { + console.error('cannot add ' + name + ' more than once [' + e.stack + ']'); + } + } else { + debuggableStore[name] = x; + } + return x; +}; +debuggable('global', global); +debuggable('config', config); + // support multiple storage back ends var Storage = require(config.storage||'./storage/file'); -var app = Express(); +var app = debuggable('app', Express()); var httpsOpts; @@ -217,7 +232,7 @@ var loadRPC = function (cb) { if (typeof(config.rpc) === 'string') { // load pin store... var Rpc = require(config.rpc); - Rpc.create(config, function (e, rpc) { + Rpc.create(config, debuggable, function (e, rpc) { if (e) { throw e; } cb(void 0, rpc); }); @@ -227,3 +242,7 @@ var loadRPC = function (cb) { }; loadRPC(createSocketServer); + +if (config.debugReplName) { + require('replify')({ name: config.debugReplName, app: debuggableStore }); +} \ No newline at end of file From 4df4c48fbd575d9ec4dc1dfa862da571a690a05c Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 6 Feb 2018 17:36:37 +0100 Subject: [PATCH 08/45] Improve UI for pad creation screen --- customize.dist/src/less2/include/creation.less | 18 ++++++++++++++---- www/common/common-ui-elements.js | 10 ++++++++-- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/customize.dist/src/less2/include/creation.less b/customize.dist/src/less2/include/creation.less index aa94beb28..17d72f28f 100644 --- a/customize.dist/src/less2/include/creation.less +++ b/customize.dist/src/less2/include/creation.less @@ -33,16 +33,26 @@ flex-wrap: wrap; justify-content: center; align-items: center; - h2, p { - width: 100%; - } h2 { + width: 100%; display: flex; + margin-bottom: 20px; justify-content: space-between; .cp-creation-help { display: none; } } + .cp-creation-help-container { + display: flex; + justify-content: space-between; + p { + padding: 0 20px; + flex-grow: 0; + flex-shrink: 0; + flex-basis: 50%; + text-align: justify; + } + } @media screen and (max-width: 500px) { width: ~"calc(100% - 30px)"; } @@ -50,7 +60,7 @@ h2 .cp-creation-help { display: inline; } - p { + .cp-creation-help-container { display: none; } } diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index d31fe0a70..432061d2e 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -1687,7 +1687,10 @@ define([ Messages.creation_ownedTitle, createHelper(Messages.creation_owned1 + '\n' + Messages.creation_owned2) ]), - setHTML(h('p'), Messages.creation_owned1 + '
' + Messages.creation_owned2), + h('div.cp-creation-help-container', [ + setHTML(h('p'), Messages.creation_owned1), + setHTML(h('p'), Messages.creation_owned2) + ]), h('input#cp-creation-owned-true.cp-creation-owned-value', { type: 'radio', name: 'cp-creation-owned', @@ -1715,7 +1718,10 @@ define([ Messages.creation_expireTitle, createHelper(Messages.creation_expire1, Messages.creation_expire2) ]), - setHTML(h('p'), Messages.creation_expire1 + '
' + Messages.creation_expire2), + h('div.cp-creation-help-container', [ + setHTML(h('p'), Messages.creation_expire1), + setHTML(h('p'), Messages.creation_expire2) + ]), h('input#cp-creation-expire-false.cp-creation-expire-value', { type: 'radio', name: 'cp-creation-expire', From 5bba9b6c39bc46299fbccbd8fc7f701c4d52ac64 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 6 Feb 2018 18:45:12 +0100 Subject: [PATCH 09/45] Refactor login to remove duplicate code --- customize.dist/login.js | 131 ++++++++++++++++++++++++++++++++- www/common/common-interface.js | 1 + www/login/main.js | 94 +++-------------------- www/register/main.js | 122 ++---------------------------- 4 files changed, 147 insertions(+), 201 deletions(-) diff --git a/customize.dist/login.js b/customize.dist/login.js index b7a9d4e84..eb229a6e9 100644 --- a/customize.dist/login.js +++ b/customize.dist/login.js @@ -6,10 +6,17 @@ define([ '/common/outer/network-config.js', '/customize/credential.js', '/bower_components/chainpad/chainpad.dist.js', + '/common/common-realtime.js', + '/common/common-constants.js', + '/common/common-interface.js', + '/common/common-feedback.js', + '/common/outer/local-store.js', + '/customize/messages.js', '/bower_components/tweetnacl/nacl-fast.min.js', '/bower_components/scrypt-async/scrypt-async.min.js', // better load speed -], function ($, Listmap, Crypto, Util, NetConfig, Cred, ChainPad) { +], function ($, Listmap, Crypto, Util, NetConfig, Cred, ChainPad, Realtime, Constants, UI, + Feedback, LocalStore, Messages) { var Exports = { Cred: Cred, }; @@ -142,5 +149,127 @@ define([ }); }; + Exports.loginOrRegisterUI = function (uname, passwd, isRegister, shouldImport, testing, test) { + var hashing = true; + + var proceed = function (result) { + var proxy = result.proxy; + proxy.edPublic = result.edPublic; + proxy.edPrivate = result.edPrivate; + proxy.curvePublic = result.curvePublic; + proxy.curvePrivate = result.curvePrivate; + + if (isRegister) { + Feedback.send('REGISTRATION', true); + } else { + Feedback.send('LOGIN', true); + } + + Realtime.whenRealtimeSyncs(result.realtime, function () { + try { + LocalStore.login(result.userHash, result.userName, function () { + hashing = false; + if (test && typeof test === "function" && test()) { console.log('testing'); + return; } + if (shouldImport) { + sessionStorage.migrateAnonDrive = 1; + } + if (sessionStorage.redirectTo) { + var h = sessionStorage.redirectTo; + var parser = document.createElement('a'); + parser.href = h; + if (parser.origin === window.location.origin) { + delete sessionStorage.redirectTo; + window.location.href = h; + return; + } + } + window.location.href = '/drive/'; + }); + } catch (e) { console.error(e); } + }); + }; + + // setTimeout 100ms to remove the keyboard on mobile devices before the loading screen + // pops up + window.setTimeout(function () { + UI.addLoadingScreen({ + loadingText: Messages.login_hashing, + hideTips: true, + }); + // We need a setTimeout(cb, 0) otherwise the loading screen is only displayed + // after hashing the password + window.setTimeout(function () { + Exports.loginOrRegister(uname, passwd, isRegister, function (err, result) { + var proxy; + if (result) { proxy = result.proxy; } + + if (err) { + switch (err) { + case 'NO_SUCH_USER': + UI.removeLoadingScreen(function () { + UI.alert(Messages.login_noSuchUser, function () { + hashing = false; + }); + }); + break; + case 'INVAL_USER': + UI.removeLoadingScreen(function () { + UI.alert(Messages.login_invalUser, function () { + hashing = false; + }); + }); + break; + case 'INVAL_PASS': + UI.removeLoadingScreen(function () { + UI.alert(Messages.login_invalPass, function () { + hashing = false; + }); + }); + break; + case 'PASS_TOO_SHORT': + UI.removeLoadingScreen(function () { + var warning = Messages._getKey('register_passwordTooShort', [ + Cred.MINIMUM_PASSWORD_LENGTH + ]); + UI.alert(warning, function () { + hashing = false; + }); + }); + break; + case 'ALREADY_REGISTERED': + // logMeIn should reset registering = false + UI.removeLoadingScreen(function () { + UI.confirm(Messages.register_alreadyRegistered, function (yes) { + if (!yes) { return; } + proxy.login_name = uname; + + if (!proxy[Constants.displayNameKey]) { + proxy[Constants.displayNameKey] = uname; + } + LocalStore.eraseTempSessionValues(); + proceed(result); + }); + }); + break; + default: // UNHANDLED ERROR + hashing = false; + UI.errorLoadingScreen(Messages.login_unhandledError); + } + return; + } + + if (testing) { return void proceed(result); } + + proxy.login_name = uname; + proxy[Constants.displayNameKey] = uname; + sessionStorage.createReadme = 1; + + proceed(result); + }); + }, 0); + }, 200); + }; + return Exports; }); diff --git a/www/common/common-interface.js b/www/common/common-interface.js index f20bb4224..248bd5e70 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -553,6 +553,7 @@ define([ var $loading, $container; if ($('#' + LOADING).length) { $loading = $('#' + LOADING); //.show(); + $loading.css('display', ''); $loading.removeClass('cp-loading-hidden'); if (loadingText) { $('#' + LOADING).find('p').text(loadingText); diff --git a/www/login/main.js b/www/login/main.js index f6a9cebb6..f0c6c7d22 100644 --- a/www/login/main.js +++ b/www/login/main.js @@ -13,7 +13,6 @@ define([ $(function () { var $main = $('#mainBlock'); var $checkImport = $('#import-recent'); - var Messages = Cryptpad.Messages; // main block is hidden in case javascript is disabled $main.removeClass('hidden'); @@ -61,90 +60,15 @@ define([ hashing = true; var shouldImport = $checkImport[0].checked; - - // setTimeout 100ms to remove the keyboard on mobile devices before the loading screen pops up - window.setTimeout(function () { - UI.addLoadingScreen({ - loadingText: Messages.login_hashing, - hideTips: true, - }); - // We need a setTimeout(cb, 0) otherwise the loading screen is only displayed after hashing the password - window.setTimeout(function () { - loginReady(function () { - var uname = $uname.val(); - var passwd = $passwd.val(); - Login.loginOrRegister(uname, passwd, false, function (err, result) { - if (!err) { - var proxy = result.proxy; - - // successful validation and user already exists - // set user hash in localStorage and redirect to drive - if (!proxy.login_name) { - result.proxy.login_name = result.userName; - } - - proxy.edPrivate = result.edPrivate; - proxy.edPublic = result.edPublic; - - proxy.curvePrivate = result.curvePrivate; - proxy.curvePublic = result.curvePublic; - - Feedback.send('LOGIN', true); - Realtime.whenRealtimeSyncs(result.realtime, function() { - LocalStore.login(result.userHash, result.userName, function () { - hashing = false; - if (test) { - localStorage.clear(); - test.pass(); - return; - } - if (shouldImport) { - sessionStorage.migrateAnonDrive = 1; - } - if (sessionStorage.redirectTo) { - var h = sessionStorage.redirectTo; - var parser = document.createElement('a'); - parser.href = h; - if (parser.origin === window.location.origin) { - delete sessionStorage.redirectTo; - window.location.href = h; - return; - } - } - window.location.href = '/drive/'; - }); - }); - return; - } - switch (err) { - case 'NO_SUCH_USER': - UI.removeLoadingScreen(function () { - UI.alert(Messages.login_noSuchUser, function () { - hashing = false; - }); - }); - break; - case 'INVAL_USER': - UI.removeLoadingScreen(function () { - UI.alert(Messages.login_invalUser, function () { - hashing = false; - }); - }); - break; - case 'INVAL_PASS': - UI.removeLoadingScreen(function () { - UI.alert(Messages.login_invalPass, function () { - hashing = false; - }); - }); - break; - default: // UNHANDLED ERROR - UI.errorLoadingScreen(Messages.login_unhandledError); - } - }); - }); - }, 0); - }, 100); + var uname = $uname.val(); + var passwd = $passwd.val(); + Login.loginOrRegisterUI(uname, passwd, false, shouldImport, Test.testing, function () { + if (test) { + localStorage.clear(); + test.pass(); + return true; + } + }); }); $('#register').on('click', function () { if (sessionStorage) { diff --git a/www/register/main.js b/www/register/main.js index 172eaf8cc..0c56dcff2 100644 --- a/www/register/main.js +++ b/www/register/main.js @@ -55,39 +55,6 @@ define([ var registering = false; var test; - var logMeIn = function (result) { - LocalStore.setUserHash(result.userHash); - - var proxy = result.proxy; - proxy.edPublic = result.edPublic; - proxy.edPrivate = result.edPrivate; - proxy.curvePublic = result.curvePublic; - proxy.curvePrivate = result.curvePrivate; - - Feedback.send('REGISTRATION', true); - - Realtime.whenRealtimeSyncs(result.realtime, function () { - LocalStore.login(result.userHash, result.userName, function () { - registering = false; - if (test) { - localStorage.clear(); - test.pass(); - return; - } - if (sessionStorage.redirectTo) { - var h = sessionStorage.redirectTo; - var parser = document.createElement('a'); - parser.href = h; - if (parser.origin === window.location.origin) { - delete sessionStorage.redirectTo; - window.location.href = h; - return; - } - } - window.location.href = '/drive/'; - }); - }); - }; $register.click(function () { if (registering) { @@ -125,89 +92,14 @@ define([ function (yes) { if (!yes) { return; } + Login.loginOrRegisterUI(uname, passwd, true, shouldImport, Test.testing, function () { + if (test) { + localStorage.clear(); + test.pass(); + return true; + } + }); registering = true; - // setTimeout 100ms to remove the keyboard on mobile devices before the loading screen pops up - window.setTimeout(function () { - UI.addLoadingScreen({ - loadingText: Messages.login_hashing, - hideTips: true, - }); - // We need a setTimeout(cb, 0) otherwise the loading screen is only displayed after hashing the password - window.setTimeout(function () { - Login.loginOrRegister(uname, passwd, true, function (err, result) { - var proxy; - if (result) { proxy = result.proxy; } - - if (err) { - switch (err) { - case 'NO_SUCH_USER': - UI.removeLoadingScreen(function () { - UI.alert(Messages.login_noSuchUser, function () { - registering = false; - }); - }); - break; - case 'INVAL_USER': - UI.removeLoadingScreen(function () { - UI.alert(Messages.login_invalUser, function () { - registering = false; - }); - }); - break; - case 'INVAL_PASS': - UI.removeLoadingScreen(function () { - UI.alert(Messages.login_invalPass, function () { - registering = false; - }); - }); - break; - case 'PASS_TOO_SHORT': - UI.removeLoadingScreen(function () { - var warning = Messages._getKey('register_passwordTooShort', [ - Cred.MINIMUM_PASSWORD_LENGTH - ]); - UI.alert(warning, function () { - registering = false; - }); - }); - break; - case 'ALREADY_REGISTERED': - // logMeIn should reset registering = false - UI.removeLoadingScreen(function () { - UI.confirm(Messages.register_alreadyRegistered, function (yes) { - if (!yes) { return; } - proxy.login_name = uname; - - if (!proxy[Constants.displayNameKey]) { - proxy[Constants.displayNameKey] = uname; - } - LocalStore.eraseTempSessionValues(); - logMeIn(result); - }); - }); - break; - default: // UNHANDLED ERROR - registering = false; - UI.errorLoadingScreen(Messages.login_unhandledError); - } - return; - } - - if (Test.testing) { return void logMeIn(result); } - - LocalStore.eraseTempSessionValues(); - if (shouldImport) { - sessionStorage.migrateAnonDrive = 1; - } - - proxy.login_name = uname; - proxy[Constants.displayNameKey] = uname; - sessionStorage.createReadme = 1; - - logMeIn(result); - }); - }, 0); - }, 200); }, { ok: Messages.register_writtenPassword, cancel: Messages.register_cancel, From a1f05e4bf1e3292c4cb13bc660eef6935d2b9169 Mon Sep 17 00:00:00 2001 From: ansuz Date: Wed, 7 Feb 2018 10:24:04 +0100 Subject: [PATCH 10/45] update pad creation screen text --- customize.dist/translations/messages.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index 32c61825f..253345c1a 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -866,16 +866,16 @@ define(function () { out.creation_ownedTitle = "Type of pad"; out.creation_ownedTrue = "Owned pad"; out.creation_ownedFalse = "Open pad"; - out.creation_owned1 = "An owned pad is a pad that you can delete from the server whenever you want. Once it is deleted, no one else can access it, even if it is stored in their CryptDrive."; + out.creation_owned1 = "An owned pad can be deleted from the server whenever the owner wants. Deleting an owned pad removes it from other users' CryptDrives."; out.creation_owned2 = "An open pad doesn't have any owner and thus, it can't be deleted from the server unless it has reached its expiration time."; out.creation_expireTitle = "Life time"; out.creation_expireTrue = "Add a life time"; out.creation_expireFalse = "Unlimited"; - out.creation_expireHours = "Hours"; - out.creation_expireDays = "Days"; - out.creation_expireMonths = "Months"; - out.creation_expire1 = "By default, a pad stored by a registered user will never be removed from the server, unless it is requested by its owner."; - out.creation_expire2 = "If you prefer, you can set a life time to make sure the pad will be permanently deleted from the server and unavailable after the specified date."; + out.creation_expireHours = "Hour(s)"; + out.creation_expireDays = "Day(s)"; + out.creation_expireMonths = "Month(s)"; + out.creation_expire1 = "An unlimited pad will not be removed from the server until its owner deletes it."; + out.creation_expire2 = "An expiring pad has a set lifetime, after which it will be automatically removed from the server and other users' CryptDrives."; out.creation_createTitle = "Create a pad"; out.creation_createFromTemplate = "From template"; out.creation_createFromScratch = "From scratch"; From 218b1fb3286ac3e8f64e0460aad82e80104c7f31 Mon Sep 17 00:00:00 2001 From: ansuz Date: Wed, 7 Feb 2018 10:24:40 +0100 Subject: [PATCH 11/45] set messages.el.js to non-executable --- customize.dist/translations/messages.el.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 customize.dist/translations/messages.el.js diff --git a/customize.dist/translations/messages.el.js b/customize.dist/translations/messages.el.js old mode 100755 new mode 100644 From 66f38fa4760676daefa290bb0558b56b0712a3ba Mon Sep 17 00:00:00 2001 From: ansuz Date: Wed, 7 Feb 2018 10:27:16 +0100 Subject: [PATCH 12/45] WIP file expiration script --- expire-channels.js | 93 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 expire-channels.js diff --git a/expire-channels.js b/expire-channels.js new file mode 100644 index 000000000..d64f3c349 --- /dev/null +++ b/expire-channels.js @@ -0,0 +1,93 @@ +var Fs = require("fs"); +var Path = require("path"); + +var nThen = require("nthen"); +var config = require("./config"); + +var root = Path.resolve(config.taskPath || './tasks'); + +var dirs; +var nt; + +var queue = function (f) { + nt = nt.nThen(f); +}; + +var tryParse = function (s) { + try { return JSON.parse(s); } + catch (e) { return null; } +}; + +var CURRENT = +new Date(); + +var handleTask = function (str, path, cb) { + var task = tryParse(str); + if (!Array.isArray(task)) { + console.error('invalid task: not array'); + return cb(); + } + if (task.length < 2) { + console.error('invalid task: too small'); + return cb(); + } + + var time = task[0]; + var command = task[1]; + var args = task.slice(2); + + if (time > CURRENT) { + // not time for this task yet + console.log('not yet time'); + return cb(); + } + + nThen(function () { + switch (command) { + case 'EXPIRE': + console.log("expiring: %s", args[0]); + // TODO actually remove the file... + break; + default: + console.log("unknown command", command); + } + }).nThen(function () { + // remove the file... + Fs.unlink(path, function (err) { + if (err) { console.error(err); } + cb(); + }); + }); +}; + +nt = nThen(function (w) { + Fs.readdir(root, w(function (e, list) { + if (e) { throw e; } + dirs = list; + })); +}).nThen(function () { + dirs.forEach(function (dir) { + queue(function (w) { + console.log('recursing into %s', dir); + Fs.readdir(Path.join(root, dir), w(function (e, list) { + list.forEach(function (fn) { + queue(function (w) { + var filePath = Path.join(root, dir, fn); + var cb = w(); + + console.log("processing file at %s", filePath); + Fs.readFile(filePath, 'utf8', function (e, str) { + if (e) { + console.error(e); + return void cb(); + } + + handleTask(str, filePath, cb); + }); + }); + }); + })); + }); + }); +}); + + From bee5494abb05b3393dc72f5515130f5489948e62 Mon Sep 17 00:00:00 2001 From: ansuz Date: Wed, 7 Feb 2018 11:07:45 +0100 Subject: [PATCH 13/45] add some sanity checks to the todo app --- www/todo/inner.js | 1 + www/todo/todo.js | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/www/todo/inner.js b/www/todo/inner.js index 6e6bcf297..748467360 100644 --- a/www/todo/inner.js +++ b/www/todo/inner.js @@ -96,6 +96,7 @@ define([ }; var addTaskUI = function (el, animate) { + if (!el) { return; } var $taskDiv = $('
', { 'class': 'cp-app-todo-task' }); diff --git a/www/todo/todo.js b/www/todo/todo.js index 07b9962cc..8c68774dd 100644 --- a/www/todo/todo.js +++ b/www/todo/todo.js @@ -39,6 +39,24 @@ define([ if (typeof(proxy.data) !== 'object') { proxy.data = {}; } if (!Array.isArray(proxy.order)) { proxy.order = []; } if (typeof(proxy.type) !== 'string') { proxy.type = 'todo'; } + + // if a key exists in order, but there is no data for it... + // remove that key + var i = proxy.order.length - 1; + for (;i >= 0; i--) { + if (typeof(proxy.data[proxy.order[i]]) === 'undefined') { + console.log('removing todo entry with no data at [%s]', i); + proxy.order.splice(i, 1); + } + } + + // if you have data, but it's not in the order array... + // add it to the order array... + Object.keys(proxy.data).forEach(function (key) { + if (proxy.order.indexOf(key) > -1) { return; } + console.log("restoring entry with missing key"); + proxy.order.unshift(key); + }); }; /* add (id, obj) push id to order, add object to data */ From 7ebfa43408d602e0b53a0d128b2e2f80293f39bd Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 7 Feb 2018 13:08:03 +0100 Subject: [PATCH 14/45] Improve assert translations to detect issues in objects (tips, type, etc.) --- customize.dist/messages.js | 79 ++++++++++++++++--------- customize.dist/translations/messages.js | 3 +- www/assert/translations/main.js | 45 +++++++------- 3 files changed, 76 insertions(+), 51 deletions(-) diff --git a/customize.dist/messages.js b/customize.dist/messages.js index 65b306f42..16128df9c 100755 --- a/customize.dist/messages.js +++ b/customize.dist/messages.js @@ -43,7 +43,7 @@ define(req, function(Util, Default, Language) { messages._checkTranslationState = function (cb) { if (typeof(cb) !== "function") { return; } - var missing = []; + var allMissing = []; var reqs = []; Object.keys(map).forEach(function (code) { if (code === defaultLanguage) { return; } @@ -54,37 +54,60 @@ define(req, function(Util, Default, Language) { Object.keys(map).forEach(function (code, i) { if (code === defaultLanguage) { return; } var translation = langs[i]; - var updated = {}; - Object.keys(Default).forEach(function (k) { - if (/^updated_[0-9]+_/.test(k) && !translation[k]) { - var key = k.split('_').slice(2).join('_'); - // Make sure we don't already have an update for that key. It should not happen - // but if it does, keep the latest version - if (updated[key]) { - var ek = updated[key]; - if (parseInt(ek.split('_')[1]) > parseInt(k.split('_')[1])) { return; } + var missing = []; + var checkInObject = function (ref, translated, path) { + var updated = {}; + Object.keys(ref).forEach(function (k) { + if (/^updated_[0-9]+_/.test(k) && !translated[k]) { + var key = k.split('_').slice(2).join('_'); + // Make sure we don't already have an update for that key. It should not happen + // but if it does, keep the latest version + if (updated[key]) { + var ek = updated[key]; + if (parseInt(ek.split('_')[1]) > parseInt(k.split('_')[1])) { return; } + } + updated[key] = k; } - updated[key] = k; - } - }); - Object.keys(Default).forEach(function (k) { - if (/^_/.test(k) || k === 'driveReadme') { return; } - if (!translation[k] || updated[k]) { - if (updated[k]) { - missing.push([code, k, 2, 'out.' + updated[k]]); - return; + }); + Object.keys(ref).forEach(function (k) { + if (/^_/.test(k) || k === 'driveReadme') { return; } + var nPath = path.slice(); + nPath.push(k); + if (!translated[k] || updated[k]) { + if (updated[k]) { + var uPath = path.slice(); + uPath.unshift('out'); + missing.push([code, nPath, 2, uPath.join('.') + '.' + updated[k]]); + return; + } + return void missing.push([code, nPath, 1]); } - missing.push([code, k, 1]); - } - }); - Object.keys(translation).forEach(function (k) { - if (/^_/.test(k) || k === 'driveReadme') { return; } - if (typeof Default[k] === "undefined") { - missing.push([code, k, 0]); - } + if (typeof ref[k] !== typeof translated[k]) { + return void missing.push([code, nPath, 3]); + } + if (typeof ref[k] === "object" && !Array.isArray(ref[k])) { + checkInObject(ref[k], translated[k], nPath); + } + }); + Object.keys(translated).forEach(function (k) { + if (/^_/.test(k) || k === 'driveReadme') { return; } + var nPath = path.slice(); + nPath.push(k); + if (typeof ref[k] === "undefined") { + missing.push([code, nPath, 0]); + } + }); + }; + checkInObject(Default, translation, []); + // Push the removals at the end + missing.sort(function (a, b) { + if (a[2] === 0 && b[2] !== 0) return 1; + if (a[2] !== 0 && b[2] === 0) return -1; + return 0; }); + Array.prototype.push.apply(allMissing, missing); // Destructive concat }); - cb(missing); + cb(allMissing); }); }; diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index 32c61825f..d5c628f8c 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -22,8 +22,7 @@ define(function () { out.button_newslide = 'New Presentation'; out.button_newwhiteboard = 'New Whiteboard'; - // NOTE: We want to update the 'common_connectionLost' key. - // Please do not add a new 'updated_common_connectionLostAndInfo' but change directly the value of 'common_connectionLost' + // NOTE: Remove updated_0_ if we need an updated_1_ out.updated_0_common_connectionLost = "Server Connection Lost
You're now in read-only mode until the connection is back."; out.common_connectionLost = out.updated_0_common_connectionLost; diff --git a/www/assert/translations/main.js b/www/assert/translations/main.js index 9a5397c73..db005ce0a 100644 --- a/www/assert/translations/main.js +++ b/www/assert/translations/main.js @@ -1,8 +1,9 @@ define([ 'jquery', - '/common/cryptpad-common.js', + '/common/common-util.js', + '/customize/messages.js', '/customize/translations/messages.js', -], function ($, Cryptpad, English) { +], function ($, Util, Messages, English) { var $body = $('body'); @@ -11,38 +12,40 @@ define([ }; var todo = function (missing) { - var str = ""; - var need = 1; + var currentLang = ""; + var currentState = 1; if (missing.length) { $body.append(pre(missing.map(function (msg) { var res = ""; - var code = msg[0]; - var key = msg[1]; - var needed = msg[2]; + var lang = msg[0]; + var key = msg[1]; // Array + var state = msg[2]; // 0 === toDelete, 1 === missing, 2 === updated, 3 === invalid (wrong type) var value = msg[3] || '""'; - if (str !== code) { - if (str !== "") + if (currentLang !== lang) { + if (currentLang !== "") { res += '\n'; } - str = code; - res += '/*\n *\n * ' + code + '\n *\n */\n\n'; + currentLang = lang; + res += '/*\n *\n * ' + lang + '\n *\n */\n\n'; } - if (need !== needed) { - need = needed; - if (need === 0) + if (currentState !== state) { + currentState = state; + if (currentState === 0) { - res += '\n// TODO: These keys are not needed anymore and should be removed ('+ code + ')\n\n'; + res += '\n// TODO: These keys are not needed anymore and should be removed ('+ lang + ')\n\n'; } } - res += (need ? '' : '// ') + 'out.' + key + ' = ' + value + ';'; - if (need === 1) { - res += ' // ' + JSON.stringify(English[key]); - } else if (need === 2) { - res += ' // TODO: Key updated --> make sure the updated key "'+ value +'" exists and is translated before that one.'; + res += (currentState ? '' : '// ') + 'out.' + key.join('.') + ' = ' + value + ';'; + if (currentState === 1) { + res += ' // ' + JSON.stringify(Util.find(English, key)); + } else if (currentState === 2) { + res += ' // TODO: Key updated --> make sure the updated key "'+ value +'" exists and is translated before this one.'; + } else if (currentState === 3) { + res += ' // NOTE: this key has an invalid type! Original value: ' + JSON.stringify(Util.find(English, key)); } return res; }).join('\n'))); @@ -50,5 +53,5 @@ define([ $body.text('// All keys are present in all translations'); } }; - Cryptpad.Messages._checkTranslationState(todo); + Messages._checkTranslationState(todo); }); From c210f097c058fa891498a2018259115a52b82f4a Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 7 Feb 2018 17:14:15 +0100 Subject: [PATCH 15/45] Refactor login part two --- customize.dist/login.js | 81 +++++++++++++++++++++-------------------- 1 file changed, 41 insertions(+), 40 deletions(-) diff --git a/customize.dist/login.js b/customize.dist/login.js index eb229a6e9..02d8936a1 100644 --- a/customize.dist/login.js +++ b/customize.dist/login.js @@ -91,7 +91,7 @@ define([ return Object.keys(proxy).length === 0; }; - Exports.loginOrRegister = function (uname, passwd, isRegister, cb) { + Exports.loginOrRegister = function (uname, passwd, isRegister, shouldImport, cb) { if (typeof(cb) !== 'function') { return; } // Usernames are all lowercase. No going back on this one @@ -144,49 +144,54 @@ define([ return void cb('ALREADY_REGISTERED', res); } - setTimeout(function () { cb(void 0, res); }); + if (isRegister) { + var proxy = rt.proxy; + proxy.edPublic = res.edPublic; + proxy.edPrivate = res.edPrivate; + proxy.curvePublic = res.curvePublic; + proxy.curvePrivate = res.curvePrivate; + proxy.login_name = uname; + proxy[Constants.displayNameKey] = uname; + sessionStorage.createReadme = 1; + Feedback.send('REGISTRATION', true); + } else { + Feedback.send('LOGIN', true); + } + + if (shouldImport) { + sessionStorage.migrateAnonDrive = 1; + } + + Realtime.whenRealtimeSyncs(rt.realtime, function () { + LocalStore.login(res.userHash, res.userName, function () { + cb(void 0, res); + }); + }); }); }); }; + Exports.redirect = function () { + if (sessionStorage.redirectTo) { + var h = sessionStorage.redirectTo; + var parser = document.createElement('a'); + parser.href = h; + if (parser.origin === window.location.origin) { + delete sessionStorage.redirectTo; + window.location.href = h; + return; + } + } + window.location.href = '/drive/'; + }; Exports.loginOrRegisterUI = function (uname, passwd, isRegister, shouldImport, testing, test) { var hashing = true; var proceed = function (result) { - var proxy = result.proxy; - proxy.edPublic = result.edPublic; - proxy.edPrivate = result.edPrivate; - proxy.curvePublic = result.curvePublic; - proxy.curvePrivate = result.curvePrivate; - - if (isRegister) { - Feedback.send('REGISTRATION', true); - } else { - Feedback.send('LOGIN', true); - } - + hashing = false; + if (test && typeof test === "function" && test()) { return; } Realtime.whenRealtimeSyncs(result.realtime, function () { - try { - LocalStore.login(result.userHash, result.userName, function () { - hashing = false; - if (test && typeof test === "function" && test()) { console.log('testing'); - return; } - if (shouldImport) { - sessionStorage.migrateAnonDrive = 1; - } - if (sessionStorage.redirectTo) { - var h = sessionStorage.redirectTo; - var parser = document.createElement('a'); - parser.href = h; - if (parser.origin === window.location.origin) { - delete sessionStorage.redirectTo; - window.location.href = h; - return; - } - } - window.location.href = '/drive/'; - }); - } catch (e) { console.error(e); } + Exports.redirect(); }); }; @@ -200,7 +205,7 @@ define([ // We need a setTimeout(cb, 0) otherwise the loading screen is only displayed // after hashing the password window.setTimeout(function () { - Exports.loginOrRegister(uname, passwd, isRegister, function (err, result) { + Exports.loginOrRegister(uname, passwd, isRegister, shouldImport, function (err, result) { var proxy; if (result) { proxy = result.proxy; } @@ -261,10 +266,6 @@ define([ if (testing) { return void proceed(result); } - proxy.login_name = uname; - proxy[Constants.displayNameKey] = uname; - sessionStorage.createReadme = 1; - proceed(result); }); }, 0); From f8399eaaa62e780691023d1d5352468848eaa66d Mon Sep 17 00:00:00 2001 From: ansuz Date: Thu, 8 Feb 2018 11:08:10 +0100 Subject: [PATCH 16/45] avoid concatenating 'undefined' in drive interface --- www/drive/inner.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/www/drive/inner.js b/www/drive/inner.js index 11d96df42..be4909415 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -2673,11 +2673,14 @@ define([ var data = JSON.parse(JSON.stringify(filesOp.getFileData(el))); if (!data || !data.href) { return void cb('INVALID_FILE'); } data.href = base + data.href; + + var roUrl; if (ro) { data.roHref = data.href; delete data.href; } else { - data.roHref = base + getReadOnlyUrl(el); + roUrl = getReadOnlyUrl(el); + if (roUrl) { data.roHref = base + roUrl; } } UIElements.getProperties(common, data, cb); From 1191c14a2989ecba2e2cb51557045972964caf6e Mon Sep 17 00:00:00 2001 From: ansuz Date: Thu, 8 Feb 2018 11:11:51 +0100 Subject: [PATCH 17/45] lint compliance --- customize.dist/messages.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/customize.dist/messages.js b/customize.dist/messages.js index 16128df9c..bffb95654 100755 --- a/customize.dist/messages.js +++ b/customize.dist/messages.js @@ -101,8 +101,8 @@ define(req, function(Util, Default, Language) { checkInObject(Default, translation, []); // Push the removals at the end missing.sort(function (a, b) { - if (a[2] === 0 && b[2] !== 0) return 1; - if (a[2] !== 0 && b[2] === 0) return -1; + if (a[2] === 0 && b[2] !== 0) { return 1; } + if (a[2] !== 0 && b[2] === 0) { return -1; } return 0; }); Array.prototype.push.apply(allMissing, missing); // Destructive concat From b5bb83b70092e48a7ef3922e04b0a1ea8e0e36ad Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 8 Feb 2018 12:24:19 +0100 Subject: [PATCH 18/45] lint compliance --- customize.dist/messages.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/customize.dist/messages.js b/customize.dist/messages.js index 16128df9c..bffb95654 100755 --- a/customize.dist/messages.js +++ b/customize.dist/messages.js @@ -101,8 +101,8 @@ define(req, function(Util, Default, Language) { checkInObject(Default, translation, []); // Push the removals at the end missing.sort(function (a, b) { - if (a[2] === 0 && b[2] !== 0) return 1; - if (a[2] !== 0 && b[2] === 0) return -1; + if (a[2] === 0 && b[2] !== 0) { return 1; } + if (a[2] !== 0 && b[2] === 0) { return -1; } return 0; }); Array.prototype.push.apply(allMissing, missing); // Destructive concat From f18917f0ac898f3dc7e3d46c5068ea0104d398b1 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 8 Feb 2018 12:24:34 +0100 Subject: [PATCH 19/45] Fix race condition at registration --- customize.dist/login.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/customize.dist/login.js b/customize.dist/login.js index 02d8936a1..5511637e8 100644 --- a/customize.dist/login.js +++ b/customize.dist/login.js @@ -80,7 +80,7 @@ define([ var rt = opt.rt = Listmap.create(config); rt.proxy .on('ready', function () { - cb(void 0, rt); + setTimeout(function () { cb(void 0, rt); }); }) .on('disconnect', function (info) { cb('E_DISCONNECT', info); @@ -162,9 +162,16 @@ define([ sessionStorage.migrateAnonDrive = 1; } - Realtime.whenRealtimeSyncs(rt.realtime, function () { - LocalStore.login(res.userHash, res.userName, function () { - cb(void 0, res); + // We have to call whenRealtimeSyncs asynchronously here because in the current + // version of listmap, onLocal calls `chainpad.contentUpdate(newValue)` + // asynchronously. + // The following setTimeout is here to make sure whenRealtimeSyncs is called after + // `contentUpdate` so that we have an update userDoc in chainpad. + setTimeout(function () { + Realtime.whenRealtimeSyncs(rt.realtime, function () { + LocalStore.login(res.userHash, res.userName, function () { + setTimeout(function () { cb(void 0, res); }); + }); }); }); }); From 81ea3b14d7b12cb909d77533f0666d773170109f Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 8 Feb 2018 12:28:53 +0100 Subject: [PATCH 20/45] Fix usage bar position in the settings app --- customize.dist/src/less2/include/limit-bar.less | 1 + 1 file changed, 1 insertion(+) diff --git a/customize.dist/src/less2/include/limit-bar.less b/customize.dist/src/less2/include/limit-bar.less index 4c11183c4..b2ea5f230 100644 --- a/customize.dist/src/less2/include/limit-bar.less +++ b/customize.dist/src/less2/include/limit-bar.less @@ -29,6 +29,7 @@ background: blue; position: absolute; left: 0; + top: 0; z-index: 1; // .usage &.cp-limit-usage-normal { background: @colortheme_green; From ea7a3c75faae7f939d472e10870e1eba01dc9b05 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 8 Feb 2018 14:06:40 +0100 Subject: [PATCH 21/45] Fix hidden content in the pad creation screen with smaller screen or zoom --- customize.dist/src/less2/include/creation.less | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/customize.dist/src/less2/include/creation.less b/customize.dist/src/less2/include/creation.less index 17d72f28f..cf2bc6b88 100644 --- a/customize.dist/src/less2/include/creation.less +++ b/customize.dist/src/less2/include/creation.less @@ -12,15 +12,16 @@ background: @colortheme_loading-bg; color: @colortheme_loading-color; display: flex; - align-items: center; + flex-flow: column; /* we need column so that the child can shrink vertically */ + justify-content: center; width: 100%; height: 100%; overflow: auto; - @media screen and (max-height: 600px), screen and (max-width: 500px) { - align-items: baseline; - } } #cp-creation { + flex: 0 1 auto; /* allows shrink */ + min-height: 0; + overflow: auto; text-align: center; font: @colortheme_app-font; width: 100%; @@ -43,6 +44,7 @@ } } .cp-creation-help-container { + width: 100%; display: flex; justify-content: space-between; p { @@ -56,7 +58,7 @@ @media screen and (max-width: 500px) { width: ~"calc(100% - 30px)"; } - @media screen and (max-height: 600px), screen and (max-width: 500px) { + @media screen and (max-height: 800px), screen and (max-width: 500px) { h2 .cp-creation-help { display: inline; } From 0b022af5db9e39138b6f63a6d7e7b944c58e346f Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 9 Feb 2018 15:45:47 +0100 Subject: [PATCH 22/45] Fix style issues in IE and mobiles --- customize.dist/src/less2/include/alertify.less | 4 ++-- customize.dist/src/less2/include/toolbar.less | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/customize.dist/src/less2/include/alertify.less b/customize.dist/src/less2/include/alertify.less index 194b780d1..b845a5d6f 100644 --- a/customize.dist/src/less2/include/alertify.less +++ b/customize.dist/src/less2/include/alertify.less @@ -139,7 +139,7 @@ > * { width: 100%; - min-width: 300px; + min-width: 260px; max-width: 500px; margin: 0 auto; text-align: left; @@ -184,7 +184,7 @@ } } .alertify-tabs-contents { - flex: 1; + flex: 1 1 auto; min-height: 0; & > div { max-height: 100%; diff --git a/customize.dist/src/less2/include/toolbar.less b/customize.dist/src/less2/include/toolbar.less index 5bdc47f43..435476090 100644 --- a/customize.dist/src/less2/include/toolbar.less +++ b/customize.dist/src/less2/include/toolbar.less @@ -131,6 +131,7 @@ white-space: nowrap; display: flex; flex-flow: column; + height: 100%; .cp-toolbar-userlist-name { flex: 1; overflow: hidden; @@ -759,7 +760,7 @@ display: inline-flex; align-items: center; max-width: 100%; - flex: 1; + flex: 1 1 auto; //margin-bottom: -1px; .cp-toolbar-users { pre { From 3bb6d5a83cb6b6c4f49dc4de6e076ee23ba5ada0 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 9 Feb 2018 15:47:29 +0100 Subject: [PATCH 23/45] Remove full history timeout --- www/common/sframe-common-history.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/www/common/sframe-common-history.js b/www/common/sframe-common-history.js index dd80cf884..27503d07a 100644 --- a/www/common/sframe-common-history.js +++ b/www/common/sframe-common-history.js @@ -44,12 +44,12 @@ define([ History.readOnly = common.getMetadataMgr().getPrivateData().readOnly; - var to = window.setTimeout(function () { + /*var to = window.setTimeout(function () { cb('[GET_FULL_HISTORY_TIMEOUT]'); - }, 30000); + }, 30000);*/ common.getFullHistory(realtime, function () { - window.clearTimeout(to); + //window.clearTimeout(to); cb(null, realtime); }); }; From 223ed73ae1d11537c4b10e7f1e36fdad0d16d142 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 9 Feb 2018 15:47:52 +0100 Subject: [PATCH 24/45] French translation for the pad creation screen --- customize.dist/translations/messages.fr.js | 16 ++++++++-------- customize.dist/translations/messages.js | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index 2c4bb6ec4..3f90e4d84 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -847,20 +847,20 @@ define(function () { out.feedback_optout = "Si vous le souhaitez, vous pouvez désactiver ces requêtes en vous rendant dans votre page de préférences, où vous trouverez une case à cocher pour désactiver le retour d'expérience."; // Creation page - out.creation_404 = "Le pad auquel vous souhaitez accéder n'existe plus. Vous pouvez créer un nouveau pad en utilisant le formulaire suivant."; + out.creation_404 = "Ce pad n'existe plus. Vous pouvez créer un nouveau pad en utilisant le formulaire suivant."; out.creation_ownedTitle = "Type de pad"; out.creation_ownedTrue = "Pad possédé"; out.creation_ownedFalse = "Pad ouvert"; - out.creation_owned1 = "Un pad possédé est un pad que vous pouvez supprimer du serveur à n'importe quel moment depuis votre CryptDrive. Une fois supprimé, personne d'autre ne peut y accéder, même si le pad est stocké dans un autre CryptDrive."; - out.creation_owned2 = "Un pad ouvert n'a pas de propriétaire et ne peut donc pas être supprimé du serveur par un utilisateur. Il pourra tout de même être supprimé automatiquement si sa date d'expiration est dépassée."; + out.creation_owned1 = "Un pad possédé peut être supprimé du serveur à tout moment quand son propriétaire le souhaite. Une fois supprimé, il disparaît du CryptDrive des autres utilisateurs."; + out.creation_owned2 = "Un pad ouvert n'a pas de propriétaire et ne peut donc pas être supprimé du serveur à moins d'avoir dépassé sa date d'expiration."; out.creation_expireTitle = "Durée de vie"; out.creation_expireTrue = "Ajouter durée de vie"; - out.creation_expireFalse = "Illimitée"; - out.creation_expireHours = "Heures"; - out.creation_expireDays = "Jours"; + out.creation_expireFalse = "Illimité"; + out.creation_expireHours = "Heure(s)"; + out.creation_expireDays = "Jour(s)"; out.creation_expireMonths = "Mois"; - out.creation_expire1 = "Par défault, un pad stocké dans le CryptDrive d'un utilisateur enregistré ne sera jamais supprimé du serveur, même s'il est inactif (à moins qu'il possède un propriétaire souhaitement le supprimer)."; - out.creation_expire2 = "Si vous le souhaitez, vous pouvez ajouter une durée de vie au pad afin d'être sûr qu'il soit supprimé du serveur, de manière permanente, à la date voulue."; + out.creation_expire1 = "Un pad illimité ne sera pas supprimé du serveur à moins que son propriétaire ne le décide."; + out.creation_expire2 = "Un pad expirant a une durée de vie définie, après laquelle il sera supprimé automatiquement du serveur et du CryptDrive des utilisateurs."; out.creation_createTitle = "Créer un pad"; out.creation_createFromTemplate = "Depuis un modèle"; out.creation_createFromScratch = "Nouveau pad vide"; diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index cf689e9b0..16c18d678 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -861,7 +861,7 @@ define(function () { out.feedback_optout = "If you would like to opt out, visit your user settings page, where you'll find a checkbox to enable or disable user feedback"; // Creation page - out.creation_404 = "This pad not longer exists. Use the following form to create a new pad"; + out.creation_404 = "This pad not longer exists. Use the following form to create a new pad."; out.creation_ownedTitle = "Type of pad"; out.creation_ownedTrue = "Owned pad"; out.creation_ownedFalse = "Open pad"; @@ -886,7 +886,7 @@ define(function () { out.creation_expiration = "Expiration time"; out.creation_propertiesTitle = "Availability"; out.creation_appMenuName = "Advanced mode (Ctrl + E)"; - out.creation_newPadModalDescription = "Click on a pad type to create it. You can check the box if you want to display the pad creation screen (for owned pad, expiration pad, etc.)."; + out.creation_newPadModalDescription = "Click on a pad type to create it. You can check the box if you want to display the pad creation screen (for owned pad, expiring pad, etc.)."; out.creation_newPadModalAdvanced = "Display the pad creation screen"; // New share modal From 3b46578621f19c46619ffa6e0969d2602452dd99 Mon Sep 17 00:00:00 2001 From: Nicolas Parquet Date: Fri, 9 Feb 2018 16:17:40 +0100 Subject: [PATCH 25/45] Fix main pages customization Co-authored-by: fvn-linagora --- server.js | 1 + 1 file changed, 1 insertion(+) diff --git a/server.js b/server.js index e803a829e..a7830cbaf 100644 --- a/server.js +++ b/server.js @@ -117,6 +117,7 @@ Fs.exists(__dirname + "/customize", function (e) { var mainPages = config.mainPages || ['index', 'privacy', 'terms', 'about', 'contact']; var mainPagePattern = new RegExp('^\/(' + mainPages.join('|') + ').html$'); +app.get(mainPagePattern, Express.static(__dirname + '/customize')); app.get(mainPagePattern, Express.static(__dirname + '/customize.dist')); app.use("/blob", Express.static(Path.join(__dirname, (config.blobPath || './blob')), { From ac527c6411dc546a94f39dc5953ccfb1922121f1 Mon Sep 17 00:00:00 2001 From: Fabien Vignon Date: Fri, 9 Feb 2018 11:57:45 +0100 Subject: [PATCH 26/45] add a logout page allows destroying user session with a minimum number of loaded dependenciesi. Can be Used for SSO forwards (logout). Co-authored-by: Nicolas PARQUET --- www/logout/index.html | 16 ++++++++++++++++ www/logout/main.js | 5 +++++ 2 files changed, 21 insertions(+) create mode 100644 www/logout/index.html create mode 100644 www/logout/main.js diff --git a/www/logout/index.html b/www/logout/index.html new file mode 100644 index 000000000..365c4bbe2 --- /dev/null +++ b/www/logout/index.html @@ -0,0 +1,16 @@ + + + + + CryptPad: Zero Knowledge, Collaborative Real Time Editing + + + + + + + + diff --git a/www/logout/main.js b/www/logout/main.js new file mode 100644 index 000000000..acd8e8b02 --- /dev/null +++ b/www/logout/main.js @@ -0,0 +1,5 @@ +define(['/bower_components/localforage/dist/localforage.min.js'], function (localForage) { + localForage.clear(); + sessionStorage.clear(); + localStorage.clear(); +}); From 728a6a868d424129a13b3c1028e82dfc7a7d4068 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 13 Feb 2018 18:20:13 +0100 Subject: [PATCH 27/45] Manage expired channels --- customize.dist/translations/messages.fr.js | 3 +++ customize.dist/translations/messages.js | 3 +++ expire-channels.js | 21 ++++++++++++--- www/common/common-interface.js | 13 +++++++-- www/common/cryptpad-common.js | 4 +++ www/common/metadata-manager.js | 6 +++++ www/common/outer/async-store.js | 3 +++ www/common/outer/chainpad-netflux-worker.js | 29 ++++++++++++++++++--- www/common/sframe-app-framework.js | 26 ++++++++++++++++-- www/common/sframe-chainpad-netflux-inner.js | 6 +++++ www/common/sframe-chainpad-netflux-outer.js | 4 +++ www/common/sframe-protocol.js | 2 ++ www/common/toolbar3.js | 11 ++++++++ 13 files changed, 120 insertions(+), 11 deletions(-) diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index 3f90e4d84..87caa929f 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -31,12 +31,15 @@ define(function () { out.wrongApp = "Impossible d'afficher le contenu de ce document temps-réel dans votre navigateur. Vous pouvez essayer de recharger la page."; out.padNotPinned = 'Ce pad va expirer dans 3 mois, {0}connectez-vous{1} ou {2}enregistrez-vous{3} pour le préserver.'; out.anonymousStoreDisabled = "L'administrateur de cette instance de CryptPad a désactivé le drive pour les utilisateurs non enregistrés. Vous devez vous connecter pour pouvoir utiliser CryptDrive."; + out.expiredError = "Ce pad a atteint sa date d'expiration est n'est donc plus disponible."; + out.expiredErrorCopy = ' Vous pouvez toujours copier son contenu ailleurs en appuyant sur Échap.
Dés que vous aurez quitté la page, il sera impossible de le récupérer.'; out.loading = "Chargement..."; out.error = "Erreur"; out.saved = "Enregistré"; out.synced = "Tout est enregistré"; out.deleted = "Pad supprimé de votre CryptDrive"; + out.deletedFromServer = "Pad supprimé du serveur"; out.realtime_unrecoverableError = "Le moteur temps-réel a rencontré une erreur critique. Cliquez sur OK pour recharger la page."; diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index 16c18d678..367e704c9 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -32,12 +32,15 @@ define(function () { out.wrongApp = "Unable to display the content of that realtime session in your browser. Please try to reload that page."; out.padNotPinned = 'This pad will expire in 3 months, {0}login{1} or {2}register{3} to preserve it.'; out.anonymousStoreDisabled = "The webmaster of this CryptPad instance has disabled the store for anonymous users. You have to log in to be able to use CryptDrive."; + out.expiredError = 'This pad has reached its expiration time and is no longer available.'; + out.expiredErrorCopy = ' You can still copy the content to another location by pressing Esc.
Once you leave this page, it will disappear forever!'; out.loading = "Loading..."; out.error = "Error"; out.saved = "Saved"; out.synced = "Everything is saved"; out.deleted = "Pad deleted from your CryptDrive"; + out.deletedFromServer = "Pad deleted from the server"; out.realtime_unrecoverableError = "The realtime engine has encountered an unrecoverable error. Click OK to reload."; diff --git a/expire-channels.js b/expire-channels.js index d64f3c349..fda2c20bb 100644 --- a/expire-channels.js +++ b/expire-channels.js @@ -2,12 +2,21 @@ var Fs = require("fs"); var Path = require("path"); var nThen = require("nthen"); -var config = require("./config"); +var config; +try { + config = require('./config'); +} catch (e) { + console.log("You can customize the configuration by copying config.example.js to config.js"); + config = require('./config.example'); +} + +var FileStorage = require(config.storage || './storage/file'); var root = Path.resolve(config.taskPath || './tasks'); var dirs; var nt; +var store; var queue = function (f) { nt = nt.nThen(f); @@ -41,17 +50,17 @@ var handleTask = function (str, path, cb) { return cb(); } - nThen(function () { + nThen(function (waitFor) { switch (command) { case 'EXPIRE': console.log("expiring: %s", args[0]); - // TODO actually remove the file... + store.removeChannel(args[0], waitFor()); break; default: console.log("unknown command", command); } }).nThen(function () { - // remove the file... + // remove the task file... Fs.unlink(path, function (err) { if (err) { console.error(err); } cb(); @@ -64,6 +73,10 @@ nt = nThen(function (w) { if (e) { throw e; } dirs = list; })); +}).nThen(function (waitFor) { + FileStorage.create(config, waitFor(function (_store) { + store = _store; + })); }).nThen(function () { dirs.forEach(function (dir) { queue(function (w) { diff --git a/www/common/common-interface.js b/www/common/common-interface.js index 248bd5e70..d0c0e1961 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -601,11 +601,20 @@ define([ }, 3750); // jquery.fadeout can get stuck }; - UI.errorLoadingScreen = function (error, transparent) { - if (!$('#' + LOADING).is(':visible')) { UI.addLoadingScreen({hideTips: true}); } + UI.errorLoadingScreen = function (error, transparent, exitable) { + if (!$('#' + LOADING).is(':visible') || $('#' + LOADING).hasClass('cp-loading-hidden')) { + UI.addLoadingScreen({hideTips: true}); + } $('.cp-loading-spinner-container').hide(); + $('#cp-loading-tip').remove(); if (transparent) { $('#' + LOADING).css('opacity', 0.8); } $('#' + LOADING).find('p').html(error || Messages.error); + if (exitable) { + $(window).focus(); + $(window).keydown(function (e) { + if (e.which === 27) { $('#' + LOADING).hide(); } + }); + } }; var $defaultIcon = $('', {"class": "fa fa-file-text-o"}); diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index fcfab50f0..5bb00e6f3 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -538,6 +538,7 @@ define([ pad.onJoinEvent = Util.mkEvent(); pad.onLeaveEvent = Util.mkEvent(); pad.onDisconnectEvent = Util.mkEvent(); + pad.onErrorEvent = Util.mkEvent(); common.getFullHistory = function (data, cb) { postMessage("GET_FULL_HISTORY", data, cb); @@ -679,6 +680,9 @@ define([ case 'PAD_DISCONNECT': { common.padRpc.onDisconnectEvent.fire(data); break; } + case 'PAD_ERROR': { + common.padRpc.onErrorEvent.fire(data); break; + } // Drive case 'DRIVE_LOG': { common.drive.onLog.fire(data); break; diff --git a/www/common/metadata-manager.js b/www/common/metadata-manager.js index 0ec1c07ea..dfc855863 100644 --- a/www/common/metadata-manager.js +++ b/www/common/metadata-manager.js @@ -115,6 +115,12 @@ define(['json.sortify'], function (Sortify) { if (!meta.user) { return; } change(true); }); + sframeChan.on('EV_RT_ERROR', function (err) { + if (err.type !== 'EEXPIRED' && err.type !== 'EDELETED') { return; } + members = []; + if (!meta.user) { return; } + change(true); + }); return Object.freeze({ updateMetadata: function (m) { diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 3b61acaa5..1ca46e7f1 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -815,6 +815,9 @@ define([ onDisconnect: function () { postMessage("PAD_DISCONNECT"); }, // post EV_PAD_DISCONNECT + onError: function (err) { + postMessage("PAD_ERROR", err); + }, // post EV_PAD_ERROR channel: data.channel, validateKey: data.validateKey, owners: data.owners, diff --git a/www/common/outer/chainpad-netflux-worker.js b/www/common/outer/chainpad-netflux-worker.js index 5c07cd93f..adb6242de 100644 --- a/www/common/outer/chainpad-netflux-worker.js +++ b/www/common/outer/chainpad-netflux-worker.js @@ -33,6 +33,7 @@ define([], function () { var onLeave = conf.onLeave; var onReady = conf.onReady; var onDisconnect = conf.onDisconnect; + var onError = conf.onError; var owners = conf.owners; var password = conf.password; var expire = conf.expire; @@ -44,6 +45,17 @@ define([], function () { var messageFromOuter = function () {}; + var error = function (err, wc) { + if (onError) { + onError({ + type: err, + loaded: !initializing + }); + if (wc && (err === "EEXPIRED" || err === "EDELETED")) { wc.leave(); } + } + else { console.error(err); } + }; + 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. @@ -96,11 +108,17 @@ define([], function () { if (peer === hk) { // if the peer is the 'history keeper', extract their message var parsed1 = JSON.parse(msg); + // First check if it is an error message (EXPIRED/DELETED) + if (parsed1.channel === wc.id && parsed1.error) { + return void error(parsed1.error, wc); + } + msg = parsed1[4]; // Check that this is a message for our channel if (parsed1[3] !== wc.id) { return; } } + lastKnownHash = msg.slice(0,64); var message = msgIn(peer, msg); @@ -177,7 +195,12 @@ define([], function () { }; var msg = ['GET_HISTORY', wc.id, cfg]; // Add the validateKey if we are the channel creator and we have a validateKey - if (hk) { network.sendto(hk, JSON.stringify(msg)); } + if (hk) { + network.sendto(hk, JSON.stringify(msg)).then(function () { + }, function (err) { + console.error(err); + }); + } } else { onRdy(); } @@ -204,8 +227,8 @@ define([], function () { // join the netflux network, promise to handle opening of the channel network.join(channel || null).then(function(wc) { onOpen(wc, network, firstConnection); - }, function(error) { - console.error(error); + }, function(err) { + console.error(err); }); }; diff --git a/www/common/sframe-app-framework.js b/www/common/sframe-app-framework.js index 9c9c1b786..2e51776d6 100644 --- a/www/common/sframe-app-framework.js +++ b/www/common/sframe-app-framework.js @@ -43,6 +43,7 @@ define([ var STATE = Object.freeze({ DISCONNECTED: 'DISCONNECTED', FORGOTTEN: 'FORGOTTEN', + DELETED: 'DELETED', INFINITE_SPINNER: 'INFINITE_SPINNER', INITIALIZING: 'INITIALIZING', HISTORY_MODE: 'HISTORY_MODE', @@ -119,8 +120,9 @@ define([ var stateChange = function (newState) { var wasEditable = (state === STATE.READY); + if (state === STATE.DELETED) { return; } if (state === STATE.INFINITE_SPINNER && newState !== STATE.READY) { return; } - if (newState === STATE.INFINITE_SPINNER) { + if (newState === STATE.INFINITE_SPINNER || newState === STATE.DELETED) { state = newState; } else if (state === STATE.DISCONNECTED && newState !== STATE.INITIALIZING) { throw new Error("Cannot transition from DISCONNECTED to " + newState); @@ -149,6 +151,10 @@ define([ evStart.reg(function () { toolbar.forgotten(); }); break; } + case STATE.DELETED: { + evStart.reg(function () { toolbar.deleted(); }); + break; + } default: } if (wasEditable !== (state === STATE.READY)) { @@ -257,6 +263,7 @@ define([ var onReady = function () { var newContentStr = cpNfInner.chainpad.getUserDoc(); + if (state === STATE.DELETED) { return; } var newPad = false; if (newContentStr === '') { newPad = true; } @@ -316,6 +323,7 @@ define([ } }; var onConnectionChange = function (info) { + if (state === STATE.DELETED) { return; } stateChange(info.state ? STATE.INITIALIZING : STATE.DISCONNECTED); if (info.state) { UI.findOKButton().click(); @@ -324,6 +332,18 @@ define([ } }; + var onError = function (err) { + stateChange(STATE.DELETED); + var msg = err.type; + if (err.type === 'EEXPIRED') { + msg = Messages.expiredError; + if (err.loaded) { + msg += Messages.expiredErrorCopy; + } + } + UI.errorLoadingScreen(msg, true, true); + }; + var setFileExporter = function (extension, fe, async) { var $export = common.createButton('export', true, {}, function () { var ext = (typeof(extension) === 'function') ? extension() : extension; @@ -441,7 +461,8 @@ define([ onLocal: onLocal, onInit: function () { stateChange(STATE.INITIALIZING); }, onReady: function () { evStart.reg(onReady); }, - onConnectionChange: onConnectionChange + onConnectionChange: onConnectionChange, + onError: onError }); var privReady = Util.once(waitFor()); @@ -457,6 +478,7 @@ define([ var infiniteSpinnerModal = false; window.setInterval(function () { if (state === STATE.DISCONNECTED) { return; } + if (state === STATE.DELETED) { return; } var l; try { l = cpNfInner.chainpad.getLag(); diff --git a/www/common/sframe-chainpad-netflux-inner.js b/www/common/sframe-chainpad-netflux-inner.js index 9467d8dae..5b8f883c5 100644 --- a/www/common/sframe-chainpad-netflux-inner.js +++ b/www/common/sframe-chainpad-netflux-inner.js @@ -34,6 +34,7 @@ define([ var onLocal = config.onLocal || function () { }; var setMyID = config.setMyID || function () { }; var onReady = config.onReady || function () { }; + var onError = config.onError || function () { }; var userName = config.userName; var initialState = config.initialState; if (config.transformFunction) { throw new Error("transformFunction is nolonger allowed"); } @@ -83,6 +84,11 @@ define([ chainpad.abort(); onConnectionChange({ state: false }); }); + sframeChan.on('EV_RT_ERROR', function (err) { + isReady = false; + chainpad.abort(); + onError(err); + }); sframeChan.on('EV_RT_CONNECT', function (content) { //content.members.forEach(userList.onJoin); isReady = false; diff --git a/www/common/sframe-chainpad-netflux-outer.js b/www/common/sframe-chainpad-netflux-outer.js index fedd90b46..2d592b65d 100644 --- a/www/common/sframe-chainpad-netflux-outer.js +++ b/www/common/sframe-chainpad-netflux-outer.js @@ -102,6 +102,10 @@ define([], function () { sframeChan.event('EV_RT_DISCONNECT'); }); + padRpc.onErrorEvent.reg(function (err) { + sframeChan.event('EV_RT_ERROR', err); + }); + // join the netflux network, promise to handle opening of the channel padRpc.joinPad({ channel: channel || null, diff --git a/www/common/sframe-protocol.js b/www/common/sframe-protocol.js index 46aa93348..b7fa01b28 100644 --- a/www/common/sframe-protocol.js +++ b/www/common/sframe-protocol.js @@ -31,6 +31,8 @@ define({ 'EV_RT_CONNECT': true, // Called after the history is finished synchronizing, no arguments. 'EV_RT_READY': true, + // Called when the server returns an error in a pad (EEXPIRED, EDELETED). + 'EV_RT_ERROR': true, // Called from both outside and inside, argument is a (string) chainpad message. 'Q_RT_MESSAGE': true, diff --git a/www/common/toolbar3.js b/www/common/toolbar3.js index a82e1ff6e..cb1bb764c 100644 --- a/www/common/toolbar3.js +++ b/www/common/toolbar3.js @@ -709,6 +709,7 @@ define([ typing = 1; $spin.text(Messages.typing); $spin.interval = window.setInterval(function () { + if (toolbar.isErrorState) { return; } var dots = Array(typing+1).join('.'); $spin.text(Messages.typing + dots); typing++; @@ -718,6 +719,7 @@ define([ var onSynced = function () { if ($spin.timeout) { clearTimeout($spin.timeout); } $spin.timeout = setTimeout(function () { + if (toolbar.isErrorState) { return; } window.clearInterval($spin.interval); typing = -1; $spin.text(Messages.saved); @@ -1094,6 +1096,15 @@ define([ } }; + // When the pad is deleted from the server + toolbar.deleted = function (/*userId*/) { + toolbar.isErrorState = true; + toolbar.connected = false; + if (toolbar.spinner) { + toolbar.spinner.text(Messages.deletedFromServer); + } + }; + // On log out, remove permanently the realtime elements of the toolbar Common.onLogout(function () { failed(); From e83e589cf0e142cf5489dec75f8557b059dd5211 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 14 Feb 2018 19:41:07 +0100 Subject: [PATCH 28/45] Kick from pad when an owned channel is deleted + whiteboard and poll --- customize.dist/translations/messages.fr.js | 1 + customize.dist/translations/messages.js | 1 + rpc.js | 2 +- www/common/common-ui-elements.js | 2 - www/common/outer/async-store.js | 2 +- www/common/outer/userObject.js | 14 ++++--- www/common/pinpad.js | 4 +- www/common/sframe-app-framework.js | 9 ++++- www/common/toolbar3.js | 1 + www/common/userObject.js | 7 ++-- www/drive/inner.js | 6 ++- www/poll/app-poll.less | 2 + www/poll/inner.js | 41 +++++++++++++++++++-- www/poll/main.js | 4 +- www/whiteboard/app-whiteboard.less | 2 + www/whiteboard/inner.js | 43 +++++++++++++++++++++- www/whiteboard/main.js | 4 +- 17 files changed, 122 insertions(+), 23 deletions(-) diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index 87caa929f..2d21cf9a6 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -33,6 +33,7 @@ define(function () { out.anonymousStoreDisabled = "L'administrateur de cette instance de CryptPad a désactivé le drive pour les utilisateurs non enregistrés. Vous devez vous connecter pour pouvoir utiliser CryptDrive."; out.expiredError = "Ce pad a atteint sa date d'expiration est n'est donc plus disponible."; out.expiredErrorCopy = ' Vous pouvez toujours copier son contenu ailleurs en appuyant sur Échap.
Dés que vous aurez quitté la page, il sera impossible de le récupérer.'; + out.deletedError = 'Ce pad a été supprimé par son propriétaire et n\'est donc plus disponible.'; out.loading = "Chargement..."; out.error = "Erreur"; diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index 367e704c9..0113dbd84 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -34,6 +34,7 @@ define(function () { out.anonymousStoreDisabled = "The webmaster of this CryptPad instance has disabled the store for anonymous users. You have to log in to be able to use CryptDrive."; out.expiredError = 'This pad has reached its expiration time and is no longer available.'; out.expiredErrorCopy = ' You can still copy the content to another location by pressing Esc.
Once you leave this page, it will disappear forever!'; + out.deletedError = 'This pad has been deleted by its owner and is no longer available.'; out.loading = "Loading..."; out.error = "Error"; diff --git a/rpc.js b/rpc.js index 94d4d4e7e..8148c189c 100644 --- a/rpc.js +++ b/rpc.js @@ -1348,7 +1348,7 @@ RPC.create = function ( case 'REMOVE_OWNED_CHANNEL': return void removeOwnedChannel(Env, msg[1], publicKey, function (e, response) { if (e) { return void Respond(e); } - Respond(void 0, response); + Respond(void 0, "OK"); }); // restricted to privileged users... case 'UPLOAD': diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 432061d2e..e0e5f1ff7 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -1655,8 +1655,6 @@ define([ var metadataMgr = common.getMetadataMgr(); var type = metadataMgr.getMetadataLazy().type; - // XXX check text for pad creation screen + translate it in French - var $body = $('body'); var $creationContainer = $('
', { id: 'cp-creation-container' }).appendTo($body); var $creation = $('
', { id: 'cp-creation' }).appendTo($creationContainer); diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 1ca46e7f1..ae7d752b6 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -897,7 +897,7 @@ define([ case 'addFolder': store.userObject.addFolder(data.path, data.name, cb); break; case 'delete': - store.userObject.delete(data.paths, cb, data.nocheck); break; + store.userObject.delete(data.paths, cb, data.nocheck, data.isOwnPadRemoved); break; case 'emptyTrash': store.userObject.emptyTrash(cb); break; case 'rename': diff --git a/www/common/outer/userObject.js b/www/common/outer/userObject.js index 3293dcb36..70a8445f7 100644 --- a/www/common/outer/userObject.js +++ b/www/common/outer/userObject.js @@ -85,8 +85,11 @@ define([ delete files[FILES_DATA][id]; }; - exp.checkDeletedFiles = function () { - // Nothing in OLD_FILES_DATA for workgroups + // Find files in FILES_DATA that are not anymore in the drive, and remove them from + // FILES_DATA. If there are owned pads, remove them from server too, unless the flag tells + // us they're already removed + exp.checkDeletedFiles = function (isOwnPadRemoved) { + // Nothing in FILES_DATA for workgroups if (workgroup || (!loggedIn && !config.testMode)) { return; } var filesList = exp.getFiles([ROOT, 'hrefArray', TRASH]); @@ -96,7 +99,8 @@ define([ var fd = exp.getFileData(id); var channelId = fd && fd.href && Hash.hrefToHexChannelId(fd.href); // If trying to remove an owned pad, remove it from server also - if (fd.owners && fd.owners.indexOf(edPublic) !== -1 && channelId) { + if (!isOwnPadRemoved && + fd.owners && fd.owners.indexOf(edPublic) !== -1 && channelId) { removeOwnedChannel(channelId, function (obj) { if (obj && obj.error) { console.error(obj.error); } }); @@ -123,7 +127,7 @@ define([ files[TRASH][obj.name].splice(idx, 1); }); }; - exp.deleteMultiplePermanently = function (paths, nocheck) { + exp.deleteMultiplePermanently = function (paths, nocheck, isOwnPadRemoved) { var hrefPaths = paths.filter(function(x) { return exp.isPathIn(x, ['hrefArray']); }); var rootPaths = paths.filter(function(x) { return exp.isPathIn(x, [ROOT]); }); var trashPaths = paths.filter(function(x) { return exp.isPathIn(x, [TRASH]); }); @@ -179,7 +183,7 @@ define([ // In some cases, we want to remove pads from a location without removing them from // OLD_FILES_DATA (replaceHref) - if (!nocheck) { exp.checkDeletedFiles(); } + if (!nocheck) { exp.checkDeletedFiles(isOwnPadRemoved); } }; // Move diff --git a/www/common/pinpad.js b/www/common/pinpad.js index 0bbaddd37..f38d7fc57 100644 --- a/www/common/pinpad.js +++ b/www/common/pinpad.js @@ -157,8 +157,8 @@ define([ } rpc.send('REMOVE_OWNED_CHANNEL', channel, function (e, response) { if (e) { return void cb(e); } - if (response && response.length) { - cb(void 0, response[0]); // I haven't tested this... + if (response && response.length && response[0] === "OK") { + cb(); } else { cb('INVALID_RESPONSE'); } diff --git a/www/common/sframe-app-framework.js b/www/common/sframe-app-framework.js index 2e51776d6..ef516e86d 100644 --- a/www/common/sframe-app-framework.js +++ b/www/common/sframe-app-framework.js @@ -340,6 +340,11 @@ define([ if (err.loaded) { msg += Messages.expiredErrorCopy; } + } else if (err.type === 'EDELETED') { + msg = Messages.deletedError; + if (err.loaded) { + msg += Messages.expiredErrorCopy; + } } UI.errorLoadingScreen(msg, true, true); }; @@ -436,7 +441,9 @@ define([ var priv = common.getMetadataMgr().getPrivateData(); if (priv.isNewFile) { var c = (priv.settings.general && priv.settings.general.creation) || {}; - if (c.skip && !priv.forceCreationScreen) { return void common.createPad(c, waitFor()); } + if (c.skip && !priv.forceCreationScreen) { + return void common.createPad(c, waitFor()); + } common.getPadCreationScreen(c, waitFor()); } }).nThen(function (waitFor) { diff --git a/www/common/toolbar3.js b/www/common/toolbar3.js index cb1bb764c..e014503c6 100644 --- a/www/common/toolbar3.js +++ b/www/common/toolbar3.js @@ -1100,6 +1100,7 @@ define([ toolbar.deleted = function (/*userId*/) { toolbar.isErrorState = true; toolbar.connected = false; + updateUserList(toolbar, config); if (toolbar.spinner) { toolbar.spinner.text(Messages.deletedFromServer); } diff --git a/www/common/userObject.js b/www/common/userObject.js index 223376963..4f7f03b23 100644 --- a/www/common/userObject.js +++ b/www/common/userObject.js @@ -556,17 +556,18 @@ define([ // DELETE // Permanently delete multiple files at once using a list of paths // NOTE: We have to be careful when removing elements from arrays (trash root, unsorted or template) - exp.delete = function (paths, cb, nocheck) { + exp.delete = function (paths, cb, nocheck, isOwnPadRemoved) { if (sframeChan) { return void sframeChan.query("Q_DRIVE_USEROBJECT", { cmd: "delete", data: { paths: paths, - nocheck: nocheck + nocheck: nocheck, + isOwnPadRemoved: isOwnPadRemoved } }, cb); } - exp.deleteMultiplePermanently(paths, nocheck); + exp.deleteMultiplePermanently(paths, nocheck, isOwnPadRemoved); if (typeof cb === "function") { cb(); } }; exp.emptyTrash = function (cb) { diff --git a/www/drive/inner.js b/www/drive/inner.js index be4909415..2f5ff9fa0 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -2715,6 +2715,8 @@ define([ UI.confirm(msgD, function(res) { $(window).focus(); if (!res) { return; } + filesOp.delete(pathsList, refresh); + /* // Try to delete each selected pad from server, and delete from drive if no error var n = nThen(function () {}); pathsList.forEach(function (p) { @@ -2726,10 +2728,12 @@ define([ sframeChan.query('Q_REMOVE_OWNED_CHANNEL', channel, waitFor(function (e) { if (e) { return void console.error(e); } - filesOp.delete([p], refresh); + filesOp.delete([p], function () {}, false, true); })); }); }); + n.nThen(function () { refresh(); }); + */ }); }; $contextMenu.on("click", "a", function(e) { diff --git a/www/poll/app-poll.less b/www/poll/app-poll.less index 88839f96e..0a5d5ca59 100644 --- a/www/poll/app-poll.less +++ b/www/poll/app-poll.less @@ -6,6 +6,7 @@ @import (once) '../../customize/src/less2/include/tokenfield.less'; @import (once) '../../customize/src/less2/include/tools.less'; @import (once) '../../customize/src/less2/include/avatar.less'; +@import (once) '../../customize/src/less2/include/creation.less'; .toolbar_main( @bg-color: @colortheme_poll-bg, @@ -15,6 +16,7 @@ .fileupload_main(); .alertify_main(); .tokenfield_main(); +.creation_main(); @poll-fore: #555; diff --git a/www/poll/inner.js b/www/poll/inner.js index dd585ec31..f88ac5763 100644 --- a/www/poll/inner.js +++ b/www/poll/inner.js @@ -1119,13 +1119,34 @@ define([ } UI.removeLoadingScreen(); - if (isNew) { + var privateDat = metadataMgr.getPrivateData(); + var skipTemp = Util.find(privateDat, + ['settings', 'general', 'creation', 'noTemplate']); + var skipCreation = Util.find(privateDat, ['settings', 'general', 'creation', 'skip']); + if (isNew && (!AppConfig.displayCreationScreen || (!skipTemp && skipCreation))) { common.openTemplatePicker(); } }; - var onDisconnect = function () { + // Manage disconnections because of network or error + var onDisconnect = function (info) { setEditable(false); + if (info && ['EEXPIRED', 'EDELETED'].indexOf(info.type) !== -1) { + APP.toolbar.deleted(); + var msg = info.type; + if (info.type === 'EEXPIRED') { + msg = Messages.expiredError; + if (info.loaded) { + msg += Messages.expiredErrorCopy; + } + } else if (info.type === 'EDELETED') { + msg = Messages.deletedError; + if (info.loaded) { + msg += Messages.expiredErrorCopy; + } + } + return void UI.errorLoadingScreen(msg, true, true); + } UI.alert(Messages.common_connectionLost, undefined, true); }; @@ -1175,6 +1196,7 @@ define([ Title.setToolbar(APP.toolbar); var $rightside = APP.toolbar.$rightside; + var $drawer = APP.toolbar.$drawer; metadataMgr.onChange(function () { var md = copyObject(metadataMgr.getMetadata()); @@ -1189,6 +1211,9 @@ define([ var $forgetPad = common.createButton('forget', true, {}, forgetCb); $rightside.append($forgetPad); + var $properties = common.createButton('properties', true); + $drawer.append($properties); + /* save as template */ if (!metadataMgr.getPrivateData().isTemplate) { var templateObj = { @@ -1201,7 +1226,7 @@ define([ /* add an export button */ var $export = common.createButton('export', true, {}, exportFile); - $rightside.append($export); + $drawer.append($export); var $help = common.createButton('', true).click(function () { showHelp(); }) .appendTo($rightside); @@ -1255,6 +1280,16 @@ define([ SFCommon.create(waitFor(function (c) { APP.common = common = c; })); }).nThen(function (waitFor) { common.getSframeChannel().onReady(waitFor()); + }).nThen(function (waitFor) { + if (!AppConfig.displayCreationScreen) { return; } + var priv = common.getMetadataMgr().getPrivateData(); + if (priv.isNewFile) { + var c = (priv.settings.general && priv.settings.general.creation) || {}; + if (c.skip && !priv.forceCreationScreen) { + return void common.createPad(c, waitFor()); + } + common.getPadCreationScreen(c, waitFor()); + } }).nThen(function (/* waitFor */) { Test.registerInner(common.getSframeChannel()); var metadataMgr = common.getMetadataMgr(); diff --git a/www/poll/main.js b/www/poll/main.js index 737038ead..85bbb6f62 100644 --- a/www/poll/main.js +++ b/www/poll/main.js @@ -36,6 +36,8 @@ define([ }; window.addEventListener('message', onMsg); }).nThen(function (/*waitFor*/) { - SFCommonO.start(); + SFCommonO.start({ + useCreationScreen: true + }); }); }); diff --git a/www/whiteboard/app-whiteboard.less b/www/whiteboard/app-whiteboard.less index 28e6a6e0c..29c739b6e 100644 --- a/www/whiteboard/app-whiteboard.less +++ b/www/whiteboard/app-whiteboard.less @@ -5,6 +5,7 @@ @import (once) '../../customize/src/less2/include/alertify.less'; @import (once) '../../customize/src/less2/include/tools.less'; @import (once) '../../customize/src/less2/include/tokenfield.less'; +@import (once) '../../customize/src/less2/include/creation.less'; .toolbar_main( @bg-color: @colortheme_whiteboard-bg, @@ -14,6 +15,7 @@ .fileupload_main(); .alertify_main(); .tokenfield_main(); +.creation_main(); // body &.cp-app-whiteboard { diff --git a/www/whiteboard/inner.js b/www/whiteboard/inner.js index 4e5729fa8..febf63c5b 100644 --- a/www/whiteboard/inner.js +++ b/www/whiteboard/inner.js @@ -415,6 +415,7 @@ define([ Title.setToolbar(toolbar); var $rightside = toolbar.$rightside; + var $drawer = toolbar.$drawer; /* save as template */ if (!metadataMgr.getPrivateData().isTemplate) { @@ -428,7 +429,7 @@ define([ /* add an export button */ var $export = common.createButton('export', true, {}, saveImage); - $rightside.append($export); + $drawer.append($export); if (common.isLoggedIn()) { common.createButton('savetodrive', true, {}, function () {}) @@ -449,6 +450,9 @@ define([ }); $rightside.append($forget); + var $properties = common.createButton('properties', true); + toolbar.$drawer.append($properties); + if (!readOnly) { makeColorButton($rightside); @@ -562,7 +566,12 @@ define([ if (readOnly) { return; } - if (isNew) { + + var privateDat = metadataMgr.getPrivateData(); + var skipTemp = Util.find(privateDat, + ['settings', 'general', 'creation', 'noTemplate']); + var skipCreation = Util.find(privateDat, ['settings', 'general', 'creation', 'skip']); + if (isNew && (!AppConfig.displayCreationScreen || (!skipTemp && skipCreation))) { common.openTemplatePicker(); } }); @@ -605,6 +614,24 @@ define([ } }; + config.onError = function (err) { + setEditable(false); + toolbar.deleted(); + var msg = err.type; + if (err.type === 'EEXPIRED') { + msg = Messages.expiredError; + if (err.loaded) { + msg += Messages.expiredErrorCopy; + } + } else if (err.type === 'EDELETED') { + msg = Messages.deletedError; + if (err.loaded) { + msg += Messages.expiredErrorCopy; + } + } + UI.errorLoadingScreen(msg, true, true); + }; + cpNfInner = common.startRealtime(config); metadataMgr = cpNfInner.metadataMgr; @@ -640,6 +667,18 @@ define([ $('body').append($div.html()); })); SFCommon.create(waitFor(function (c) { APP.common = common = c; })); + }).nThen(function (waitFor) { + common.getSframeChannel().onReady(waitFor()); + }).nThen(function (waitFor) { + if (!AppConfig.displayCreationScreen) { return; } + var priv = common.getMetadataMgr().getPrivateData(); + if (priv.isNewFile) { + var c = (priv.settings.general && priv.settings.general.creation) || {}; + if (c.skip && !priv.forceCreationScreen) { + return void common.createPad(c, waitFor()); + } + common.getPadCreationScreen(c, waitFor()); + } }).nThen(function (/*waitFor*/) { andThen(common); }); diff --git a/www/whiteboard/main.js b/www/whiteboard/main.js index ce1f14d9c..1c63ad811 100644 --- a/www/whiteboard/main.js +++ b/www/whiteboard/main.js @@ -36,6 +36,8 @@ define([ }; window.addEventListener('message', onMsg); }).nThen(function (/*waitFor*/) { - SFCommonO.start(); + SFCommonO.start({ + useCreationScreen: true + }); }); }); From f004c4d7012fcc695d2f54441a778683fd5070b8 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 14 Feb 2018 19:42:00 +0100 Subject: [PATCH 29/45] lint compliance --- rpc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpc.js b/rpc.js index 8148c189c..e241a271f 100644 --- a/rpc.js +++ b/rpc.js @@ -1346,7 +1346,7 @@ RPC.create = function ( }); case 'REMOVE_OWNED_CHANNEL': - return void removeOwnedChannel(Env, msg[1], publicKey, function (e, response) { + return void removeOwnedChannel(Env, msg[1], publicKey, function (e) { if (e) { return void Respond(e); } Respond(void 0, "OK"); }); From ef0c08130d546476c307cb4923571019ae9e9f02 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 15 Feb 2018 11:33:31 +0100 Subject: [PATCH 30/45] Stop the process when expire-channels is done --- expire-channels.js | 7 ++++++- storage/file.js | 6 +++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/expire-channels.js b/expire-channels.js index fda2c20bb..a37a4677b 100644 --- a/expire-channels.js +++ b/expire-channels.js @@ -78,7 +78,7 @@ nt = nThen(function (w) { store = _store; })); }).nThen(function () { - dirs.forEach(function (dir) { + dirs.forEach(function (dir, dIdx) { queue(function (w) { console.log('recursing into %s', dir); Fs.readdir(Path.join(root, dir), w(function (e, list) { @@ -98,6 +98,11 @@ nt = nThen(function (w) { }); }); }); + if (dIdx === (dirs.length - 1)) { + queue(function () { + store.shutdown(); + }); + } })); }); }); diff --git a/storage/file.js b/storage/file.js index 33df00692..08a9f19cd 100644 --- a/storage/file.js +++ b/storage/file.js @@ -418,6 +418,7 @@ module.exports.create = function ( openFileLimit: conf.openFileLimit || 2048, }; // 0x1ff -> 777 + var it; Fs.mkdir(env.root, 0x1ff, function (err) { if (err && err.code !== 'EEXIST') { // TODO: somehow return a nice error @@ -465,9 +466,12 @@ module.exports.create = function ( if (!isValidChannelId(channelName)) { return void cb(new Error('EINVAL')); } clearChannel(env, channelName, cb); }, + shutdown: function () { + clearInterval(it); + } }); }); - setInterval(function () { + it = setInterval(function () { flushUnusedChannels(env, function () { }); }, 5000); }; From 70e014cdfcf502393abcf0b4630686336ad122fe Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 15 Feb 2018 11:34:44 +0100 Subject: [PATCH 31/45] Hide infinite spinner and disconnect modals when pad is deleted --- .../src/less2/include/creation.less | 6 +++++ www/common/common-ui-elements.js | 19 ++++++++++++++ www/common/sframe-app-framework.js | 17 +++---------- www/common/sframe-common.js | 1 + www/poll/inner.js | 25 +++++++------------ www/whiteboard/inner.js | 22 ++++++---------- 6 files changed, 45 insertions(+), 45 deletions(-) diff --git a/customize.dist/src/less2/include/creation.less b/customize.dist/src/less2/include/creation.less index cf2bc6b88..58d5a53ba 100644 --- a/customize.dist/src/less2/include/creation.less +++ b/customize.dist/src/less2/include/creation.less @@ -160,5 +160,11 @@ } } } + .cp-creation-deleted { + background: #111; + padding: 10px; + text-align: justify; + font-weight: bold; + } } } diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index e0e5f1ff7..8e4fce0e4 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -1829,5 +1829,24 @@ define([ }, Messages.creation_settings))).appendTo($creation); }; + UIElements.onServerError = function (common, err, toolbar, cb) { + if (["EDELETED", "EEXPIRED"].indexOf(err.type) === -1) { return; } + var msg = err.type; + if (err.type === 'EEXPIRED') { + msg = Messages.expiredError; + if (err.loaded) { + msg += Messages.expiredErrorCopy; + } + } else if (err.type === 'EDELETED') { + msg = Messages.deletedError; + if (err.loaded) { + msg += Messages.expiredErrorCopy; + } + } + if (toolbar && typeof toolbar.deleted === "function") { toolbar.deleted(); } + UI.errorLoadingScreen(msg, true, true); + (cb || function () {})(); + }; + return UIElements; }); diff --git a/www/common/sframe-app-framework.js b/www/common/sframe-app-framework.js index ef516e86d..7540f4a0b 100644 --- a/www/common/sframe-app-framework.js +++ b/www/common/sframe-app-framework.js @@ -333,20 +333,9 @@ define([ }; var onError = function (err) { - stateChange(STATE.DELETED); - var msg = err.type; - if (err.type === 'EEXPIRED') { - msg = Messages.expiredError; - if (err.loaded) { - msg += Messages.expiredErrorCopy; - } - } else if (err.type === 'EDELETED') { - msg = Messages.deletedError; - if (err.loaded) { - msg += Messages.expiredErrorCopy; - } - } - UI.errorLoadingScreen(msg, true, true); + common.onServerError(err, toolbar, function () { + stateChange(STATE.DELETED); + }); }; var setFileExporter = function (extension, fe, async) { diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index 029118b88..3f4070eaf 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -92,6 +92,7 @@ define([ funcs.createMarkdownToolbar = callWithCommon(UIElements.createMarkdownToolbar); funcs.getPadCreationScreen = callWithCommon(UIElements.getPadCreationScreen); funcs.createNewPadModal = callWithCommon(UIElements.createNewPadModal); + funcs.onServerError = callWithCommon(UIElements.onServerError); // Thumb funcs.displayThumbnail = callWithCommon(Thumb.displayThumbnail); diff --git a/www/poll/inner.js b/www/poll/inner.js index f88ac5763..8f31116a1 100644 --- a/www/poll/inner.js +++ b/www/poll/inner.js @@ -1130,27 +1130,20 @@ define([ // Manage disconnections because of network or error var onDisconnect = function (info) { - setEditable(false); - if (info && ['EEXPIRED', 'EDELETED'].indexOf(info.type) !== -1) { - APP.toolbar.deleted(); - var msg = info.type; - if (info.type === 'EEXPIRED') { - msg = Messages.expiredError; - if (info.loaded) { - msg += Messages.expiredErrorCopy; - } - } else if (info.type === 'EDELETED') { - msg = Messages.deletedError; - if (info.loaded) { - msg += Messages.expiredErrorCopy; - } - } - return void UI.errorLoadingScreen(msg, true, true); + if (APP.unrecoverable) { return; } + if (info && info.type) { + // Server error + return void common.onServerError(info, APP.toolbar, function () { + APP.unrecoverable = true; + setEditable(false); + }); } + setEditable(false); UI.alert(Messages.common_connectionLost, undefined, true); }; var onReconnect = function () { + if (APP.unrecoverable) { return; } setEditable(true); UI.findOKButton().click(); }; diff --git a/www/whiteboard/inner.js b/www/whiteboard/inner.js index febf63c5b..8d2621aaf 100644 --- a/www/whiteboard/inner.js +++ b/www/whiteboard/inner.js @@ -598,6 +598,7 @@ define([ }; config.onAbort = function () { + if (APP.unrecoverable) { return; } // inform of network disconnect setEditable(false); toolbar.failed(); @@ -605,6 +606,7 @@ define([ }; config.onConnectionChange = function (info) { + if (APP.unrecoverable) { return; } setEditable(info.state); if (info.state) { initializing = true; @@ -615,27 +617,17 @@ define([ }; config.onError = function (err) { - setEditable(false); - toolbar.deleted(); - var msg = err.type; - if (err.type === 'EEXPIRED') { - msg = Messages.expiredError; - if (err.loaded) { - msg += Messages.expiredErrorCopy; - } - } else if (err.type === 'EDELETED') { - msg = Messages.deletedError; - if (err.loaded) { - msg += Messages.expiredErrorCopy; - } - } - UI.errorLoadingScreen(msg, true, true); + common.onServerError(err, toolbar, function () { + APP.unrecoverable = true; + setEditable(false); + }); }; cpNfInner = common.startRealtime(config); metadataMgr = cpNfInner.metadataMgr; cpNfInner.onInfiniteSpinner(function () { + if (APP.unrecoverable) { return; } setEditable(false); UI.confirm(Messages.realtime_unrecoverableError, function (yes) { if (!yes) { return; } From 54a91f11532af1fa342c59f052b2a2ac830709b1 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 15 Feb 2018 15:38:39 +0100 Subject: [PATCH 32/45] Ability to reorder and edit tasks --- bower.json | 3 ++- www/todo/app-todo.less | 7 +++++++ www/todo/inner.js | 36 +++++++++++++++++++++++++++++++++--- www/todo/todo.js | 17 +++++++++++++++++ 4 files changed, 59 insertions(+), 4 deletions(-) diff --git a/bower.json b/bower.json index f9501dfeb..7bf775e64 100644 --- a/bower.json +++ b/bower.json @@ -45,7 +45,8 @@ "bootstrap-tokenfield": "^0.12.1", "localforage": "^1.5.2", "html2canvas": "^0.4.1", - "croppie": "^2.5.0" + "croppie": "^2.5.0", + "sortablejs": "#^1.6.0" }, "resolutions": { "bootstrap": "v4.0.0-alpha.6" diff --git a/www/todo/app-todo.less b/www/todo/app-todo.less index d4fe830b3..5008498b1 100644 --- a/www/todo/app-todo.less +++ b/www/todo/app-todo.less @@ -88,6 +88,13 @@ color: #777; } + .cp-app-todo-task-input { + margin: @spacing; + flex: 1; + min-width: 0; + font-weight: bold; + display: none; + } .cp-app-todo-task-text { margin: @spacing; flex: 1; diff --git a/www/todo/inner.js b/www/todo/inner.js index 748467360..d502d2990 100644 --- a/www/todo/inner.js +++ b/www/todo/inner.js @@ -9,6 +9,7 @@ define([ '/common/common-hash.js', '/todo/todo.js', '/customize/messages.js', + '/bower_components/sortablejs/Sortable.min.js', 'css!/bower_components/bootstrap/dist/css/bootstrap.min.css', 'less!/bower_components/components-font-awesome/css/font-awesome.min.css', @@ -23,7 +24,8 @@ define([ UI, Hash, Todo, - Messages + Messages, + Sortable ) { var APP = window.APP = {}; @@ -47,6 +49,17 @@ define([ var onReady = function () { var todo = Todo.init(APP.lm.proxy); + Sortable.create($list[0], { + store: { + get: function (sortable) { + return todo.getOrder(); + }, + set: function (sortable) { + todo.reorder(sortable.toArray()); + } + } + }); + var deleteTask = function(id) { todo.remove(id); @@ -106,6 +119,7 @@ define([ $taskDiv.appendTo($list); } $taskDiv.data('id', el); + $taskDiv.attr('data-id', el); makeCheckbox(el, function (/*state*/) { APP.display(); @@ -121,9 +135,25 @@ define([ $taskDiv.addClass('cp-app-todo-task-complete'); } - $('', { 'class': 'cp-app-todo-task-text' }) + var $input = $('', { + type: 'text', + 'class': 'cp-app-todo-task-input' + }).val(entry.task).keydown(function (e) { + if (e.which === 13) { + todo.val(el, 'task', $input.val().trim()); + $input.hide(); + $span.text($input.val().trim()); + $span.show(); + } + }).appendTo($taskDiv); + + var $span = $('', { 'class': 'cp-app-todo-task-text' }) .text(entry.task) - .appendTo($taskDiv); + .appendTo($taskDiv) + .click(function () { + $input.show(); + $span.hide(); + }); /*$('', { 'class': 'cp-app-todo-task-date' }) .text(new Date(entry.ctime).toLocaleString()) .appendTo($taskDiv);*/ diff --git a/www/todo/todo.js b/www/todo/todo.js index 8c68774dd..8727180c8 100644 --- a/www/todo/todo.js +++ b/www/todo/todo.js @@ -77,6 +77,17 @@ define([ if (proxy.data[id]) { delete proxy.data[id]; } }; + /* change the order in the proxy (with a check to make sure that nothing is missing */ + var reorder = function (proxy, order) { + var existingOrder = proxy.order.slice().sort(); + var newOrder = order.slice().sort(); + if (JSON.stringify(existingOrder) === JSON.stringify(newOrder)) { + proxy.order = order.slice(); + } else { + console.error("Can't reorder the tasks. Some tasks are missing or added"); + } + }; + Todo.init = function (proxy) { var api = {}; initialize(proxy); @@ -90,6 +101,12 @@ define([ api.remove = function (id) { return remove(proxy, id); }; + api.getOrder = function () { + return proxy.order.slice(); + }; + api.reorder = function (order) { + return reorder(proxy, order); + }; return api; }; From 037a6ccc35f85f2e89f9db771a557a6f9f44711c Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 16 Feb 2018 12:02:20 +0100 Subject: [PATCH 33/45] Fix INVALID_RESPONSE error in the file app --- rpc.js | 2 +- www/common/rpc.js | 2 +- www/common/sframe-common-outer.js | 10 ++++++---- www/todo/inner.js | 7 ++++--- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/rpc.js b/rpc.js index e241a271f..465413fd1 100644 --- a/rpc.js +++ b/rpc.js @@ -1181,7 +1181,7 @@ RPC.create = function ( }); case 'IS_NEW_CHANNEL': return void isNewChannel(Env, msg[1], function (e, isNew) { - respond(null, [null, isNew, null]); + respond(e, [null, isNew, null]); }); default: console.error("unsupported!"); diff --git a/www/common/rpc.js b/www/common/rpc.js index e86615111..bcbc98662 100644 --- a/www/common/rpc.js +++ b/www/common/rpc.js @@ -102,7 +102,7 @@ types of messages: } // HACK to hide messages from the anon rpc - if (parsed.length !== 4) { + if (parsed.length !== 4 && parsed[1] !== 'ERROR') { console.log(parsed); console.error("received message [%s] for txid[%s] with no callback", msg, txid); } diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 0bbd40eea..57261372d 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -132,10 +132,12 @@ define([ // Check if the pad exists on server if (!window.location.hash) { isNewFile = true; return; } - Cryptpad.isNewChannel(window.location.href, waitFor(function (e, isNew) { - if (e) { return console.error(e); } - isNewFile = Boolean(isNew); - })); + if (realtime) { + Cryptpad.isNewChannel(window.location.href, waitFor(function (e, isNew) { + if (e) { return console.error(e); } + isNewFile = Boolean(isNew); + })); + } }).nThen(function () { var readOnly = secret.keys && !secret.keys.editKeyStr; var isNewHash = true; diff --git a/www/todo/inner.js b/www/todo/inner.js index d502d2990..f34592568 100644 --- a/www/todo/inner.js +++ b/www/todo/inner.js @@ -51,7 +51,7 @@ define([ Sortable.create($list[0], { store: { - get: function (sortable) { + get: function () { return todo.getOrder(); }, set: function (sortable) { @@ -135,6 +135,8 @@ define([ $taskDiv.addClass('cp-app-todo-task-complete'); } + var $span = $('', { 'class': 'cp-app-todo-task-text' }); + var $input = $('', { type: 'text', 'class': 'cp-app-todo-task-input' @@ -147,8 +149,7 @@ define([ } }).appendTo($taskDiv); - var $span = $('', { 'class': 'cp-app-todo-task-text' }) - .text(entry.task) + $span.text(entry.task) .appendTo($taskDiv) .click(function () { $input.show(); From 2ac9c3ba66585b5526fceeaf5bc0bb2583786938 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 16 Feb 2018 12:33:33 +0100 Subject: [PATCH 34/45] Report RPC errors when deleting owned pads --- www/common/outer/userObject.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/www/common/outer/userObject.js b/www/common/outer/userObject.js index 70a8445f7..12d6d225e 100644 --- a/www/common/outer/userObject.js +++ b/www/common/outer/userObject.js @@ -3,8 +3,9 @@ define([ '/common/common-util.js', '/common/common-hash.js', '/common/common-realtime.js', + '/common/common-feedback.js', '/customize/messages.js' -], function (AppConfig, Util, Hash, Realtime, Messages) { +], function (AppConfig, Util, Hash, Realtime, Feedback, Messages) { var module = {}; var clone = function (o) { @@ -102,7 +103,12 @@ define([ if (!isOwnPadRemoved && fd.owners && fd.owners.indexOf(edPublic) !== -1 && channelId) { removeOwnedChannel(channelId, function (obj) { - if (obj && obj.error) { console.error(obj.error); } + if (obj && obj.error) { + console.error(obj.error); + // RPC may not be responding + // Send a report that can be handled manually + Feedback.send('ERROR_DELETING_OWNED_PAD=' + channelId, true); + } }); } if (channelId) { toClean.push(channelId); } From 649fefad547377afe4dff6cd4b5e7cfca273353f Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 16 Feb 2018 15:25:53 +0100 Subject: [PATCH 35/45] Send cookie when history keeper has changed --- www/common/rpc.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/www/common/rpc.js b/www/common/rpc.js index bcbc98662..7c5012592 100644 --- a/www/common/rpc.js +++ b/www/common/rpc.js @@ -217,6 +217,15 @@ types of messages: }); }); + if (network.onHistoryKeeperChange) { + network.onHistoryKeeperChange(function () { + send('COOKIE', "", function (e) { + if (e) { return void cb(e); } + ctx.connected = true; + }); + }); + } + send('COOKIE', "", function (e) { if (e) { return void cb(e); } // callback to provide 'send' method to whatever needs it From 11fabea327d1d9bc56bc23c8c6c7e039d7d2ce77 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 16 Feb 2018 16:13:01 +0100 Subject: [PATCH 36/45] update footer and package.json version --- customize.dist/pages.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/customize.dist/pages.js b/customize.dist/pages.js index 4de6e6594..a9fbeabde 100644 --- a/customize.dist/pages.js +++ b/customize.dist/pages.js @@ -72,7 +72,7 @@ define([ ]) ]) ]), - h('div.cp-version-footer', "CryptPad v1.25.0 (Zombie)") + h('div.cp-version-footer', "CryptPad v1.26.0 (undefined)") ]); }; diff --git a/package.json b/package.json index eb6149506..16e4f69fb 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cryptpad", "description": "realtime collaborative visual editor with zero knowlege server", - "version": "1.25.0", + "version": "1.26.0", "dependencies": { "chainpad-server": "^2.0.0", "express": "~4.10.1", From 91437c558ed64014576c183653897cefe7a91b87 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 16 Feb 2018 17:06:11 +0100 Subject: [PATCH 37/45] fix weird messages --- customize.dist/translations/messages.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index 0113dbd84..956aa2147 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -478,7 +478,7 @@ define(function () { out.register_mustAcceptTerms = "You must accept the terms of service."; out.register_mustRememberPass = "We cannot reset your password if you forget it. It's very important that you remember it! Please check the checkbox to confirm."; - out.register_whyRegister = "Why signing up?"; + out.register_whyRegister = "Why sign up?"; out.register_header = "Welcome to CryptPad"; out.register_explanation = [ "

Lets go over a couple things first:

", @@ -890,7 +890,7 @@ define(function () { out.creation_expiration = "Expiration time"; out.creation_propertiesTitle = "Availability"; out.creation_appMenuName = "Advanced mode (Ctrl + E)"; - out.creation_newPadModalDescription = "Click on a pad type to create it. You can check the box if you want to display the pad creation screen (for owned pad, expiring pad, etc.)."; + out.creation_newPadModalDescription = "Click on a pad type to create it. You can check the box if you want to display the pad creation screen (for owned pads, expiring pads, etc.)."; out.creation_newPadModalAdvanced = "Display the pad creation screen"; // New share modal From 16447e031398fda8c2ed9a5aba29c367c4bafea9 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 16 Feb 2018 17:28:22 +0100 Subject: [PATCH 38/45] Stop expiration code if no directories for tasks --- expire-channels.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/expire-channels.js b/expire-channels.js index a37a4677b..4e22dfce4 100644 --- a/expire-channels.js +++ b/expire-channels.js @@ -72,6 +72,10 @@ nt = nThen(function (w) { Fs.readdir(root, w(function (e, list) { if (e) { throw e; } dirs = list; + if (dirs.length === 0) { + w.abort(); + return; + } })); }).nThen(function (waitFor) { FileStorage.create(config, waitFor(function (_store) { From b04e3def641c6dfd0ef61278f99161920fd1d4b4 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 19 Feb 2018 11:26:43 +0100 Subject: [PATCH 39/45] Fix invalid translation key --- www/drive/inner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/drive/inner.js b/www/drive/inner.js index 2f5ff9fa0..cbeb0b08f 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -2232,7 +2232,7 @@ define([ // Only Trash and Root are available in not-owned files manager if (!path || displayedCategories.indexOf(path[0]) === -1) { - log(Messages.categoryError); + log(Messages.fm_categoryError); currentPath = [ROOT]; _displayDirectory(currentPath); return; From 5dbc99343a7d52eb2688fd2ee98bfe23f47e4b6a Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 19 Feb 2018 11:38:57 +0100 Subject: [PATCH 40/45] Improve 'rename' input in the drive --- www/drive/app-drive.less | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/www/drive/app-drive.less b/www/drive/app-drive.less index 4c0ff9723..083eeebe4 100644 --- a/www/drive/app-drive.less +++ b/www/drive/app-drive.less @@ -512,7 +512,11 @@ span { } input { width: 100%; - margin-top: 5px; + margin: 0; + padding: 0; + border-radius: 0; + border: 1px solid #ddd; + font-size: 14px; } .cp-app-drive-element-state { position: absolute; @@ -568,6 +572,11 @@ span { } li { display: table-row; + input { + border: 1px solid #ddd; + margin: 0; + padding: 0 4px; + } &> span { padding: 0 5px; display: table-cell; From 766fa728411fa807e675ff23407ce2e42663876f Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 19 Feb 2018 14:07:21 +0100 Subject: [PATCH 41/45] Fix initial value of the preview link in the share modal --- www/common/common-ui-elements.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 8e4fce0e4..1d41d8308 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -283,10 +283,11 @@ define([ present: present }); }; - var getLinkValue = function () { - var edit = $(link).find('#cp-share-editable-true').is(':checked'); - var embed = $(link).find('#cp-share-embed').is(':checked'); - var present = $(link).find('#cp-share-present').is(':checked'); + var getLinkValue = function (initValue) { + var val = initValue || {}; + var edit = initValue ? val.edit : $(link).find('#cp-share-editable-true').is(':checked'); + var embed = initValue ? val.embed : $(link).find('#cp-share-embed').is(':checked'); + var present = initValue ? val.present : $(link).find('#cp-share-present').is(':checked'); var hash = (edit && hashes.editHash) ? hashes.editHash : hashes.viewHash; var href = origin + pathname + '#' + hash; @@ -375,6 +376,7 @@ define([ } if (val.embed) { $(link).find('#cp-share-embed').attr('checked', true); } if (val.present) { $(link).find('#cp-share-present').attr('checked', true); } + $(link).find('#cp-share-link-preview').val(getLinkValue(val)); }); return tabs; }; From 2eef1e96e60f9104d9c8213aa94e1a1f40c7c0a9 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 20 Feb 2018 10:21:15 +0100 Subject: [PATCH 42/45] Remove invalid error reports --- www/common/outer/userObject.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/www/common/outer/userObject.js b/www/common/outer/userObject.js index 12d6d225e..dadbfb463 100644 --- a/www/common/outer/userObject.js +++ b/www/common/outer/userObject.js @@ -104,9 +104,13 @@ define([ fd.owners && fd.owners.indexOf(edPublic) !== -1 && channelId) { removeOwnedChannel(channelId, function (obj) { if (obj && obj.error) { - console.error(obj.error); + // If the error is that the file is already removed, nothing to + // report, it's a normal behavior (pad expired probably) + if (obj.error.code === 'ENOENT') { return; } + // RPC may not be responding // Send a report that can be handled manually + console.error(obj.error); Feedback.send('ERROR_DELETING_OWNED_PAD=' + channelId, true); } }); From 15a81960373ba66ff855385ec6e49d0c5173f8a6 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 20 Feb 2018 10:39:02 +0100 Subject: [PATCH 43/45] Enable pad creation screen --- www/common/application_config_internal.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/www/common/application_config_internal.js b/www/common/application_config_internal.js index 91520c99a..f6760f897 100644 --- a/www/common/application_config_internal.js +++ b/www/common/application_config_internal.js @@ -83,8 +83,9 @@ define(function() { contacts: 'fa-users', }; - // EXPERIMENTAL: Enabling "displayCreationScreen" may cause UI issues and possible loss of data - config.displayCreationScreen = false; + // Ability to create owned pads and expiring pads through a new pad creation screen. + // The new screen can be disabled by the users in their settings page + config.displayCreationScreen = true; // Prevent anonymous users from storing pads in their drive config.disableAnonymousStore = false; From 5f50bedce2a5b24b1ff6f25d248d9dd74d1aae70 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 20 Feb 2018 14:42:11 +0100 Subject: [PATCH 44/45] Fix empty properties for readonly pads --- www/common/common-ui-elements.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 1d41d8308..462c806fb 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -141,7 +141,7 @@ define([ }; var getPadProperties = function (common, data, cb) { var $d = $('
'); - if (!data || !data.href) { return void cb(void 0, $d); } + if (!data || (!data.href && !data.roHref)) { return void cb(void 0, $d); } if (data.href) { $('