From 4e8335bfdddc5368a10bed37eaa68cf921f3730e Mon Sep 17 00:00:00 2001 From: ansuz Date: Wed, 24 Jan 2018 11:01:15 +0100 Subject: [PATCH 01/72] fix support for custom button classes --- www/common/common-interface.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/www/common/common-interface.js b/www/common/common-interface.js index c256d2656..bd062a1bc 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -111,12 +111,14 @@ define([ return input; }; - dialog.okButton = function (content) { - return h('button.ok.primary', { tabindex: '2', }, content || Messages.okButton); + dialog.okButton = function (content, classString) { + var sel = typeof(classString) === 'string'? 'button.ok.' + classString:'button.ok.primary' + return h(sel, { tabindex: '2', }, content || Messages.okButton); }; - dialog.cancelButton = function (content) { - return h('button.cancel', { tabindex: '1'}, content || Messages.cancelButton); + dialog.cancelButton = function (content, classString) { + var sel = typeof(classString) === 'string'? 'button.' + classString:'button.cancel' + return h(sel, { tabindex: '1'}, content || Messages.cancelButton); }; dialog.message = function (text) { @@ -464,8 +466,8 @@ define([ message = dialog.message(msg); } - var ok = dialog.okButton(opt.ok); - var cancel = dialog.cancelButton(opt.cancel); + var ok = dialog.okButton(opt.ok, opt.okClass); + var cancel = dialog.cancelButton(opt.cancel, opt.cancelClass); var frame = dialog.frame([ message, From a5b7b0191fe98ca4ff7a8e5caf59a6a42cdc201e Mon Sep 17 00:00:00 2001 From: ansuz Date: Wed, 24 Jan 2018 11:31:34 +0100 Subject: [PATCH 02/72] correct validation for 'channelName' --- storage/file.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/storage/file.js b/storage/file.js index 329d7f74f..33df00692 100644 --- a/storage/file.js +++ b/storage/file.js @@ -9,7 +9,7 @@ const Pull = require('pull-stream'); const isValidChannelId = function (id) { return typeof(id) === 'string' && - [32, 48].indexOf(id.length) > -1 && + id.length >= 32 && id.length < 50 && /^[a-zA-Z0-9=+-]*$/.test(id); }; @@ -470,4 +470,4 @@ module.exports.create = function ( setInterval(function () { flushUnusedChannels(env, function () { }); }, 5000); -}; \ No newline at end of file +}; From 80cd3e208a991376cedbb52cd66fbf7edafe8eed Mon Sep 17 00:00:00 2001 From: ansuz Date: Wed, 24 Jan 2018 11:35:05 +0100 Subject: [PATCH 03/72] lint compliance --- www/common/common-interface.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/www/common/common-interface.js b/www/common/common-interface.js index bd062a1bc..f35b361ba 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -112,12 +112,12 @@ define([ }; dialog.okButton = function (content, classString) { - var sel = typeof(classString) === 'string'? 'button.ok.' + classString:'button.ok.primary' + var sel = typeof(classString) === 'string'? 'button.ok.' + classString:'button.ok.primary'; return h(sel, { tabindex: '2', }, content || Messages.okButton); }; dialog.cancelButton = function (content, classString) { - var sel = typeof(classString) === 'string'? 'button.' + classString:'button.cancel' + var sel = typeof(classString) === 'string'? 'button.' + classString:'button.cancel'; return h(sel, { tabindex: '1'}, content || Messages.cancelButton); }; From 2b8414ca78e6548f5db0464176a8f4116f845bf3 Mon Sep 17 00:00:00 2001 From: ansuz Date: Wed, 24 Jan 2018 16:16:46 +0100 Subject: [PATCH 04/72] provide hints for running CryptPad --- www/common/boot2.js | 9 +++++++++ www/common/sframe-boot2.js | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/www/common/boot2.js b/www/common/boot2.js index 11dfa1649..a9d0606db 100644 --- a/www/common/boot2.js +++ b/www/common/boot2.js @@ -37,6 +37,15 @@ define([ window.alert("CryptPad needs localStorage to work, try a different browser"); }; + window.onerror = function (e) { + if (/requirejs\.org/.test(e)) { + console.log(); + console.error("Require.js threw a Script Error. This probably means you're missing a dependency for CryptPad.\nIt is recommended that the admin of this server runs `bower install && bower update` to get the latest code, then modify their cache version.\nBest of luck,\nThe CryptPad Developers"); + return void console.log(); + } + throw e; + }; + try { var test_key = 'localStorage_test'; var testval = Math.random().toString(); diff --git a/www/common/sframe-boot2.js b/www/common/sframe-boot2.js index fe614ba33..0d0eaa90c 100644 --- a/www/common/sframe-boot2.js +++ b/www/common/sframe-boot2.js @@ -36,5 +36,14 @@ define([ // This test is completed in common-interface.js Test(function (t) { Test.__ASYNC_BLOCKER__ = t; }); + window.onerror = function (e) { + if (/requirejs\.org/.test(e)) { + console.log(); + console.error("Require.js threw a Script Error. This probably means you're missing a dependency for CryptPad.\nIt is recommended that the admin of this server runs `bower install && bower update` to get the latest code, then modify their cache version.\nBest of luck,\nThe CryptPad Developers"); + return void console.log(); + } + throw e; + }; + require([document.querySelector('script[data-bootload]').getAttribute('data-bootload')]); }); From 4d0312115a0effa23184daa14211a2f60fd369d1 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 25 Jan 2018 12:09:20 +0100 Subject: [PATCH 05/72] Add a link to the account server for subscribers in settings --- customize.dist/translations/messages.fr.js | 1 + customize.dist/translations/messages.js | 1 + www/settings/inner.js | 12 +++++++++++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index f28fdbbd9..83e652588 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -490,6 +490,7 @@ define(function () { out.settings_cat_code = "Code"; out.settings_cat_pad = "Documents texte"; out.settings_cat_creation = "Nouveau pad"; + out.settings_cat_subscription = "Abonnement"; out.settings_title = "Préférences"; out.settings_save = "Sauver"; diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index ffe96f79a..f096341b6 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -495,6 +495,7 @@ define(function () { out.settings_cat_code = "Code"; out.settings_cat_pad = "Rich text"; out.settings_cat_creation = "New pad"; + out.settings_cat_subscription = "Subscription"; out.settings_title = "Settings"; out.settings_save = "Save"; diff --git a/www/settings/inner.js b/www/settings/inner.js index b6a554b37..18aa90b04 100644 --- a/www/settings/inner.js +++ b/www/settings/inner.js @@ -64,7 +64,13 @@ define([ 'code': [ 'cp-settings-code-indent-unit', 'cp-settings-code-indent-type' - ] + ], + 'subscription': { + onClick: function () { + var urls = common.getMetadataMgr().getPrivateData().accounts; + window.open(urls.upgradeURL); + } + } }; if (!AppConfig.dislayCreationScreen) { @@ -786,12 +792,16 @@ define([ if (key === 'code') { $category.append($('', {'class': 'fa fa-file-code-o' })); } if (key === 'pad') { $category.append($('', {'class': 'fa fa-file-word-o' })); } if (key === 'creation') { $category.append($('', {'class': 'fa fa-plus-circle' })); } + if (key === 'subscription') { $category.append($('', {'class': 'fa fa-star-o' })); } if (key === active) { $category.addClass('cp-leftside-active'); } $category.click(function () { + if (!Array.isArray(categories[key]) && categories[key].onClick) { + categories[key].onClick(); + } active = key; $categories.find('.cp-leftside-active').removeClass('cp-leftside-active'); $category.addClass('cp-leftside-active'); From fd4ed3ce3bb2dca9ac269938eca5c482db4573da Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 25 Jan 2018 12:10:46 +0100 Subject: [PATCH 06/72] Fix subscription button in settings --- www/settings/inner.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/www/settings/inner.js b/www/settings/inner.js index 18aa90b04..8df9c9564 100644 --- a/www/settings/inner.js +++ b/www/settings/inner.js @@ -10,6 +10,7 @@ define([ '/customize/messages.js', '/common/hyperscript.js', '/customize/application_config.js', + '/api/config', '/bower_components/file-saver/FileSaver.min.js', 'css!/bower_components/bootstrap/dist/css/bootstrap.min.css', @@ -26,7 +27,8 @@ define([ Hash, Messages, h, - AppConfig + AppConfig, + ApiConfig ) { var saveAs = window.saveAs; @@ -84,6 +86,9 @@ define([ var displaynameIdx = categories.account.indexOf('cp-settings-displayname'); categories.account.splice(displaynameIdx, 1); } + if (!ApiConfig.allowSubscriptions) { + delete categories.subscription; + } var create = {}; @@ -801,6 +806,7 @@ define([ $category.click(function () { if (!Array.isArray(categories[key]) && categories[key].onClick) { categories[key].onClick(); + return; } active = key; $categories.find('.cp-leftside-active').removeClass('cp-leftside-active'); From 1817a7924f346c5ec587662d334369ad30828217 Mon Sep 17 00:00:00 2001 From: ansuz Date: Thu, 25 Jan 2018 12:17:35 +0100 Subject: [PATCH 07/72] don't require heapdump by default --- config.example.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/config.example.js b/config.example.js index fd7b45f96..8fb4acb00 100644 --- a/config.example.js +++ b/config.example.js @@ -8,7 +8,9 @@ var domain = ' http://localhost:3000/'; // If your system doesn't support dumping, comment this out and install with // `npm install --production` // See: https://strongloop.github.io/strongloop.com/strongblog/how-to-heap-snapshots/ -require('heapdump'); + +// to enable this feature, uncomment the line below: +// require('heapdump'); module.exports = { From 8dd1c97d6c814745010b9ccd1bdd68a0d04543f5 Mon Sep 17 00:00:00 2001 From: kpcyrd Date: Tue, 23 Jan 2018 10:28:58 +0100 Subject: [PATCH 08/72] Fix docker build --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 96a5ff520..da60937bf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ COPY . /cryptpad WORKDIR /cryptpad RUN apk add --no-cache git tini \ - && npm install \ + && npm install --production \ && npm install -g bower \ && bower install --allow-root From d2b0c8a5cd85b17aa06f10c5a9bc80b3b27e6820 Mon Sep 17 00:00:00 2001 From: kpcyrd Date: Tue, 23 Jan 2018 10:47:20 +0100 Subject: [PATCH 09/72] Add docker build to travis tests --- .dockerignore | 4 +++- .travis.yml | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.dockerignore b/.dockerignore index 880c21fe3..f2099e1fc 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,5 +1,7 @@ data Dockerfile docker-compose.yml +.dockerignore .git -.gitignore \ No newline at end of file +.gitignore +node_modules diff --git a/.travis.yml b/.travis.yml index d77e49fa1..047f0502e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,3 +9,4 @@ node_js: script: - npm run-script lint - npm run-script flow + - docker build -t xwiki/cryptpad . From 3d3f58b3fca87bdf9674c4d827681e1ef8f95950 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 25 Jan 2018 14:20:55 +0100 Subject: [PATCH 10/72] Improve share modal customization to make it work with async functions --- www/common/common-ui-elements.js | 4 ++-- www/common/toolbar3.js | 26 ++++++++++++++------------ 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index e43a66176..a11ec5859 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -375,8 +375,8 @@ define([ } if (val.embed) { $(link).find('#cp-share-embed').attr('checked', true); } if (val.present) { $(link).find('#cp-share-present').attr('checked', true); } - UI.openCustomModal(UI.dialog.tabs(tabs)); }); + return tabs; }; UIElements.createFileShareModal = function (config) { var origin = config.origin; @@ -451,7 +451,7 @@ define([ pathname: pathname }); } - UI.openCustomModal(UI.dialog.tabs(tabs)); + return tabs; }; UIElements.createButton = function (common, type, rightside, data, callback) { diff --git a/www/common/toolbar3.js b/www/common/toolbar3.js index c7afbaca8..a82e1ff6e 100644 --- a/www/common/toolbar3.js +++ b/www/common/toolbar3.js @@ -444,13 +444,14 @@ define([ 'class': 'fa fa-share-alt cp-toolbar-share-button', title: Messages.shareButton }); + var modal = UIElements.createShareModal({ + origin: origin, + pathname: pathname, + hashes: hashes, + common: Common + }); $shareBlock.click(function () { - UIElements.createShareModal({ - origin: origin, - pathname: pathname, - hashes: hashes, - common: Common - }); + UI.openCustomModal(UI.dialog.tabs(modal)); }); toolbar.$leftside.append($shareBlock); @@ -472,13 +473,14 @@ define([ 'class': 'fa fa-share-alt cp-toolbar-share-button', title: Messages.shareButton }); + var modal = UIElements.createFileShareModal({ + origin: origin, + pathname: pathname, + hashes: hashes, + common: Common + }); $shareBlock.click(function () { - UIElements.createFileShareModal({ - origin: origin, - pathname: pathname, - hashes: hashes, - common: Common - }); + UI.openCustomModal(UI.dialog.tabs(modal)); }); toolbar.$leftside.append($shareBlock); From 9a317018e28297853012d87289bebb1f913ee7f9 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 25 Jan 2018 17:54:21 +0100 Subject: [PATCH 11/72] Remove deleted pads from the drive --- www/common/common-hash.js | 2 +- www/common/common-util.js | 2 +- www/common/cryptpad-common.js | 7 ++ www/common/outer/async-store.js | 76 ++++++++++++++------- www/common/outer/chainpad-netflux-worker.js | 20 +----- www/common/outer/store-rpc.js | 3 + www/common/sframe-protocol.js | 2 + www/common/userObject.js | 12 ++++ www/drive/inner.js | 9 ++- www/drive/main.js | 6 ++ 10 files changed, 96 insertions(+), 43 deletions(-) diff --git a/www/common/common-hash.js b/www/common/common-hash.js index d7d95e78a..d62204ac4 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -6,7 +6,7 @@ define([ ], function (Util, Messages, Crypto) { var Nacl = window.nacl; - var Hash = {}; + var Hash = window.CryptPad_Hash = {}; var uint8ArrayToHex = Util.uint8ArrayToHex; var hexToBase64 = Util.hexToBase64; diff --git a/www/common/common-util.js b/www/common/common-util.js index 030536751..98eda7373 100644 --- a/www/common/common-util.js +++ b/www/common/common-util.js @@ -1,6 +1,6 @@ (function (window) { define([], function () { - var Util = {}; + var Util = window.CryptPad_Util = {}; // If once is true, after the event has been fired, any further handlers which are // registered will fire immediately, and this type of event cannot be fired twice. diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 5ec198a44..214939072 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -196,6 +196,13 @@ define([ postMessage("CLEAR_OWNED_CHANNEL", channel, cb); }; + common.getDeletedPads = function (cb) { + postMessage("GET_DELETED_PADS", null, function (obj) { + if (obj && obj.error) { return void cb(obj.error); } + cb(null, obj); + }); + }; + common.uploadComplete = function (cb) { postMessage("UPLOAD_COMPLETE", null, function (obj) { if (obj && obj.error) { return void cb(obj.error); } diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index e00500472..5c135a3fb 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -94,9 +94,27 @@ define([ return list; }; - var getCanonicalChannelList = function () { - return Util.deduplicateString(getUserChannelList()).sort(); + var getExpirableChannelList = function () { + var list = []; + store.userObject.getFiles([store.userObject.FILES_DATA]).forEach(function (id) { + var data = store.userObject.getFileData(id); + var edPublic = store.proxy.edPublic; + + // Push channels owned by someone else or channel that should have expired + // because of the expiration time + if ((data.owners && data.owners.indexOf(edPublic) === -1) || + data.expire < (+new Date())) { + list.push(Hash.hrefToHexChannelId(data.href)); + } + }); + return list; + }; + + var getCanonicalChannelList = function (expirable) { + var list = expirable ? getExpirableChannelList() : getUserChannelList(); + return Util.deduplicateString(list).sort(); }; + ////////////////////////////////////////////////////////////////// /////////////////////// RPC ////////////////////////////////////// ////////////////////////////////////////////////////////////////// @@ -172,6 +190,37 @@ define([ }); }; + var arePinsSynced = function (cb) { + if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } + + var list = getCanonicalChannelList(false); + var local = Hash.hashChannelList(list); + store.rpc.getServerHash(function (e, hash) { + if (e) { return void cb(e); } + cb(null, hash === local); + }); + }; + + var resetPins = function (cb) { + if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } + + var list = getCanonicalChannelList(false); + store.rpc.reset(list, function (e, hash) { + if (e) { return void cb(e); } + cb(null, hash); + }); + }; + + Store.getDeletedPads = function (data, cb) { + if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } + + //var list = getCanonicalChannelList(true); + + // TODO: rpc to get the deleted pads here and send this list in the callback + + cb([]); + }; + Store.uploadComplete = function (data, cb) { if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } store.rpc.uploadComplete(function (err, res) { @@ -196,27 +245,6 @@ define([ }); }; - var arePinsSynced = function (cb) { - if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } - - var list = getCanonicalChannelList(); - var local = Hash.hashChannelList(list); - store.rpc.getServerHash(function (e, hash) { - if (e) { return void cb(e); } - cb(null, hash === local); - }); - }; - - var resetPins = function (cb) { - if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } - - var list = getCanonicalChannelList(); - store.rpc.reset(list, function (e, hash) { - if (e) { return void cb(e); } - cb(null, hash); - }); - }; - Store.uploadChunk = function (data, cb) { store.rpc.send.unauthenticated('UPLOAD', data.chunk, function (e, msg) { cb({ @@ -579,6 +607,8 @@ define([ contains = true; pad.atime = +new Date(); pad.title = title; + pad.owners = owners; + pad.expire = expire; // If the href is different, it means we have a stronger one if (href !== pad.href) { isStronger = true; } diff --git a/www/common/outer/chainpad-netflux-worker.js b/www/common/outer/chainpad-netflux-worker.js index f136e2a1e..5c07cd93f 100644 --- a/www/common/outer/chainpad-netflux-worker.js +++ b/www/common/outer/chainpad-netflux-worker.js @@ -57,27 +57,11 @@ define([], function () { // shim between chainpad and netflux var msgIn = function (peerId, msg) { return msg.replace(/^cp\|/, ''); - - /*try { - var decryptedMsg = Crypto.decrypt(msg, validateKey); - return decryptedMsg; - } catch (err) { - console.error(err); - return msg; - }*/ }; var msgOut = function (msg) { if (readOnly) { return; } return msg; - /*try { - var cmsg = Crypto.encrypt(msg); - if (msg.indexOf('[4') === 0) { cmsg = 'cp|' + cmsg; } - return cmsg; - } catch (err) { - console.log(msg); - throw err; - }*/ }; var onMsg = function(peer, msg, wc, network, direct) { @@ -93,7 +77,9 @@ define([], function () { if (parsed.channel === wc.id && !validateKey) { validateKey = parsed.validateKey; } - padData = parsed; + if (parsed.channel === wc.id) { + padData = parsed; + } // We have to return even if it is not the current channel: // we don't want to continue with other channels messages here return; diff --git a/www/common/outer/store-rpc.js b/www/common/outer/store-rpc.js index 39895b68c..c1b69dab7 100644 --- a/www/common/outer/store-rpc.js +++ b/www/common/outer/store-rpc.js @@ -49,6 +49,9 @@ define([ case 'UNPIN_PADS': { Store.unpinPads(data, cb); break; } + case 'GET_DELETED_PADS': { + Store.getDeletedPads(data, cb); break; + } case 'GET_PINNED_USAGE': { Store.getPinnedUsage(data, cb); break; } diff --git a/www/common/sframe-protocol.js b/www/common/sframe-protocol.js index 96014c992..5035e2af2 100644 --- a/www/common/sframe-protocol.js +++ b/www/common/sframe-protocol.js @@ -201,6 +201,8 @@ define({ // Inner drive needs to send command and receive updates from the async store 'Q_DRIVE_USEROBJECT': true, 'Q_DRIVE_GETOBJECT': true, + // Get the pads deleted from the server by other users to remove them from the drive + 'Q_DRIVE_GETDELETED': true, // Store's userObject need to send log messages to inner to display them in the UI 'EV_DRIVE_LOG': true, // Refresh the drive when the drive has changed ('change' or 'remove' events) diff --git a/www/common/userObject.js b/www/common/userObject.js index ecead9f2b..223376963 100644 --- a/www/common/userObject.js +++ b/www/common/userObject.js @@ -380,6 +380,18 @@ define([ var trashpaths = _findFileInTrash([TRASH], file); return rootpaths.concat(templatepaths, trashpaths); }; + + // Get drive ids of files from their channel ids + exp.findChannels = function (channels) { + var allFilesList = files[FILES_DATA]; + var channels64 = channels.slice().map(Util.hexToBase64); + return getFiles([FILES_DATA]).filter(function (k) { + var data = allFilesList[k]; + var parsed = Hash.parsePadUrl(data.href); + return parsed.hashData && channels64.indexOf(parsed.hashData.channel) !== -1; + }); + }; + exp.search = function (value) { if (typeof(value) !== "string") { return []; } value = value.trim(); diff --git a/www/drive/inner.js b/www/drive/inner.js index b2a1a774b..afdf8165c 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -2975,6 +2975,14 @@ define([ refresh(); UI.removeLoadingScreen(); + + sframeChan.query('Q_DRIVE_GETDELETED', null, function (err, data) { + var ids = filesOp.findChannels(data); + ids.forEach(function (id) { + var paths = filesOp.findFile(id); + filesOp.delete(paths, refresh); + }); + }); }; var setHistory = function (bool, update) { @@ -3091,7 +3099,6 @@ define([ throw new Error("Corrupted drive"); } andThen(common, proxy); - UI.removeLoadingScreen(); var onDisconnect = APP.onDisconnect = function (noAlert) { setEditable(false); diff --git a/www/drive/main.js b/www/drive/main.js index 552a6a0c6..61afafb2f 100644 --- a/www/drive/main.js +++ b/www/drive/main.js @@ -56,6 +56,12 @@ define([ cb(obj); }); }); + sframeChan.on('Q_DRIVE_GETDELETED', function (data, cb) { + Cryptpad.getDeletedPads(function (err, obj) { + if (err) { return void console.error(err); } + cb(obj); + }); + }); Cryptpad.onNetworkDisconnect.reg(function () { sframeChan.event('EV_NETWORK_DISCONNECT'); }); From 86144eda22ed94d55936863598577b998aad2c0a Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 26 Jan 2018 12:26:58 +0100 Subject: [PATCH 12/72] Display a message about pads deleted from the server --- customize.dist/translations/messages.fr.js | 1 + customize.dist/translations/messages.js | 1 + www/common/outer/async-store.js | 4 ++-- www/drive/inner.js | 6 ++++++ 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index 83e652588..a9aa54448 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -415,6 +415,7 @@ define(function () { "Cette action supprimera votre CryptDrive et son historique de votre navigateur, mais les pads existeront toujours (de manière chiffrée) sur notre serveur."; out.fm_padIsOwned = "Vous êtes le propriétaire de ce pad"; out.fm_padIsOwnedOther = "Ce pad est la propriété d'un autre utilisateur"; + out.fm_deletedPads = "Ces pads n'existent plus sur le serveur, ils ont été supprimés de votre CryptDrive: {0}"; // File - Context menu out.fc_newfolder = "Nouveau dossier"; out.fc_rename = "Renommer"; diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index f096341b6..551e36516 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -417,6 +417,7 @@ define(function () { "This will remove your CryptDrive and its history from your browser, but your pads will still exist (encrypted) on our server."; out.fm_padIsOwned = "You are the owner of this pad"; out.fm_padIsOwnedOther = "This pad is owned by another user"; + out.fm_deletedPads = "These pads no longer exist on the server, they've been removed from your CryptDrive: {0}"; // File - Context menu out.fc_newfolder = "New folder"; out.fc_rename = "Rename"; diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 5c135a3fb..d8fb6ef3b 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -102,8 +102,8 @@ define([ // Push channels owned by someone else or channel that should have expired // because of the expiration time - if ((data.owners && data.owners.indexOf(edPublic) === -1) || - data.expire < (+new Date())) { + if ((data.owners && data.owners.length && data.owners.indexOf(edPublic) === -1) || + (data.expire && data.expire < (+new Date()))) { list.push(Hash.hrefToHexChannelId(data.href)); } }); diff --git a/www/drive/inner.js b/www/drive/inner.js index afdf8165c..ba1c8c4d8 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -1435,6 +1435,7 @@ define([ case FILES_DATA: pName = FILES_DATA_NAME; break; case SEARCH: pName = SEARCH_NAME; break; case RECENT: pName = RECENT_NAME; break; + case OWNED: pName = OWNED_NAME; break; default: pName = name; } return pName; @@ -2978,10 +2979,15 @@ define([ sframeChan.query('Q_DRIVE_GETDELETED', null, function (err, data) { var ids = filesOp.findChannels(data); + var titles = []; ids.forEach(function (id) { + var title = filesOp.getTitle(id); + titles.push(title); var paths = filesOp.findFile(id); filesOp.delete(paths, refresh); }); + if (!titles.length) { return; } + UI.log(Messages._getKey('fm_deletedPads', [titles.join(', ')])); }); }; From 462c229b172685b711b6724042c1b754655161dc Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 26 Jan 2018 12:52:25 +0100 Subject: [PATCH 13/72] Add links to features table --- customize.dist/pages.js | 9 +++++++++ customize.dist/src/less2/pages/page-features.less | 15 +++++++++++++++ customize.dist/translations/messages.fr.js | 2 ++ customize.dist/translations/messages.js | 2 ++ 4 files changed, 28 insertions(+) diff --git a/customize.dist/pages.js b/customize.dist/pages.js index 40ea81467..4de6e6594 100644 --- a/customize.dist/pages.js +++ b/customize.dist/pages.js @@ -100,6 +100,7 @@ define([ h('div.collapse.navbar-collapse.justify-content-end#menuCollapse', [ h('a.nav-item.nav-link', { href: '/what-is-cryptpad.html'}, Msg.topbar_whatIsCryptpad), h('a.nav-item.nav-link', { href: 'https://blog.cryptpad.fr/'}, Msg.blog), + h('a.nav-item.nav-link', { href: '/features.html'}, Msg.features), h('a.nav-item.nav-link', { href: '/contact.html'}, Msg.contact), h('a.nav-item.nav-link', { href: '/about.html'}, Msg.about), ].concat(rightLinks)) @@ -332,6 +333,11 @@ define([ h('td') ]), ]) + ]), + h('div#cp-features-register', [ + h('a', { + href: '/register/' + }, h('button.cp-features-register-button', 'Register for free')) ]) ]), infopageFooter() @@ -601,6 +607,9 @@ define([ setHTML(h('p.register-explanation'), Msg.register_explanation) ]), h('div#userForm.form-group.hidden.col-md-6', [ + h('a', { + href: '/features.html' + }, Msg.register_whyRegister), h('input.form-control#username', { type: 'text', autocomplete: 'off', diff --git a/customize.dist/src/less2/pages/page-features.less b/customize.dist/src/less2/pages/page-features.less index cc44bc89d..37f2be245 100644 --- a/customize.dist/src/less2/pages/page-features.less +++ b/customize.dist/src/less2/pages/page-features.less @@ -50,3 +50,18 @@ table#cp-features-table { } } +#cp-features-register { + text-align: center; + padding: 20px; +} +.cp-features-register-button { + font-size: 20px; + color: #fff; + background: @cryptpad_color_blue; + border: 2px solid @cryptpad_color_blue; + border-radius: 0; + &:hover { + transform: scale(1.05); + } +} + diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index a9aa54448..1de3f4856 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -475,6 +475,7 @@ define(function () { out.register_warning = "Zero Knowledge signifie que nous ne pouvons pas récupérer vos données si vous perdez vos identifiants."; out.register_alreadyRegistered = "Cet utilisateur existe déjà, souhaitez-vous vous connecter ?"; + out.register_whyRegister = "Pourquoi s'inscrire ?"; out.register_header = "Bienvenue dans CryptPad"; out.register_explanation = [ "

Faisons d'abord le point sur certaines choses

", @@ -708,6 +709,7 @@ define(function () { // features.html + out.features = "Fonctionnalités"; out.features_title = "Tableau des fonctionnalités"; out.features_feature = "Fonctionnalité"; out.features_anon = "Utilisateur anonyme"; diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index 551e36516..8b4f4502f 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -473,6 +473,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_header = "Welcome to CryptPad"; out.register_explanation = [ "

Lets go over a couple things first:

", @@ -718,6 +719,7 @@ define(function () { // features.html + out.features = "Features"; out.features_title = "Features table"; out.features_feature = "Feature"; out.features_anon = "Anonymous user"; From b4b51ed86af6ee900b1f50830f54734be9af5f6b Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 26 Jan 2018 15:24:07 +0100 Subject: [PATCH 14/72] add a subsystem for scheduling tasks --- .gitignore | 1 + server.js | 56 ++++++++++++++++------------- storage/tasks.js | 94 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 126 insertions(+), 25 deletions(-) create mode 100644 storage/tasks.js diff --git a/.gitignore b/.gitignore index fc1136152..741aedaf7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ datastore +tasks www/bower_components/* node_modules /config.js diff --git a/server.js b/server.js index a51c94343..5a2cbfbb8 100644 --- a/server.js +++ b/server.js @@ -9,6 +9,7 @@ var WebSocketServer = require('ws').Server; var NetfluxSrv = require('./node_modules/chainpad-server/NetfluxWebsocketSrv'); var Package = require('./package.json'); var Path = require("path"); +var nThen = require("nthen"); var config; try { @@ -198,32 +199,37 @@ if (config.httpSafePort) { var wsConfig = { server: httpServer }; -var createSocketServer = function (err, rpc) { - if(!config.useExternalWebsocket) { - if (websocketPort !== config.httpPort) { - console.log("setting up a new websocket server"); - wsConfig = { port: websocketPort}; - } - var wsSrv = new WebSocketServer(wsConfig); - Storage.create(config, function (store) { - NetfluxSrv.run(store, wsSrv, config, rpc); - }); - } -}; +var rpc; -var loadRPC = function (cb) { - config.rpc = typeof(config.rpc) === 'undefined'? './rpc.js' : config.rpc; +var nt = nThen(function (w) { + if (!config.enableTaskScheduling) { return; } + var Tasks = require("./storage/tasks"); - if (typeof(config.rpc) === 'string') { - // load pin store... - var Rpc = require(config.rpc); - Rpc.create(config, function (e, rpc) { - if (e) { throw e; } - cb(void 0, rpc); - }); - } else { - cb(); + console.log("loading task scheduler"); + Tasks.create(config, w(function (e, tasks) { + config.tasks = tasks; + })); +}).nThen(function (w) { + config.rpc = typeof(config.rpc) === 'undefined'? './rpc.js' : config.rpc; + if (typeof(config.rpc) !== 'string') { return; } + // load pin store... + var Rpc = require(config.rpc); + Rpc.create(config, w(function (e, _rpc) { + if (e) { + w.abort(); + throw e; + } + rpc = _rpc; + })); +}).nThen(function () { + if(config.useExternalWebsocket) { return; } + if (websocketPort !== config.httpPort) { + console.log("setting up a new websocket server"); + wsConfig = { port: websocketPort}; } -}; + var wsSrv = new WebSocketServer(wsConfig); + Storage.create(config, function (store) { + NetfluxSrv.run(store, wsSrv, config, rpc); + }); +}); -loadRPC(createSocketServer); diff --git a/storage/tasks.js b/storage/tasks.js new file mode 100644 index 000000000..b25a3a637 --- /dev/null +++ b/storage/tasks.js @@ -0,0 +1,94 @@ +var Fs = require("fs"); +var Path = require("path"); +var nacl = require("tweetnacl"); +var nThen = require("nthen"); + +var Tasks = module.exports; + +var encode = function (time, command, args) { + if (typeof(time) !== 'number') { return null; } + if (typeof(command) !== 'string') { return null; } + if (!Array.isArray(args)) { return [time, command]; } + return [time, command].concat(args); +}; + +var randomId = function () { + var bytes = Array.prototype.slice.call(nacl.randomBytes(16)); + return bytes.map(function (b) { + var n = Number(b & 0xff).toString(16); + return n.length === 1? '0' + n: n; + }).join(''); +}; + +var mkPath = function (env, id) { + return Path.join(env.root, id.slice(0, 2), id) + '.ndjson'; +}; + +var getFreeId = function (env, cb, tries) { + if (tries > 5) { return void cb('ETOOMANYTRIES'); } + + // generate a unique id + var id = randomId(); + + // derive a path from that id + var path = mkPath(env, id); + + Fs.stat(path, function (err) { + if (err && err.code === "ENOENT") { + cb(void 0, id); + } else { + getFreeId(env, cb); + } + }); +}; + +var write = function (env, task, cb) { + var str = JSON.stringify(task) + '\n'; + var id = nacl.util.encodeBase64(nacl.hash(nacl.util.decodeUTF8(str))).replace(/\//g, '-'); + + var path = mkPath(env, id); + nThen(function (w) { + // check if the file already exists... + Fs.stat(path, w(function (err) { + if (err && err.code === 'ENOENT') { return; } + w.abort(); cb(); + })); + }).nThen(function (w) { + // create the parent directory if it does not exist + var dir = id.slice(0, 2); + var dirpath = Path.join(env.root, dir); + + Fs.mkdir(dirpath, 0x1ff, w(function (err) { + if (err && err.code !== 'EEXIST') { + return void cb(err); + } + })); + }).nThen(function (w) { + // write the file to the path + Fs.writeFile(mkPath(env, id), str, function (e) { + if (e) { return void cb(e); } + cb(); + }); + }); +}; + +Tasks.create = function (config, cb) { + var env = { + root: config.taskPath || './tasks', + }; + + // make sure the path exists... + Fs.mkdir(env.root, 0x1ff, function (err) { + if (err && err.code !== 'EEXIST') { + throw err; + } + cb(void 0, { + write: function (time, command, args, cb) { + var task = encode(time, command, args); + write(env, task, cb); + }, + }); + }); +}; + + From 36550b781f6fc0261b304f281f5820c554d321db Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 26 Jan 2018 15:44:34 +0100 Subject: [PATCH 15/72] Don't check for stronger hash if we have an edit hash --- www/common/common-hash.js | 2 ++ www/common/cryptpad-common.js | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/www/common/common-hash.js b/www/common/common-hash.js index d62204ac4..2ebe18331 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -300,6 +300,8 @@ Version 1 var rHref = href || getRelativeHref(window.location.href); var parsed = parsePadUrl(rHref); if (!parsed.hash) { return false; } + // We can't have a stronger hash if we're already in edit mode + if (parsed.hashData && parsed.hashData.mode === 'edit') { return; } var stronger; Object.keys(recents).some(function (id) { var pad = recents[id]; diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 214939072..ad5141c03 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -557,6 +557,11 @@ define([ return void cb(null, hashes); } + if (hashes.editHash) { + // no need to find stronger if we already have edit hash + return void cb(null, hashes); + } + postMessage("GET_STRONGER_HASH", { href: window.location.href }, function (hash) { From 74deb60f0f250b80ec26c7c90f7677ab39346aca Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 26 Jan 2018 16:34:36 +0100 Subject: [PATCH 16/72] Fix issues with the new context menu in drive --- www/drive/inner.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/www/drive/inner.js b/www/drive/inner.js index ba1c8c4d8..b2e8359ba 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -802,6 +802,7 @@ define([ hide.push('properties'); hide.push('rename'); hide.push('openparent'); + hide.push('hashtag'); } if (containsFolder && paths.length > 1) { // Cannot open multiple folders @@ -911,6 +912,7 @@ define([ //var actions = []; var toShow = filterContextMenu(menuType, paths); var $actions = $contextMenu.find('a'); + $contextMenu.data('paths', paths); $actions = $actions.filter(function (i, el) { return toShow.some(function (className) { return $(el).is(className); }); }); @@ -922,9 +924,6 @@ define([ } else { $a.text($(el).text()); } - $(el).data('paths', paths); - //$(el).data('path', path); - //:$(el).data('element', $element); $container.append($a); $a.click(function() { $(el).click(); }); }); @@ -2733,6 +2732,7 @@ define([ var parsed = Hash.parsePadUrl(data.href); var channel = Util.base64ToHex(parsed.hashData.channel); n = n.nThen(function (waitFor) { + // XXX use the delete channel rpc sframeChan.query('Q_CONTACTS_CLEAR_OWNED_CHANNEL', channel, waitFor(function (e) { if (e) { return void console.error(e); } From 5089f1206d5c829d7249cf71a1c2b1d06bf92110 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 29 Jan 2018 10:38:00 +0100 Subject: [PATCH 17/72] Pad creation screen translations and link to settings --- customize.dist/src/less2/include/creation.less | 9 +++++++++ customize.dist/translations/messages.fr.js | 18 ++++++++++++++++++ customize.dist/translations/messages.js | 1 + www/common/common-hash.js | 2 +- www/common/common-ui-elements.js | 7 +++++++ www/settings/inner.js | 4 ++-- www/settings/main.js | 11 ++++++++++- 7 files changed, 48 insertions(+), 4 deletions(-) diff --git a/customize.dist/src/less2/include/creation.less b/customize.dist/src/less2/include/creation.less index c7b795e4f..aa94beb28 100644 --- a/customize.dist/src/less2/include/creation.less +++ b/customize.dist/src/less2/include/creation.less @@ -139,5 +139,14 @@ } } } + .cp-creation-settings { + justify-content: left; + a { + color: #0275d8; + &:hover { + color: lighten(#0275d8, 10%); + } + } + } } } diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index 1de3f4856..c0ebd20ff 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -842,6 +842,24 @@ 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_ownedTitle = "Type ode 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_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_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_createTitle = "Créer un pad"; + out.creation_createFromTemplate = "Depuis un modèle"; + out.creation_createFromScratch = "Nouveau pad vide"; + out.creation_settings = "Préférences des nouveaux pads"; // Properties about creation data out.creation_owners = "Propriétaires"; out.creation_ownedByOther = "Possédé par un autre utilisateur"; diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index 8b4f4502f..cf2ed10f3 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -876,6 +876,7 @@ define(function () { out.creation_createTitle = "Create a pad"; out.creation_createFromTemplate = "From template"; out.creation_createFromScratch = "From scratch"; + out.creation_settings = "New Pad settings"; // Properties about creation data out.creation_owners = "Owners"; out.creation_ownedByOther = "Owned by another user"; diff --git a/www/common/common-hash.js b/www/common/common-hash.js index 2ebe18331..870b19dfe 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -176,7 +176,7 @@ Version 1 secret.keys = Crypto.createEditCryptor(); secret.key = Crypto.createEditCryptor().editKeyStr; }; - if (!secretHash && !/#/.test(window.location.href)) { + if (!secretHash && !window.location.hash) { //!/#/.test(window.location.href)) { generate(); return secret; } else { diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index a11ec5859..d31fe0a70 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -1816,6 +1816,13 @@ define([ $button.click(function () { create(); }); + + // Settings button + var origin = common.getMetadataMgr().getPrivateData().origin; + $(h('div.cp-creation-settings', h('a', { + href: origin + '/settings/#creation', + target: '_blank' + }, Messages.creation_settings))).appendTo($creation); }; return UIElements; diff --git a/www/settings/inner.js b/www/settings/inner.js index 8df9c9564..5105ad2b7 100644 --- a/www/settings/inner.js +++ b/www/settings/inner.js @@ -75,7 +75,7 @@ define([ } }; - if (!AppConfig.dislayCreationScreen) { + if (!AppConfig.displayCreationScreen) { delete categories.creation; } if (AppConfig.disableFeedback) { @@ -789,7 +789,7 @@ define([ var $categories = $('
', {'class': 'cp-sidebarlayout-categories'}) .appendTo(APP.$leftside); APP.$usage = $('
', {'class': 'usage'}).appendTo(APP.$leftside); - var active = 'account'; + var active = privateData.category || 'account'; Object.keys(categories).forEach(function (key) { var $category = $('
', {'class': 'cp-sidebarlayout-category'}).appendTo($categories); if (key === 'account') { $category.append($('', {'class': 'fa fa-user-o'})); } diff --git a/www/settings/main.js b/www/settings/main.js index 42f501075..6b4dd8b8b 100644 --- a/www/settings/main.js +++ b/www/settings/main.js @@ -66,9 +66,18 @@ define([ Cryptpad.mergeAnonDrive(cb); }); }; + var category; + if (window.location.hash) { + category = window.location.hash.slice(1); + window.location.hash = ''; + } + var addData = function (obj) { + if (category) { obj.category = category; } + }; SFCommonO.start({ noRealtime: true, - addRpc: addRpc + addRpc: addRpc, + addData: addData }); }); }); From f134f11b7318abed25ce11d20406f97c309f075a Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 29 Jan 2018 12:40:09 +0100 Subject: [PATCH 18/72] new getDeletedPads rpc --- rpc.js | 40 ++++++++++++++++++++++++++++++++- www/common/outer/async-store.js | 30 +++++++++++++++---------- www/common/pinpad.js | 17 +++++++++++++- 3 files changed, 73 insertions(+), 14 deletions(-) diff --git a/rpc.js b/rpc.js index ebc6ae96d..dad806a5b 100644 --- a/rpc.js +++ b/rpc.js @@ -11,6 +11,8 @@ var Path = require("path"); var Https = require("https"); const Package = require('./package.json'); const Pinned = require('./pinned'); +const Saferphore = require("saferphore"); +const nThen = require("nthen"); var RPC = module.exports; @@ -355,6 +357,33 @@ var getMultipleFileSize = function (Env, channels, cb) { }); }; +/* accepts a list, and returns a sublist of channel or file ids which seem + to have been deleted from the server (file size 0) + + we might consider that we should only say a file is gone if fs.stat returns + ENOENT, but for now it's simplest to just rely on getFileSize... +*/ +var getDeletedPads = function (Env, channels, cb) { + if (!Array.isArray(channels)) { return cb('INVALID_LIST'); } + var L = channels.length; + + var sem = Saferphore.create(10); + var absentees = []; + nThen(function (w) { + for (var i = 0; i < L; i++) { + let channel = channels[i]; + sem.take(function (give) { + getFileSize(Env, channel, w(give(function (e, size) { + if (e) { return; } + if (size === 0) { absentees.push(channel); } + }))); + }); + } + }).nThen(function () { + cb(void 0, absentees); + }); +}; + var getTotalSize = function (Env, publicKey, cb) { var bytes = 0; return void getChannelList(Env, publicKey, function (channels) { @@ -1005,7 +1034,8 @@ var isUnauthenticatedCall = function (call) { 'GET_MULTIPLE_FILE_SIZE', 'IS_CHANNEL_PINNED', 'IS_NEW_CHANNEL', - 'GET_HISTORY_OFFSET' + 'GET_HISTORY_OFFSET', + 'GET_DELETED_PADS', ].indexOf(call) !== -1; }; @@ -1128,6 +1158,14 @@ RPC.create = function (config /*:Config_t*/, cb /*:(?Error, ?Function)=>void*/) } respond(e, [null, dict, null]); }); + case 'GET_DELETED_PADS': + return void getDeletedPads(Env, msg[1], function (e, list) { + if (e) { + WARN(e, msg[1]); + return respond(e); + } + respond(e, [null, list, null]); + }); case 'IS_CHANNEL_PINNED': return void isChannelPinned(Env, msg[1], function (isPinned) { respond(null, [null, isPinned, null]); diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index d8fb6ef3b..bf8879d6f 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -211,16 +211,6 @@ define([ }); }; - Store.getDeletedPads = function (data, cb) { - if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } - - //var list = getCanonicalChannelList(true); - - // TODO: rpc to get the deleted pads here and send this list in the callback - - cb([]); - }; - Store.uploadComplete = function (data, cb) { if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } store.rpc.uploadComplete(function (err, res) { @@ -337,6 +327,24 @@ define([ }); }; + Store.getDeletedPads = function (data, cb) { + if (!store.anon_rpc) { return void cb({error: 'ANON_RPC_NOT_READY'}); } + var list = getCanonicalChannelList(true); + if (!Array.isArray(list)) { + return void cb({error: 'INVALID_FILE_LIST'}); + } + + store.anon_rpc.send('GET_DELETED_PADS', list, function (e, res) { + console.log(e, res); + if (e) { return void cb({error: e}); } + if (res && res.length && Array.isArray(res[0])) { + cb(res[0]); + } else { + cb({error: 'UNEXPECTED_RESPONSE'}); + } + }); + }; + Store.initAnonRpc = function (data, cb) { require([ '/common/rpc.js', @@ -349,8 +357,6 @@ define([ }); }; - - ////////////////////////////////////////////////////////////////// /////////////////////// Store //////////////////////////////////// ////////////////////////////////////////////////////////////////// diff --git a/www/common/pinpad.js b/www/common/pinpad.js index 47eb97892..0bbaddd37 100644 --- a/www/common/pinpad.js +++ b/www/common/pinpad.js @@ -145,7 +145,22 @@ define([ if (response && response.length) { cb(void 0, response[0]); } else { - cb(); + cb('INVALID_RESPONSE'); + } + }); + }; + + exp.removeOwnedChannel = function (channel, cb) { + if (typeof(channel) !== 'string' || channel.length !== 32) { + // can't use this on files because files can't be owned... + return void cb('INVALID_ARGUMENTS'); + } + 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... + } else { + cb('INVALID_RESPONSE'); } }); }; From a1d9f44bbd196179f49b67d46da204a9be407da7 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 29 Jan 2018 12:45:38 +0100 Subject: [PATCH 19/72] Delete owned pads from server when removed from the owner's drive --- customize.dist/translations/messages.js | 3 +- www/common/outer/async-store.js | 2 + www/common/outer/userObject.js | 11 +++- www/common/sframe-common.js | 11 ++-- www/drive/inner.js | 73 +++++++++++++------------ 5 files changed, 54 insertions(+), 46 deletions(-) diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index cf2ed10f3..f68714e45 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -381,7 +381,8 @@ define(function () { out.fm_removePermanentlyDialog = "Are you sure you want to remove that element from your CryptDrive permanently?"; out.fm_removeSeveralDialog = "Are you sure you want to move these {0} elements to the trash?"; out.fm_removeDialog = "Are you sure you want to move {0} to the trash?"; - out.fm_deleteOwnedPads = "Are you sure you want to remove permanently this pad from the server?"; + out.fm_deleteOwnedPad = "Are you sure you want to remove permanently this pad from the server?"; + out.fm_deleteOwnedPads = "Are you sure you want to remove permanently these pads from the server?"; out.fm_restoreDialog = "Are you sure you want to restore {0} to its previous location?"; out.fm_unknownFolderError = "The selected or last visited directory no longer exist. Opening the parent folder..."; out.fm_contextMenuError = "Unable to open the context menu for that element. If the problem persist, try to reload the page."; diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index d8fb6ef3b..d8447fb2e 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -901,6 +901,8 @@ define([ var userObject = store.userObject = UserObject.init(proxy.drive, { pinPads: Store.pinPads, unpinPads: Store.unpinPads, + removeOwnedChannel: function () {}, // XXX + edPublic: store.proxy.edPublic, loggedIn: store.loggedIn, log: function (msg) { postMessage("DRIVE_LOG", msg); diff --git a/www/common/outer/userObject.js b/www/common/outer/userObject.js index 32e3e3b92..e50a5f219 100644 --- a/www/common/outer/userObject.js +++ b/www/common/outer/userObject.js @@ -17,8 +17,12 @@ define([ console.error("unpinPads was not provided"); }; var pinPads = config.pinPads; + var removeOwnedChannel = config.removeOwnedChannel || function () { + console.error("removeOwnedChannel was not provided"); + }; var loggedIn = config.loggedIn; var workgroup = config.workgroup; + var edPublic = config.edPublic; var ROOT = exp.ROOT; var FILES_DATA = exp.FILES_DATA; @@ -90,9 +94,12 @@ define([ exp.getFiles([FILES_DATA]).forEach(function (id) { if (filesList.indexOf(id) === -1) { var fd = exp.getFileData(id); - if (fd && fd.href) { - toClean.push(Hash.hrefToHexChannelId(fd.href)); + 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) { + removeOwnedChannel(channelId); } + if (channelId) { toClean.push(channelId); } spliceFileData(id); } }); diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index d2aaf90ea..029118b88 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -392,15 +392,12 @@ define([ UI.log(data.logText); }); - ctx.metadataMgr.onChange(function () { - try { - var feedback = ctx.metadataMgr.getPrivateData().feedbackAllowed; - Feedback.init(feedback); - } catch (e) { Feedback.init(false); } - }); - ctx.metadataMgr.onReady(waitFor()); }).nThen(function () { + try { + var feedback = ctx.metadataMgr.getPrivateData().feedbackAllowed; + Feedback.init(feedback); + } catch (e) { Feedback.init(false); } ctx.sframeChan.ready(); cb(funcs); }); diff --git a/www/drive/inner.js b/www/drive/inner.js index b2e8359ba..65547c60d 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -2682,9 +2682,11 @@ define([ $contextMenu.find('.cp-app-drive-context-delete').text(Messages.fc_remove) .attr('data-icon', 'fa-eraser'); } - var deletePaths = function (paths) { - var pathsList = []; - paths.forEach(function (p) { pathsList.push(p.path); }); + var deletePaths = function (paths, pathsList) { + pathsList = pathsList || []; + if (paths) { + paths.forEach(function (p) { pathsList.push(p.path); }); + } var msg = Messages._getKey("fm_removeSeveralPermanentlyDialog", [paths.length]); if (paths.length === 1) { msg = Messages.fm_removePermanentlyDialog; @@ -2695,6 +2697,33 @@ define([ filesOp.delete(pathsList, refresh); }); }; + var deleteOwnedPaths = function (paths, pathsList) { + pathsList = pathsList || []; + if (paths) { + paths.forEach(function (p) { pathsList.push(p.path); }); + } + var msgD = paths ? Messages.fm_deleteOwnedPads : Messages.fm_deleteContainsOwned; + UI.confirm(msgD, function(res) { + $(window).focus(); + if (!res) { return; } + // Try to delete each selected pad from server, and delete from drive if no error + var n = nThen(function () {}); + pathsList.forEach(function (p) { + var el = filesOp.find(p); + var data = filesOp.getFileData(el); + var parsed = Hash.parsePadUrl(data.href); + var channel = Util.base64ToHex(parsed.hashData.channel); + n = n.nThen(function (waitFor) { + // XXX use the delete channel rpc + sframeChan.query('Q_CONTACTS_CLEAR_OWNED_CHANNEL', channel, + waitFor(function (e) { + if (e) { return void console.error(e); } + filesOp.delete([p.path], refresh); + })); + }); + }); + }); + }; $contextMenu.on("click", "a", function(e) { e.stopPropagation(); var paths = $contextMenu.data('paths'); @@ -2720,28 +2749,7 @@ define([ moveElements(pathsList, [TRASH], false, refresh); } else if ($(this).hasClass('cp-app-drive-context-deleteowned')) { - var msgD = Messages.fm_deleteOwnedPads; - UI.confirm(msgD, function(res) { - $(window).focus(); - if (!res) { return; } - // Try to delete each selected pad from server, and delete from drive if no error - var n = nThen(function () {}); - paths.forEach(function (p) { - var el = filesOp.find(p.path); - var data = filesOp.getFileData(el); - var parsed = Hash.parsePadUrl(data.href); - var channel = Util.base64ToHex(parsed.hashData.channel); - n = n.nThen(function (waitFor) { - // XXX use the delete channel rpc - sframeChan.query('Q_CONTACTS_CLEAR_OWNED_CHANNEL', channel, - waitFor(function (e) { - if (e) { return void console.error(e); } - filesOp.delete([p.path], refresh); - })); - }); - }); - }); - return; + deleteOwnedPaths(paths); } else if ($(this).hasClass('cp-app-drive-context-open')) { paths.forEach(function (p) { @@ -2879,18 +2887,11 @@ define([ if (!$(elmt).data('path')) { return; } paths.push($(elmt).data('path')); }); - // If we are in the trash or anon pad or if we are holding the "shift" key, delete permanently, + if (!paths.length) { return; } + // If we are in the trash or anon pad or if we are holding the "shift" key, + // delete permanently if (!APP.loggedIn || isTrash || e.shiftKey) { - var msg = Messages._getKey("fm_removeSeveralPermanentlyDialog", [paths.length]); - if (paths.length === 1) { - msg = Messages.fm_removePermanentlyDialog; - } - - UI.confirm(msg, function(res) { - $(window).focus(); - if (!res) { return; } - filesOp.delete(paths, refresh); - }); + deletePaths(null, paths); return; } // else move to trash From 8e2c9ebfa325d529327eded45da09152623f4fd9 Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 29 Jan 2018 14:26:24 +0100 Subject: [PATCH 20/72] lint compliance or something --- rpc.js | 18 +++++++++++------- storage/tasks.js | 2 +- www/common/outer/async-store.js | 1 - 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/rpc.js b/rpc.js index dad806a5b..83d166e2e 100644 --- a/rpc.js +++ b/rpc.js @@ -369,15 +369,19 @@ var getDeletedPads = function (Env, channels, cb) { var sem = Saferphore.create(10); var absentees = []; + + var job = function (channel, wait) { + return function (give) { + getFileSize(Env, channel, wait(give(function (e, size) { + if (e) { return; } + if (size === 0) { absentees.push(channel); } + }))); + }; + }; + nThen(function (w) { for (var i = 0; i < L; i++) { - let channel = channels[i]; - sem.take(function (give) { - getFileSize(Env, channel, w(give(function (e, size) { - if (e) { return; } - if (size === 0) { absentees.push(channel); } - }))); - }); + sem.take(job(channels[i], w)); } }).nThen(function () { cb(void 0, absentees); diff --git a/storage/tasks.js b/storage/tasks.js index b25a3a637..85df70a8d 100644 --- a/storage/tasks.js +++ b/storage/tasks.js @@ -63,7 +63,7 @@ var write = function (env, task, cb) { return void cb(err); } })); - }).nThen(function (w) { + }).nThen(function () { // write the file to the path Fs.writeFile(mkPath(env, id), str, function (e) { if (e) { return void cb(e); } diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index bf8879d6f..7198c819f 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -335,7 +335,6 @@ define([ } store.anon_rpc.send('GET_DELETED_PADS', list, function (e, res) { - console.log(e, res); if (e) { return void cb({error: e}); } if (res && res.length && Array.isArray(res[0])) { cb(res[0]); From 1c39e8b1bb1283803849efceefc7fe5bb7e470a8 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 29 Jan 2018 15:17:20 +0100 Subject: [PATCH 21/72] Fix worker app --- www/worker/inner.js | 1 + www/worker/worker.js | 16 +++++++++------- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/www/worker/inner.js b/www/worker/inner.js index 7b2186d2c..3f7df3f16 100644 --- a/www/worker/inner.js +++ b/www/worker/inner.js @@ -32,6 +32,7 @@ define([ sFrameChan.onReady(waitFor()); }).nThen(function (/*waitFor*/) { var $container = $('#cp-app-worker-container'); + $('', {href:'http://localhost:3000/worker/', target:'_blank'}).text('other').appendTo($container); var $bar = $('.cp-toolbar-container'); var displayed = ['useradmin', 'newpad', 'limit', 'pageTitle']; diff --git a/www/worker/worker.js b/www/worker/worker.js index 095ef8980..f1e0c8a0f 100644 --- a/www/worker/worker.js +++ b/www/worker/worker.js @@ -29,16 +29,17 @@ require.config({ }); var i = 0; +var id = Math.floor(Math.random()*100000); onconnect = function(e) { console.log(e); console.log(i); var port = e.ports[0]; console.log('here'); - require([ - '/common/outer/async-store.js' - ], function (Store) { - console.log(Store); + //require([ + // '/common/outer/async-store.js' + //], function (Store) { + //console.log(Store); console.log(self.Proxy); var n = i; port.postMessage({state: 'READY'}); @@ -46,8 +47,9 @@ onconnect = function(e) { console.log('worker received'); console.log(e.data); port.postMessage('hello CryptPad'+n); + port.postMessage('This is '+id); }; - var data = { + /*var data = { query: function (cmd, data, cb) { console.log(cmd, data); }, @@ -64,7 +66,7 @@ onconnect = function(e) { }); port.postMessage('Store is connected!'); port.postMessage('Your username is ' + ret.store.proxy['cryptpad.username']); - }); + });*/ i++; - }); + //}); }; From bdafc101c9edb1ea141fbd077f153573fcf1276f Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 29 Jan 2018 15:17:53 +0100 Subject: [PATCH 22/72] Ability to remove owned channels from the drive --- customize.dist/translations/messages.fr.js | 3 ++- www/common/cryptpad-common.js | 3 +++ www/common/outer/async-store.js | 10 ++++++++-- www/common/outer/store-rpc.js | 3 +++ www/common/outer/userObject.js | 4 +++- www/common/sframe-common-outer.js | 3 +++ www/common/sframe-protocol.js | 3 +++ www/drive/inner.js | 12 ++++++------ 8 files changed, 31 insertions(+), 10 deletions(-) diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index c0ebd20ff..81003b3c6 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -377,7 +377,8 @@ define(function () { out.fm_emptyTrashDialog = "Êtes-vous sûr de vouloir vider la corbeille ?"; out.fm_removeSeveralPermanentlyDialog = "Êtes-vous sûr de vouloir supprimer ces {0} éléments de votre CryptDrive de manière permanente ?"; out.fm_removePermanentlyDialog = "Êtes-vous sûr de vouloir supprimer cet élément de votre CryptDrive de manière permanente ?"; - out.fm_deleteOwnedPads = "Êtes-vous sûr de vouloir supprimer définitivement ce pad du serveur ?"; + out.fm_deleteOwnedPad = "Êtes-vous sûr de vouloir supprimer définitivement ce pad du serveur ?"; + out.fm_deleteOwnedPads = "Êtes-vous sûr de vouloir supprimer définitivement ces pads du serveur ?"; out.fm_restoreDialog = "Êtes-vous sûr de vouloir restaurer {0} à son emplacement précédent ?"; out.fm_removeSeveralDialog = "Êtes-vous sûr de vouloir déplacer ces {0} éléments vers la corbeille ?"; out.fm_removeDialog = "Êtes-vous sûr de vouloir déplacer {0} vers la corbeille ?"; diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index ad5141c03..fcfab50f0 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -195,6 +195,9 @@ define([ common.clearOwnedChannel = function (channel, cb) { postMessage("CLEAR_OWNED_CHANNEL", channel, cb); }; + common.removeOwnedChannel = function (channel, cb) { + postMessage("REMOVE_OWNED_CHANNEL", channel, cb); + }; common.getDeletedPads = function (cb) { postMessage("GET_DELETED_PADS", null, function (obj) { diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index c0ac5b915..3b61acaa5 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -190,6 +190,13 @@ define([ }); }; + Store.removeOwnedChannel = function (data, cb) { + if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } + store.rpc.removeOwnedChannel(data, function (err) { + cb({error:err}); + }); + }; + var arePinsSynced = function (cb) { if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } @@ -335,7 +342,6 @@ define([ } store.anon_rpc.send('GET_DELETED_PADS', list, function (e, res) { - console.log(e, res); if (e) { return void cb({error: e}); } if (res && res.length && Array.isArray(res[0])) { cb(res[0]); @@ -907,7 +913,7 @@ define([ var userObject = store.userObject = UserObject.init(proxy.drive, { pinPads: Store.pinPads, unpinPads: Store.unpinPads, - removeOwnedChannel: function () {}, // XXX + removeOwnedChannel: Store.removeOwnedChannel, edPublic: store.proxy.edPublic, loggedIn: store.loggedIn, log: function (msg) { diff --git a/www/common/outer/store-rpc.js b/www/common/outer/store-rpc.js index c1b69dab7..9fe45556e 100644 --- a/www/common/outer/store-rpc.js +++ b/www/common/outer/store-rpc.js @@ -31,6 +31,9 @@ define([ case 'CLEAR_OWNED_CHANNEL': { Store.clearOwnedChannel(data, cb); break; } + case 'REMOVE_OWNED_CHANNEL': { + Store.removeOwnedChannel(data, cb); break; + } case 'UPLOAD_CHUNK': { Store.uploadChunk(data, cb); break; } diff --git a/www/common/outer/userObject.js b/www/common/outer/userObject.js index e50a5f219..3293dcb36 100644 --- a/www/common/outer/userObject.js +++ b/www/common/outer/userObject.js @@ -97,7 +97,9 @@ define([ 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) { - removeOwnedChannel(channelId); + removeOwnedChannel(channelId, function (obj) { + if (obj && obj.error) { console.error(obj.error); } + }); } if (channelId) { toClean.push(channelId); } spliceFileData(id); diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 55346db6a..0bbd40eea 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -501,6 +501,9 @@ define([ sframeChan.on('Q_CONTACTS_CLEAR_OWNED_CHANNEL', function (channel, cb) { Cryptpad.clearOwnedChannel(channel, cb); }); + sframeChan.on('Q_REMOVE_OWNED_CHANNEL', function (channel, cb) { + Cryptpad.removeOwnedChannel(channel, cb); + }); if (cfg.addRpc) { cfg.addRpc(sframeChan, Cryptpad, Utils); diff --git a/www/common/sframe-protocol.js b/www/common/sframe-protocol.js index 5035e2af2..46aa93348 100644 --- a/www/common/sframe-protocol.js +++ b/www/common/sframe-protocol.js @@ -209,6 +209,9 @@ define({ 'EV_DRIVE_CHANGE': true, 'EV_DRIVE_REMOVE': true, + // Remove an owned pad from the server + 'Q_REMOVE_OWNED_CHANNEL': true, + // Notifications about connection and disconnection from the network 'EV_NETWORK_DISCONNECT': true, 'EV_NETWORK_RECONNECT': true, diff --git a/www/drive/inner.js b/www/drive/inner.js index 65547c60d..5bb678a9c 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -2687,8 +2687,8 @@ define([ if (paths) { paths.forEach(function (p) { pathsList.push(p.path); }); } - var msg = Messages._getKey("fm_removeSeveralPermanentlyDialog", [paths.length]); - if (paths.length === 1) { + var msg = Messages._getKey("fm_removeSeveralPermanentlyDialog", [pathsList.length]); + if (pathsList.length === 1) { msg = Messages.fm_removePermanentlyDialog; } UI.confirm(msg, function(res) { @@ -2702,7 +2702,8 @@ define([ if (paths) { paths.forEach(function (p) { pathsList.push(p.path); }); } - var msgD = paths ? Messages.fm_deleteOwnedPads : Messages.fm_deleteContainsOwned; + var msgD = pathsList.length === 1 ? Messages.fm_deleteOwnedPad : + Messages.fm_deleteOwnedPads; UI.confirm(msgD, function(res) { $(window).focus(); if (!res) { return; } @@ -2714,11 +2715,10 @@ define([ var parsed = Hash.parsePadUrl(data.href); var channel = Util.base64ToHex(parsed.hashData.channel); n = n.nThen(function (waitFor) { - // XXX use the delete channel rpc - sframeChan.query('Q_CONTACTS_CLEAR_OWNED_CHANNEL', channel, + sframeChan.query('Q_REMOVE_OWNED_CHANNEL', channel, waitFor(function (e) { if (e) { return void console.error(e); } - filesOp.delete([p.path], refresh); + filesOp.delete([p], refresh); })); }); }); From 8a1c04ff5864589f40b99a4ce25d6fb1bc8471bf Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 29 Jan 2018 15:33:34 +0100 Subject: [PATCH 23/72] Missing french translation keys --- customize.dist/translations/messages.fr.js | 3 +++ customize.dist/translations/messages.js | 1 + 2 files changed, 4 insertions(+) diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index 81003b3c6..fa48452d4 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -570,7 +570,9 @@ define(function () { out.upload_uploadPending = "Vous avez déjà un fichier en cours d'importation. Souhaitez-vous l'annuler et importer ce nouveau fichier ?"; out.upload_success = "Votre fichier ({0}) a été importé avec succès et ajouté à votre CryptDrive."; out.upload_notEnoughSpace = "Il n'y a pas assez d'espace libre dans votre CryptDrive pour ce fichier."; + out.upload_notEnoughSpaceBrief = "Pas assez d'espace"; out.upload_tooLarge = "Ce fichier dépasse la taille maximale autorisée."; + out.upload_tooLargeBrief = 'Fichier trop volumineux'; out.upload_choose = "Choisir un fichier"; out.upload_pending = "En attente"; out.upload_cancelled = "Annulé"; @@ -580,6 +582,7 @@ define(function () { out.upload_mustLogin = "Vous devez vous connecter pour importer un fichier"; out.download_button = "Déchiffrer et télécharger"; out.download_mt_button = "Télécharger"; + out.download_resourceNotAvailable = "Le fichier demandé n'est pas disponible..."; out.todo_title = "CryptTodo"; out.todo_newTodoNamePlaceholder = "Décrivez votre tâche..."; diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index f68714e45..e8783ec74 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -742,6 +742,7 @@ define(function () { out.features_f_multiple = "Use on multiple devices"; out.features_f_multiple_notes = "Easy way to access your pads from any device"; out.features_f_logoutEverywhere = "Log out from other devices"; + out.features_f_logoutEverywhere_notes = ""; // Used in the French translation to explain out.features_f_templates = "Use templates"; out.features_f_templates_notes = "Create templates and create new pads from your templates"; out.features_f_profile = "Create a profile"; From 8294686f80dc0fd6a2c1b1b77c3abbf783506ec2 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 29 Jan 2018 15:37:23 +0100 Subject: [PATCH 24/72] Fix assert translations for deprecated keys --- customize.dist/messages.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/customize.dist/messages.js b/customize.dist/messages.js index 612994d54..65b306f42 100755 --- a/customize.dist/messages.js +++ b/customize.dist/messages.js @@ -79,7 +79,7 @@ define(req, function(Util, Default, Language) { }); Object.keys(translation).forEach(function (k) { if (/^_/.test(k) || k === 'driveReadme') { return; } - if (!Default[k]) { + if (typeof Default[k] === "undefined") { missing.push([code, k, 0]); } }); From 5db0274fc35b918b538b4d7b968102297ff6baff Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 29 Jan 2018 15:48:53 +0100 Subject: [PATCH 25/72] add default config for new server options --- config.example.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/config.example.js b/config.example.js index 8fb4acb00..0af71afbb 100644 --- a/config.example.js +++ b/config.example.js @@ -194,6 +194,18 @@ module.exports = { */ }, + /* some features may require that the server be able to schedule tasks + far into the future, such as: + > "three months from now, this channel should expire" + To disable these features, set 'enableTaskScheduling' to false + */ + enableTaskScheduling: true, + + /* if you would like the list of scheduled tasks to be stored in + a custom location, change the path below: + */ + taskPath: './tasks', + /* * By default, CryptPad also contacts our accounts server once a day to check for changes in * the people who have accounts. This check-in will also send the version of your CryptPad From de838a3184ac8bfc5807c93c6add797c63260003 Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 29 Jan 2018 16:12:35 +0100 Subject: [PATCH 26/72] fix usage bar styles --- customize.dist/src/less2/include/limit-bar.less | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/customize.dist/src/less2/include/limit-bar.less b/customize.dist/src/less2/include/limit-bar.less index 12f26ef4b..4c11183c4 100644 --- a/customize.dist/src/less2/include/limit-bar.less +++ b/customize.dist/src/less2/include/limit-bar.less @@ -8,9 +8,10 @@ width: 100%; margin-top: 20px; .cp-limit-bar { - padding: 5px; + display: inline-flex; + justify-content: center; + align-items: center; - display: inline-block; max-width: 100%; margin: 3px; box-sizing: border-box; @@ -18,7 +19,6 @@ background: white; position: relative; text-align: center; - vertical-align: middle; width: ~"calc(100% - 6px)"; height: 35px; line-height: 25px; From e80e8339c2ad4eec689e37742928982fe151141e Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 29 Jan 2018 16:16:42 +0100 Subject: [PATCH 27/72] Typo in translation --- customize.dist/translations/messages.fr.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index fa48452d4..b13f2bc7f 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -847,7 +847,7 @@ define(function () { // 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_ownedTitle = "Type ode pad"; + 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."; From ad2cde4705ecbcc60743106fc03939c68d9dd348 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 29 Jan 2018 19:04:00 +0100 Subject: [PATCH 28/72] Add warning about anonymous drive in localStorage --- customize.dist/translations/messages.fr.js | 3 ++- customize.dist/translations/messages.js | 3 ++- www/drive/inner.js | 5 +++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index b13f2bc7f..2c4bb6ec4 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -393,7 +393,8 @@ define(function () { out.updated_0_fm_info_trash = "Vider la corbeille permet de libérer de l'espace dans votre CryptDrive"; out.fm_info_trash = out.updated_0_fm_info_trash; out.fm_info_allFiles = 'Contient tous les fichiers de "Documents", "Fichiers non triés" et "Corbeille". Vous ne pouvez pas supprimer ou déplacer des fichiers depuis cet endroit.'; // Same here - out.fm_info_anonymous = 'Vous n\'êtes pas connecté, ces pads risquent donc d\'être supprimés (découvrez pourquoi). ' + + out.fm_info_anonymous = 'Vous n\'êtes pas connecté, ces pads seront donc supprimés après 3 mois d\'inactivité (découvrez pourquoi). ' + + 'Ils sont stockés dans votre navigateur donc nettoyer votre historique peut les faire disparaître.
' + 'Inscrivez-vous ou connectez-vous pour les maintenir en vie.'; out.fm_info_owned = "Vous êtes propriétaire des pads affichés dans cette catégorie. Cela signifie que vous pouvez choisir de les supprimer définitivement du serveur à n'importe quel moment. Ils seront alors inaccessibles pour tous les autres utilisateurs."; out.fm_alert_backupUrl = "Lien de secours pour ce CryptDrive.
" + diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index e8783ec74..32c61825f 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -396,7 +396,8 @@ define(function () { out.fm_info_trash = out.updated_0_fm_info_trash; out.fm_info_allFiles = 'Contains all the files from "Documents", "Unsorted" and "Trash". You can\'t move or remove files from here.'; // Same here out.fm_info_anonymous = 'You are not logged in so your pads will expire after 3 months (find out more). ' + - 'Sign up or Log in to keep them alive.'; + 'They are stored in your browser so clearing history may make them disappear.
' + + 'Sign up or Log in to keep them alive.
'; out.fm_info_owned = "You are the owner of the pads displayed here. This means you can remove them permanently from the server whenever you want. If you do so, other users won't be able to access them anymore."; out.fm_alert_backupUrl = "Backup link for this drive.
" + "It is highly recommended that you keep it secret.
" + diff --git a/www/drive/inner.js b/www/drive/inner.js index 5bb678a9c..11d96df42 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -1509,6 +1509,11 @@ define([ if (!APP.loggedIn) { msg = Messages.fm_info_anonymous; $box.html(msg); + $box.find('a[target!="_blank"]').click(function (e) { + e.preventDefault(); + var href = $(this).attr('href'); + common.gotoURL(href); + }); return $box; } if (!msg || APP.store['hide-info-' + path[0]] === '1') { From 0b1795e5a1fb52bdba6b0a23e0b18b163606521e Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 31 Jan 2018 12:08:55 +0100 Subject: [PATCH 29/72] Fix an issue preventing the share modal to be used more than once --- www/common/common-interface.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/www/common/common-interface.js b/www/common/common-interface.js index f35b361ba..c4e95ae63 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -317,11 +317,11 @@ define([ message = dialog.message(msg); } - var close = Util.once(function (el) { + var close = function (el) { var $el = $(el).fadeOut(150, function () { - $el.remove(); + $el.detach(); }); - }); + }; var navs = []; opt.buttons.forEach(function (b) { @@ -329,7 +329,7 @@ define([ var button = h('button', { tabindex: '1', 'class': b.className || '' }, b.name); $(button).click(function () { b.onClick(); - close($(this).parents('.alertify').first()); + close($(button).parents('.alertify').first()); }); if (b.keys && b.keys.length) { $(button).attr('data-keys', JSON.stringify(b.keys)); } navs.push(button); @@ -352,6 +352,7 @@ define([ if (!$(el).is(':visible')) { return; } $(el).click(); }, frame); + $(el).removeAttr('data-keys'); }); document.body.appendChild(frame); $(frame).focus(); From 91e42f1518549b6e526db46007a408ed4c2fd3db Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 31 Jan 2018 12:11:10 +0100 Subject: [PATCH 30/72] Fix the bug introduced in the share modal fix --- www/common/common-interface.js | 1 - 1 file changed, 1 deletion(-) diff --git a/www/common/common-interface.js b/www/common/common-interface.js index c4e95ae63..f20bb4224 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -352,7 +352,6 @@ define([ if (!$(el).is(':visible')) { return; } $(el).click(); }, frame); - $(el).removeAttr('data-keys'); }); document.body.appendChild(frame); $(frame).focus(); From fb192a2c457594f20a9583120826475468eb61ae Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 31 Jan 2018 14:42:34 +0100 Subject: [PATCH 31/72] Add a configurable background color in the file app in view mode --- www/common/application_config_internal.js | 5 +++++ www/file/inner.js | 2 ++ www/pad/inner.js | 7 +++++-- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/www/common/application_config_internal.js b/www/common/application_config_internal.js index 24c46b790..91520c99a 100644 --- a/www/common/application_config_internal.js +++ b/www/common/application_config_internal.js @@ -42,6 +42,11 @@ define(function() { '#800080', // purple ]; + // Background color in the apps with centered content: + // - file app in view mode + // - rich text app when editor's width reduced in settings + config.appBackgroundColor = '#666'; + // Set enableTemplates to false to remove the button allowing users to save a pad as a template // and remove the template category in CryptDrive config.enableTemplates = true; diff --git a/www/file/inner.js b/www/file/inner.js index 2dbe765ce..b4b327998 100644 --- a/www/file/inner.js +++ b/www/file/inner.js @@ -224,6 +224,8 @@ define([ if (decrypting) { return; } decrypting = true; displayFile(ev, sizeMb, function (err) { + $appContainer.css('background-color', + common.getAppConfig().appBackgroundColor); if (err) { UI.alert(err); } }); }; diff --git a/www/pad/inner.js b/www/pad/inner.js index b54dd7dd3..6945f6d45 100644 --- a/www/pad/inner.js +++ b/www/pad/inner.js @@ -31,6 +31,7 @@ define([ '/common/common-hash.js', '/common/common-util.js', '/bower_components/chainpad/chainpad.dist.js', + '/customize/application_config.js', '/bower_components/diff-dom/diffDOM.js', @@ -50,7 +51,8 @@ define([ ApiConfig, Hash, Util, - ChainPad) + ChainPad, + AppConfig) { var DiffDom = window.diffDOM; @@ -592,7 +594,8 @@ define([ } // Used in ckeditor-config.js Ckeditor.CRYPTPAD_URLARGS = ApiConfig.requireConf.urlArgs; - var newCss = '.cke_body_width { background: #666; height: 100%; }' + + var backColor = AppConfig.appBackgroundColor; + var newCss = '.cke_body_width { background: '+ backColor +'; height: 100%; }' + '.cke_body_width body {' + 'max-width: 50em; padding: 10px 30px; margin: 0 auto; min-height: 100%;'+ 'box-sizing: border-box;'+ From 80df45f257b0ac8d3258efe6e431e3510aa1e8c6 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 1 Feb 2018 10:09:08 +0100 Subject: [PATCH 32/72] 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 33/72] 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 34/72] 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 35/72] 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 36/72] 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 37/72] 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 38/72] 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 39/72] 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 40/72] 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 41/72] 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 42/72] 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 43/72] 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 44/72] 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 45/72] 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 46/72] 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 47/72] 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 48/72] 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 49/72] 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 50/72] 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 51/72] 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 52/72] 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 53/72] 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 54/72] 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 55/72] 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 56/72] 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 57/72] 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 58/72] 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 59/72] 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 60/72] 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 61/72] 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 62/72] 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 63/72] 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 64/72] 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 65/72] 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 66/72] 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 67/72] 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 68/72] 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 69/72] 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 70/72] 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 71/72] 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 299a2f4c080810faf0f6101deca5543e6c8f20a0 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 19 Feb 2018 12:09:57 +0100 Subject: [PATCH 72/72] FAQ placeholder --- config.example.js | 3 ++- customize.dist/pages.js | 36 +++++++++++++++++++++++++ customize.dist/src/less2/main.less | 1 + customize.dist/translations/messages.js | 24 +++++++++++++++++ 4 files changed, 63 insertions(+), 1 deletion(-) diff --git a/config.example.js b/config.example.js index 2c234aee4..7e728451a 100644 --- a/config.example.js +++ b/config.example.js @@ -126,7 +126,8 @@ module.exports = { 'about', 'contact', 'what-is-cryptpad', - 'features' + 'features', + 'faq' ], /* Limits, Donations, Subscriptions and Contact diff --git a/customize.dist/pages.js b/customize.dist/pages.js index a9fbeabde..37bdc5b42 100644 --- a/customize.dist/pages.js +++ b/customize.dist/pages.js @@ -373,6 +373,42 @@ define([ ]); }; + Pages['/faq.html'] = function () { + var categories = []; + var faq = Msg.faq; + Object.keys(faq).forEach(function (c) { + var questions = []; + Object.keys(faq[c]).forEach(function (q) { + var item = faq[c][q]; + if (typeof item !== "object") { return; } + var answer = h('p.cp-faq-questions-a'); + var question = h('p.cp-faq-questions-q'); + $(question).click(function () { + if ($(answer).is(':visible')) { + return void $(answer).slideUp(); + } + $(answer).slideDown(); + }); + questions.push(h('div.cp-faq-questions-items', [ + setHTML(question, item.q), + setHTML(answer, item.a) + ])); + }); + categories.push(h('div.cp-faq-category', [ + h('h3', faq[c].title), + h('div.cp-faq-category-questions', questions) + ])); + }); + return h('div#cp-main', [ + infopageTopbar(), + h('div.container.cp-container', [ + h('center', h('h1', Msg.faq_title)), + h('div.cp-faq-container', categories) + ]), + infopageFooter() + ]); + }; + Pages['/terms.html'] = function () { return h('div#cp-main', [ infopageTopbar(), diff --git a/customize.dist/src/less2/main.less b/customize.dist/src/less2/main.less index c1bb5bba4..dadbef539 100644 --- a/customize.dist/src/less2/main.less +++ b/customize.dist/src/less2/main.less @@ -10,6 +10,7 @@ body.cp-page-what-is-cryptpad { @import "./pages/page-what-is-cryptpad.less"; } body.cp-page-about { @import "./pages/page-about.less"; } body.cp-page-privacy { @import "./pages/page-privacy.less"; } body.cp-page-features { @import "./pages/page-features.less"; } +body.cp-page-faq { @import "./pages/page-faq.less"; } body.cp-page-terms { @import "./pages/page-terms.less"; } // Set the HTML style for the apps which shouldn't have a body scrollbar diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index 956aa2147..eaceee812 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -759,6 +759,30 @@ define(function () { out.features_f_storage_anon = "Pads deleted after 3 months"; out.features_f_storage_registered = "Free: 50MB
Premium: 5GB/20GB/50GB"; + // faq.html + + out.faq_link = "FAQ"; + out.faq_title = "Frequently Asked Questions"; + out.faq = {}; + out.faq.cat1 = { + title: 'Category 1', + q1: { + q: 'What is a pad?', + a: 'A realtime collaborative document...' + }, + q2: { + q: 'Question 2?', + a: '42' + } + }; + out.faq.cat2 = { + title: 'Category 2', + q1: { + q: 'A new question?', + a: 'The answer' + } + }; + // terms.html out.tos_title = "CryptPad Terms of Service";