From e216f7debe55b4141f84fc854d932d74fe9daa9d Mon Sep 17 00:00:00 2001 From: Weblate Date: Sun, 1 Nov 2020 17:42:01 +0100 Subject: [PATCH 01/84] Translated using Weblate (German) Currently translated at 100.0% (1371 of 1371 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/de/ Translated using Weblate (German) Currently translated at 99.9% (1370 of 1371 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/de/ --- www/common/translations/messages.de.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/www/common/translations/messages.de.json b/www/common/translations/messages.de.json index 781c20b65..6b5046aa0 100644 --- a/www/common/translations/messages.de.json +++ b/www/common/translations/messages.de.json @@ -1459,5 +1459,13 @@ "admin_setlimitHint": "Lege individuelle Begrenzungen für Benutzer anhand ihrer öffentlichen Schlüssel fest. Du kannst bestehende Regeln aktualisieren oder entfernen.", "access_destroyPad": "Dokument oder Ordner endgültig zerstören", "fm_shareFolderPassword": "Diesen Ordner mit einem Passwort schützen (optional)", - "fm_deletedFolder": "Gelöschter Ordner" + "fm_deletedFolder": "Gelöschter Ordner", + "tag_edit": "Ändern", + "tag_add": "Hinzufügen", + "loading_state_4": "Teams laden", + "loading_state_3": "Geteilte Ordner laden", + "loading_state_2": "Inhalte aktualisieren", + "loading_state_1": "Drive laden", + "loading_state_0": "Oberfläche vorbereiten", + "loading_state_5": "Dokument rekonstruieren" } From 174c71d130f75abe003bc8f1bbb22967da59727f Mon Sep 17 00:00:00 2001 From: Weblate Date: Tue, 3 Nov 2020 11:49:42 +0100 Subject: [PATCH 02/84] Translated using Weblate (French) Currently translated at 100.0% (1371 of 1371 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/fr/ --- www/common/translations/messages.fr.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/www/common/translations/messages.fr.json b/www/common/translations/messages.fr.json index 7d8a84efb..70909c44d 100644 --- a/www/common/translations/messages.fr.json +++ b/www/common/translations/messages.fr.json @@ -1465,5 +1465,7 @@ "loading_state_3": "Chargement des dossiers partagés", "loading_state_2": "Mise à jour du contenu", "loading_state_1": "Chargement du drive", - "loading_state_0": "Construction de l'interface" + "loading_state_0": "Construction de l'interface", + "tag_edit": "Modifier", + "tag_add": "Ajouter" } From 26ef547e8479702663d6a3fdcc823919ec72c5ea Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 8 Oct 2020 17:53:39 +0200 Subject: [PATCH 03/84] Import a template into a spreadsheet --- www/common/common-ui-elements.js | 1 + www/common/cryptpad-common.js | 1 + www/common/onlyoffice/inner.js | 133 ++++++++++++++++++++++++++---- www/common/sframe-common-outer.js | 8 ++ www/secureiframe/inner.js | 1 + 5 files changed, 129 insertions(+), 15 deletions(-) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 8b9415220..e5cdf873a 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -639,6 +639,7 @@ define([ button .click(common.prepareFeedback(type)) .click(function () { + if (callback) { return void callback(); } UIElements.openTemplatePicker(common, true); }); break; diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 696627cbe..d7c580f90 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -637,6 +637,7 @@ define([ if (!val) { return void cb('ENOENT'); } + if (data.oo) { return void cb(val); } // OnlyOffice template: are handled in inner try { // Try to fix the title before importing the template var parsed = JSON.parse(val); diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index 103381f62..11dfb0189 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -416,7 +416,7 @@ define([ clearTimeout(pendingChanges[key]); delete pendingChanges[key]; }); - if (APP.stopHistory) { APP.history = false; } + if (APP.stopHistory || APP.template) { APP.history = false; } startOO(blob, type, true); }; @@ -426,14 +426,15 @@ define([ var file = getFileType(); blob.name = (metadataMgr.getMetadataLazy().title || file.doc) + '.' + file.type; var data = { - hash: APP.history ? ooChannel.historyLastHash : ooChannel.lastHash, - index: APP.history ? ooChannel.currentIndex : ooChannel.cpIndex + hash: (APP.history || APP.template) ? ooChannel.historyLastHash : ooChannel.lastHash, + index: (APP.history || APP.template) ? ooChannel.currentIndex : ooChannel.cpIndex }; fixSheets(); ooChannel.ready = false; ooChannel.queue = []; data.callback = function () { + if (APP.template) { APP.template = false; } resetData(blob, file); }; @@ -1296,6 +1297,13 @@ define([ } } + if (APP.template) { + getEditor().setViewModeDisconnect(); + UI.removeLoadingScreen(); + makeCheckpoint(true); + return; + } + if (APP.history) { try { getEditor().asc_setRestriction(true); @@ -1829,6 +1837,108 @@ define([ pinImages(); }; + var loadCp = function (cp, keepQueue) { + loadLastDocument(cp, function () { + var file = getFileType(); + var type = common.getMetadataMgr().getPrivateData().ooType; + var blob = loadInitDocument(type, true); + if (!keepQueue) { ooChannel.queue = []; } + resetData(blob, file); + }, function (blob, file) { + if (!keepQueue) { ooChannel.queue = []; } + resetData(blob, file); + }); + }; + + var loadTemplate = function (href, pw, parsed) { + APP.history = true; + APP.template = true; + getEditor().setViewModeDisconnect(); + var content = parsed.content; + + // Get checkpoint + var hashes = content.hashes || {}; + var idx = sortCpIndex(hashes); + var lastIndex = idx[idx.length - 1]; + var lastCp = hashes[lastIndex]; + + // Current cp or initial hash (invalid hash ==> initial hash) + var toHash = lastCp.hash || 'NONE'; + // Last hash + var fromHash = 'NONE'; + + sframeChan.query('Q_GET_HISTORY_RANGE', { + href: href, + password: pw, + channel: content.channel, + lastKnownHash: fromHash, + toHash: toHash, + }, function (err, data) { + if (err) { return void console.error(err); } + if (!Array.isArray(data.messages)) { return void console.error('Not an array!'); } + + // The first "cp" in history is the empty doc. It doesn't include the first patch + // of the history + var initialCp = !lastCp.hash; + + var messages = (data.messages || []).slice(initialCp ? 0 : 1); + + ooChannel.queue = messages.map(function (obj) { + return { + hash: obj.serverHash, + msg: JSON.parse(obj.msg) + }; + }); + ooChannel.historyLastHash = ooChannel.lastHash; + ooChannel.currentIndex = ooChannel.cpIndex; + console.error(ooChannel.historyLastHash); + loadCp(lastCp, true); + }); + }; + + var openTemplatePicker = function () { + var metadataMgr = common.getMetadataMgr(); + var type = metadataMgr.getPrivateData().app; + var sframeChan = common.getSframeChannel(); + var pickerCfgInit = { + types: [type], + where: ['template'], + hidden: true + }; + var pickerCfg = { + types: [type], + where: ['template'], + }; + var onConfirm = function () { + common.openFilePicker(pickerCfg, function (data) { + if (data.type !== type) { return; } + UI.addLoadingScreen({hideTips: true}); + sframeChan.query('Q_OO_TEMPLATE_USE', { + href: data.href, + }, function (err, val) { + var parsed; + try { + parsed = JSON.parse(val); + } catch (e) { + console.error(e, val); + UI.removeLoadingScreen(); + return void UI.warn(Messages.error); + } + console.error(data); + loadTemplate(data.href, data.password, parsed); + }); + }); + }; + sframeChan.query("Q_TEMPLATE_EXIST", type, function (err, data) { + if (data) { + common.openFilePicker(pickerCfgInit); + onConfirm(); + } else { + UI.alert(Messages.template_empty); + } + }); + }; + config.onInit = function (info) { var privateData = metadataMgr.getPrivateData(); @@ -1850,6 +1960,7 @@ define([ Title.setToolbar(toolbar); if (window.CP_DEV_MODE) { + var $save = common.createButton('save', true, {}, function () { makeCheckpoint(true); }); @@ -1866,18 +1977,6 @@ define([ APP.stopHistory = true; makeCheckpoint(true); }; - var loadCp = function (cp, keepQueue) { - loadLastDocument(cp, function () { - var file = getFileType(); - var type = common.getMetadataMgr().getPrivateData().ooType; - var blob = loadInitDocument(type, true); - if (!keepQueue) { ooChannel.queue = []; } - resetData(blob, file); - }, function (blob, file) { - if (!keepQueue) { ooChannel.queue = []; } - resetData(blob, file); - }); - }; var onPatch = function (patch) { // Patch on the current cp ooChannel.send(JSON.parse(patch.msg)); @@ -1971,6 +2070,10 @@ define([ load: loadSnapshot }); toolbar.$drawer.append($snapshot); + + // Import template + var $template = common.createButton('importtemplate', true, {}, openTemplatePicker); + $template.appendTo(toolbar.$drawer); })(); } diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index bba393afb..b0feaee4f 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -1102,6 +1102,10 @@ define([ nSecret = Utils.Hash.getSecrets('drive', hash, password); } } + if (data.href) { + var _parsed = Utils.Hash.parsePadUrl(data.href); + nSecret = Utils.Hash.getSecrets(_parsed.type, _parsed.hash, data.password); + } var channel = nSecret.channel; var validate = nSecret.keys.validateKey; var crypto = Crypto.createEncryptor(nSecret.keys); @@ -1282,6 +1286,10 @@ define([ sframeChan.on('Q_TEMPLATE_USE', function (data, cb) { Cryptpad.useTemplate(data, Cryptget, cb); }); + sframeChan.on('Q_OO_TEMPLATE_USE', function (data, cb) { + data.oo = true; + Cryptpad.useTemplate(data, Cryptget, cb); + }); sframeChan.on('Q_TEMPLATE_EXIST', function (type, cb) { Cryptpad.listTemplates(type, function (err, templates) { cb(templates.length > 0); diff --git a/www/secureiframe/inner.js b/www/secureiframe/inner.js index 344a4fb39..c20d53a80 100644 --- a/www/secureiframe/inner.js +++ b/www/secureiframe/inner.js @@ -124,6 +124,7 @@ define([ } sframeChan.event("EV_SECURE_ACTION", { type: parsed.type, + password: data.password, href: data.url, name: data.name }); From 935df47018ff92671ffc3bf311ddbfcf128fb0bd Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 9 Oct 2020 15:24:32 +0200 Subject: [PATCH 04/84] Add templates for OnlyOffice in the PCS --- www/common/common-ui-elements.js | 2 +- www/common/onlyoffice/inner.js | 22 +++++++++++++++++----- www/common/sframe-common-outer.js | 24 ++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index e5cdf873a..a2f965113 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -2095,7 +2095,7 @@ define([ var sframeChan = common.getSframeChannel(); var metadataMgr = common.getMetadataMgr(); var privateData = metadataMgr.getPrivateData(); - var type = metadataMgr.getMetadataLazy().type; + var type = metadataMgr.getMetadataLazy().type || privateData.app; var fromFileData = privateData.fromFileData; var $body = $('body'); diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index 11dfb0189..8caddb18a 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -114,6 +114,7 @@ define([ }; var getEditor = function () { + if (!window.frames || !window.frames[0]) { return; } return window.frames[0].editor || window.frames[0].editorCell; }; @@ -1853,14 +1854,15 @@ define([ var loadTemplate = function (href, pw, parsed) { APP.history = true; APP.template = true; - getEditor().setViewModeDisconnect(); + var editor = getEditor(); + if (editor) { editor.setViewModeDisconnect(); } var content = parsed.content; // Get checkpoint var hashes = content.hashes || {}; var idx = sortCpIndex(hashes); var lastIndex = idx[idx.length - 1]; - var lastCp = hashes[lastIndex]; + var lastCp = hashes[lastIndex] || {}; // Current cp or initial hash (invalid hash ==> initial hash) var toHash = lastCp.hash || 'NONE'; @@ -2232,11 +2234,18 @@ define([ openRtChannel(function () { setMyId(); oldHashes = JSON.parse(JSON.stringify(content.hashes)); - loadDocument(newDoc, useNewDefault); initializing = false; + common.openPadChat(APP.onLocal); + + if (APP.startWithTemplate) { + var template = APP.startWithTemplate; + loadTemplate(template.href, template.password, template.content); + return; + } + + loadDocument(newDoc, useNewDefault); setEditable(!readOnly); UI.removeLoadingScreen(); - common.openPadChat(APP.onLocal); }); }; @@ -2360,8 +2369,11 @@ define([ })); SFCommon.create(waitFor(function (c) { APP.common = common = c; })); }).nThen(function (waitFor) { + common.getSframeChannel().on('EV_OO_TEMPLATE', function (data) { + APP.startWithTemplate = data; + }); common.handleNewFile(waitFor, { - noTemplates: true + //noTemplates: true }); }).nThen(function (/*waitFor*/) { andThen(common); diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index b0feaee4f..bc2175f62 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -1646,6 +1646,7 @@ define([ rtConfig.metadata.validateKey = (secret.keys && secret.keys.validateKey) || undefined; Utils.rtConfig = rtConfig; + var templatePw; nThen(function(waitFor) { if (data.templateId) { if (data.templateId === -1) { @@ -1654,11 +1655,34 @@ define([ } Cryptpad.getPadData(data.templateId, waitFor(function (err, d) { data.template = d.href; + templatePw = d.password; })); } }).nThen(function () { var cryptputCfg = $.extend(true, {}, rtConfig, {password: password}); if (data.template) { + // Start OO with a template... + // Cryptget and give href, password and content to inner + if (parsed.type === "sheet") { + var then = function () { + startRealtime(rtConfig); + cb(); + }; + var _parsed = Utils.Hash.parsePadUrl(data.template); + Cryptget.get(_parsed.hash, function (err, val) { + if (err || !val) { return void then(); } + try { + var parsed = JSON.parse(val); + sframeChan.event('EV_OO_TEMPLATE', { + href: data.template, + password: templatePw, + content: parsed + }); + } catch (e) { console.error(e); } + then(); + }, {password: templatePw}); + return; + } // Pass rtConfig to useTemplate because Cryptput will create the file and // we need to have the owners and expiration time in the first line on the // server From c1b9ca56850f8d0442888abba40e30ab78df6482 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 9 Oct 2020 15:51:57 +0200 Subject: [PATCH 05/84] Remove dev log --- www/common/onlyoffice/inner.js | 1 - 1 file changed, 1 deletion(-) diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index 8caddb18a..c265d2d2b 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -1893,7 +1893,6 @@ define([ }); ooChannel.historyLastHash = ooChannel.lastHash; ooChannel.currentIndex = ooChannel.cpIndex; - console.error(ooChannel.historyLastHash); loadCp(lastCp, true); }); }; From ef506eec008eadd662d4d78fc46e8110eb504a37 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 9 Oct 2020 16:10:39 +0200 Subject: [PATCH 06/84] Add error message on JS or LESS blocking error --- customize.dist/loading.js | 5 +++++ www/common/LessLoader.js | 7 ++++++- www/common/sframe-boot2.js | 3 +++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/customize.dist/loading.js b/customize.dist/loading.js index 32841f253..fe86bf266 100644 --- a/customize.dist/loading.js +++ b/customize.dist/loading.js @@ -260,6 +260,11 @@ button.primary:hover{ '

', '' ].join(''); + window.CryptPad_loadingError = function (err) { + document.querySelector('.cp-loading-spinner-container').setAttribute('style', 'display:none;'); + document.querySelector('#cp-loading-message').setAttribute('style', 'display:block;'); + document.querySelector('#cp-loading-message').innerText = err; + }; return function () { var intr; var append = function () { diff --git a/www/common/LessLoader.js b/www/common/LessLoader.js index b463a126c..2c3de14cc 100644 --- a/www/common/LessLoader.js +++ b/www/common/LessLoader.js @@ -167,7 +167,12 @@ define([ if (css) { return void loadSubmodulesAndInject(css, url, done, stack); } console.debug('CACHE MISS ' + url); ((/\.less([\?\#].*)?$/.test(url)) ? loadLess : loadCSS)(url, function (err, css) { - if (!css) { return void console.error(err); } + if (!css) { + if (window.CryptPad_loadingError) { + window.CryptPad_loadingError('LESS: ' + (err && err.message)); + } + return void console.error(err); + } var output = fixAllURLs(css, url); cachePut(url, output); loadSubmodulesAndInject(output, url, done, stack); diff --git a/www/common/sframe-boot2.js b/www/common/sframe-boot2.js index 0d0eaa90c..5a68237ad 100644 --- a/www/common/sframe-boot2.js +++ b/www/common/sframe-boot2.js @@ -42,6 +42,9 @@ define([ 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(); } + if (window.CryptPad_loadingError) { + window.CryptPad_loadingError(e); + } throw e; }; From 03a7e0e8eb9b4d446dfc414af219faef98758ef2 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 14 Oct 2020 15:20:56 +0200 Subject: [PATCH 07/84] New loading screen --- customize.dist/loading.js | 57 ++++++++++++++++++++- customize.dist/messages.js | 13 +++-- www/common/LessLoader.js | 7 +++ www/common/common-interface.js | 6 +++ www/common/outer/async-store.js | 32 ++++++++---- www/common/outer/sharedfolder.js | 13 ++++- www/common/outer/team.js | 20 +++++++- www/common/sframe-app-framework.js | 9 +--- www/common/sframe-chainpad-netflux-inner.js | 2 +- www/common/sframe-common-outer.js | 8 +-- www/common/sframe-common.js | 3 +- 11 files changed, 131 insertions(+), 39 deletions(-) diff --git a/customize.dist/loading.js b/customize.dist/loading.js index fe86bf266..dd269fa60 100644 --- a/customize.dist/loading.js +++ b/customize.dist/loading.js @@ -1,6 +1,6 @@ // dark #326599 // light #4591c4 -define([], function () { +define(['/customize/messages.js'], function (Messages) { var loadingStyle = (function(){/* #cp-loading { visibility: visible; @@ -199,6 +199,13 @@ p.cp-password-info{ white-space: nowrap; text-overflow: ellipsis; } +.cp-loading-progress-list li i { + width: 22px; +} +.cp-loading-progress-list li span{ + margin-left: 20px; +} + .cp-loading-progress-bar { height: 24px; background: white; @@ -257,9 +264,57 @@ button.primary:hover{ '
', '', '
', + '
', + '
', + '
', + '
', '

', '' ].join(''); + + // XXX + var types = ['less', 'drive', 'migrate', 'sf', 'team', 'pad']; + Messages.loading_state_0 = "Less"; + Messages.loading_state_1 = "Drive"; + Messages.loading_state_2 = "Migrate"; + Messages.loading_state_3 = "SF"; + Messages.loading_state_4 = "Team"; + Messages.loading_state_5 = "Pad"; + var current; + var makeList = function (data) { + var c = types.indexOf(data.type); + current = c; + var getLi = function (i) { + var check = (i < c || (i === c && data.progress === 100)) ? 'fa-check-square-o' + : 'fa-square-o'; + var percent = i < c ? '(100%)' : (i === c ? '('+Math.floor(data.progress)+'%)' : '(0%)'); + return '
  • '+Messages['loading_state_'+i]+'' + + ''+percent+''; + }; + var list = '
      '; + types.forEach(function (el, i) { + list += getLi(i); + }); + list += '
    '; + return list; + }; + var makeBar = function (data) { + var c = types.indexOf(data.type); + var l = types.length; + var p = (data.progress / l) + (100 * c / l); + var bar = '
    '+ + '
    '+ + '
    '; + return bar; + }; + + var updateLoadingProgress = function (data) { + var c = types.indexOf(data.type); + if (c < current) { return console.error(data); } + document.querySelector('.cp-loading-progress-list').innerHTML = makeList(data); + document.querySelector('.cp-loading-progress-container').innerHTML = makeBar(data); + }; + window.CryptPad_updateLoadingProgress = updateLoadingProgress; window.CryptPad_loadingError = function (err) { document.querySelector('.cp-loading-spinner-container').setAttribute('style', 'display:none;'); document.querySelector('#cp-loading-message').setAttribute('style', 'display:block;'); diff --git a/customize.dist/messages.js b/customize.dist/messages.js index 11d814540..40dbbfb95 100755 --- a/customize.dist/messages.js +++ b/customize.dist/messages.js @@ -52,13 +52,12 @@ require.config({ }); var req = [ - '/common/common-util.js', '/customize/application_config.js', '/customize/translations/messages.js' ]; if (language && map[language]) { req.push('/customize/translations/messages.' + language + '.js'); } -define(req, function(Util, AppConfig, Default, Language) { +define(req, function(AppConfig, Default, Language) { map.en = 'English'; var defaultLanguage = 'en'; @@ -78,15 +77,15 @@ define(req, function(Util, AppConfig, Default, Language) { var extend = function (a, b) { for (var k in b) { - if (Util.isObject(b[k])) { - a[k] = Util.isObject(a[k]) ? a[k] : {}; - extend(a[k], b[k]); - continue; - } if (Array.isArray(b[k])) { a[k] = b[k].slice(); continue; } + if (b[k] && typeof(b[k]) === "object") { + a[k] = (a[k] && typeof(a[k]) === "object" && !Array.isArray(a[k])) ? a[k] : {}; + extend(a[k], b[k]); + continue; + } a[k] = b[k] || a[k]; } }; diff --git a/www/common/LessLoader.js b/www/common/LessLoader.js index 2c3de14cc..0a8a1e914 100644 --- a/www/common/LessLoader.js +++ b/www/common/LessLoader.js @@ -150,6 +150,7 @@ define([ }).nThen(function () { cb(); }); }; + var idx = 0; module.exports.load = function (url /*:string*/, cb /*:()=>void*/, stack /*:?Array*/) { var btime = stack ? null : +new Date(); stack = stack || []; @@ -163,6 +164,12 @@ define([ cb(); }; stack.push(url); + if (window.CryptPad_updateLoadingProgress) { + window.CryptPad_updateLoadingProgress({ + type: 'less', + progress: idx++ + }); + } cacheGet(url, function (css) { if (css) { return void loadSubmodulesAndInject(css, url, done, stack); } console.debug('CACHE MISS ' + url); diff --git a/www/common/common-interface.js b/www/common/common-interface.js index 2e6c3a609..368d8f017 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -896,6 +896,11 @@ define([ } }; UI.updateLoadingProgress = function (data, isDrive) { + if (window.CryptPad_updateLoadingProgress) { + window.CryptPad_updateLoadingProgress(data); + } + // XXX + /* var $loading = $('#' + LOADING); if (!$loading.length || loading.error) { return; } $loading.find('.cp-loading-progress').show(); @@ -947,6 +952,7 @@ define([ } } } + */ }; UI.removeLoadingScreen = function (cb) { // Release the test blocker, hopefully every test has been registered. diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index e849ce20d..b2b3b5f68 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -1373,11 +1373,15 @@ define([ } } }; - var loadUniversal = function (Module, type, waitFor) { + var loadUniversal = function (Module, type, waitFor, clientId) { if (store.modules[type]) { return; } store.modules[type] = Module.init({ Store: Store, store: store, + updateLoadingProgress: function (data) { + data.type = "team"; + postMessage(clientId, 'LOADING_DRIVE', data); + }, updateMetadata: function () { broadcast([], "UPDATE_METADATA"); }, @@ -2480,9 +2484,6 @@ define([ addSharedFolderHandler(); nThen(function (waitFor) { - postMessage(clientId, 'LOADING_DRIVE', { - state: 2 - }); userObject.migrate(waitFor()); }).nThen(function (waitFor) { initAnonRpc(null, null, waitFor()); @@ -2490,22 +2491,25 @@ define([ }).nThen(function (waitFor) { Migrate(proxy, waitFor(), function (version, progress) { postMessage(clientId, 'LOADING_DRIVE', { - state: (2 + (version / 10)), + type: 'migrate', progress: progress }); }, store); }).nThen(function (waitFor) { - postMessage(clientId, 'LOADING_DRIVE', { - state: 3 - }); userObject.fixFiles(); - SF.loadSharedFolders(Store, store.network, store, userObject, waitFor); + SF.loadSharedFolders(Store, store.network, store, userObject, waitFor, function (obj) { + var data = { + type: 'sf', + progress: 100*obj.progress/obj.max + }; + postMessage(clientId, 'LOADING_DRIVE', data); + }); loadCursor(); loadOnlyOffice(); loadUniversal(Messenger, 'messenger', waitFor); store.messenger = store.modules['messenger']; loadUniversal(Profile, 'profile', waitFor); - loadUniversal(Team, 'team', waitFor); + loadUniversal(Team, 'team', waitFor, clientId); loadUniversal(History, 'history', waitFor); cleanFriendRequests(); }).nThen(function () { @@ -2607,6 +2611,12 @@ define([ if (!hash) { return void cb({error: '[Store.init] Unable to find or create a drive hash. Aborting...'}); } + + var updateProgress = function (data) { + data.type = 'drive'; + postMessage(clientId, 'LOADING_DRIVE', data); + }; + // No password for drive var secret = Hash.getSecrets('drive', hash); store.driveChannel = secret.channel; @@ -2620,6 +2630,7 @@ define([ userName: 'fs', logLevel: 1, ChainPad: ChainPad, + updateProgress: updateProgress, classic: true, }; var rt = window.rt = Listmap.create(listmapConfig); @@ -2643,7 +2654,6 @@ define([ && !drive['filesData']) { drive[Constants.oldStorageKey] = []; } - postMessage(clientId, 'LOADING_DRIVE', { state: 1 }); // Drive already exist: return the existing drive, don't load data from legacy store onReady(clientId, returned, cb); }) diff --git a/www/common/outer/sharedfolder.js b/www/common/outer/sharedfolder.js index f02ea9c5a..e34cd64df 100644 --- a/www/common/outer/sharedfolder.js +++ b/www/common/outer/sharedfolder.js @@ -320,9 +320,12 @@ define([ - userObject: userObject associated to the main drive - handler: a function (sfid, rt) called for each shared folder loaded */ - SF.loadSharedFolders = function (Store, network, store, userObject, waitFor) { + SF.loadSharedFolders = function (Store, network, store, userObject, waitFor, progress) { var shared = Util.find(store.proxy, ['drive', UserObject.SHARED_FOLDERS]) || {}; + var steps = Object.keys(shared).length; + var i = 1; var w = waitFor(); + progress = progress || function () {}; nThen(function (waitFor) { Object.keys(shared).forEach(function (id) { var sf = shared[id]; @@ -330,7 +333,13 @@ define([ network: network, store: store, isNewChannel: Store.isNewChannel - }, id, sf, waitFor()); + }, id, sf, waitFor(function () { + progress({ + progress: i, + max: steps + }); + i++; + })); }); }).nThen(function () { setTimeout(w); diff --git a/www/common/outer/team.js b/www/common/outer/team.js index e5a40505e..de9206511 100644 --- a/www/common/outer/team.js +++ b/www/common/outer/team.js @@ -328,7 +328,13 @@ define([ ctx.teams[id] = team; registerChangeEvents(ctx, team, proxy); SF.checkMigration(team.secondaryKey, proxy, team.userObject, waitFor()); - SF.loadSharedFolders(ctx.Store, ctx.store.network, team, team.userObject, waitFor); + SF.loadSharedFolders(ctx.Store, ctx.store.network, team, + team.userObject, waitFor, function (data) { + ctx.progress += 70/(ctx.numberOfTeams * data.max); + ctx.updateProgress({ + progress: ctx.progress + }); + }); }).nThen(function () { if (!team.rpc) { return; } var list = getTeamChannelList(ctx, id); @@ -361,6 +367,9 @@ define([ }; + // Progress: + // One team = (30/(#teams))% + // One shared folder = (70/(#teams * #folders))% var openChannel = function (ctx, teamData, id, _cb) { var cb = Util.once(Util.mkAsync(_cb)); @@ -526,6 +535,10 @@ define([ Feedback.send("TEAM_RIGHTS_OWNER"); } }).nThen(function () { + ctx.progress += 30/ctx.numberOfTeams; + ctx.updateProgress({ + progress: ctx.progress + }); onReady(ctx, id, lm, roster, keys, null, cb); }); }; @@ -1686,10 +1699,13 @@ define([ emit: emit, onReadyHandlers: {}, teams: {}, - updateMetadata: cfg.updateMetadata + updateMetadata: cfg.updateMetadata, + updateProgress: cfg.updateLoadingProgress, + progress: 0 }; var teams = store.proxy.teams = store.proxy.teams || {}; + ctx.numberOfTeams = Object.keys(teams).length; // Listen for changes in our access rights (if another worker receives edit access) ctx.store.proxy.on('change', ['teams'], function (o, n, p) { diff --git a/www/common/sframe-app-framework.js b/www/common/sframe-app-framework.js index beaf20b10..3097cf6d2 100644 --- a/www/common/sframe-app-framework.js +++ b/www/common/sframe-app-framework.js @@ -441,9 +441,9 @@ define([ var versionHashEl; var onInit = function () { UI.updateLoadingProgress({ - state: 2, + type: 'pad', progress: 0.1 - }, false); + }); stateChange(STATE.INITIALIZING); if ($('.cp-help-container').length) { var privateDat = cpNfInner.metadataMgr.getPrivateData(); @@ -471,8 +471,6 @@ define([ var newContentStr = cpNfInner.chainpad.getUserDoc(); if (state === STATE.DELETED) { return; } - UI.updateLoadingProgress({ state: -1 }, false); - if (toolbar) { // Check if we have a new chainpad instance toolbar.resetChainpad(cpNfInner.chainpad); @@ -708,9 +706,6 @@ define([ nThen(function (waitFor) { UI.addLoadingScreen(); SFCommon.create(waitFor(function (c) { common = c; })); - UI.updateLoadingProgress({ - state: 1 - }, false); }).nThen(function (waitFor) { common.getSframeChannel().onReady(waitFor()); }).nThen(function (waitFor) { diff --git a/www/common/sframe-chainpad-netflux-inner.js b/www/common/sframe-chainpad-netflux-inner.js index b6f4c6aaf..8e8bb84f2 100644 --- a/www/common/sframe-chainpad-netflux-inner.js +++ b/www/common/sframe-chainpad-netflux-inner.js @@ -140,7 +140,7 @@ define([ chainpad.message(content); if (isHistory && updateLoadingProgress) { updateLoadingProgress({ - state: 2, + type: 'pad', progress: isHistory }, false); isHistory++; diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index bc2175f62..94dc8ab14 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -131,13 +131,7 @@ define([ if (sframeChan) { sframeChan.event('EV_LOADING_INFO', data); } }); - Cryptpad.ready(waitFor(function () { - if (sframeChan) { - sframeChan.event('EV_LOADING_INFO', { - state: -1 - }); - } - }), { + Cryptpad.ready(waitFor(), { driveEvents: cfg.driveEvents, currentPad: currentPad }); diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index 8d4898911..c5f0ad26a 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -686,7 +686,8 @@ define([ }); ctx.sframeChan.on('EV_LOADING_INFO', function (data) { - UI.updateLoadingProgress(data, 'drive'); + //UI.updateLoadingProgress(data, 'drive'); + UI.updateLoadingProgress(data); }); ctx.sframeChan.on('EV_NEW_VERSION', function () { From 3171864a892add4d8e72326fff0bb2bf303ed50e Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 14 Oct 2020 15:21:26 +0200 Subject: [PATCH 08/84] lint compliance --- www/common/common-interface.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/common-interface.js b/www/common/common-interface.js index 368d8f017..9f1f08634 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -895,7 +895,7 @@ define([ todo(); } }; - UI.updateLoadingProgress = function (data, isDrive) { + UI.updateLoadingProgress = function (data) { if (window.CryptPad_updateLoadingProgress) { window.CryptPad_updateLoadingProgress(data); } From 4985ffae7503433adedab7ace300387f8fa18cbc Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 14 Oct 2020 17:32:15 +0200 Subject: [PATCH 09/84] Fix FOUC in the loading screen --- customize.dist/loading.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/customize.dist/loading.js b/customize.dist/loading.js index dd269fa60..e35a5e409 100644 --- a/customize.dist/loading.js +++ b/customize.dist/loading.js @@ -2,6 +2,17 @@ // light #4591c4 define(['/customize/messages.js'], function (Messages) { var loadingStyle = (function(){/* +@font-face { + font-family: 'Open Sans'; + src: url('/bower_components/open-sans-fontface/fonts/Regular/OpenSans-Regular.eot'); + src: url('/bower_components/open-sans-fontface/fonts/Regular/OpenSans-Regular.eot?#iefix') format('embedded-opentype'), + url('/bower_components/open-sans-fontface/fonts/Regular/OpenSans-Regular.woff') format('woff'), + url('/bower_components/open-sans-fontface/fonts/Regular/OpenSans-Regular.ttf') format('truetype'), + url('/bower_components/open-sans-fontface/fonts/Regular/OpenSans-Regular.svg#OpenSansRegular') format('svg'); + font-weight: normal; + font-style: normal; +} + #cp-loading { visibility: visible; position: fixed; @@ -19,6 +30,7 @@ define(['/customize/messages.js'], function (Messages) { flex-flow: column; justify-content: center; align-items: center; + font: 20px 'Open Sans', 'Helvetica Neue', sans-serif !important; } #cp-loading.cp-loading-hidden { opacity: 0; @@ -199,6 +211,17 @@ p.cp-password-info{ white-space: nowrap; text-overflow: ellipsis; } +.cp-loading-progress-list ul { + list-style: none; + padding-left: 0; +} +.cp-loading-progress-list li { + padding: 0px 5px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} .cp-loading-progress-list li i { width: 22px; } From a9e2ebc72a40a61ffa71828df8930d91011477c8 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 14 Oct 2020 17:53:13 +0200 Subject: [PATCH 10/84] Fix percent > 100 --- customize.dist/loading.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/customize.dist/loading.js b/customize.dist/loading.js index e35a5e409..cf9af5e39 100644 --- a/customize.dist/loading.js +++ b/customize.dist/loading.js @@ -310,7 +310,8 @@ button.primary:hover{ var getLi = function (i) { var check = (i < c || (i === c && data.progress === 100)) ? 'fa-check-square-o' : 'fa-square-o'; - var percent = i < c ? '(100%)' : (i === c ? '('+Math.floor(data.progress)+'%)' : '(0%)'); + var p = Math.min(Math.floor(data.progress), 100); + var percent = i < c ? '(100%)' : (i === c ? '('+p+'%)' : '(0%)'); return '
  • '+Messages['loading_state_'+i]+'' + ''+percent+''; }; @@ -324,7 +325,8 @@ button.primary:hover{ var makeBar = function (data) { var c = types.indexOf(data.type); var l = types.length; - var p = (data.progress / l) + (100 * c / l); + var progress = Math.min(data.progress, 100); + var p = (progress / l) + (100 * c / l); var bar = '
    '+ '
    '+ '
    '; From c4aa9ff8dc95c20e7992d03ca8ba1b416f46bcf1 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 15 Oct 2020 14:48:17 +0200 Subject: [PATCH 11/84] Add more steps to the loading progress bar --- customize.dist/loading.js | 18 +++++++++++++----- www/common/outer/async-store.js | 8 ++++++++ www/common/sframe-common.js | 7 +++++++ 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/customize.dist/loading.js b/customize.dist/loading.js index cf9af5e39..38eae2515 100644 --- a/customize.dist/loading.js +++ b/customize.dist/loading.js @@ -294,6 +294,7 @@ button.primary:hover{ '

    ', '' ].join(''); + var built = false; // XXX var types = ['less', 'drive', 'migrate', 'sf', 'team', 'pad']; @@ -334,18 +335,25 @@ button.primary:hover{ }; var updateLoadingProgress = function (data) { + if (!built) { return; } var c = types.indexOf(data.type); if (c < current) { return console.error(data); } - document.querySelector('.cp-loading-progress-list').innerHTML = makeList(data); - document.querySelector('.cp-loading-progress-container').innerHTML = makeBar(data); + try { + document.querySelector('.cp-loading-progress-list').innerHTML = makeList(data); + document.querySelector('.cp-loading-progress-container').innerHTML = makeBar(data); + } catch (e) { console.error(e); } }; window.CryptPad_updateLoadingProgress = updateLoadingProgress; window.CryptPad_loadingError = function (err) { - document.querySelector('.cp-loading-spinner-container').setAttribute('style', 'display:none;'); - document.querySelector('#cp-loading-message').setAttribute('style', 'display:block;'); - document.querySelector('#cp-loading-message').innerText = err; + if (!built) { return; } + try { + document.querySelector('.cp-loading-spinner-container').setAttribute('style', 'display:none;'); + document.querySelector('#cp-loading-message').setAttribute('style', 'display:block;'); + document.querySelector('#cp-loading-message').innerText = err; + } catch (e) { console.error(e); } }; return function () { + built = true; var intr; var append = function () { if (!document.body) { return; } diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index b2b3b5f68..64cb068b1 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -2488,6 +2488,10 @@ define([ }).nThen(function (waitFor) { initAnonRpc(null, null, waitFor()); initRpc(null, null, waitFor()); + postMessage(clientId, 'LOADING_DRIVE', { + type: 'migrate', + progress: 0 + }); }).nThen(function (waitFor) { Migrate(proxy, waitFor(), function (version, progress) { postMessage(clientId, 'LOADING_DRIVE', { @@ -2496,6 +2500,10 @@ define([ }); }, store); }).nThen(function (waitFor) { + postMessage(clientId, 'LOADING_DRIVE', { + type: 'sf', + progress: 0 + }); userObject.fixFiles(); SF.loadSharedFolders(Store, store.network, store, userObject, waitFor, function (obj) { var data = { diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index c5f0ad26a..82b52c9f7 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -619,6 +619,13 @@ define([ } window.CryptPad_sframe_common = true; + if (window.CryptPad_updateLoadingProgress) { + window.CryptPad_updateLoadingProgress({ + type: 'drive', + progress: 0 + }); + } + nThen(function (waitFor) { var msgEv = Util.mkEvent(); var iframe = window.parent; From bafd3405ae333c168589c8506b443608a7776acf Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 15 Oct 2020 14:58:56 +0200 Subject: [PATCH 12/84] More improvements --- customize.dist/loading.js | 5 +++-- www/common/LessLoader.js | 2 +- www/common/sframe-chainpad-netflux-inner.js | 7 +++++++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/customize.dist/loading.js b/customize.dist/loading.js index 38eae2515..338b5882e 100644 --- a/customize.dist/loading.js +++ b/customize.dist/loading.js @@ -297,7 +297,7 @@ button.primary:hover{ var built = false; // XXX - var types = ['less', 'drive', 'migrate', 'sf', 'team', 'pad']; + var types = ['less', 'drive', 'migrate', 'sf', 'team', 'pad', 'end']; Messages.loading_state_0 = "Less"; Messages.loading_state_1 = "Drive"; Messages.loading_state_2 = "Migrate"; @@ -309,7 +309,7 @@ button.primary:hover{ var c = types.indexOf(data.type); current = c; var getLi = function (i) { - var check = (i < c || (i === c && data.progress === 100)) ? 'fa-check-square-o' + var check = (i < c || (i === c && data.progress >= 100)) ? 'fa-check-square-o' : 'fa-square-o'; var p = Math.min(Math.floor(data.progress), 100); var percent = i < c ? '(100%)' : (i === c ? '('+p+'%)' : '(0%)'); @@ -318,6 +318,7 @@ button.primary:hover{ }; var list = '
      '; types.forEach(function (el, i) { + if (i >= 6) { return; } list += getLi(i); }); list += '
    '; diff --git a/www/common/LessLoader.js b/www/common/LessLoader.js index 0a8a1e914..5b52b3d2c 100644 --- a/www/common/LessLoader.js +++ b/www/common/LessLoader.js @@ -167,7 +167,7 @@ define([ if (window.CryptPad_updateLoadingProgress) { window.CryptPad_updateLoadingProgress({ type: 'less', - progress: idx++ + progress: 4*idx++ }); } cacheGet(url, function (css) { diff --git a/www/common/sframe-chainpad-netflux-inner.js b/www/common/sframe-chainpad-netflux-inner.js index 8e8bb84f2..abb1cebdf 100644 --- a/www/common/sframe-chainpad-netflux-inner.js +++ b/www/common/sframe-chainpad-netflux-inner.js @@ -149,6 +149,13 @@ define([ }); sframeChan.on('EV_RT_READY', function () { if (isReady) { return; } + if (updateLoadingProgress) { + updateLoadingProgress({ + type: 'end', + progress: 0 + }, false); + isHistory++; + } isReady = true; isHistory = false; chainpad.start(); From c9dae93c04af9f18752cdbe80f47127d0364741c Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 23 Oct 2020 10:15:41 +0530 Subject: [PATCH 13/84] optimize Util.throttle for extremely high call frequencies --- www/common/common-util.js | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/www/common/common-util.js b/www/common/common-util.js index 0e86ecc8c..eba7ad5f9 100644 --- a/www/common/common-util.js +++ b/www/common/common-util.js @@ -306,11 +306,44 @@ }; Util.throttle = function (f, ms) { + var last = 0; var to; - var g = function () { - clearTimeout(to); - to = setTimeout(Util.bake(f, Util.slice(arguments)), ms); + var args; + + var defer = function (delay) { + console.log("setTimeout(stuff, %s)", delay); + // no timeout: run function `f` in `ms` milliseconds + // unless `g` is called again in the meantime + to = setTimeout(function () { + // wipe the current timeout handler + to = undefined; + + // take the current time + var now = +new Date(); + // compute time passed since `last` + var diff = now - last; + if (diff < ms) { + // don't run `f` if `g` was called since this timeout was set + // instead calculate how much further in the future your next + // timeout should be scheduled + return void defer(ms - diff); + } + + // else run `f` with the most recently supplied arguments + f.apply(null, args); + }, ms); }; + + var g = function () { + // every time you call this function store the time + last = +new Date(); + // remember what arguments were passed + args = Util.slice(arguments); + // if there is a pending timeout then do nothing + if (to) { return; } + defer(ms); + }; + g.clear = function () { clearTimeout(to); to = undefined; From 106fb5edb47e99af70daf4830cf64f21dabd1c0f Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 23 Oct 2020 10:20:24 +0530 Subject: [PATCH 14/84] remove annoying console.log --- www/common/common-util.js | 1 - 1 file changed, 1 deletion(-) diff --git a/www/common/common-util.js b/www/common/common-util.js index eba7ad5f9..e781bc2eb 100644 --- a/www/common/common-util.js +++ b/www/common/common-util.js @@ -311,7 +311,6 @@ var args; var defer = function (delay) { - console.log("setTimeout(stuff, %s)", delay); // no timeout: run function `f` in `ms` milliseconds // unless `g` is called again in the meantime to = setTimeout(function () { From abb2a568bbf35f4e616610719eb49d3c4f748f3f Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 23 Oct 2020 10:35:29 +0530 Subject: [PATCH 15/84] fix an incorrect reference and add a small script to help profile timer accuracy --- scripts/tests/throttle-test.js | 34 ++++++++++++++++++++++++++++++++++ www/common/common-util.js | 2 +- 2 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 scripts/tests/throttle-test.js diff --git a/scripts/tests/throttle-test.js b/scripts/tests/throttle-test.js new file mode 100644 index 000000000..e84fc4a8a --- /dev/null +++ b/scripts/tests/throttle-test.js @@ -0,0 +1,34 @@ +var Util = require("../../lib/common-util"); + +(function (throttle) { + var last = 0; + var last_call = 0; + var f = Util.throttle(function (boop) { + var now = +new Date(); + if (last) { + console.log("last execution was %sms ago", now - last); + } else { + console.log("this is the first execution"); + } + last = now; + + //console.log('time of execution:', now); + console.log(boop); + }, 1000); + + [150, 250, 580, 850, 1500, 2200, 3990, 5000].forEach(function (delay) { + setTimeout(function () { + var now = +new Date(); + + if (last_call) { + console.log("last call was %sms ago", now - last_call); + } + + last_call = now; + //console.log("time of call for delay(%s):", delay, now); + f(delay); + }, delay); + }); +}(Util.throttle2)); + + diff --git a/www/common/common-util.js b/www/common/common-util.js index e781bc2eb..41797e7c8 100644 --- a/www/common/common-util.js +++ b/www/common/common-util.js @@ -330,7 +330,7 @@ // else run `f` with the most recently supplied arguments f.apply(null, args); - }, ms); + }, delay); }; var g = function () { From c9a3cad78b35407c1b65934d4653a64c70447e1f Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 23 Oct 2020 15:44:47 +0200 Subject: [PATCH 16/84] Fix various issues with restricted pads --- lib/historyKeeper.js | 4 ++ www/common/cryptget.js | 73 ++++++++++++++++++++++-- www/common/cryptpad-common.js | 93 ++++++++++++++++++++++++------- www/common/sframe-common-outer.js | 28 ++++++---- 4 files changed, 162 insertions(+), 36 deletions(-) diff --git a/lib/historyKeeper.js b/lib/historyKeeper.js index d95c0e99f..cfdb14717 100644 --- a/lib/historyKeeper.js +++ b/lib/historyKeeper.js @@ -80,6 +80,10 @@ module.exports.create = function (Env, cb) { return void cb(); } + // If the channel is restricted, send the history keeper ID so that they + // can try to authenticate + allowed.unshift(Env.id); + // otherwise they're not allowed. // respond with a special error that includes the list of keys // which would be allowed... diff --git a/www/common/cryptget.js b/www/common/cryptget.js index ab707a49b..e394788d7 100644 --- a/www/common/cryptget.js +++ b/www/common/cryptget.js @@ -1,12 +1,15 @@ define([ '/bower_components/chainpad-crypto/crypto.js', '/bower_components/chainpad-netflux/chainpad-netflux.js', + '/bower_components/netflux-websocket/netflux-client.js', '/common/common-util.js', '/common/common-hash.js', '/common/common-realtime.js', '/common/outer/network-config.js', + '/common/pinpad.js', + '/bower_components/nthen/index.js', '/bower_components/chainpad/chainpad.dist.js', -], function (Crypto, CPNetflux, Util, Hash, Realtime, NetConfig) { +], function (Crypto, CPNetflux, Netflux, Util, Hash, Realtime, NetConfig, Pinpad, nThen) { var finish = function (S, err, doc) { if (S.done) { return; } S.cb(err, doc); @@ -28,6 +31,50 @@ define([ } }; + var makeNetwork = function (cb) { + var wsUrl = NetConfig.getWebsocketURL(); + Netflux.connect(wsUrl).then(function (network) { + cb(null, network); + }, function (err) { + cb(err); + }); + }; + + var start = function (Session, config) { + // Create a network and authenticate with all our keys if necessary, + // then start chainpad-netflux + nThen(function (waitFor) { + if (Session.hasNetwork) { return; } + makeNetwork(waitFor(function (err, network) { + if (err) { return; } + config.network = network; + })); + }).nThen(function () { + Session.realtime = CPNetflux.start(config); + }); + }; + + var onRejected = function (config, Session, data, cb) { + // Check if we can authenticate + if (!Array.isArray(data) || !data.length || data[0].length !== 16) { + return void cb(true); + } + if (!Array.isArray(Session.accessKeys)) { return void cb(true); } + + // Authenticate + config.network.historyKeeper = data[0]; + nThen(function (waitFor) { + Session.accessKeys.forEach(function (obj) { + Pinpad.create(config.network, obj, waitFor(function (e) { + console.log('done', obj); + if (e) { console.error(e); } + })); + }); + }).nThen(function () { + cb(); + }); + }; + var makeConfig = function (hash, opt) { var secret; if (typeof(hash) === 'string') { @@ -67,7 +114,15 @@ define([ progress = progress || function () {}; var config = makeConfig(hash, opt); - var Session = { cb: cb, hasNetwork: Boolean(opt.network) }; + var Session = { + cb: cb, + accessKeys: opt.accessKeys, + hasNetwork: Boolean(opt.network) + }; + + config.onRejected = function (data, cb) { + onRejected(config, Session, data, cb); + }; config.onReady = function (info) { var rt = Session.session = info.realtime; @@ -95,7 +150,7 @@ define([ overwrite(config, opt); - Session.realtime = CPNetflux.start(config); + start(Session, config); }; var put = function (hash, doc, cb, opt) { @@ -105,7 +160,15 @@ define([ opt = opt || {}; var config = makeConfig(hash, opt); - var Session = { cb: cb, hasNetwork: Boolean(opt.network) }; + var Session = { + cb: cb, + accessKeys: opt.accessKeys, + hasNetwork: Boolean(opt.network) + }; + + config.onRejected = function (data, cb) { + onRejected(config, Session, data, cb); + }; config.onReady = function (info) { var realtime = Session.session = info.realtime; @@ -126,7 +189,7 @@ define([ }; overwrite(config, opt); - Session.session = CPNetflux.start(config); + start(Session, config); }; return { diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index d7c580f90..4613e5756 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -68,6 +68,38 @@ define([ }, cb); }; + common.getAccessKeys = function (cb) { + var keys = []; + Nthen(function (waitFor) { + // Push account keys + postMessage("GET", { + key: ['edPrivate'], + }, waitFor(function (obj) { + if (obj.error) { return; } + try { + keys.push({ + edPrivate: obj, + edPublic: Hash.getSignPublicFromPrivate(obj) + }); + } catch (e) { console.error(e); } + })); + // Push teams keys + postMessage("GET", { + key: ['teams'], + }, waitFor(function (obj) { + if (obj.error) { return; } + Object.keys(obj || {}).forEach(function (id) { + var t = obj[id]; + var _keys = t.keys.drive || {}; + if (!_keys.edPrivate) { return; } + keys.push(t.keys.drive); + }); + })); + }).nThen(function () { + cb(keys); + }); + }; + common.makeNetwork = function (cb) { require([ '/bower_components/netflux-websocket/netflux-client.js', @@ -629,6 +661,10 @@ define([ optsPut.password = password; })); } + common.getAccessKeys(waitFor(function (keys) { + optsGet.accessKeys = keys; + optsPut.accessKeys = keys; + })); }).nThen(function () { Crypt.get(parsed.hash, function (err, val) { if (err) { @@ -667,19 +703,28 @@ define([ password: data.password, initialState: parsed.type === 'poll' ? '{}' : undefined }; - Crypt.get(parsed.hash, _waitFor(function (err, _val) { - if (err) { - _waitFor.abort(); - return void cb(err); - } - try { - val = JSON.parse(_val); - fixPadMetadata(val, true); - } catch (e) { - _waitFor.abort(); - return void cb(e.message); - } - }), optsGet); + var next = _waitFor(); + Nthen(function (waitFor) { + // Authenticate in case the pad os restricted + common.getAccessKeys(waitFor(function (keys) { + optsGet.accessKeys = keys; + })); + }).nThen(function () { + Crypt.get(parsed.hash, function (err, _val) { + if (err) { + _waitFor.abort(); + return void cb(err); + } + try { + val = JSON.parse(_val); + fixPadMetadata(val, true); + next(); + } catch (e) { + _waitFor.abort(); + return void cb(e.message); + } + }, optsGet); + }); return; } @@ -742,9 +787,6 @@ define([ }).nThen(function () { Crypt.put(parsed2.hash, JSON.stringify(val), function () { cb(); - Crypt.get(parsed2.hash, function (err, val) { - console.warn(val); - }); }, optsPut); }); @@ -1007,7 +1049,7 @@ define([ oldSecret = Hash.getSecrets(parsed.type, parsed.hash, optsGet.password); oldChannel = oldSecret.channel; common.getPadMetadata({channel: oldChannel}, waitFor(function (metadata) { - oldMetadata = metadata; + oldMetadata = metadata || {}; })); common.getMetadata(waitFor(function (err, data) { if (err) { @@ -1059,6 +1101,11 @@ define([ if (expire) { optsPut.metadata.expire = (expire - (+new Date())) / 1000; // Lifetime in seconds } + }).nThen(function (waitFor) { + common.getAccessKeys(waitFor(function (keys) { + optsGet.accessKeys = keys; + optsPut.accessKeys = keys; + })); }).nThen(function (waitFor) { Crypt.get(parsed.hash, waitFor(function (err, val) { if (err) { @@ -1075,6 +1122,8 @@ define([ } }), optsGet); }).nThen(function (waitFor) { + optsPut.metadata.restricted = oldMetadata.restricted; + optsPut.metadata.allowed = oldMetadata.allowed; Crypt.put(newHash, cryptgetVal, waitFor(function (err) { if (err) { waitFor.abort(); @@ -1310,11 +1359,17 @@ define([ validateKey: newSecret.keys.validateKey }, }; + var optsGet = {}; Nthen(function (waitFor) { common.getPadAttribute('', waitFor(function (err, _data) { padData = _data; + optsGet.password = padData.password; }), href); + common.getAccessKeys(waitFor(function (keys) { + optsGet.accessKeys = keys; + optsPut.accessKeys = keys; + })); }).nThen(function (waitFor) { oldSecret = Hash.getSecrets(parsed.type, parsed.hash, padData.password); @@ -1393,9 +1448,7 @@ define([ waitFor.abort(); return void cb({ error: 'CANT_PARSE' }); } - }), { - password: padData.password - }); + }), optsGet); }).nThen(function (waitFor) { // Re-encrypt rtchannel oldRtChannel = Util.find(cryptgetVal, ['content', 'channel']); diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 94dc8ab14..c837a9b72 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -1382,8 +1382,10 @@ define([ }; var i = 0; sframeChan.on('Q_CRYPTGET', function (data, cb) { + var keys; var todo = function () { data.opts.network = cgNetwork; + data.opts.accessKeys = keys; Cryptget.get(data.hash, function (err, val) { cb({ error: err, @@ -1402,17 +1404,21 @@ define([ cgNetwork = undefined; } i++; - if (!cgNetwork) { - cgNetwork = true; - return void Cryptpad.makeNetwork(function (err, nw) { - console.log(nw); - cgNetwork = nw; - todo(); - }); - } else if (cgNetwork === true) { - return void whenCGReady(todo); - } - todo(); + + Cryptpad.getAccessKeys(function (_keys) { + keys = _keys; + if (!cgNetwork) { + cgNetwork = true; + return void Cryptpad.makeNetwork(function (err, nw) { + console.log(nw); + cgNetwork = nw; + todo(); + }); + } else if (cgNetwork === true) { + return void whenCGReady(todo); + } + todo(); + }); }); sframeChan.on('EV_CRYPTGET_DISCONNECT', function () { if (!cgNetwork) { return; } From d3dd5f991d6df93ef1944678d09475bcdfa053fd Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 26 Oct 2020 17:24:35 +0530 Subject: [PATCH 17/84] proposed nginx configuration to enable XLSX export without disabling print from other apps --- docs/example.nginx.conf | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/example.nginx.conf b/docs/example.nginx.conf index 117eb2cc4..324df90c0 100644 --- a/docs/example.nginx.conf +++ b/docs/example.nginx.conf @@ -57,11 +57,6 @@ server { add_header Access-Control-Allow-Origin "*"; # add_header X-Frame-Options "SAMEORIGIN"; - # Enable SharedArrayBuffer in Firefox (for .xlsx export) - add_header Cross-Origin-Resource-Policy cross-origin; - add_header Cross-Origin-Opener-Policy same-origin; - add_header Cross-Origin-Embedder-Policy require-corp; - # Insert the path to your CryptPad repository root here root /home/cryptpad/cryptpad; index index.html; @@ -113,6 +108,14 @@ server { if ($uri = "/sheet/inner.html") { set $unsafe 1; } if ($uri ~ ^\/common\/onlyoffice\/.*\/index\.html.*$) { set $unsafe 1; } + set $coop ''; + if ($uri ~ ^\/sheet\/.*$) { set $coop 'same-origin'; } + + # Enable SharedArrayBuffer in Firefox (for .xlsx export) + add_header Cross-Origin-Resource-Policy cross-origin; + add_header Cross-Origin-Opener-Policy $coop; + add_header Cross-Origin-Embedder-Policy require-corp; + # everything except the sandbox domain is a privileged scope, as they might be used to handle keys if ($host != $sandbox_domain) { set $unsafe 0; } From 5cf8ca70e684100a42d88839a3f7c5203ea04140 Mon Sep 17 00:00:00 2001 From: ansuz Date: Mon, 26 Oct 2020 17:34:34 +0530 Subject: [PATCH 18/84] don't pin falsey document ids --- lib/commands/pin-rpc.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/commands/pin-rpc.js b/lib/commands/pin-rpc.js index 8b111438d..93d922620 100644 --- a/lib/commands/pin-rpc.js +++ b/lib/commands/pin-rpc.js @@ -162,7 +162,7 @@ Pinning.pinChannel = function (Env, safeKey, channels, cb) { // only pin channels which are not already pinned var toStore = channels.filter(function (channel) { - return pinned.indexOf(channel) === -1; + return channel && pinned.indexOf(channel) === -1; }); if (toStore.length === 0) { @@ -204,7 +204,7 @@ Pinning.unpinChannel = function (Env, safeKey, channels, cb) { // only unpin channels which are pinned var toStore = channels.filter(function (channel) { - return pinned.indexOf(channel) !== -1; + return channel && pinned.indexOf(channel) !== -1; }); if (toStore.length === 0) { From 6f76ee445c650207eb328ffe0e9ac23668e90e35 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 26 Oct 2020 14:57:54 +0100 Subject: [PATCH 19/84] Fix UI issues when printing code --- customize.dist/src/less2/include/app-print.less | 9 +++++---- customize.dist/src/print-landscape.css | 5 +++++ customize.dist/src/print.css | 4 ++++ www/code/app-code.less | 1 - www/code/inner.js | 1 + www/pad/inner.html | 2 +- www/pad/inner.js | 7 +++++++ www/slide/inner.js | 1 + 8 files changed, 24 insertions(+), 6 deletions(-) create mode 100644 customize.dist/src/print-landscape.css create mode 100644 customize.dist/src/print.css diff --git a/customize.dist/src/less2/include/app-print.less b/customize.dist/src/less2/include/app-print.less index e4c344b20..7f42f100b 100644 --- a/customize.dist/src/less2/include/app-print.less +++ b/customize.dist/src/less2/include/app-print.less @@ -9,10 +9,6 @@ max-height: none; overflow: visible; display: block; - @page { - margin: 0; - size: landscape; - } // Slide app body.cp-app-slide { display: block; @@ -48,11 +44,15 @@ // Code app body.cp-app-code { display: block; + height: auto; * { visibility: hidden; height: auto; max-height: none; } + .cp-toolbar-userlist-drawer { + display: none; + } #cme_toolbox { display: none; } @@ -64,6 +64,7 @@ #cp-app-code-preview { display: block; #cp-app-code-print { + font-size: 20px; display: block; overflow: visible !important; width: 100%; diff --git a/customize.dist/src/print-landscape.css b/customize.dist/src/print-landscape.css new file mode 100644 index 000000000..26a9d495e --- /dev/null +++ b/customize.dist/src/print-landscape.css @@ -0,0 +1,5 @@ +@page { + margin: 0; + size: A4 landscape; +} + diff --git a/customize.dist/src/print.css b/customize.dist/src/print.css new file mode 100644 index 000000000..1baf803d4 --- /dev/null +++ b/customize.dist/src/print.css @@ -0,0 +1,4 @@ +@page { + margin: 3cm; + size: A4 portrait; +} diff --git a/www/code/app-code.less b/www/code/app-code.less index 0ca86fd8b..6557d39f6 100644 --- a/www/code/app-code.less +++ b/www/code/app-code.less @@ -136,7 +136,6 @@ #cp-app-code-print { position: relative; display: none; - margin: 50px; .markdown_preformatted-code; .markdown_gfm-table(black); } diff --git a/www/code/inner.js b/www/code/inner.js index 1956ab3f4..68b58bce4 100644 --- a/www/code/inner.js +++ b/www/code/inner.js @@ -42,6 +42,7 @@ define([ 'cm/addon/fold/comment-fold', 'cm/addon/display/placeholder', + 'css!/customize/src/print.css', 'less!/code/app-code.less' ], function ( diff --git a/www/pad/inner.html b/www/pad/inner.html index b7ccd4d00..e4dbcdf95 100644 --- a/www/pad/inner.html +++ b/www/pad/inner.html @@ -1,5 +1,5 @@ - + diff --git a/www/pad/inner.js b/www/pad/inner.js index 19c2063d8..eeabbb337 100644 --- a/www/pad/inner.js +++ b/www/pad/inner.js @@ -47,6 +47,7 @@ define([ '/bower_components/diff-dom/diffDOM.js', + 'css!/customize/src/print.css', 'css!/bower_components/bootstrap/dist/css/bootstrap.min.css', 'css!/bower_components/components-font-awesome/css/font-awesome.min.css', 'less!/pad/app-pad.less' @@ -500,6 +501,12 @@ define([ var mkPrintButton = function (framework, editor) { var $printButton = framework._.sfCommon.createButton('print', true); $printButton.click(function () { + /* + // NOTE: alternative print system in case we keep having more issues on Firefox + var $iframe = $('html').find('iframe'); + var iframe = $iframe[0].contentWindow; + iframe.print(); + */ editor.execCommand('print'); framework.feedback('PRINT_PAD'); }); diff --git a/www/slide/inner.js b/www/slide/inner.js index 3af7185c5..de30eb9fc 100644 --- a/www/slide/inner.js +++ b/www/slide/inner.js @@ -15,6 +15,7 @@ define([ 'css!/bower_components/bootstrap/dist/css/bootstrap.min.css', 'css!/bower_components/components-font-awesome/css/font-awesome.min.css', + 'css!/customize/src/print-landscape.css', 'less!/slide/app-slide.less', 'css!cm/lib/codemirror.css', From 9c16312dad4cb8780d58ad6144929c1a0ed447a2 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 26 Oct 2020 16:45:36 +0100 Subject: [PATCH 20/84] Improve tag UI --- .../src/less2/include/tokenfield.less | 16 ++++ www/common/common-interface.js | 76 ++++++++++++++++++- 2 files changed, 91 insertions(+), 1 deletion(-) diff --git a/customize.dist/src/less2/include/tokenfield.less b/customize.dist/src/less2/include/tokenfield.less index 42a06624c..e3d2c3e5d 100644 --- a/customize.dist/src/less2/include/tokenfield.less +++ b/customize.dist/src/less2/include/tokenfield.less @@ -20,6 +20,22 @@ margin: 0 10px; padding: 0; width: ~"calc(100% - 20px)"; + span.tokenfield-empty { + font-size: 14px; + font-style: italic; + color: lighten(@cryptpad_text_col, 10%); + } + .cp-tokenfield-container { + width: 100%; + } + .cp-tokenfield-form { + display: flex; + width: 100%; + input { + flex: 1; + min-width: 0 !important; + } + } .token { box-sizing: border-box; display: inline-flex; diff --git a/www/common/common-interface.js b/www/common/common-interface.js index 9f1f08634..244b6856f 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -280,8 +280,82 @@ define([ }; var $root = $t.parent(); + + Messages.add = "Add"; // XXX + Messages.edit = "Edit"; // XXX + var $input = $root.find('.token-input'); + var $button = $(h('button.btn.btn-primary', [ + h('i.fa.fa-plus'), + h('span', Messages.add) + ])); + + + $button.click(function () { + $t.tokenfield('createToken', $input.val()); + }); + + var $container = $(h('span.cp-tokenfield-container')); + var $form = $(h('span.cp-tokenfield-form')); + $container.insertAfter($input); + + // Fix the UI to keep the "add" or "edit" button at the correct location + var isEdit = false; + var called = false; + var resetUI = function () { + called = true; + setTimeout(function () { + $container.find('.tokenfield-empty').remove(); + var $tokens = $root.find('.token').prependTo($container); + if (!$tokens.length) { + $container.prepend(h('span.tokenfield-empty', Messages.kanban_noTags)); + } + $form.append($input); + $form.append($button); + if (isEdit) { $button.find('span').text(Messages.edit); } + else { $button.find('span').text(Messages.add); } + $container.append($form); + $input.focus(); + isEdit = false; + called = false; + }); + }; + resetUI(); + + $t.on('tokenfield:removedtoken', function () { + resetUI(); + }); + $t.on('tokenfield:editedtoken', function () { + resetUI(); + }); + $t.on('tokenfield:createdtoken', function () { + $input.val(''); + resetUI(); + }); + $t.on('tokenfield:edittoken', function () { + isEdit = true; + }); + + // Fix UI issue where the input could go outside of the container + var MutationObserver = window.MutationObserver; + var observer = new MutationObserver(function(mutations) { + if (called) { return; } + mutations.forEach(function(mutation) { + for (var i = 0; i < mutation.addedNodes.length; i++) { + if (mutation.addedNodes[i].classList && + mutation.addedNodes[i].classList.contains('token-input')) { + resetUI(); + break; + } + } + }); + }); + observer.observe($root[0], { + childList: true, + subtree: false + }); + $t.on('tokenfield:removetoken', function () { - $root.find('.token-input').focus(); + $input.focus(); }); t.preventDuplicates = function (cb) { From aa547a7b762e2e2ac0861585c1f2b347a8bfb63b Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 27 Oct 2020 08:12:23 +0530 Subject: [PATCH 21/84] align nodejs http headers with example nginx --- docs/example.nginx.conf | 16 ++++++++-------- lib/defaults.js | 3 --- server.js | 16 ++++++++++++++-- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/docs/example.nginx.conf b/docs/example.nginx.conf index 324df90c0..8319c657b 100644 --- a/docs/example.nginx.conf +++ b/docs/example.nginx.conf @@ -57,6 +57,14 @@ server { add_header Access-Control-Allow-Origin "*"; # add_header X-Frame-Options "SAMEORIGIN"; + set $coop ''; + if ($uri ~ ^\/sheet\/.*$) { set $coop 'same-origin'; } + + # Enable SharedArrayBuffer in Firefox (for .xlsx export) + add_header Cross-Origin-Resource-Policy cross-origin; + add_header Cross-Origin-Opener-Policy $coop; + add_header Cross-Origin-Embedder-Policy require-corp; + # Insert the path to your CryptPad repository root here root /home/cryptpad/cryptpad; index index.html; @@ -108,14 +116,6 @@ server { if ($uri = "/sheet/inner.html") { set $unsafe 1; } if ($uri ~ ^\/common\/onlyoffice\/.*\/index\.html.*$) { set $unsafe 1; } - set $coop ''; - if ($uri ~ ^\/sheet\/.*$) { set $coop 'same-origin'; } - - # Enable SharedArrayBuffer in Firefox (for .xlsx export) - add_header Cross-Origin-Resource-Policy cross-origin; - add_header Cross-Origin-Opener-Policy $coop; - add_header Cross-Origin-Embedder-Policy require-corp; - # everything except the sandbox domain is a privileged scope, as they might be used to handle keys if ($host != $sandbox_domain) { set $unsafe 0; } diff --git a/lib/defaults.js b/lib/defaults.js index 329e16f4c..4110e63d4 100644 --- a/lib/defaults.js +++ b/lib/defaults.js @@ -48,9 +48,6 @@ Default.httpHeaders = function () { "X-XSS-Protection": "1; mode=block", "X-Content-Type-Options": "nosniff", "Access-Control-Allow-Origin": "*", - "Cross-Origin-Resource-Policy": 'cross-origin', - "Cross-Origin-Opener-Policy": 'same-origin', - "Cross-Origin-Embedder-Policy": 'require-corp', }; }; diff --git a/server.js b/server.js index 0e0c2d79e..60247f47a 100644 --- a/server.js +++ b/server.js @@ -60,6 +60,10 @@ var app = Express(); } }()); +var applyHeaderMap = function (res, map) { + for (let header in map) { res.setHeader(header, map[header]); } +}; + var setHeaders = (function () { // load the default http headers unless the admin has provided their own via the config file var headers; @@ -96,14 +100,21 @@ var setHeaders = (function () { } if (Object.keys(headers).length) { return function (req, res) { + // apply a bunch of cross-origin headers for XLSX export in FF and printing elsewhere + applyHeaderMap(res, { + "Cross-Origin-Resource-Policy": 'cross-origin', + "Cross-Origin-Opener-Policy": /^\/sheet\//.test(req.url)? 'same-origin': '', + "Cross-Origin-Embedder-Policy": 'require-corp', + }); + + // targeted CSP, generic policies, maybe custom headers const h = [ - ///^\/pad\/inner\.html.*/, /^\/common\/onlyoffice\/.*\/index\.html.*/, /^\/(sheet|ooslide|oodoc)\/inner\.html.*/, ].some((regex) => { return regex.test(req.url); }) ? padHeaders : headers; - for (let header in h) { res.setHeader(header, h[header]); } + applyHeaderMap(res, h); }; } return function () {}; @@ -139,6 +150,7 @@ app.use(function (req, res, next) { setHeaders(req, res); if (/[\?\&]ver=[^\/]+$/.test(req.url)) { res.setHeader("Cache-Control", "max-age=31536000"); } + else { res.setHeader("Cache-Control", "no-cache"); } next(); }); From 12c4451f532ac747ff94e1b066ce7a123d10a24a Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 27 Oct 2020 12:46:27 +0530 Subject: [PATCH 22/84] lint compliance --- scripts/tests/throttle-test.js | 34 ---------------------------------- 1 file changed, 34 deletions(-) delete mode 100644 scripts/tests/throttle-test.js diff --git a/scripts/tests/throttle-test.js b/scripts/tests/throttle-test.js deleted file mode 100644 index e84fc4a8a..000000000 --- a/scripts/tests/throttle-test.js +++ /dev/null @@ -1,34 +0,0 @@ -var Util = require("../../lib/common-util"); - -(function (throttle) { - var last = 0; - var last_call = 0; - var f = Util.throttle(function (boop) { - var now = +new Date(); - if (last) { - console.log("last execution was %sms ago", now - last); - } else { - console.log("this is the first execution"); - } - last = now; - - //console.log('time of execution:', now); - console.log(boop); - }, 1000); - - [150, 250, 580, 850, 1500, 2200, 3990, 5000].forEach(function (delay) { - setTimeout(function () { - var now = +new Date(); - - if (last_call) { - console.log("last call was %sms ago", now - last_call); - } - - last_call = now; - //console.log("time of call for delay(%s):", delay, now); - f(delay); - }, delay); - }); -}(Util.throttle2)); - - From 1f1b40b83db9b7eabe56c6bb26c63181a8aeac4e Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 27 Oct 2020 12:47:15 +0530 Subject: [PATCH 23/84] move some implictly global state to env.js --- lib/commands/metadata.js | 4 +--- lib/env.js | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/commands/metadata.js b/lib/commands/metadata.js index 1d758564b..3d20f36e6 100644 --- a/lib/commands/metadata.js +++ b/lib/commands/metadata.js @@ -2,7 +2,6 @@ const Data = module.exports; const Meta = require("../metadata"); -const WriteQueue = require("../write-queue"); const Core = require("./core"); const Util = require("../common-util"); const HK = require("../hk-util"); @@ -53,7 +52,6 @@ Data.getMetadata = function (Env, channel, cb, Server, netfluxId) { value: value } */ -var queueMetadata = WriteQueue(); Data.setMetadata = function (Env, safeKey, data, cb, Server) { var unsafeKey = Util.unescapeKeyCharacters(safeKey); @@ -63,7 +61,7 @@ Data.setMetadata = function (Env, safeKey, data, cb, Server) { if (!command || typeof (command) !== 'string') { return void cb('INVALID_COMMAND'); } if (Meta.commands.indexOf(command) === -1) { return void cb('UNSUPPORTED_COMMAND'); } - queueMetadata(channel, function (next) { + Env.queueMetadata(channel, function (next) { Data.getMetadataRaw(Env, channel, function (err, metadata) { if (err) { cb(err); diff --git a/lib/env.js b/lib/env.js index 97ebd20ce..b1fc6680b 100644 --- a/lib/env.js +++ b/lib/env.js @@ -45,6 +45,7 @@ module.exports.create = function (config) { queueStorage: WriteQueue(), queueDeletes: WriteQueue(), queueValidation: WriteQueue(), + queueMetadata: WriteQueue(), batchIndexReads: BatchRead("HK_GET_INDEX"), batchMetadata: BatchRead('GET_METADATA'), From 8de5ce43518ab927f1805eccfd837c3f9d05e4df Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 27 Oct 2020 10:41:35 +0100 Subject: [PATCH 24/84] Fix Shared FOlder issues in the drive --- www/common/drive-ui.js | 3 +++ www/common/userObject.js | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index 649b5fe86..5d6a71b9d 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -4549,6 +4549,9 @@ define([ var rEl = manager.find(restorePath); if (manager.isFile(rEl)) { restoreName = manager.getTitle(rEl); + } else if (manager.isSharedFolder(rEl)) { + var sfData = manager.getSharedFolderData(rEl); + restoreName = sfData.title || sfData.lastTitle || Messages.fm_deletedFolder; } else { restoreName = restorePath[1]; } diff --git a/www/common/userObject.js b/www/common/userObject.js index 680ef3d0b..ca716f25f 100644 --- a/www/common/userObject.js +++ b/www/common/userObject.js @@ -846,7 +846,8 @@ define([ }; exp.ownedInTrash = function (isOwned) { return getFiles([TRASH]).map(function (id) { - var data = exp.getFileData(id); + var data = isSharedFolder(id) ? files[SHARED_FOLDERS][id] : exp.getFileData(id); + if (!data) { return; } return isOwned(data.owners) ? data.channel : undefined; }).filter(Boolean); }; From fda0cda1de22cf702522cd248557572f4f152c68 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 28 Oct 2020 16:10:34 +0100 Subject: [PATCH 25/84] Fix new loading screen errors --- customize.dist/loading.js | 27 +------- www/common/common-interface.js | 105 ++++++------------------------- www/common/common-ui-elements.js | 10 ++- www/common/sframe-common.js | 8 ++- 4 files changed, 36 insertions(+), 114 deletions(-) diff --git a/customize.dist/loading.js b/customize.dist/loading.js index 338b5882e..92c645acb 100644 --- a/customize.dist/loading.js +++ b/customize.dist/loading.js @@ -175,32 +175,6 @@ p.cp-password-info{ #cp-loading .cp-loading-spinner-container > div { height: 100px; } -#cp-loading-tip { - position: fixed; - z-index: 10000000; - top: 80%; - left: 0; - right: 0; - text-align: center; - transition: opacity 750ms; - transition-delay: 3000ms; -} -@media screen and (max-height: 600px) { - #cp-loading-tip { - display: none; - } -} -#cp-loading-tip span { - background: #222; - color: #fafafa; - text-align: center; - font-size: 1.3em; - opacity: 0.7; - font-family: 'Open Sans', 'Helvetica Neue', sans-serif; - padding: 15px; - max-width: 60%; - display: inline-block; -} .cp-loading-progress { width: 100%; margin: 20px; @@ -340,6 +314,7 @@ button.primary:hover{ var c = types.indexOf(data.type); if (c < current) { return console.error(data); } try { + document.querySelector('.cp-loading-spinner-container').style.display = 'none'; document.querySelector('.cp-loading-progress-list').innerHTML = makeList(data); document.querySelector('.cp-loading-progress-container').innerHTML = makeBar(data); } catch (e) { console.error(e); } diff --git a/www/common/common-interface.js b/www/common/common-interface.js index 244b6856f..d423b48ee 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -932,35 +932,33 @@ define([ var LOADING = 'cp-loading'; - var loading = { - error: false, - driveState: 0, - padState: 0 - }; UI.addLoadingScreen = function (config) { config = config || {}; var loadingText = config.loadingText; var todo = function () { var $loading = $('#' + LOADING); + // Show the loading screen $loading.css('display', ''); $loading.removeClass('cp-loading-hidden'); - $('.cp-loading-spinner-container').show(); - if (!config.noProgress && !$loading.find('.cp-loading-progress').length) { + if (config.newProgress) { + // XXX re-add progress bar for step 6 after password prompt for PPP + // XXX also burn after reading var progress = h('div.cp-loading-progress', [ - h('p.cp-loading-progress-drive'), - h('p.cp-loading-progress-pad') + h('p.cp-loading-progress-list'), + h('p.cp-loading-progress-container') ]); - $(progress).hide(); - $loading.find('.cp-loading-container').append(progress); - } else if (config.noProgress) { - $loading.find('.cp-loading-progress').remove(); + $loading.find('.cp-loading-spinner-container').after(progress); } + if (!$loading.find('.cp-loading-progress').length) { + // Add spinner + $('.cp-loading-spinner-container').show(); + } + // Add loading text if (loadingText) { $('#' + LOADING).find('#cp-loading-message').show().text(loadingText); } else { $('#' + LOADING).find('#cp-loading-message').hide().text(''); } - loading.error = false; }; if ($('#' + LOADING).length) { todo(); @@ -973,60 +971,6 @@ define([ if (window.CryptPad_updateLoadingProgress) { window.CryptPad_updateLoadingProgress(data); } - // XXX - /* - var $loading = $('#' + LOADING); - if (!$loading.length || loading.error) { return; } - $loading.find('.cp-loading-progress').show(); - var $progress; - if (isDrive) { - // Drive state - if (loading.driveState === -1) { return; } // Already loaded - $progress = $loading.find('.cp-loading-progress-drive'); - if (!$progress.length) { return; } // Can't find the box to display data - - // If state is -1, remove the box, drive is loaded - if (data.state === -1) { - loading.driveState = -1; - $progress.remove(); - } else { - if (data.state < loading.driveState) { return; } // We should not display old data - // Update the current state - loading.driveState = data.state; - data.progress = data.progress || 100; - data.msg = Messages['loading_drive_'+ Math.floor(data.state)] || ''; - $progress.html(data.msg); - if (data.progress) { - $progress.append(h('div.cp-loading-progress-bar', [ - h('div.cp-loading-progress-bar-value', {style: 'width:'+data.progress+'%;'}) - ])); - } - } - } else { - // Pad state - if (loading.padState === -1) { return; } // Already loaded - $progress = $loading.find('.cp-loading-progress-pad'); - if (!$progress.length) { return; } // Can't find the box to display data - - // If state is -1, remove the box, pad is loaded - if (data.state === -1) { - loading.padState = -1; - $progress.remove(); - } else { - if (data.state < loading.padState) { return; } // We should not display old data - // Update the current state - loading.padState = data.state; - data.progress = data.progress || 100; - data.msg = Messages['loading_pad_'+data.state] || ''; - $progress.html(data.msg); - if (data.progress) { - $progress.append(h('div.cp-loading-progress-bar', [ - h('div.cp-loading-progress-bar-value', {style: 'width:'+data.progress+'%;'}) - ])); - } - } - } - */ }; UI.removeLoadingScreen = function (cb) { // Release the test blocker, hopefully every test has been registered. @@ -1034,31 +978,23 @@ define([ cb = cb || function () {}; if (Test.__ASYNC_BLOCKER__) { Test.__ASYNC_BLOCKER__.pass(); } - $('#' + LOADING).addClass("cp-loading-hidden"); + var $loading = $('#' + LOADING); + $loading.addClass("cp-loading-hidden"); // Hide the loading screen + $loading.find('.cp-loading-progress').remove(); // Remove the progress list setTimeout(cb, 750); - loading.error = false; - var $tip = $('#cp-loading-tip').css('top', '') - // loading.less sets transition-delay: $wait-time - // and transition: opacity $fadeout-time - .css({ - 'opacity': 0, - 'pointer-events': 'none', - }); - window.setTimeout(function () { - $tip.remove(); - }, 3750); - // jquery.fadeout can get stuck }; UI.errorLoadingScreen = function (error, transparent, exitable) { var $loading = $('#' + LOADING); if (!$loading.is(':visible') || $loading.hasClass('cp-loading-hidden')) { - UI.addLoadingScreen({hideTips: true}); + UI.addLoadingScreen(); } - loading.error = true; + // Remove the progress list $loading.find('.cp-loading-progress').remove(); + // Hide the spinner $('.cp-loading-spinner-container').hide(); - $('#cp-loading-tip').remove(); if (transparent) { $loading.css('opacity', 0.9); } + + // Add the error message var $error = $loading.find('#cp-loading-message').show(); if (error instanceof Element) { $error.html('').append(error); @@ -1070,7 +1006,6 @@ define([ $(window).keydown(function (e) { if (e.which === 27) { $loading.hide(); - loading.error = false; if (typeof(exitable) === "function") { exitable(); } } }); diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index a2f965113..bc548d8f7 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -2532,10 +2532,16 @@ define([ var submit = function () { var value = $password.find('.cp-password-input').val(); - UI.addLoadingScreen(); + UI.addLoadingScreen({newProgress: true}); + if (window.CryptPad_updateLoadingProgress) { + window.CryptPad_updateLoadingProgress({ + type: 'pad', + progress: 0 + }); + } common.getSframeChannel().query('Q_PAD_PASSWORD_VALUE', value, function (err, data) { if (!data) { - UIElements.displayPasswordPrompt(common, cfg, true); + return void UIElements.displayPasswordPrompt(common, cfg, true); } }); }; diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index 82b52c9f7..f7a2c41c3 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -345,7 +345,13 @@ define([ } if (priv.burnAfterReading) { UIElements.displayBurnAfterReadingPage(funcs, waitFor(function () { - UI.addLoadingScreen(); + UI.addLoadingScreen({newProgress: true}); + if (window.CryptPad_updateLoadingProgress) { + window.CryptPad_updateLoadingProgress({ + type: 'pad', + progress: 0 + }); + } ctx.sframeChan.event('EV_BURN_AFTER_READING'); })); } From da8e2416a179c0eeca652a0e6b91dbe6035f3bee Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 28 Oct 2020 16:32:22 +0100 Subject: [PATCH 26/84] Center loading steps --- customize.dist/loading.js | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/customize.dist/loading.js b/customize.dist/loading.js index 92c645acb..56e9a4cab 100644 --- a/customize.dist/loading.js +++ b/customize.dist/loading.js @@ -178,6 +178,7 @@ p.cp-password-info{ .cp-loading-progress { width: 100%; margin: 20px; + text-align: center; } .cp-loading-progress p { margin: 5px; @@ -185,6 +186,10 @@ p.cp-password-info{ white-space: nowrap; text-overflow: ellipsis; } +.cp-loading-progress-list { + text-align: left; + display: inline-block; +} .cp-loading-progress-list ul { list-style: none; padding-left: 0; @@ -200,7 +205,10 @@ p.cp-password-info{ width: 22px; } .cp-loading-progress-list li span{ - margin-left: 20px; + margin-left: 10px; +} +.cp-loading-progress-list li span.percent { + position: absolute; } .cp-loading-progress-bar { @@ -285,10 +293,12 @@ button.primary:hover{ var getLi = function (i) { var check = (i < c || (i === c && data.progress >= 100)) ? 'fa-check-square-o' : 'fa-square-o'; - var p = Math.min(Math.floor(data.progress), 100); - var percent = i < c ? '(100%)' : (i === c ? '('+p+'%)' : '(0%)'); - return '
  • '+Messages['loading_state_'+i]+'' + - ''+percent+''; + var percentStr = ''; + if (i === c) { + var p = Math.min(Math.floor(data.progress), 100); + percentStr = '('+p+'%)'; + } + return '
  • '+Messages['loading_state_'+i]+'' + percentStr; }; var list = '
      '; types.forEach(function (el, i) { From 00694e8ebdf840563517a8fb03d733edc7400f2c Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 29 Oct 2020 14:06:16 +0100 Subject: [PATCH 27/84] Stop using sessionStorage when creating/opening pads --- www/common/common-hash.js | 29 +++++++++++ www/common/cryptpad-common.js | 35 ++++++------- www/common/drive-ui.js | 87 +++++++++++++------------------ www/common/notifications.js | 24 ++++----- www/common/sframe-common-outer.js | 81 +++++++++++++++------------- 5 files changed, 137 insertions(+), 119 deletions(-) diff --git a/www/common/common-hash.js b/www/common/common-hash.js index 0b584d4c3..ff8764da4 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -169,6 +169,17 @@ Version 1 /code/#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI */ + var getNewPadOpts = function (hashArr) { + var k; + // Check if we have a ownerKey for this pad + hashArr.some(function (data) { + if (/^newpad=/.test(data)) { + k = data.slice(7); + return true; + } + }); + return k || ''; + }; var getVersionHash = function (hashArr) { var k; // Check if we have a ownerKey for this pad @@ -202,6 +213,7 @@ Version 1 parsed.present = options.indexOf('present') !== -1; parsed.embed = options.indexOf('embed') !== -1; parsed.versionHash = getVersionHash(options); + parsed.newPadOpts = getNewPadOpts(options); parsed.ownerKey = getOwnerKey(options); }; @@ -217,6 +229,13 @@ Version 1 password: parsed.password }; }; + + if (/^\/newpad=/.test(hash)) { + return { + newPadOpts: hash.slice(8, -1) + }; + } + if (hash.slice(0,1) !== '/' && hash.length >= 56) { // Version 0 // Old hash parsed.channel = hash.slice(0, 32); @@ -237,6 +256,9 @@ Version 1 if (versionHash) { hash += 'hash=' + Crypto.b64RemoveSlashes(versionHash) + '/'; } + if (opts.newPadOpts) { + hash += 'newpad=' + opts.newPadOpts + '/'; + } return hash; }; @@ -372,6 +394,10 @@ Version 1 var url = '/'; if (!ret.type) { return url; } url += ret.type + '/'; + // New pad with options: append the options to the hash + if (!ret.hashData && options.newPadOpts) { + return url + '#/newpad=' + options.newPadOpts + '/'; + } if (!ret.hashData) { return url; } if (ret.hashData.type !== 'pad') { return url + '#' + ret.hash; } if (ret.hashData.version === 0) { return url + '#' + ret.hash; } @@ -575,6 +601,9 @@ Version 1 // Valid hash? if (parsed.hash) { if (!parsed.hashData) { return; } + // New pad: only newPadOpts allowed + if (Object.keys(parsed.hashData).length === 1 && + parsed.hashData.newPadOpts) { return true; } // Version should be a number if (typeof(parsed.hashData.version) === "undefined") { return; } // pads and files should have a base64 (or hex) key diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 4613e5756..70ebe5bcc 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -148,6 +148,21 @@ define([ send(); }; + common.setTabHref = function (href) { + var ohc = window.onhashchange; + window.onhashchange = function () {}; + window.location.href = href; + window.onhashchange = ohc; + ohc({reset: true}); + }; + common.setTabHash = function (hash) { + var ohc = window.onhashchange; + window.onhashchange = function () {}; + window.location.hash = hash; + window.onhashchange = ohc; + ohc({reset: true}); + }; + // RESTRICTED // Settings only common.resetDrive = function (cb) { @@ -2090,26 +2105,6 @@ define([ language: common.getLanguage(), driveEvents: true //rdyCfg.driveEvents // Boolean }; - // if a pad is created from a file - if (sessionStorage[Constants.newPadFileData]) { - common.fromFileData = JSON.parse(sessionStorage[Constants.newPadFileData]); - var _parsed1 = Hash.parsePadUrl(common.fromFileData.href); - var _parsed2 = Hash.parsePadUrl(window.location.href); - if (_parsed1.hashData.type === 'pad') { - if (_parsed1.type !== _parsed2.type) { delete common.fromFileData; } - } - delete sessionStorage[Constants.newPadFileData]; - } - - if (sessionStorage[Constants.newPadPathKey]) { - common.initialPath = sessionStorage[Constants.newPadPathKey]; - delete sessionStorage[Constants.newPadPathKey]; - } - - if (sessionStorage[Constants.newPadTeamKey]) { - common.initialTeam = sessionStorage[Constants.newPadTeamKey]; - delete sessionStorage[Constants.newPadTeamKey]; - } var channelIsReady = waitFor(); diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index 5d6a71b9d..08a3f1279 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -1125,6 +1125,18 @@ define([ var hiddenHref = Hash.hashToHref(hash, parsed.type); window.open(APP.origin + hiddenHref); }; + var openIn = function (type, path, team, fData) { + var obj = JSON.stringify({ + p: path, + t: team, + d: fData + }); + var str = encodeURIComponent(obj); + var parsed = Hash.parsePadUrl(Hash.hashToHref('', type)); + var opts = parsed.getOptions(); + opts.newPadOpts = str; + common.openURL(parsed.getUrl(opts)); + }; var refresh = APP.refresh = function () { APP.displayDirectory(currentPath); @@ -2667,12 +2679,7 @@ define([ .click(function () { var type = $(this).attr('data-type') || 'pad'; var path = manager.isPathIn(currentPath, [TRASH]) ? '' : currentPath; - nThen(function (waitFor) { - common.sessionStorage.put(Constants.newPadPathKey, path, waitFor()); - common.sessionStorage.put(Constants.newPadTeamKey, APP.team, waitFor()); - }).nThen(function () { - common.openURL('/' + type + '/'); - }); + openIn(type, path, APP.team); }); }; var createNewButton = function (isInRoot, $container) { @@ -4227,31 +4234,19 @@ define([ else if ($this.hasClass('cp-app-drive-context-makeacopy')) { if (paths.length !== 1) { return; } el = manager.find(paths[0].path); - var _metadata = manager.getFileData(el); - var _simpleData = { - title: _metadata.filename || _metadata.title, - href: _metadata.href || _metadata.roHref, - password: _metadata.password, - channel: _metadata.channel, - }; - nThen(function (waitFor) { + (function () { var path = currentPath; if (path[0] !== ROOT) { path = [ROOT]; } - common.sessionStorage.put(Constants.newPadFileData, JSON.stringify(_simpleData), waitFor()); - common.sessionStorage.put(Constants.newPadPathKey, path, waitFor()); - common.sessionStorage.put(Constants.newPadTeamKey, APP.team, waitFor()); - }).nThen(function () { + var _metadata = manager.getFileData(el); + var _simpleData = { + title: _metadata.filename || _metadata.title, + href: _metadata.href || _metadata.roHref, + password: _metadata.password, + channel: _metadata.channel, + }; var parsed = Hash.parsePadUrl(_metadata.href || _metadata.roHref); - common.openURL(Hash.hashToHref('', parsed.type)); - // We need to restore sessionStorage for the next time we want to create a pad from this tab - // NOTE: the 100ms timeout is to fix a race condition in firefox where sessionStorage - // would be deleted before the new tab was created - setTimeout(function () { - common.sessionStorage.put(Constants.newPadFileData, '', function () {}); - common.sessionStorage.put(Constants.newPadPathKey, '', function () {}); - common.sessionStorage.put(Constants.newPadTeamKey, '', function () {}); - }, 100); - }); + openIn(parsed.type, path, APP.team, _simpleData); + })(); } else if ($this.hasClass('cp-app-drive-context-openincode')) { if (paths.length !== 1) { return; } @@ -4264,23 +4259,20 @@ define([ password: metadata.password, channel: metadata.channel, }; - nThen(function (waitFor) { - common.sessionStorage.put(Constants.newPadFileData, JSON.stringify(simpleData), waitFor()); - common.sessionStorage.put(Constants.newPadPathKey, currentPath, waitFor()); - common.sessionStorage.put(Constants.newPadTeamKey, APP.team, waitFor()); - }).nThen(function () { - common.openURL('/code/'); - // We need to restore sessionStorage for the next time we want to create a pad from this tab - // NOTE: the 100ms timeout is to fix a race condition in firefox where sessionStorage - // would be deleted before the new tab was created - setTimeout(function () { - common.sessionStorage.put(Constants.newPadFileData, '', function () {}); - common.sessionStorage.put(Constants.newPadPathKey, '', function () {}); - common.sessionStorage.put(Constants.newPadTeamKey, '', function () {}); - }, 100); - }); + (function () { + var path = currentPath; + if (path[0] !== ROOT) { path = [ROOT]; } + var _metadata = manager.getFileData(el); + var _simpleData = { + title: _metadata.filename || _metadata.title, + href: _metadata.href || _metadata.roHref, + password: _metadata.password, + channel: _metadata.channel, + }; + var parsed = Hash.parsePadUrl(_metadata.href || _metadata.roHref); + openIn('code', path, APP.team, _simpleData); + })(); } - else if ($this.hasClass('cp-app-drive-context-expandall') || $this.hasClass('cp-app-drive-context-collapseall')) { if (paths.length !== 1) { return; } @@ -4482,12 +4474,7 @@ define([ else if ($this.hasClass("cp-app-drive-context-newdoc")) { var ntype = $this.data('type') || 'pad'; var path2 = manager.isPathIn(currentPath, [TRASH]) ? '' : currentPath; - nThen(function (waitFor) { - common.sessionStorage.put(Constants.newPadPathKey, path2, waitFor()); - common.sessionStorage.put(Constants.newPadTeamKey, APP.team, waitFor()); - }).nThen(function () { - common.openURL('/' + ntype + '/'); - }); + openIn(ntype, path2, APP.team); } else if ($this.hasClass("cp-app-drive-context-properties")) { if (type === 'trash') { diff --git a/www/common/notifications.js b/www/common/notifications.js index 44125ed71..1686b87f9 100644 --- a/www/common/notifications.js +++ b/www/common/notifications.js @@ -108,21 +108,17 @@ define([ return Messages._getKey(key, [name, title, teamName]); }; content.handler = function() { - var todo = function() { - common.openURL(msg.content.href); - defaultDismiss(common, data)(); - }; - nThen(function(waitFor) { - if (msg.content.isTemplate) { - common.sessionStorage.put(Constants.newPadPathKey, ['template'], waitFor()); - } - if (teamNotification) { - common.sessionStorage.put(Constants.newPadTeamKey, teamNotification, waitFor()); - } - common.sessionStorage.put('newPadPassword', msg.content.password || '', waitFor()); - }).nThen(function() { - todo(); + var obj = JSON.stringify({ + p: msg.content.isTemplate ? ['template'] : undefined, + t: teamNotification || undefined, + pw: msg.content.password || '' }); + var str = encodeURIComponent(obj); + var parsed = Hash.parsePadUrl(msg.content.href); + var opts = parsed.getOptions(); + opts.newPadOpts = str; + common.openURL(parsed.getUrl(opts)); + defaultDismiss(common, data)(); }; if (!content.archived) { content.dismissHandler = defaultDismiss(common, data); diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index c837a9b72..1e149abeb 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -26,7 +26,7 @@ define([ }; var AppConfig; var Test; - var password; + var password, newPadPassword; var initialPathInDrive; var burnAfterReading; @@ -196,15 +196,47 @@ define([ } // Rendered (maybe hidden) hash var renderedParsed = Utils.Hash.parsePadUrl(window.location.href); - var ohc = window.onhashchange; - window.onhashchange = function () {}; - window.location.href = renderedParsed.getUrl(opts); - window.onhashchange = ohc; - ohc({reset: true}); + Cryptpad.setTabHref(renderedParsed.getUrl(opts)); } })); }; + // New pad options + if (parsed.hashData && parsed.hashData.newPadOpts) { + try { + var newPad = JSON.parse(decodeURIComponent(parsed.hashData.newPadOpts)); + Cryptpad.initialTeam = newPad.t; + Cryptpad.initialPath = newPad.p; + newPadPassword = newPad.pw; + if (newPad.d) { + Cryptpad.fromFileData = newPad.d; + var _parsed1 = Utils.Hash.parsePadUrl(Cryptpad.fromFileData.href); + if (_parsed1.hashData.type === 'pad' && _parsed1.type !== parsed.type) { + delete Cryptpad.fromFileData; + } + } + } catch (e) { + console.error(e, parsed.hashData.newPadOpts); + } + delete parsed.hashData.newPadOpts; + + // If it's a new pad, don't check password + if (!Object.keys(parsed.hashData).length) { + delete parsed.hashData; + parsed.hash = ''; + currentPad.hash = ''; + Cryptpad.setTabHash(''); + return void todo(); + } + // Otherwise, existing pad (new for us) + var opts = parsed.getOptions(); + delete opts.newPadOpts; + parsed = Utils.Hash.parsePadUrl(parsed.getUrl(opts)); + currentPad.hash = parsed.hash; + Cryptpad.setTabHash(parsed.hash); + } + + if (!parsed.hashData) { // No hash, no need to check for a password return void todo(); } @@ -334,9 +366,8 @@ define([ password = val; }), parsed.getUrl()); }).nThen(function (w) { - if (!password && !stored && sessionStorage.newPadPassword) { - passwordCfg.value = sessionStorage.newPadPassword; - delete sessionStorage.newPadPassword; + if (!password && !stored && newPadPassword) { + passwordCfg.value = newPadPassword; } if (parsed.type === "file") { @@ -356,7 +387,7 @@ define([ waitFor.abort(); return; } - if (!isNew) { return void todo(); } + if (!e && !isNew) { return void todo(); } if (parsed.hashData.mode === 'view' && (password || !parsed.hashData.password)) { // Error, wrong password stored, the view seed has changed with the password // password will never work @@ -507,18 +538,10 @@ define([ sframeChan.onReg('EV_METADATA_UPDATE', updateMeta); Utils.LocalStore.onLogin(function () { - var ohc = window.onhashchange; - window.onhashchange = function () {}; - window.location.hash = currentPad.hash; - window.onhashchange = ohc; - ohc({reset: true}); + Cryptpad.setTabHash(currentPad.hash); }); Utils.LocalStore.onLogout(function () { - var ohc = window.onhashchange; - window.onhashchange = function () {}; - window.location.hash = currentPad.hash; - window.onhashchange = ohc; - ohc({reset: true}); + Cryptpad.setTabHash(currentPad.hash); sframeChan.event('EV_LOGOUT'); }); @@ -1166,11 +1189,7 @@ define([ var hiddenParsed = Utils.Hash.parsePadUrl(window.location.href); // Update the hash in the address bar - var ohc = window.onhashchange; - window.onhashchange = function () {}; - window.location.href = hiddenParsed.getUrl(opts); - window.onhashchange = ohc; - ohc({reset: true}); + Cryptpad.setTabHref(hiddenParsed.getUrl(opts)); }); @@ -1521,11 +1540,7 @@ define([ // in the address bar Cryptpad.padRpc.onChannelDeleted.reg(function (channel) { if (channel !== secret.channel) { return; } - var ohc = window.onhashchange; - window.onhashchange = function () {}; - window.location.href = currentPad.href; - window.onhashchange = ohc; - ohc({reset: true}); + Cryptpad.setTabHref(currentPad.href); }); // Join the netflux channel @@ -1609,13 +1624,9 @@ define([ Utils.crypto = Utils.Crypto.createEncryptor(Utils.secret.keys); // Update the hash in the address bar - var ohc = window.onhashchange; - window.onhashchange = function () {}; - window.location.hash = newHash; currentPad.hash = newHash; currentPad.href = '/' + parsed.type + '/#' + newHash; - window.onhashchange = ohc; - ohc({reset: true}); + Cryptpad.setTabHash(newHash); // Update metadata values and send new metadata inside parsed = Utils.Hash.parsePadUrl(currentPad.href); From 19f3dfa6771f414d4205741667c56b5c3939aa9d Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 29 Oct 2020 14:39:23 +0100 Subject: [PATCH 28/84] Prompt to store edit link to your drive when you're a team viewer --- www/common/outer/async-store.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 64cb068b1..3e1a5bb69 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -1080,6 +1080,11 @@ define([ if (data.teamId && s.id !== data.teamId) { return; } if (storeLocally && s.id) { return; } + // If this is an edit link but we don't have edit rights, this entry is not useful + if (h.mode === "edit" && !s.secondaryKey) { + return; + } + var res = s.manager.findChannel(channel, true); if (res.length) { sendTo.push(s.id); From da31b714822e70af44f5d59a3043064f4fa0e150 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 29 Oct 2020 14:44:14 +0100 Subject: [PATCH 29/84] Fix safe links with wrong access rights --- www/common/sframe-common-outer.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 1e149abeb..901577179 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -343,10 +343,11 @@ define([ return void noPadData('NO_RESULT'); } // Data found but weaker? warn + expire = res.expire; if (edit && !res.href) { newHref = res.roHref; + return; } - expire = res.expire; // We have good data, keep the hash in memory newHref = edit ? res.href : (res.roHref || res.href); })); From 7eea957a4d2e3a17b6983323a5c9a0c8768d4bb1 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 29 Oct 2020 15:10:39 +0100 Subject: [PATCH 30/84] Fix issue newPadPath with new templates --- www/common/common-ui-elements.js | 14 ++++++++------ www/common/sframe-common-outer.js | 3 +++ www/secureiframe/main.js | 9 ++++++++- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index bc548d8f7..2d0e02b14 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -2270,12 +2270,14 @@ define([ icon: h('span.cptools.cptools-new-template') }); } - allData.unshift({ - name: Messages.creation_noTemplate, - id: 0, - //icon: h('span.fa.fa-file') - icon: UI.getFileIcon({type: type}) - }); + if (!privateData.newTemplate) { + allData.unshift({ + name: Messages.creation_noTemplate, + id: 0, + //icon: h('span.fa.fa-file') + icon: UI.getFileIcon({type: type}) + }); + } var redraw = function (index) { if (index < 0) { i = 0; } else if (index > allData.length - 1) { return; } diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 901577179..382d6f710 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -488,6 +488,8 @@ define([ fileHost: ApiConfig.fileHost, readOnly: readOnly, isTemplate: isTemplate, + newTemplate: Array.isArray(Cryptpad.initialPath) + && Cryptpad.initialPath[0] === "template", feedbackAllowed: Utils.Feedback.state, isPresent: parsed.hashData && parsed.hashData.present, isEmbed: parsed.hashData && parsed.hashData.embed, @@ -1662,6 +1664,7 @@ define([ nThen(function(waitFor) { if (data.templateId) { if (data.templateId === -1) { + isTemplate = true; initialPathInDrive = ['template']; return; } diff --git a/www/secureiframe/main.js b/www/secureiframe/main.js index eca166592..060248097 100644 --- a/www/secureiframe/main.js +++ b/www/secureiframe/main.js @@ -74,6 +74,7 @@ define([ }; window.addEventListener('message', whenReady); }).nThen(function () { + var isTemplate = config.data.isTemplate; var updateMeta = function () { //console.log('EV_METADATA_UPDATE'); var metaObj; @@ -85,6 +86,12 @@ define([ } metaObj = n; })); + if (typeof(isTemplate) === "undefined") { + Cryptpad.isTemplate(currentPad.href, waitFor(function (err, t) { + if (err) { console.log(err); } + isTemplate = t; + })); + } }).nThen(function (/*waitFor*/) { metaObj.doc = {}; var additionalPriv = { @@ -96,7 +103,7 @@ define([ feedbackAllowed: Utils.Feedback.state, hashes: config.data.hashes, password: config.data.password, - isTemplate: config.data.isTemplate, + isTemplate: isTemplate, file: config.data.file, }; for (var k in additionalPriv) { metaObj.priv[k] = additionalPriv[k]; } From 47eace89bfe05cb8d6f4fdd7a435c1b7d579d6e9 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 29 Oct 2020 17:14:39 +0100 Subject: [PATCH 31/84] Encrypt password before putting it in the hash --- www/common/outer/async-store.js | 1 + www/common/outer/mailbox-handlers.js | 8 +++++++- www/common/sframe-common-outer.js | 9 ++++++++- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 3e1a5bb69..138993607 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -2647,6 +2647,7 @@ define([ classic: true, }; var rt = window.rt = Listmap.create(listmapConfig); + store.driveSecret = secret; store.proxy = rt.proxy; store.loggedIn = typeof(data.userHash) !== "undefined"; diff --git a/www/common/outer/mailbox-handlers.js b/www/common/outer/mailbox-handlers.js index 573879061..e5080b8e5 100644 --- a/www/common/outer/mailbox-handlers.js +++ b/www/common/outer/mailbox-handlers.js @@ -2,7 +2,8 @@ define([ '/common/common-messaging.js', '/common/common-hash.js', '/common/common-util.js', -], function (Messaging, Hash, Util) { + '/bower_components/chainpad-crypto/crypto.js', +], function (Messaging, Hash, Util, Crypto) { // Random timeout between 10 and 30 times your sync time (lag + chainpad sync) var getRandomTimeout = function (ctx) { @@ -221,6 +222,11 @@ define([ toRemove = old.data; } + if (content.password) { + var key = ctx.store.driveSecret.keys.cryptKey; + content.password = Crypto.encrypt(content.password, key); + } + // Update the data channels[channel] = { mode: mode, diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 382d6f710..0e51b1794 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -207,7 +207,14 @@ define([ var newPad = JSON.parse(decodeURIComponent(parsed.hashData.newPadOpts)); Cryptpad.initialTeam = newPad.t; Cryptpad.initialPath = newPad.p; - newPadPassword = newPad.pw; + if (newPad.pw) { + try { + var uHash = Utils.LocalStore.getUserHash(); + var uSecret = Utils.Hash.getSecrets('drive', uHash); + var uKey = uSecret.keys.cryptKey; + newPadPassword = Crypto.decrypt(newPad.pw, uKey); + } catch (e) { console.error(e); } + } if (newPad.d) { Cryptpad.fromFileData = newPad.d; var _parsed1 = Utils.Hash.parsePadUrl(Cryptpad.fromFileData.href); From f0eda8b795f1dbaba525460244de979909a6811d Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 29 Oct 2020 17:16:41 +0100 Subject: [PATCH 32/84] Fix 'store pad prompt' always displayed --- www/common/outer/async-store.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 138993607..653c4d1f5 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -1081,7 +1081,7 @@ define([ if (storeLocally && s.id) { return; } // If this is an edit link but we don't have edit rights, this entry is not useful - if (h.mode === "edit" && !s.secondaryKey) { + if (h.mode === "edit" && s.id && !s.secondaryKey) { return; } From 85f83eb97672e29972017f80b6ae7e9db809c8d8 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 29 Oct 2020 17:26:23 +0100 Subject: [PATCH 33/84] Fix loading screen error --- customize.dist/loading.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/customize.dist/loading.js b/customize.dist/loading.js index 56e9a4cab..525f58e45 100644 --- a/customize.dist/loading.js +++ b/customize.dist/loading.js @@ -333,6 +333,8 @@ button.primary:hover{ window.CryptPad_loadingError = function (err) { if (!built) { return; } try { + var node = document.querySelector('.cp-loading-progress'); + if (node.parentNode) { node.parentNode.removeChild(node); } document.querySelector('.cp-loading-spinner-container').setAttribute('style', 'display:none;'); document.querySelector('#cp-loading-message').setAttribute('style', 'display:block;'); document.querySelector('#cp-loading-message').innerText = err; From 02966d0a5d463b7287f6a4da21dc9012e445318f Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 30 Oct 2020 13:44:51 +0530 Subject: [PATCH 34/84] remove hardcoded translations --- customize.dist/loading.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/customize.dist/loading.js b/customize.dist/loading.js index 525f58e45..4f8b79125 100644 --- a/customize.dist/loading.js +++ b/customize.dist/loading.js @@ -278,14 +278,7 @@ button.primary:hover{ ].join(''); var built = false; - // XXX var types = ['less', 'drive', 'migrate', 'sf', 'team', 'pad', 'end']; - Messages.loading_state_0 = "Less"; - Messages.loading_state_1 = "Drive"; - Messages.loading_state_2 = "Migrate"; - Messages.loading_state_3 = "SF"; - Messages.loading_state_4 = "Team"; - Messages.loading_state_5 = "Pad"; var current; var makeList = function (data) { var c = types.indexOf(data.type); From a579b13ab36cf1b780367378f12cd19da657cdec Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 30 Oct 2020 17:03:51 +0530 Subject: [PATCH 35/84] remove hardcoded translations and resolved notes --- www/common/common-interface.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/www/common/common-interface.js b/www/common/common-interface.js index d423b48ee..6d682afa7 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -281,12 +281,10 @@ define([ var $root = $t.parent(); - Messages.add = "Add"; // XXX - Messages.edit = "Edit"; // XXX var $input = $root.find('.token-input'); var $button = $(h('button.btn.btn-primary', [ h('i.fa.fa-plus'), - h('span', Messages.add) + h('span', Messages.tag_add) ])); @@ -311,7 +309,7 @@ define([ } $form.append($input); $form.append($button); - if (isEdit) { $button.find('span').text(Messages.edit); } + if (isEdit) { $button.find('span').text(Messages.tag_edit); } else { $button.find('span').text(Messages.add); } $container.append($form); $input.focus(); @@ -941,8 +939,6 @@ define([ $loading.css('display', ''); $loading.removeClass('cp-loading-hidden'); if (config.newProgress) { - // XXX re-add progress bar for step 6 after password prompt for PPP - // XXX also burn after reading var progress = h('div.cp-loading-progress', [ h('p.cp-loading-progress-list'), h('p.cp-loading-progress-container') From 589d32c0629c6a98cc5e3efaee6398468808e657 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 30 Oct 2020 15:00:12 +0100 Subject: [PATCH 36/84] Stop using sessionStorage for login redirect and new pad options --- customize.dist/login.js | 31 +++++++-- customize.dist/pages/features.js | 8 --- customize.dist/pages/index.js | 8 ++- www/common/common-hash.js | 97 +++++++++++++++++++++++----- www/common/common-ui-elements.js | 46 +++---------- www/common/cryptpad-common.js | 20 ++---- www/common/drive-ui.js | 19 ++---- www/common/notifications.js | 13 ++-- www/common/onlyoffice/inner.js | 8 +-- www/common/outer/mailbox-handlers.js | 5 ++ www/common/sframe-common-outer.js | 65 +++++++++++-------- www/common/sframe-common.js | 9 +-- www/common/toolbar.js | 8 +-- www/debug/main.js | 7 +- www/drive/main.js | 27 ++++---- www/file/inner.js | 4 +- www/profile/inner.js | 8 +-- www/secureiframe/main.js | 6 -- www/teams/inner.js | 12 +--- 19 files changed, 207 insertions(+), 194 deletions(-) diff --git a/customize.dist/login.js b/customize.dist/login.js index 35121f113..d07d4c4c3 100644 --- a/customize.dist/login.js +++ b/customize.dist/login.js @@ -28,6 +28,19 @@ define([ }; var Nacl = window.nacl; + + var redirectTo = '/drive/'; + var setRedirectTo = function () { + var parsed = Hash.parsePadUrl(window.location.href); + if (parsed.hashData && parsed.hashData.newPadOpts) { + var newPad = Hash.decodeDataOptions(parsed.hashData.newPadOpts); + redirectTo = newPad.href; + } + }; + if (window.location.hash) { + setRedirectTo(); + } + var allocateBytes = Exports.allocateBytes = function (bytes) { var dispense = Cred.dispenser(bytes); @@ -118,11 +131,11 @@ define([ }; var setMergeAnonDrive = function () { - sessionStorage.migrateAnonDrive = 1; + Exports.mergeAnonDrive = 1; }; var setCreateReadme = function () { - sessionStorage.createReadme = 1; + Exports.createReadme = 1; }; Exports.loginOrRegister = function (uname, passwd, isRegister, shouldImport, cb) { @@ -416,12 +429,20 @@ define([ }); }; Exports.redirect = function () { - if (sessionStorage.redirectTo) { - var h = sessionStorage.redirectTo; + if (redirectTo) { + var h = redirectTo; + var loginOpts = {}; + if (Exports.mergeAnonDrive) { + loginOpts.mergeAnonDrive = 1; + } + if (Exports.createReadme) { + loginOpts.createReadme = 1; + } + h = Hash.getLoginURL(h, loginOpts); + var parser = document.createElement('a'); parser.href = h; if (parser.origin === window.location.origin) { - delete sessionStorage.redirectTo; window.location.href = h; return; } diff --git a/customize.dist/pages/features.js b/customize.dist/pages/features.js index 6403ec36a..56bb29664 100644 --- a/customize.dist/pages/features.js +++ b/customize.dist/pages/features.js @@ -22,14 +22,6 @@ define([ target: '_blank', rel: 'noopener noreferrer' }, h('button.cp-features-register-button', Msg.features_f_subscribe)); - /*$(premiumButton).click(function (e) { - if (LocalStore.isLoggedIn()) { return; } - // Not logged in: go to /login with a redirect to this page - e.preventDefault(); - e.stopPropagation(); - sessionStorage.redirectTo = '/features.html'; - window.location.href = '/login/'; - });*/ var anonymousFeatures = h('div.col-12.col-sm-4.cp-anon-user',[ diff --git a/customize.dist/pages/index.js b/customize.dist/pages/index.js index 300637748..c718b363f 100644 --- a/customize.dist/pages/index.js +++ b/customize.dist/pages/index.js @@ -4,12 +4,13 @@ define([ '/common/hyperscript.js', '/common/common-feedback.js', '/common/common-interface.js', + '/common/common-hash.js', '/common/textFit.min.js', '/customize/messages.js', '/customize/application_config.js', '/common/outer/local-store.js', '/customize/pages.js' -], function ($, Config, h, Feedback, UI, TextFit, Msg, AppConfig, LocalStore, Pages) { +], function ($, Config, h, Feedback, UI, Hash, TextFit, Msg, AppConfig, LocalStore, Pages) { var urlArgs = Config.requireConf.urlArgs; var isAvailableType = function (x) { @@ -46,8 +47,9 @@ define([ var href = '/'+ x[0] +'/'; var attr = isEnabled ? { href: href } : { onclick: function () { - sessionStorage.redirectTo = href; - window.location.href = '/login/'; + var href = Hash.hashToHref('', 'login'); + var url = Hash.getNewPadURL(href, { href: href }); + window.location.href = url; } }; if (!isEnabled) { diff --git a/www/common/common-hash.js b/www/common/common-hash.js index ff8764da4..06a1d7fef 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -169,6 +169,17 @@ Version 1 /code/#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI */ + var getLoginOpts = function (hashArr) { + var k; + // Check if we have a ownerKey for this pad + hashArr.some(function (data) { + if (/^login=/.test(data)) { + k = data.slice(6); + return true; + } + }); + return k || ''; + }; var getNewPadOpts = function (hashArr) { var k; // Check if we have a ownerKey for this pad @@ -214,28 +225,51 @@ Version 1 parsed.embed = options.indexOf('embed') !== -1; parsed.versionHash = getVersionHash(options); parsed.newPadOpts = getNewPadOpts(options); + parsed.loginOpts = getLoginOpts(options); parsed.ownerKey = getOwnerKey(options); }; + // Version 4: only login or newpad options, smae for all the apps + if (hashArr[1] && hashArr[1] === '4') { + parsed.getHash = function (opts) { + if (!opts || !Object.keys(opts).length) { return ''; } + var hash = '/4/'; + if (opts.newPadOpts) { hash += 'newpad=' + opts.newPadOpts + '/'; } + if (opts.loginOpts) { hash += 'login=' + opts.loginOpts + '/'; } + return hash; + }; + parsed.getOptions = function () { + var options = {}; + if (parsed.newPadOpts) { options.newPadOpts = parsed.newPadOpts; } + if (parsed.loginOpts) { options.loginOpts = parsed.loginOpts; } + return options; + }; + + parsed.version = 4; + options = hashArr.slice(2); + addOptions(); + + return parsed; + } + + // The other versions depends on the type if (['media', 'file', 'user', 'invite'].indexOf(type) === -1) { parsed.type = 'pad'; - parsed.getHash = function () { return hash; }; + parsed.getHash = function () { + return hash; + }; parsed.getOptions = function () { return { embed: parsed.embed, present: parsed.present, ownerKey: parsed.ownerKey, versionHash: parsed.versionHash, + newPadOpts: parsed.newPadOpts, + loginOpts: parsed.loginOpts, password: parsed.password }; }; - if (/^\/newpad=/.test(hash)) { - return { - newPadOpts: hash.slice(8, -1) - }; - } - if (hash.slice(0,1) !== '/' && hash.length >= 56) { // Version 0 // Old hash parsed.channel = hash.slice(0, 32); @@ -256,9 +290,8 @@ Version 1 if (versionHash) { hash += 'hash=' + Crypto.b64RemoveSlashes(versionHash) + '/'; } - if (opts.newPadOpts) { - hash += 'newpad=' + opts.newPadOpts + '/'; - } + if (opts.newPadOpts) { hash += 'newpad=' + opts.newPadOpts + '/'; } + if (opts.loginOpts) { hash += 'login=' + opts.loginOpts + '/'; } return hash; }; @@ -306,6 +339,8 @@ Version 1 embed: parsed.embed, present: parsed.present, ownerKey: parsed.ownerKey, + newPadOpts: parsed.newPadOpts, + loginOpts: parsed.loginOpts, password: parsed.password }; }; @@ -317,6 +352,8 @@ Version 1 if (parsed.password || opts.password) { hash += 'p/'; } if (opts.embed) { hash += 'embed/'; } if (opts.present) { hash += 'present/'; } + if (opts.newPadOpts) { hash += 'newpad=' + opts.newPadOpts + '/'; } + if (opts.loginOpts) { hash += 'login=' + opts.loginOpts + '/'; } return hash; }; @@ -389,18 +426,26 @@ Version 1 var idx; + // When we start without a hash, use version 4 links to add login or newpad options + var getHash = function (opts) { + if (!opts || !Object.keys(opts).length) { return ''; } + var hash = '/4/'; + if (opts.newPadOpts) { hash += 'newpad=' + opts.newPadOpts + '/'; } + if (opts.loginOpts) { hash += 'login=' + opts.loginOpts + '/'; } + return hash; + }; ret.getUrl = function (options) { options = options || {}; var url = '/'; if (!ret.type) { return url; } url += ret.type + '/'; // New pad with options: append the options to the hash - if (!ret.hashData && options.newPadOpts) { - return url + '#/newpad=' + options.newPadOpts + '/'; + if (!ret.hashData && options && Object.keys(options).length) { + return url + '#' + getHash(options); } if (!ret.hashData) { return url; } - if (ret.hashData.type !== 'pad') { return url + '#' + ret.hash; } - if (ret.hashData.version === 0) { return url + '#' + ret.hash; } + //if (ret.hashData.version === 0) { return url + '#' + ret.hash; } + //if (ret.hashData.type !== 'pad') { return url + '#' + ret.hash; } var hash = ret.hashData.getHash(options); url += '#' + hash; return url; @@ -549,7 +594,6 @@ Version 1 secret = JSON.parse(JSON.stringify(secret)); if (!secret.keys && !secret.key) { - console.error('e'); return hashes; } else if (!secret.keys) { secret.keys = {}; @@ -615,6 +659,29 @@ Version 1 return true; }; + Hash.decodeDataOptions = function (opts) { + var b64 = decodeURIComponent(opts); + var str = Nacl.util.encodeUTF8(Nacl.util.decodeBase64(b64)); + return JSON.parse(str); + }; + Hash.encodeDataOptions = function (opts) { + var str = JSON.stringify(opts); + var b64 = Nacl.util.encodeBase64(Nacl.util.decodeUTF8(str)); + return encodeURIComponent(b64); + }; + Hash.getNewPadURL = function (href, opts) { + var parsed = Hash.parsePadUrl(href); + var options = parsed.getOptions(); + options.newPadOpts = Hash.encodeDataOptions(opts); + return parsed.getUrl(options); + }; + Hash.getLoginURL = function (href, opts) { + var parsed = Hash.parsePadUrl(href); + var options = parsed.getOptions(); + options.loginOpts = Hash.encodeDataOptions(opts); + return parsed.getUrl(options); + }; + return Hash; }; diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 2d0e02b14..e977f1f49 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -254,20 +254,15 @@ define([ className: 'secondary', name: Messages.login_register, onClick: function () { - common.setLoginRedirect(function () { - common.gotoURL('/register/'); - }); + common.setLoginRedirect('register'); } }, { className: 'secondary', name: Messages.login_login, onClick: function () { - common.setLoginRedirect(function () { - common.gotoURL('/login/'); - }); + common.setLoginRedirect('login'); } - } - ] + }] }; } }; @@ -1788,9 +1783,7 @@ define([ attributes: {'class': 'cp-toolbar-menu-login fa fa-sign-in'}, content: h('span', Messages.login_login), action: function () { - Common.setLoginRedirect(function () { - window.parent.location = origin+'/login/'; - }); + Common.setLoginRedirect('login'); }, }); options.push({ @@ -1798,9 +1791,7 @@ define([ attributes: {'class': 'cp-toolbar-menu-register fa fa-user-plus'}, content: h('span', Messages.login_register), action: function () { - Common.setLoginRedirect(function () { - window.parent.location = origin+'/register/'; - }); + Common.setLoginRedirect('register'); }, }); } @@ -1925,8 +1916,6 @@ define([ $modal.find('.cp-modal').append($title); $modal.find('.cp-modal').append($description); - var $advanced; - var $container = $('
      '); var i = 0; var types = AppConfig.availablePadTypes.filter(function (p) { @@ -1950,15 +1939,7 @@ define([ $element.attr('data-type', p); $element.click(function () { $modal.hide(); - if ($advanced && Util.isChecked($advanced)) { - common.sessionStorage.put(Constants.displayPadCreationScreen, true, function (){ - common.openURL('/' + p + '/'); - }); - return; - } - common.sessionStorage.put(Constants.displayPadCreationScreen, "", function () { - common.openURL('/' + p + '/'); - }); + common.openURL('/' + p + '/'); }); }); @@ -1983,12 +1964,6 @@ define([ } return; } - if (e.which === 32 && $advanced) { - $advanced.prop('checked', !$advanced.prop('checked')); - $modal.focus(); - e.stopPropagation(); - e.preventDefault(); - } }); @@ -2753,13 +2728,8 @@ define([ $(link).click(function (e) { e.preventDefault(); e.stopPropagation(); - if (msg.content.password) { - common.sessionStorage.put('newPadPassword', msg.content.password, function () { - common.openURL(msg.content.href); - }); - return; - } - common.openURL(msg.content.href); + var obj = { pw: msg.content.password || '' }; + common.openURL(Hash.getNewPadURL(msg.content.href, obj)); }); var div = h('div', [ diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 70ebe5bcc..36413d89d 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -1880,8 +1880,9 @@ define([ LocalStore.logout(); // redirect them to log in, and come back when they're done. - sessionStorage.redirectTo = currentPad.href; - window.location.href = '/login/'; + var href = Hash.hashToHref('', 'login'); + var url = Hash.getNewPadURL(href, { href: currentPad.href }); + window.location.href = url; }; common.startAccountDeletion = function (data, cb) { @@ -2277,12 +2278,6 @@ define([ if (data.anonHash && !cfg.userHash) { LocalStore.setFSHash(data.anonHash); } - /*if (cfg.userHash && sessionStorage) { - // copy User_hash into sessionStorage because cross-domain iframes - // on safari replaces localStorage with sessionStorage or something - sessionStorage.setItem(Constants.userHashKey, cfg.userHash); - }*/ - if (cfg.userHash) { var localToken = tryParsing(localStorage.getItem(Constants.tokenKey)); if (localToken === null) { @@ -2337,21 +2332,18 @@ define([ postMessage("DISCONNECT"); }); }).nThen(function (waitFor) { - if (sessionStorage.createReadme) { + if (common.createReadme) { var data = { driveReadme: Messages.driveReadme, driveReadmeTitle: Messages.driveReadmeTitle, }; postMessage("CREATE_README", data, waitFor(function (e) { if (e && e.error) { return void console.error(e.error); } - delete sessionStorage.createReadme; })); } }).nThen(function (waitFor) { - if (sessionStorage.migrateAnonDrive) { - common.mergeAnonDrive(waitFor(function() { - delete sessionStorage.migrateAnonDrive; - })); + if (common.migrateAnonDrive) { + common.mergeAnonDrive(waitFor()); } }).nThen(function (waitFor) { if (AppConfig.afterLogin) { diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index 08a3f1279..53e496ef4 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -1126,16 +1126,13 @@ define([ window.open(APP.origin + hiddenHref); }; var openIn = function (type, path, team, fData) { - var obj = JSON.stringify({ + var obj = { p: path, t: team, d: fData - }); - var str = encodeURIComponent(obj); - var parsed = Hash.parsePadUrl(Hash.hashToHref('', type)); - var opts = parsed.getOptions(); - opts.newPadOpts = str; - common.openURL(parsed.getUrl(opts)); + }; + var href = Hash.hashToHref('', type); + common.openURL(Hash.getNewPadURL(href, obj)); }; var refresh = APP.refresh = function () { @@ -4252,13 +4249,6 @@ define([ if (paths.length !== 1) { return; } var p = paths[0]; el = manager.find(p.path); - var metadata = manager.getFileData(el); - var simpleData = { - title: metadata.filename || metadata.title, - href: metadata.href, - password: metadata.password, - channel: metadata.channel, - }; (function () { var path = currentPath; if (path[0] !== ROOT) { path = [ROOT]; } @@ -4269,7 +4259,6 @@ define([ password: _metadata.password, channel: _metadata.channel, }; - var parsed = Hash.parsePadUrl(_metadata.href || _metadata.roHref); openIn('code', path, APP.team, _simpleData); })(); } diff --git a/www/common/notifications.js b/www/common/notifications.js index 1686b87f9..caf5bfe00 100644 --- a/www/common/notifications.js +++ b/www/common/notifications.js @@ -7,8 +7,7 @@ define([ '/common/common-util.js', '/common/common-constants.js', '/customize/messages.js', - '/bower_components/nthen/index.js' -], function($, h, Hash, UI, UIElements, Util, Constants, Messages, nThen) { +], function($, h, Hash, UI, UIElements, Util, Constants, Messages) { var handlers = {}; @@ -108,16 +107,12 @@ define([ return Messages._getKey(key, [name, title, teamName]); }; content.handler = function() { - var obj = JSON.stringify({ + var obj = { p: msg.content.isTemplate ? ['template'] : undefined, t: teamNotification || undefined, pw: msg.content.password || '' - }); - var str = encodeURIComponent(obj); - var parsed = Hash.parsePadUrl(msg.content.href); - var opts = parsed.getOptions(); - opts.newPadOpts = str; - common.openURL(parsed.getUrl(opts)); + }; + common.openURL(Hash.getNewPadURL(msg.content.href, obj)); defaultDismiss(common, data)(); }; if (!content.archived) { diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index c265d2d2b..ce0dbcc3f 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -460,15 +460,11 @@ define([ var actions = h('div', [cancel, register, login]); var modal = UI.cornerPopup(Messages.oo_login, actions, '', {alt: true}); $(register).click(function () { - common.setLoginRedirect(function () { - common.gotoURL('/register/'); - }); + common.setLoginRedirect('register'); modal.delete(); }); $(login).click(function () { - common.setLoginRedirect(function () { - common.gotoURL('/login/'); - }); + common.setLoginRedirect('login'); modal.delete(); }); $(cancel).click(function () { diff --git a/www/common/outer/mailbox-handlers.js b/www/common/outer/mailbox-handlers.js index e5080b8e5..326b4afc7 100644 --- a/www/common/outer/mailbox-handlers.js +++ b/www/common/outer/mailbox-handlers.js @@ -321,6 +321,11 @@ define([ var channel = content.channel || content.teamChannel; + if (content.password) { + var key = ctx.store.driveSecret.keys.cryptKey; + content.password = Crypto.encrypt(content.password, key); + } + if (addOwners[channel]) { return void cb(true); } addOwners[channel] = { type: box.type, diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 0e51b1794..01a5ceaf8 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -131,12 +131,28 @@ define([ if (sframeChan) { sframeChan.event('EV_LOADING_INFO', data); } }); + try { + var parsed = Utils.Hash.parsePadUrl(currentPad.href); + var options = parsed.getOptions(); + if (options.loginOpts) { + var loginOpts = Utils.Hash.decodeDataOptions(options.loginOpts); + if (loginOpts.createReadme) { Cryptpad.createReadme = true; } + if (loginOpts.mergeAnonDrive) { Cryptpad.migrateAnonDrive = true; } + // Remove newPadOpts from the hash + delete options.loginOpts; + currentPad.href = parsed.getUrl(options); + currentPad.hash = parsed.hashData.getHash ? parsed.hashData.getHash(options) + : ''; + } + } catch (e) { console.error(e); } + Cryptpad.ready(waitFor(), { driveEvents: cfg.driveEvents, currentPad: currentPad }); - if (window.history && window.history.replaceState && currentPad.hash) { + // Remove the login hash if needed + if (window.history && window.history.replaceState && (currentPad.hash || window.location.hash)) { var nHash = currentPad.hash; if (!/^#/.test(nHash)) { nHash = '#' + nHash; } window.history.replaceState({}, window.document.title, nHash); @@ -202,9 +218,10 @@ define([ }; // New pad options - if (parsed.hashData && parsed.hashData.newPadOpts) { + var options = parsed.getOptions(); + if (options.newPadOpts) { try { - var newPad = JSON.parse(decodeURIComponent(parsed.hashData.newPadOpts)); + var newPad = Utils.Hash.decodeDataOptions(options.newPadOpts); Cryptpad.initialTeam = newPad.t; Cryptpad.initialPath = newPad.p; if (newPad.pw) { @@ -225,22 +242,20 @@ define([ } catch (e) { console.error(e, parsed.hashData.newPadOpts); } - delete parsed.hashData.newPadOpts; + delete options.newPadOpts; + + currentPad.href = parsed.getUrl(options); + currentPad.hash = parsed.hashData.getHash ? parsed.hashData.getHash(options) + : ''; + var version = parsed.hashData.version; + parsed = Utils.Hash.parsePadUrl(currentPad.href); + Cryptpad.setTabHash(currentPad.hash); // If it's a new pad, don't check password - if (!Object.keys(parsed.hashData).length) { - delete parsed.hashData; - parsed.hash = ''; - currentPad.hash = ''; - Cryptpad.setTabHash(''); + if (version === 4) { return void todo(); } - // Otherwise, existing pad (new for us) - var opts = parsed.getOptions(); - delete opts.newPadOpts; - parsed = Utils.Hash.parsePadUrl(parsed.getUrl(opts)); - currentPad.hash = parsed.hash; - Cryptpad.setTabHash(parsed.hash); + // Otherwise, continue } @@ -460,9 +475,6 @@ define([ var defaultTitle = Utils.UserObject.getDefaultName(parsed); var edPublic, curvePublic, notifications, isTemplate; var settings = {}; - var forceCreationScreen = cfg.useCreationScreen && - sessionStorage[Utils.Constants.displayPadCreationScreen]; - delete sessionStorage[Utils.Constants.displayPadCreationScreen]; var isSafe = ['debug', 'profile', 'drive', 'teams'].indexOf(currentPad.app) !== -1; var updateMeta = function () { //console.log('EV_METADATA_UPDATE'); @@ -507,7 +519,6 @@ define([ }, isNewFile: isNewFile, isDeleted: isNewFile && currentPad.hash.length > 0, - forceCreationScreen: forceCreationScreen, password: password, channel: secret.channel, enableSF: localStorage.CryptPad_SF === "1", // TODO to remove when enabled by default @@ -635,9 +646,10 @@ define([ Cryptpad.mailbox.execCommand(data, cb); }); - sframeChan.on('Q_SET_LOGIN_REDIRECT', function (data, cb) { - sessionStorage.redirectTo = currentPad.href; - cb(); + sframeChan.on('EV_SET_LOGIN_REDIRECT', function (page) { + var href = Utils.Hash.hashToHref('', page); + var url = Utils.Hash.getNewPadURL(href, { href: currentPad.href }); + window.location.href = url; }); sframeChan.on('Q_STORE_IN_TEAM', function (data, cb) { @@ -1065,11 +1077,10 @@ define([ password: password, title: currentTitle }; - sessionStorage[Utils.Constants.newPadFileData] = JSON.stringify(data); - window.open(window.location.pathname); - setTimeout(function () { - delete sessionStorage[Utils.Constants.newPadFileData]; - }, 100); + var obj = { d: data }; + var href = window.location.pathname; + var url = Utils.Hash.getNewPadURL(href, obj); + window.open(url); }); // Messaging diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index f7a2c41c3..ffff122b5 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -307,9 +307,8 @@ define([ ctx.sframeChan.event('EV_SET_HASH', hash); }; - funcs.setLoginRedirect = function (cb) { - cb = cb || $.noop; - ctx.sframeChan.query('Q_SET_LOGIN_REDIRECT', null, cb); + funcs.setLoginRedirect = function (page) { + ctx.sframeChan.query('EV_SET_LOGIN_REDIRECT', page); }; funcs.isPresentUrl = function (cb) { @@ -758,9 +757,7 @@ define([ var mustLogin = privateData.registeredOnly; if (mustLogin) { UI.alert(Messages.mustLogin, function () { - funcs.setLoginRedirect(function () { - funcs.gotoURL('/login/'); - }); + funcs.setLoginRedirect('login'); }, {forefront: true}); return; } diff --git a/www/common/toolbar.js b/www/common/toolbar.js index 9a96039f6..78e4e91f3 100644 --- a/www/common/toolbar.js +++ b/www/common/toolbar.js @@ -773,15 +773,11 @@ MessengerUI, Messages) { ]); $msg.find('a.cp-pnp-login').click(function (ev) { ev.preventDefault(); - Common.setLoginRedirect(function () { - window.parent.location = o + '/login/'; - }); + Common.setLoginRedirect('login'); }); $msg.find('a.cp-pnp-register').click(function (ev) { ev.preventDefault(); - Common.setLoginRedirect(function () { - window.parent.location = o + '/register/'; - }); + Common.setLoginRedirect('register'); }); $('.cp-toolbar-top').append($msg); //UI.addTooltips(); diff --git a/www/debug/main.js b/www/debug/main.js index 68055b1d4..b901beec0 100644 --- a/www/debug/main.js +++ b/www/debug/main.js @@ -53,14 +53,9 @@ define([ }; window.addEventListener('message', onMsg); }).nThen(function (/*waitFor*/) { - var hash = localStorage[Constants.userHashKey]; + var hash = localStorage[Constants.userHashKey] || localStorage[Constants.fileHashKey]; var drive = hash && ('#'+hash === window.location.hash); if (!window.location.hash) { - if (!hash) { - sessionStorage.redirectTo = '/debug/'; - window.location.href = '/login/'; - return; - } drive = true; window.location.hash = hash; } else { diff --git a/www/drive/main.js b/www/drive/main.js index 8db013c0f..5eb9dbc67 100644 --- a/www/drive/main.js +++ b/www/drive/main.js @@ -46,9 +46,12 @@ define([ window.addEventListener('message', onMsg); }).nThen(function (/*waitFor*/) { var afterSecrets = function (Cryptpad, Utils, secret, cb, sframeChan) { - var _hash = hash.slice(1); - if (_hash && Utils.LocalStore.isLoggedIn()) { - // Add a shared folder! + var parsed = Utils.Hash.parsePadUrl(href); + var isSf = parsed.hashData && parsed.hashData.type === 'pad'; + if (!isSf) { return void cb(); } + + // SF and logged in: add shared folder + if (Utils.LocalStore.isLoggedIn()) { Cryptpad.addSharedFolder(null, secret, function (id) { if (id && typeof(id) === "object" && id.error) { sframeChan.event("EV_RESTRICTED_ERROR"); @@ -65,16 +68,16 @@ define([ cb(); }); return; - } else if (_hash) { - var id = Utils.Util.createRandomInteger(); - window.CryptPad_newSharedFolder = id; - var data = { - href: Utils.Hash.getRelativeHref(Cryptpad.currentPad.href), - password: secret.password - }; - return void Cryptpad.loadSharedFolder(id, data, cb); } - cb(); + + // Anon shared folder + var id = Utils.Util.createRandomInteger(); + window.CryptPad_newSharedFolder = id; + var data = { + href: Utils.Hash.getRelativeHref(Cryptpad.currentPad.href), + password: secret.password + }; + Cryptpad.loadSharedFolder(id, data, cb); }; var addRpc = function (sframeChan, Cryptpad, Utils) { sframeChan.on('EV_BURN_ANON_DRIVE', function () { diff --git a/www/file/inner.js b/www/file/inner.js index 81019d0be..b2aef53ed 100644 --- a/www/file/inner.js +++ b/www/file/inner.js @@ -231,9 +231,7 @@ define([ if (!common.isLoggedIn()) { UI.removeLoadingScreen(); return UI.alert(Messages.upload_mustLogin, function () { - common.setLoginRedirect(function () { - common.gotoURL('/login/'); - }); + common.setLoginRedirect('login'); }); } diff --git a/www/profile/inner.js b/www/profile/inner.js index cddae4611..7a872fdd0 100644 --- a/www/profile/inner.js +++ b/www/profile/inner.js @@ -611,15 +611,11 @@ define([ var actions = h('div', [cancel, register, login]); var modal = UI.cornerPopup(Messages.profile_login, actions, '', {alt: true}); $(register).click(function () { - common.setLoginRedirect(function () { - common.gotoURL('/register/'); - }); + common.setLoginRedirect('register'); modal.delete(); }); $(login).click(function () { - common.setLoginRedirect(function () { - common.gotoURL('/login/'); - }); + common.setLoginRedirect('login'); modal.delete(); }); $(cancel).click(function () { diff --git a/www/secureiframe/main.js b/www/secureiframe/main.js index 060248097..49e94a252 100644 --- a/www/secureiframe/main.js +++ b/www/secureiframe/main.js @@ -86,12 +86,6 @@ define([ } metaObj = n; })); - if (typeof(isTemplate) === "undefined") { - Cryptpad.isTemplate(currentPad.href, waitFor(function (err, t) { - if (err) { console.log(err); } - isTemplate = t; - })); - } }).nThen(function (/*waitFor*/) { metaObj.doc = {}; var additionalPriv = { diff --git a/www/teams/inner.js b/www/teams/inner.js index 2eb234b12..a75ed94db 100644 --- a/www/teams/inner.js +++ b/www/teams/inner.js @@ -1267,14 +1267,10 @@ define([ anonRegister = h('button.btn.btn-secondary', Messages.login_register), ])); $(anonLogin).click(function () { - common.setLoginRedirect(function () { - common.gotoURL('/login/'); - }); + common.setLoginRedirect('login'); }); $(anonRegister).click(function () { - common.setLoginRedirect(function () { - common.gotoURL('/register/'); - }); + common.setLoginRedirect('register'); }); waitFor.abort(); }).nThen(function () { @@ -1402,9 +1398,7 @@ define([ var hash = privateData.teamInviteHash; if (!hash && !driveAPP.loggedIn) { UI.alert(Messages.mustLogin, function () { - common.setLoginRedirect(function () { - common.gotoURL('/login/'); - }); + common.setLoginRedirect('login'); }, {forefront: true}); return; } From 0e0c224ab187187c03d80c27e779bf10b5926086 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 30 Oct 2020 15:05:28 +0100 Subject: [PATCH 37/84] Catch errors in outer too --- www/common/boot2.js | 3 +++ www/common/sframe-common-outer.js | 3 +++ 2 files changed, 6 insertions(+) diff --git a/www/common/boot2.js b/www/common/boot2.js index fc3968fb3..30f776a25 100644 --- a/www/common/boot2.js +++ b/www/common/boot2.js @@ -43,6 +43,9 @@ define([ 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(); } + if (window.CryptPad_loadingError) { + window.CryptPad_loadingError(e); + } throw e; }; diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 01a5ceaf8..d476d02d4 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -123,6 +123,9 @@ define([ }); SFrameChannel.create(msgEv, postMsg, waitFor(function (sfc) { Utils.sframeChan = sframeChan = sfc; + window.CryptPad_loadingError = function (e) { + sfc.event('EV_LOADING_ERROR', e) + }; })); }); window.addEventListener('message', whenReady); From 926b1d842f5ee84ef6bb0aba16fe93549ba2134a Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 30 Oct 2020 15:13:22 +0100 Subject: [PATCH 38/84] Stop using sessionStorage in CryptPad --- www/auth/index.html | 9 - www/auth/main.js | 193 --------------------- www/common/common-constants.js | 4 - www/common/onlyoffice/main.js | 2 +- www/common/outer/async-store.js | 1 - www/common/outer/local-store.js | 16 -- www/common/sframe-channel.js | 166 ------------------ www/common/sframe-common-outer.js | 9 - www/common/sframe-common.js | 9 - www/common/sframe-protocol.js | 271 ------------------------------ www/login/main.js | 6 +- www/logout/main.js | 1 - www/register/main.js | 6 +- 13 files changed, 6 insertions(+), 687 deletions(-) delete mode 100644 www/auth/index.html delete mode 100644 www/auth/main.js delete mode 100644 www/common/sframe-channel.js delete mode 100644 www/common/sframe-protocol.js diff --git a/www/auth/index.html b/www/auth/index.html deleted file mode 100644 index 685ca37c4..000000000 --- a/www/auth/index.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/www/auth/main.js b/www/auth/main.js deleted file mode 100644 index fcbeaf3af..000000000 --- a/www/auth/main.js +++ /dev/null @@ -1,193 +0,0 @@ -define([ - 'jquery', - '/api/config', - '/common/cryptget.js', - '/common/pinpad.js', - '/common/common-constants.js', - '/common/common-hash.js', - '/common/outer/local-store.js', - '/common/outer/login-block.js', - '/common/outer/network-config.js', - '/customize/login.js', - '/common/test.js', - '/bower_components/nthen/index.js', - '/bower_components/netflux-websocket/netflux-client.js', - '/bower_components/tweetnacl/nacl-fast.min.js' -], function ($, ApiConfig, Crypt, Pinpad, Constants, Hash, LocalStore, Block, NetConfig, Login, Test, nThen, Netflux) { - var Nacl = window.nacl; - - var signMsg = function (msg, privKey) { - var signKey = Nacl.util.decodeBase64(privKey); - var buffer = Nacl.util.decodeUTF8(msg); - return Nacl.util.encodeBase64(Nacl.sign(buffer, signKey)); - }; - - // TODO: Allow authing for any domain as long as the user clicks an "accept" button - // inside of the iframe. - var AUTHORIZED_DOMAINS = [ - /\.cryptpad\.fr$/, - /^http(s)?:\/\/localhost\:/ - ]; - - // Safari is weird about localStorage in iframes but seems to let sessionStorage slide. - localStorage[Constants.userHashKey] = localStorage[Constants.userHashKey] || - sessionStorage[Constants.userHashKey]; - - var proxy; - var rpc; - var network; - var rpcError; - var contacts = {}; - - var loadProxy = function (hash) { - nThen(function (waitFor) { - var wsUrl = NetConfig.getWebsocketURL(); - var w = waitFor(); - Netflux.connect(wsUrl).then(function (_network) { - network = _network; - w(); - }, function (err) { - rpcError = err; - console.error(err); - }); - }).nThen(function (waitFor) { - Crypt.get(hash, waitFor(function (err, val) { - if (err) { - waitFor.abort(); - console.error(err); - return; - } - try { - var parsed = JSON.parse(val); - proxy = parsed; - } catch (e) { - console.log("Can't parse user drive", e); - } - }), { - network: network - }); - }).nThen(function () { - var origin = ApiConfig.fileHost || window.location.origin; - // Get contacts and extract their avatar channel and key - var getData = function (obj, href) { - var parsed = Hash.parsePadUrl(href); - if (!parsed || parsed.type !== "file") { return; } - var secret = Hash.getSecrets('file', parsed.hash); - if (!secret.keys || !secret.channel) { return; } - obj.avatarKey = Hash.encodeBase64(secret.keys && secret.keys.cryptKey); - obj.avatarSrc = origin + Hash.getBlobPathFromHex(secret.channel); - }; - contacts.teams = proxy.teams || {}; - contacts.friends = proxy.friends || {}; - Object.keys(contacts.friends).map(function (key) { - var friend = contacts.friends[key]; - if (!friend) { return; } - var ret = { - edPublic: friend.edPublic, - name: friend.displayName, - }; - getData(ret, friend.avatar); - contacts.friends[key] = ret; - }); - Object.keys(contacts.teams).map(function (key) { - var team = contacts.teams[key]; - if (!team) { return; } - var avatar = team.metadata && team.metadata.avatar; - var ret = { - edPublic: team.keys && team.keys.drive && team.keys.drive.edPublic, - name: team.metadata && team.metadata.name - }; - getData(ret, avatar); - contacts.teams[key] = ret; - }); - contacts.origin = window.location.origin; - }).nThen(function (waitFor) { - if (!network) { return void waitFor.abort(); } - Pinpad.create(network, proxy, waitFor(function (e, call) { - if (e) { - rpcError = e; - return void waitFor.abort(); - } - rpc = call; - })); - }).nThen(function () { - Test(function () { - // This is only here to maybe trigger an error. - window.drive = proxy['drive']; - Test.passed(); - }); - }); - }; - - var whenReady = function (cb) { - if (proxy && (rpc || rpcError)) { return void cb(); } - console.log('CryptPad not ready...'); - setTimeout(function () { - whenReady(cb); - }, 100); - }; - - $(window).on("message", function (jqe) { - var evt = jqe.originalEvent; - var data = JSON.parse(evt.data); - var domain = evt.origin; - var srcWindow = evt.source; - var ret = { txid: data.txid }; - console.log('CP receiving', data); - if (data.cmd === 'PING') { - ret.res = 'PONG'; - } else if (data.cmd === 'LOGIN') { - Login.loginOrRegister(data.data.name, data.data.password, false, false, function (err) { - if (err) { - ret.error = 'LOGIN_ERROR'; - srcWindow.postMessage(JSON.stringify(ret), domain); - return; - } - loadProxy(LocalStore.getUserHash()); - srcWindow.postMessage(JSON.stringify(ret), domain); - }); - return; - } else if (data.cmd === 'SIGN') { - if (!AUTHORIZED_DOMAINS.filter(function (x) { return x.test(domain); }).length) { - ret.error = "UNAUTH_DOMAIN"; - } else if (!LocalStore.isLoggedIn()) { - ret.error = "NOT_LOGGED_IN"; - } else { - return void whenReady(function () { - var sig = signMsg(data.data, proxy.edPrivate); - ret.res = { - uname: proxy.login_name, - edPublic: proxy.edPublic, - sig: sig - }; - ret.contacts = contacts; - srcWindow.postMessage(JSON.stringify(ret), domain); - }); - } - } else if (data.cmd === 'UPDATE_LIMIT') { - return void whenReady(function () { - if (rpcError) { - // Tell the user on accounts that there was an issue and they need to wait maximum 24h or contact an admin - ret.warning = true; - srcWindow.postMessage(JSON.stringify(ret), domain); - return; - } - rpc.updatePinLimits(function (e, limit, plan, note) { - if (e) { - ret.warning = true; - } - ret.res = [limit, plan, note]; - srcWindow.postMessage(JSON.stringify(ret), domain); - }); - }); - } else { - ret.error = "UNKNOWN_CMD"; - } - srcWindow.postMessage(JSON.stringify(ret), domain); - }); - - var userHash = LocalStore.getUserHash(); - if (userHash) { - loadProxy(userHash); - } -}); diff --git a/www/common/common-constants.js b/www/common/common-constants.js index 553665574..17db2302c 100644 --- a/www/common/common-constants.js +++ b/www/common/common-constants.js @@ -5,10 +5,6 @@ define(['/customize/application_config.js'], function (AppConfig) { userNameKey: 'User_name', blockHashKey: 'Block_hash', fileHashKey: 'FS_hash', - // sessionStorage - newPadPathKey: "newPadPath", - newPadTeamKey: "newPadTeam", - newPadFileData: "newPadFileData", // Store displayNameKey: 'cryptpad.username', oldStorageKey: 'CryptPad_RECENTPADS', diff --git a/www/common/onlyoffice/main.js b/www/common/onlyoffice/main.js index 83d887548..0560b95bd 100644 --- a/www/common/onlyoffice/main.js +++ b/www/common/onlyoffice/main.js @@ -55,7 +55,7 @@ define([ var addData = function (obj) { obj.ooType = window.location.pathname.replace(/^\//, '').replace(/\/$/, ''); obj.ooVersionHash = version; - obj.ooForceVersion = localStorage.CryptPad_ooVersion || sessionStorage.CryptPad_ooVersion || ""; + obj.ooForceVersion = localStorage.CryptPad_ooVersion || ""; }; var addRpc = function (sframeChan, Cryptpad, Utils) { sframeChan.on('Q_OO_SAVE', function (data, cb) { diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 653c4d1f5..62e078050 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -2730,7 +2730,6 @@ define([ * - userHash or anonHash * Todo in cb * - LocalStore.setFSHash if needed - * - sessionStorage.User_Hash * - stuff with tokenKey * Event to outer * - requestLogin diff --git a/www/common/outer/local-store.js b/www/common/outer/local-store.js index 774924148..d15c2e8c6 100644 --- a/www/common/outer/local-store.js +++ b/www/common/outer/local-store.js @@ -82,19 +82,6 @@ define([ localStorage.setItem(Constants.userNameKey, name); if (cb) { cb(); } }; - var eraseTempSessionValues = LocalStore.eraseTempSessionValues = function () { - // delete sessionStorage values that might have been left over - // from the main page's /user redirect - [ - 'login', - 'login_user', - 'login_pass', - 'login_rmb', - 'register' - ].forEach(function (k) { - delete sessionStorage[k]; - }); - }; var logoutHandlers = []; LocalStore.logout = function (cb, isDeletion) { [ @@ -104,10 +91,8 @@ define([ 'loginToken', 'plan', ].forEach(function (k) { - sessionStorage.removeItem(k); localStorage.removeItem(k); delete localStorage[k]; - delete sessionStorage[k]; }); try { Object.keys(localStorage || {}).forEach(function (k) { @@ -122,7 +107,6 @@ define([ if (!LocalStore.getFSHash()) { LocalStore.setFSHash(Hash.createRandomHash('drive')); } - eraseTempSessionValues(); if (!isDeletion) { logoutHandlers.forEach(function (h) { diff --git a/www/common/sframe-channel.js b/www/common/sframe-channel.js deleted file mode 100644 index a7edf0814..000000000 --- a/www/common/sframe-channel.js +++ /dev/null @@ -1,166 +0,0 @@ -// This file provides the API for the channel for talking to and from the sandbox iframe. -define([ - '/common/sframe-protocol.js', - '/common/common-util.js' -], function (SFrameProtocol, Util) { - - var mkTxid = function () { - return Math.random().toString(16).replace('0.', '') + Math.random().toString(16).replace('0.', ''); - }; - - var create = function (ow, cb, isSandbox, sendData) { - var otherWindow; - var evReady = Util.mkEvent(true); - var handlers = {}; - var queries = {}; - - // list of handlers which are registered from the other side... - var insideHandlers = []; - var callWhenRegistered = {}; - - var chan = {}; - - // Send a query. channel.query('Q_SOMETHING', { args: "whatever" }, function (reply) { ... }); - chan.query = function (q, content, cb, opts) { - if (!otherWindow) { throw new Error('not yet initialized'); } - if (!SFrameProtocol[q]) { - throw new Error('please only make queries are defined in sframe-protocol.js'); - } - opts = opts || {}; - var txid = mkTxid(); - var to = opts.timeout || 30000; - var timeout = setTimeout(function () { - delete queries[txid]; - console.log("Timeout making query " + q); - }, to); - queries[txid] = function (data, msg) { - clearTimeout(timeout); - delete queries[txid]; - cb(undefined, data.content, msg); - }; - evReady.reg(function () { - otherWindow.postMessage(JSON.stringify({ - txid: txid, - content: content, - q: q - }), '*'); - }); - }; - - // Fire an event. channel.event('EV_SOMETHING', { args: "whatever" }); - var event = chan.event = function (e, content) { - if (!SFrameProtocol[e]) { - throw new Error('please only fire events that are defined in sframe-protocol.js'); - } - if (e.indexOf('EV_') !== 0) { - throw new Error('please only use events (starting with EV_) for event messages'); - } - evReady.reg(function () { - otherWindow.postMessage(JSON.stringify({ content: content, q: e }), '*'); - }); - }; - - // Be notified on query or event. channel.on('EV_SOMETHING', function (args, reply) { ... }); - // If the type is a query, your handler will be invoked with a reply function that takes - // one argument (the content to reply with). - chan.on = function (queryType, handler, quiet) { - if (!SFrameProtocol[queryType]) { - throw new Error('please only register handlers which are defined in sframe-protocol.js'); - } - (handlers[queryType] = handlers[queryType] || []).push(function (data, msg) { - handler(data.content, function (replyContent) { - if (queryType.indexOf('Q_') !== 0) { throw new Error("replies to events are invalid"); } - msg.source.postMessage(JSON.stringify({ - txid: data.txid, - content: replyContent - }), '*'); - }, msg); - }); - if (!quiet) { - event('EV_REGISTER_HANDLER', queryType); - } - }; - - // If a particular handler is registered, call the callback immediately, otherwise it will be called - // when that handler is first registered. - // channel.whenReg('Q_SOMETHING', function () { ...query Q_SOMETHING?... }); - chan.whenReg = function (queryType, cb, always) { - if (!SFrameProtocol[queryType]) { - throw new Error('please only register handlers which are defined in sframe-protocol.js'); - } - var reg = always; - if (insideHandlers.indexOf(queryType) > -1) { - cb(); - } else { - reg = true; - } - if (reg) { - (callWhenRegistered[queryType] = callWhenRegistered[queryType] || []).push(cb); - } - }; - - // Same as whenReg except it will invoke every time there is another registration, not just once. - chan.onReg = function (queryType, cb) { chan.whenReg(queryType, cb, true); }; - - chan.on('EV_REGISTER_HANDLER', function (content) { - if (callWhenRegistered[content]) { - callWhenRegistered[content].forEach(function (f) { f(); }); - delete callWhenRegistered[content]; - } - insideHandlers.push(content); - }); - chan.whenReg('EV_REGISTER_HANDLER', evReady.fire); - - // Make sure both iframes are ready - var isReady =false; - chan.onReady = function (h) { - if (isReady) { - return void h(); - } - if (typeof(h) !== "function") { return; } - chan.on('EV_RPC_READY', function () { isReady = true; h(); }); - }; - chan.ready = function () { - chan.whenReg('EV_RPC_READY', function () { - chan.event('EV_RPC_READY'); - }); - }; - - var txid; - window.addEventListener('message', function (msg) { - var data = JSON.parse(msg.data); - if (ow !== msg.source) { - return; - //console.log("DROP Message from unexpected source"); - //console.log(msg); - } else if (!otherWindow) { - otherWindow = ow; - sendData = sendData || {}; - sendData.txid = data.txid; - ow.postMessage(JSON.stringify(sendData), '*'); - cb(chan); - } else if (typeof(data.q) === 'string' && handlers[data.q]) { - handlers[data.q].forEach(function (f) { - f(data || JSON.parse(msg.data), msg); - data = undefined; - }); - } else if (typeof(data.q) === 'undefined' && queries[data.txid]) { - queries[data.txid](data, msg); - } else if (data.txid === txid) { - // stray message from init - return; - } else { - console.log("DROP Unhandled message"); - console.log(msg); - } - }); - if (isSandbox) { - // we're in the sandbox - otherWindow = ow; - evReady.fire(); - cb(chan); - } - }; - - return { create: create }; -}); diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index d476d02d4..f1fd0ac8d 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -1181,15 +1181,6 @@ define([ }); }); - sframeChan.on('Q_SESSIONSTORAGE_PUT', function (data, cb) { - if (typeof (data.value) === "undefined") { - delete sessionStorage[data.key]; - } else { - sessionStorage[data.key] = data.value; - } - cb(); - }); - sframeChan.on('Q_IS_ONLY_IN_SHARED_FOLDER', function (data, cb) { Cryptpad.isOnlyInSharedFolder(secret.channel, function (err, t) { if (err) { return void cb({error: err}); } diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index ffff122b5..3eb86ff93 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -462,15 +462,6 @@ define([ }); }; - funcs.sessionStorage = { - put: function (key, value, cb) { - ctx.sframeChan.query('Q_SESSIONSTORAGE_PUT', { - key: key, - value: value - }, cb); - } - }; - funcs.setDisplayName = function (name, cb) { cb = cb || $.noop; ctx.sframeChan.query('Q_SETTINGS_SET_DISPLAY_NAME', name, cb); diff --git a/www/common/sframe-protocol.js b/www/common/sframe-protocol.js deleted file mode 100644 index 5eb9d829e..000000000 --- a/www/common/sframe-protocol.js +++ /dev/null @@ -1,271 +0,0 @@ -// This file defines all of the RPC calls which are used between the inner and outer iframe. -// Define *querys* (which expect a response) using Q_ -// Define *events* (which expect no response) using EV_ -// Please document the queries and events you create, and please please avoid making generic -// "do stuff" events/queries which are used for many different things because it makes the -// protocol unclear. -// -// WARNING: At this point, this protocol is still EXPERIMENTAL. This is not it's final form. -// We need to define protocol one piece at a time and then when we are satisfied that we -// fully understand the problem, we will define the *right* protocol and this file will be dynomited. -// -define({ - // When the iframe first launches, this query is sent repeatedly by the controller - // to wait for it to awake and give it the requirejs config to use. - 'Q_INIT': true, - - // When either the outside or inside registers a query handler, this is sent. - 'EV_REGISTER_HANDLER': true, - - // When an iframe is ready to receive messages - 'EV_RPC_READY': true, - - // Realtime events called from the outside. - // When someone joins the pad, argument is a string with their netflux id. - 'EV_RT_JOIN': true, - // When someone leaves the pad, argument is a string with their netflux id. - 'EV_RT_LEAVE': true, - // When you have been disconnected, no arguments. - 'EV_RT_DISCONNECT': true, - // When you have connected, argument is an object with myID: string, members: list, readOnly: boolean. - '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, - - // Called from the outside, this informs the inside whenever the user's data has been changed. - // The argument is the object representing the content of the user profile minus the netfluxID - // which changes per-reconnect. - 'EV_METADATA_UPDATE': true, - - // Takes one argument only, the title to set for the CURRENT pad which the user is looking at. - // This changes the pad title in drive ONLY, the pad title needs to be changed inside of the - // iframe and synchronized with the other users. This will not trigger a EV_METADATA_UPDATE - // because the metadata contained in EV_METADATA_UPDATE does not contain the pad title. - // It also sets the page (tab) title to the selected title, unles it is overridden by - // the EV_SET_TAB_TITLE event. - 'Q_SET_PAD_TITLE_IN_DRIVE': true, - // Set the page title (tab title) to the selected value which will override the pad title. - // The new title value can contain {title}, which will be replaced by the pad title when it - // is set or modified. - 'EV_SET_TAB_TITLE': true, - - // Update the user's display-name which will be shown to contacts and people in the same pads. - 'Q_SETTINGS_SET_DISPLAY_NAME': true, - - // Log the user out in all the tabs - 'Q_LOGOUT': true, - // Tell the user that he has been logged out from outside (probably from another tab) - 'EV_LOGOUT': true, - - // When moving to the login or register page from a pad, we need to redirect to that pad at the - // end of the login process. This query set the current href to the sessionStorage. - 'Q_SET_LOGIN_REDIRECT': true, - - // Store the editing or readonly link of the current pad to the clipboard (share button). - 'Q_STORE_LINK_TO_CLIPBOARD': true, - - // Use anonymous rpc from inside the iframe (for avatars & pin usage). - 'Q_ANON_RPC_MESSAGE': true, - - // Get the user's pin limit, usage and plan - 'Q_PIN_GET_USAGE': true, - - // Write/update the login block when the account password is changed - 'Q_WRITE_LOGIN_BLOCK': true, - - // Remove login blocks - 'Q_REMOVE_LOGIN_BLOCK': true, - - // Check the pin limit to determine if we can store the pad in the drive or if we should. - // display a warning - 'Q_GET_PIN_LIMIT_STATUS': true, - - // Move a pad to the trash when using the forget button. - 'Q_MOVE_TO_TRASH': true, - - // Request the full history from the server when the users clicks on the history button. - // Callback is called when the FULL_HISTORY_END message is received in the outside. - 'Q_GET_FULL_HISTORY': true, - 'Q_GET_HISTORY_RANGE': true, - // When a (full) history message is received from the server. - 'EV_RT_HIST_MESSAGE': true, - - // Save a pad as a template using the toolbar button - 'Q_SAVE_AS_TEMPLATE': true, - - // Friend requests from the userlist - 'Q_SEND_FRIEND_REQUEST': true, // Up query - 'Q_INCOMING_FRIEND_REQUEST': true, // Down query - 'EV_FRIEND_REQUEST': true, // Down event when the request is complete - - // Set the tab notification when the content of the pad changes - 'EV_NOTIFY': true, - - // Send the new settings to the inner iframe when they are changed in the proxy - 'EV_SETTINGS_UPDATE': true, - - // Get and set (pad) attributes stored in the drive from the inner iframe - 'Q_GET_ATTRIBUTE': true, - 'Q_SET_ATTRIBUTE': true, - 'Q_GET_PAD_ATTRIBUTE': true, - 'Q_SET_PAD_ATTRIBUTE': true, - - // Check if a pad is only in a shared folder or (also) in the main drive. - // This allows us to change the behavior of some buttons (trash icon...) - 'Q_IS_ONLY_IN_SHARED_FOLDER': true, - - // Open/close the File picker (sent from the iframe to the outside) - 'EV_FILE_PICKER_OPEN': true, - 'EV_FILE_PICKER_CLOSE': true, - 'EV_FILE_PICKER_REFRESH': true, - // File selected in the file picker: sent from the filepicker iframe to the outside - // and then send to the inner iframe - 'EV_FILE_PICKED': true, - - // Get all the files from the drive to display them in a file picker secure app - 'Q_GET_FILES_LIST': true, - - // Template picked, replace the content of the pad - 'Q_TEMPLATE_USE': true, - // Check if we have template(s) for the selected pad type - 'Q_TEMPLATE_EXIST': true, - - // File upload queries and events - 'Q_UPLOAD_FILE': true, - 'EV_FILE_UPLOAD_STATE': true, - 'Q_CANCEL_PENDING_FILE_UPLOAD': true, - - // Make the browser window navigate to a given URL, if no URL is passed then it will reload. - 'EV_GOTO_URL': true, - // Make the parent window open a given URL in a new tab. It allows us to keep sessionStorage - // form the parent window. - 'EV_OPEN_URL': true, - - // Present mode URL - 'Q_PRESENT_URL_GET_VALUE': true, - 'EV_PRESENT_URL_SET_VALUE': true, - - // Put one or more entries to the cache which will go in localStorage. - // Cache is wiped after each new release - 'EV_CACHE_PUT': true, - - // Chat - 'EV_CHAT_EVENT': true, - 'Q_CHAT_COMMAND': true, - 'Q_CHAT_OPENPADCHAT': true, - - // Cursor - 'EV_CURSOR_EVENT': true, - 'Q_CURSOR_COMMAND': true, - 'Q_CURSOR_OPENCHANNEL': true, - - // Put one or more entries to the localStore which will go in localStorage. - 'EV_LOCALSTORE_PUT': true, - // Put one entry in the parent sessionStorage - 'Q_SESSIONSTORAGE_PUT': true, - - // Merge the anonymous drive (FS_hash) into the current logged in user's drive, to keep the pads - // in the drive at registration. - 'Q_MERGE_ANON_DRIVE': true, - - // Add or remove the avatar from the profile. - // We have to pin/unpin the avatar and store/remove the value from the user object - 'Q_PROFILE_AVATAR_ADD': true, - 'Q_PROFILE_AVATAR_REMOVE': true, - - // Store outside and get thumbnails inside (stored with localForage (indexedDB) outside) - 'Q_THUMBNAIL_SET': true, - 'Q_THUMBNAIL_GET': true, - - // Settings app only - // Clear all thumbnails - 'Q_THUMBNAIL_CLEAR': true, - // Backup and restore a drive - 'Q_SETTINGS_DRIVE_GET': true, - 'Q_SETTINGS_DRIVE_SET': true, - 'Q_SETTINGS_DRIVE_RESET': true, - // Logout from all the devices where the account is logged in - 'Q_SETTINGS_LOGOUT': true, - // Import pads from this computer's anon session into the current user account - 'Q_SETTINGS_IMPORT_LOCAL': true, - 'Q_SETTINGS_DELETE_ACCOUNT': true, - - // Store the language selected in the iframe into localStorage outside - 'Q_LANGUAGE_SET': true, - - // Anonymous users can empty their drive and remove FS_hash from localStorage - 'EV_BURN_ANON_DRIVE': true, - // Inner drive needs to send command and receive updates from the async store - 'Q_DRIVE_USEROBJECT': true, - 'Q_DRIVE_GETOBJECT': true, - 'Q_DRIVE_RESTORE': 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) - 'EV_DRIVE_CHANGE': true, - 'EV_DRIVE_REMOVE': true, - // Set shared folder hash in the address bar - 'EV_DRIVE_SET_HASH': true, - - // Remove an owned pad from the server - 'Q_REMOVE_OWNED_CHANNEL': true, - // Clear an owned pad from the server (preserve metadata) - 'Q_CLEAR_OWNED_CHANNEL': true, - - // Notifications about connection and disconnection from the network - 'EV_NETWORK_DISCONNECT': true, - 'EV_NETWORK_RECONNECT': true, - // Reload on new version - 'EV_NEW_VERSION': true, - - // Pad creation screen: create a pad with the selected attributes (owned, expire) - 'Q_CREATE_PAD': true, - // Get the available templates - 'Q_CREATE_TEMPLATES': true, - - // This is for sending data out of the iframe when we are in testing mode - // The exact protocol is defined in common/test.js - 'EV_TESTDATA': true, - - // OnlyOffice: save a new version - 'Q_OO_SAVE': true, - - // Ask for the pad password when a pad is protected - 'EV_PAD_PASSWORD': true, - 'Q_PAD_PASSWORD_VALUE': true, - // Change pad password - 'Q_PAD_PASSWORD_CHANGE': true, - - // Migrate drive to owned drive - 'Q_CHANGE_USER_PASSWORD': true, - - // Loading events to display in the loading screen - 'EV_LOADING_INFO': true, - // Critical error outside the iframe during loading screen - 'EV_LOADING_ERROR': true, - - // Chrome 68 bug... - 'EV_CHROME_68': true, - - // Get all existing tags - 'Q_GET_ALL_TAGS': true, - - // Store pads in the drive - 'EV_AUTOSTORE_DISPLAY_POPUP': true, - 'Q_AUTOSTORE_STORE': true, - 'Q_IS_PAD_STORED': true, - - // Import mediatag from a pad - 'Q_IMPORT_MEDIATAG': true, - - // Ability to get a pad's content from its hash - 'Q_CRYPTGET': true, - 'EV_CRYPTGET_DISCONNECT': true, - -}); diff --git a/www/login/main.js b/www/login/main.js index 40a82b27e..768c012a8 100644 --- a/www/login/main.js +++ b/www/login/main.js @@ -67,10 +67,8 @@ define([ }); }); $('#register').on('click', function () { - if (sessionStorage) { - if ($uname.val()) { - sessionStorage.login_user = $uname.val(); - } + if ($uname.val()) { + localStorage.login_user = $uname.val(); } window.location.href = '/register/'; }); diff --git a/www/logout/main.js b/www/logout/main.js index acd8e8b02..d23bb8969 100644 --- a/www/logout/main.js +++ b/www/logout/main.js @@ -1,5 +1,4 @@ define(['/bower_components/localforage/dist/localforage.min.js'], function (localForage) { localForage.clear(); - sessionStorage.clear(); localStorage.clear(); }); diff --git a/www/register/main.js b/www/register/main.js index e2c30ea64..39069bd94 100644 --- a/www/register/main.js +++ b/www/register/main.js @@ -37,9 +37,9 @@ define([ var $passwd = $('#password'); var $confirm = $('#password-confirm'); - if (sessionStorage.login_user) { - delete sessionStorage.login_user; - $uname.val(sessionStorage.login_user); + if (localStorage.login_user) { + $uname.val(localStorage.login_user); + delete loginStorage.login_user; } [ $uname, $passwd, $confirm] From 2e56e07c2487cedb275e800b92fc81b41d69786f Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 2 Nov 2020 11:02:33 +0100 Subject: [PATCH 39/84] Keep the app name in version 4 hashes --- www/common/common-hash.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/www/common/common-hash.js b/www/common/common-hash.js index 06a1d7fef..42925d6d6 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -165,8 +165,15 @@ var factory = function (Util, Crypto, Keys, Nacl) { /* Version 0 /pad/#67b8385b07352be53e40746d2be6ccd7XAYSuJYYqa9NfmInyHci7LNy -Version 1 +Version 1: Add support for read-only access /code/#/1/edit/3Ujt4F2Sjnjbis6CoYWpoQ/usn4+9CqVja8Q7RZOGTfRgqI +Version 2: Add support for password-protection + /code/#/2/code/edit/u5ACvxAYmhvG0FtrNn9FJQcf/p/ +Version 3: Safe links + /code/#/3/code/edit/f0d8055aa640a97e7fd25020ca4e93b3/ +Version 4: Data URL when not a realtime link yet (new pad or "static" app) + /login/#/4/login/newpad=eyJocmVmIjoiaHR0cDovL2xvY2FsaG9zdDozMDAwL2NvZGUvIy8yL2NvZGUvZWRpdC91NUFDdnhBWW1odkcwRnRyTm45RklRY2YvIn0%3D/ + /drive/#/4/drive/login=e30%3D/ */ var getLoginOpts = function (hashArr) { @@ -233,7 +240,7 @@ Version 1 if (hashArr[1] && hashArr[1] === '4') { parsed.getHash = function (opts) { if (!opts || !Object.keys(opts).length) { return ''; } - var hash = '/4/'; + var hash = '/4/' + type + '/'; if (opts.newPadOpts) { hash += 'newpad=' + opts.newPadOpts + '/'; } if (opts.loginOpts) { hash += 'login=' + opts.loginOpts + '/'; } return hash; @@ -246,7 +253,8 @@ Version 1 }; parsed.version = 4; - options = hashArr.slice(2); + parsed.app = hashArr[2]; + options = hashArr.slice(3); addOptions(); return parsed; @@ -429,7 +437,7 @@ Version 1 // When we start without a hash, use version 4 links to add login or newpad options var getHash = function (opts) { if (!opts || !Object.keys(opts).length) { return ''; } - var hash = '/4/'; + var hash = '/4/' + ret.type + '/'; if (opts.newPadOpts) { hash += 'newpad=' + opts.newPadOpts + '/'; } if (opts.loginOpts) { hash += 'login=' + opts.loginOpts + '/'; } return hash; From 864beb713634632847da9fff071f8e553df17f77 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 2 Nov 2020 12:46:44 +0100 Subject: [PATCH 40/84] Fix various issues --- customize.dist/pages/index.js | 4 ++-- www/common/common-hash.js | 7 ++----- www/common/outer/local-store.js | 1 + 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/customize.dist/pages/index.js b/customize.dist/pages/index.js index c718b363f..8a8fa3773 100644 --- a/customize.dist/pages/index.js +++ b/customize.dist/pages/index.js @@ -47,8 +47,8 @@ define([ var href = '/'+ x[0] +'/'; var attr = isEnabled ? { href: href } : { onclick: function () { - var href = Hash.hashToHref('', 'login'); - var url = Hash.getNewPadURL(href, { href: href }); + var loginURL = Hash.hashToHref('', 'login'); + var url = Hash.getNewPadURL(loginURL, { href: href }); window.location.href = url; } }; diff --git a/www/common/common-hash.js b/www/common/common-hash.js index 42925d6d6..199c2cb54 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -236,7 +236,7 @@ Version 4: Data URL when not a realtime link yet (new pad or "static" app) parsed.ownerKey = getOwnerKey(options); }; - // Version 4: only login or newpad options, smae for all the apps + // Version 4: only login or newpad options, same for all the apps if (hashArr[1] && hashArr[1] === '4') { parsed.getHash = function (opts) { if (!opts || !Object.keys(opts).length) { return ''; } @@ -653,9 +653,6 @@ Version 4: Data URL when not a realtime link yet (new pad or "static" app) // Valid hash? if (parsed.hash) { if (!parsed.hashData) { return; } - // New pad: only newPadOpts allowed - if (Object.keys(parsed.hashData).length === 1 && - parsed.hashData.newPadOpts) { return true; } // Version should be a number if (typeof(parsed.hashData.version) === "undefined") { return; } // pads and files should have a base64 (or hex) key @@ -670,7 +667,7 @@ Version 4: Data URL when not a realtime link yet (new pad or "static" app) Hash.decodeDataOptions = function (opts) { var b64 = decodeURIComponent(opts); var str = Nacl.util.encodeUTF8(Nacl.util.decodeBase64(b64)); - return JSON.parse(str); + return Util.tryParse(str) || {}; }; Hash.encodeDataOptions = function (opts) { var str = JSON.stringify(opts); diff --git a/www/common/outer/local-store.js b/www/common/outer/local-store.js index d15c2e8c6..0aff1f9ce 100644 --- a/www/common/outer/local-store.js +++ b/www/common/outer/local-store.js @@ -94,6 +94,7 @@ define([ localStorage.removeItem(k); delete localStorage[k]; }); + sessionStorage.clear(); try { Object.keys(localStorage || {}).forEach(function (k) { // Remvoe everything in localStorage except CACHE and FS_hash From f2faeaad07e8edccbe3ac412f90f0ef571c5c7c9 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 2 Nov 2020 12:55:40 +0100 Subject: [PATCH 41/84] Backward compatiility with sessionStorage --- www/common/cryptpad-common.js | 26 ++++++++++++++++++++++++-- www/common/sframe-common-outer.js | 7 ++++++- www/register/main.js | 2 +- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 36413d89d..8874871d8 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -2107,6 +2107,28 @@ define([ driveEvents: true //rdyCfg.driveEvents // Boolean }; + // FIXME Backward compatibility + if (sessionStorage.newPadFileData) { + common.fromFileData = JSON.parse(sessionStorage.newPadFileData); + var _parsed1 = Hash.parsePadUrl(common.fromFileData.href); + var _parsed2 = Hash.parsePadUrl(window.location.href); + if (_parsed1.hashData.type === 'pad') { + if (_parsed1.type !== _parsed2.type) { delete common.fromFileData; } + } + delete sessionStorage.newPadFileData; + } + + if (sessionStorage.newPadPath) { + common.initialPath = sessionStorage.newPadPath; + delete sessionStorage.newPadPath; + } + + if (sessionStorage.newPadTeam) { + common.initialTeam = sessionStorage.newPadTeam; + delete sessionStorage.newPadTeam; + } + + var channelIsReady = waitFor(); var msgEv = Util.mkEvent(); @@ -2332,7 +2354,7 @@ define([ postMessage("DISCONNECT"); }); }).nThen(function (waitFor) { - if (common.createReadme) { + if (common.createReadme || sessionStorage.createReadme) { var data = { driveReadme: Messages.driveReadme, driveReadmeTitle: Messages.driveReadmeTitle, @@ -2342,7 +2364,7 @@ define([ })); } }).nThen(function (waitFor) { - if (common.migrateAnonDrive) { + if (common.migrateAnonDrive || sessionStorage.migrateAnonDrive) { common.mergeAnonDrive(waitFor()); } }).nThen(function (waitFor) { diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index f1fd0ac8d..b7ce64d4d 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -124,7 +124,7 @@ define([ SFrameChannel.create(msgEv, postMsg, waitFor(function (sfc) { Utils.sframeChan = sframeChan = sfc; window.CryptPad_loadingError = function (e) { - sfc.event('EV_LOADING_ERROR', e) + sfc.event('EV_LOADING_ERROR', e); }; })); }); @@ -260,6 +260,11 @@ define([ } // Otherwise, continue } + // FIXME Backward compatibility + if (sessionStorage.newPadPassword && !newPadPassword) { + newPadPassword = sessionStorage.newPadPassword; + delete sessionStorage.newPadPassword; + } if (!parsed.hashData) { // No hash, no need to check for a password diff --git a/www/register/main.js b/www/register/main.js index 39069bd94..2bf9d9f39 100644 --- a/www/register/main.js +++ b/www/register/main.js @@ -39,7 +39,7 @@ define([ if (localStorage.login_user) { $uname.val(localStorage.login_user); - delete loginStorage.login_user; + delete localStorage.login_user; } [ $uname, $passwd, $confirm] From 4e4147f021c8749091553637c46146fd9ba94239 Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 3 Nov 2020 16:48:12 +0530 Subject: [PATCH 42/84] update version string for v3.24.0 (YunnanLakeNewt) --- customize.dist/pages.js | 2 +- package-lock.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/customize.dist/pages.js b/customize.dist/pages.js index 1db764bcc..74c46c4ec 100644 --- a/customize.dist/pages.js +++ b/customize.dist/pages.js @@ -62,7 +62,7 @@ define([ var imprintUrl = AppConfig.imprint && (typeof(AppConfig.imprint) === "boolean" ? '/imprint.html' : AppConfig.imprint); - Pages.versionString = "CryptPad v3.23.2 (XerusDaamsi reloaded)"; + Pages.versionString = "CryptPad v3.24.0 (YunnanLakeNewt)"; // used for the about menu Pages.imprintLink = AppConfig.imprint ? footLink(imprintUrl, 'imprint') : undefined; diff --git a/package-lock.json b/package-lock.json index a9ee6e5e7..775f42f68 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "cryptpad", - "version": "3.23.2", + "version": "3.24.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index a849b2c2d..37fd6c686 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cryptpad", "description": "realtime collaborative visual editor with zero knowlege server", - "version": "3.23.2", + "version": "3.24.0", "license": "AGPL-3.0+", "repository": { "type": "git", From 3db6d2fff90aa578f4c5529e6dad06879e690e3d Mon Sep 17 00:00:00 2001 From: ansuz Date: Wed, 4 Nov 2020 08:32:14 +0530 Subject: [PATCH 43/84] scroll pad table of contents if necessary --- www/pad/app-pad.less | 1 + 1 file changed, 1 insertion(+) diff --git a/www/pad/app-pad.less b/www/pad/app-pad.less index b413eed6a..489cbb17d 100644 --- a/www/pad/app-pad.less +++ b/www/pad/app-pad.less @@ -27,6 +27,7 @@ body.cp-app-pad { #cp-app-pad-toc { @toc-level-indent: 15px; + overflow-y: auto; margin-top: 10px; margin-left: 10px; width: 200px; From 016bdc73b6cbe02557fbe9cdfb4dd32becd0cce9 Mon Sep 17 00:00:00 2001 From: Weblate Date: Wed, 4 Nov 2020 05:04:22 +0100 Subject: [PATCH 44/84] Translated using Weblate (English) Currently translated at 100.0% (1372 of 1372 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/en/ --- www/common/translations/messages.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/www/common/translations/messages.json b/www/common/translations/messages.json index fa96fc5a7..c76b9a7c1 100644 --- a/www/common/translations/messages.json +++ b/www/common/translations/messages.json @@ -1467,5 +1467,6 @@ "loading_state_4": "Load Teams", "loading_state_5": "Reconstruct document", "tag_add": "Add", - "tag_edit": "Edit" + "tag_edit": "Edit", + "error_unhelpfulScriptError": "Script Error: See browser console for details" } From 553f29b43163db4b7af20120729c45266faa68f7 Mon Sep 17 00:00:00 2001 From: Weblate Date: Wed, 4 Nov 2020 05:04:23 +0100 Subject: [PATCH 45/84] Translated using Weblate (French) Currently translated at 100.0% (1372 of 1372 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/fr/ --- www/common/translations/messages.fr.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/www/common/translations/messages.fr.json b/www/common/translations/messages.fr.json index 70909c44d..1d1779035 100644 --- a/www/common/translations/messages.fr.json +++ b/www/common/translations/messages.fr.json @@ -1467,5 +1467,6 @@ "loading_state_1": "Chargement du drive", "loading_state_0": "Construction de l'interface", "tag_edit": "Modifier", - "tag_add": "Ajouter" + "tag_add": "Ajouter", + "error_unhelpfulScriptError": "Erreur de script : consultez la console du navigateur pour plus de détails" } From 59eaf8187c9a097fc0f679da784ed2615e9b1bd8 Mon Sep 17 00:00:00 2001 From: ansuz Date: Wed, 4 Nov 2020 10:28:52 +0530 Subject: [PATCH 46/84] handle unhelpful 'Script error.' message --- customize.dist/loading.js | 16 +++++++++++++--- www/common/sframe-boot2.js | 2 +- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/customize.dist/loading.js b/customize.dist/loading.js index 4f8b79125..740c66081 100644 --- a/customize.dist/loading.js +++ b/customize.dist/loading.js @@ -312,25 +312,35 @@ button.primary:hover{ return bar; }; + var hasErrored = false; var updateLoadingProgress = function (data) { - if (!built) { return; } + if (!built || !data) { return; } var c = types.indexOf(data.type); if (c < current) { return console.error(data); } try { document.querySelector('.cp-loading-spinner-container').style.display = 'none'; document.querySelector('.cp-loading-progress-list').innerHTML = makeList(data); document.querySelector('.cp-loading-progress-container').innerHTML = makeBar(data); - } catch (e) { console.error(e); } + } catch (e) { + if (!hasErrored) { console.error(e); } + } }; window.CryptPad_updateLoadingProgress = updateLoadingProgress; + window.CryptPad_loadingError = function (err) { if (!built) { return; } + hasErrored = true; + var err2; + if (err === 'Script error.') { + err2 = Messages.error_unhelpfulScriptError; + } + try { var node = document.querySelector('.cp-loading-progress'); if (node.parentNode) { node.parentNode.removeChild(node); } document.querySelector('.cp-loading-spinner-container').setAttribute('style', 'display:none;'); document.querySelector('#cp-loading-message').setAttribute('style', 'display:block;'); - document.querySelector('#cp-loading-message').innerText = err; + document.querySelector('#cp-loading-message').innerText = err2 || err; } catch (e) { console.error(e); } }; return function () { diff --git a/www/common/sframe-boot2.js b/www/common/sframe-boot2.js index 5a68237ad..66a93545f 100644 --- a/www/common/sframe-boot2.js +++ b/www/common/sframe-boot2.js @@ -43,7 +43,7 @@ define([ return void console.log(); } if (window.CryptPad_loadingError) { - window.CryptPad_loadingError(e); + return void window.CryptPad_loadingError(e); } throw e; }; From c4e1b5f7d42ffaf6fd8c931dbb28d562ba53db54 Mon Sep 17 00:00:00 2001 From: ansuz Date: Wed, 4 Nov 2020 10:32:45 +0530 Subject: [PATCH 47/84] refactor two mostly-identical functions --- www/common/cryptpad-common.js | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 8874871d8..ca65ce77b 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -148,20 +148,19 @@ define([ send(); }; - common.setTabHref = function (href) { - var ohc = window.onhashchange; - window.onhashchange = function () {}; - window.location.href = href; - window.onhashchange = ohc; - ohc({reset: true}); - }; - common.setTabHash = function (hash) { - var ohc = window.onhashchange; - window.onhashchange = function () {}; - window.location.hash = hash; - window.onhashchange = ohc; - ohc({reset: true}); - }; + (function () { + var bypassHashChange = function (key) { + return function (value) { + var ohc = window.onhashchange; + window.onhashchange = function () {}; + window.location[key] = value; + window.onhashchange = ohc; + ohc({reset: true}); + }; + }; + common.setTabHref = bypassHashChange('href'); + common.setTabHash = bypassHashChange('hash'); + }()); // RESTRICTED // Settings only From 0f4013505d2707c226dc223df76ee700f6963a00 Mon Sep 17 00:00:00 2001 From: ansuz Date: Wed, 4 Nov 2020 12:26:53 +0530 Subject: [PATCH 48/84] prevent typeError for undefined button when logged out --- www/common/onlyoffice/inner.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index ce0dbcc3f..28d06dd32 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -2070,7 +2070,9 @@ define([ // Import template var $template = common.createButton('importtemplate', true, {}, openTemplatePicker); - $template.appendTo(toolbar.$drawer); + if ($template && typeof($template.appendTo) === 'function') { + $template.appendTo(toolbar.$drawer); + } })(); } From 9203b88538834cefdd1a89e817c373e862a2607e Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 4 Nov 2020 11:22:31 +0100 Subject: [PATCH 49/84] Fix UI language --- customize.dist/loading.js | 1 + customize.dist/messages.js | 8 +++-- www/admin/inner.html | 2 +- www/admin/main.js | 28 ++---------------- www/code/inner.html | 2 +- www/common/onlyoffice/main.js | 39 +++--------------------- www/common/sframe-app-outer.js | 38 +++--------------------- www/common/sframe-boot.js | 2 +- www/common/sframe-common-outer.js | 49 ++++++++++++++++++++++++++++++- www/contacts/inner.html | 2 +- www/contacts/main.js | 28 ++---------------- www/debug/inner.html | 2 +- www/drive/inner.html | 2 +- www/drive/main.js | 38 +++--------------------- www/file/inner.html | 2 +- www/file/main.js | 38 +++--------------------- www/kanban/inner.html | 2 +- www/notifications/inner.html | 2 +- www/notifications/main.js | 28 ++---------------- www/oodoc/inner.html | 2 +- www/ooslide/inner.html | 2 +- www/pad/inner.html | 2 +- www/poll/inner.html | 2 +- www/poll/main.js | 38 +++--------------------- www/profile/inner.html | 2 +- www/profile/main.js | 28 ++---------------- www/secureiframe/inner.html | 2 +- www/secureiframe/main.js | 7 +++-- www/settings/inner.html | 2 +- www/settings/main.js | 28 ++---------------- www/sheet/inner.html | 2 +- www/slide/inner.html | 2 +- www/support/inner.html | 2 +- www/support/main.js | 28 ++---------------- www/teams/inner.html | 2 +- www/teams/main.js | 38 +++--------------------- www/todo/inner.html | 2 +- www/whiteboard/inner.html | 2 +- www/worker/inner.html | 2 +- www/worker/main.js | 28 ++---------------- 40 files changed, 121 insertions(+), 415 deletions(-) diff --git a/customize.dist/loading.js b/customize.dist/loading.js index 740c66081..edb66ef0a 100644 --- a/customize.dist/loading.js +++ b/customize.dist/loading.js @@ -337,6 +337,7 @@ button.primary:hover{ try { var node = document.querySelector('.cp-loading-progress'); + if (!node) { return; } if (node.parentNode) { node.parentNode.removeChild(node); } document.querySelector('.cp-loading-spinner-container').setAttribute('style', 'display:none;'); document.querySelector('#cp-loading-message').setAttribute('style', 'display:block;'); diff --git a/customize.dist/messages.js b/customize.dist/messages.js index 40dbbfb95..303375e4e 100755 --- a/customize.dist/messages.js +++ b/customize.dist/messages.js @@ -26,7 +26,9 @@ var getStoredLanguage = function () { return localStorage && localStorage.getIte var getBrowserLanguage = function () { return navigator.language || navigator.userLanguage || ''; }; var getLanguage = messages._getLanguage = function () { if (window.cryptpadLanguage) { return window.cryptpadLanguage; } - if (getStoredLanguage()) { return getStoredLanguage(); } + try { + if (getStoredLanguage()) { return getStoredLanguage(); } + } catch (e) { console.log(e); } var l = getBrowserLanguage(); // Edge returns 'fr-FR' --> transform it to 'fr' and check again return map[l] ? l : @@ -65,7 +67,9 @@ define(req, function(AppConfig, Default, Language) { if (AppConfig.availableLanguages.indexOf(language) === -1) { language = defaultLanguage; Language = Default; - localStorage.setItem(LS_LANG, language); + try { + localStorage.setItem(LS_LANG, language); + } catch (e) { console.log(e); } } Object.keys(map).forEach(function (l) { if (l === defaultLanguage) { return; } diff --git a/www/admin/inner.html b/www/admin/inner.html index 01bda5fab..eeb234d0c 100644 --- a/www/admin/inner.html +++ b/www/admin/inner.html @@ -2,7 +2,7 @@ - + diff --git a/www/admin/main.js b/www/admin/main.js index 817d2bd2e..8a6ec7a70 100644 --- a/www/admin/main.js +++ b/www/admin/main.js @@ -3,38 +3,14 @@ define([ '/bower_components/nthen/index.js', '/api/config', '/common/dom-ready.js', - '/common/requireconfig.js', '/common/sframe-common-outer.js', -], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) { - var requireConfig = RequireConfig(); +], function (nThen, ApiConfig, DomReady, SFCommonO) { // Loaded in load #2 nThen(function (waitFor) { DomReady.onReady(waitFor()); }).nThen(function (waitFor) { - var req = { - cfg: requireConfig, - req: [ '/common/loading.js' ], - pfx: window.location.origin - }; - window.rc = requireConfig; - window.apiconf = ApiConfig; - document.getElementById('sbox-iframe').setAttribute('src', - ApiConfig.httpSafeOrigin + '/admin/inner.html?' + requireConfig.urlArgs + - '#' + encodeURIComponent(JSON.stringify(req))); - - // This is a cheap trick to avoid loading sframe-channel in parallel with the - // loading screen setup. - var done = waitFor(); - var onMsg = function (msg) { - var data = JSON.parse(msg.data); - if (data.q !== 'READY') { return; } - window.removeEventListener('message', onMsg); - var _done = done; - done = function () { }; - _done(); - }; - window.addEventListener('message', onMsg); + SFCommonO.initIframe(waitFor); }).nThen(function (/*waitFor*/) { var addRpc = function (sframeChan, Cryptpad/*, Utils*/) { // Adding a new avatar from the profile: pin it and store it in the object diff --git a/www/code/inner.html b/www/code/inner.html index a4ea56206..b25534297 100644 --- a/www/code/inner.html +++ b/www/code/inner.html @@ -2,7 +2,7 @@ - + diff --git a/www/contacts/main.js b/www/contacts/main.js index 38d6c5e71..faf92f94e 100644 --- a/www/contacts/main.js +++ b/www/contacts/main.js @@ -3,38 +3,14 @@ define([ '/bower_components/nthen/index.js', '/api/config', '/common/dom-ready.js', - '/common/requireconfig.js', '/common/sframe-common-outer.js' -], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) { - var requireConfig = RequireConfig(); +], function (nThen, ApiConfig, DomReady, SFCommonO) { // Loaded in load #2 nThen(function (waitFor) { DomReady.onReady(waitFor()); }).nThen(function (waitFor) { - var req = { - cfg: requireConfig, - req: [ '/common/loading.js' ], - pfx: window.location.origin - }; - window.rc = requireConfig; - window.apiconf = ApiConfig; - document.getElementById('sbox-iframe').setAttribute('src', - ApiConfig.httpSafeOrigin + '/contacts/inner.html?' + requireConfig.urlArgs + - '#' + encodeURIComponent(JSON.stringify(req))); - - // This is a cheap trick to avoid loading sframe-channel in parallel with the - // loading screen setup. - var done = waitFor(); - var onMsg = function (msg) { - var data = JSON.parse(msg.data); - if (data.q !== 'READY') { return; } - window.removeEventListener('message', onMsg); - var _done = done; - done = function () { }; - _done(); - }; - window.addEventListener('message', onMsg); + SFCommonO.initIframe(waitFor); }).nThen(function (/*waitFor*/) { SFCommonO.start({ noRealtime: true, diff --git a/www/debug/inner.html b/www/debug/inner.html index 2ac53948c..7936c04f2 100644 --- a/www/debug/inner.html +++ b/www/debug/inner.html @@ -2,7 +2,7 @@ - + diff --git a/www/notifications/main.js b/www/notifications/main.js index 20c8653f9..785fb3b5d 100644 --- a/www/notifications/main.js +++ b/www/notifications/main.js @@ -3,38 +3,14 @@ define([ '/bower_components/nthen/index.js', '/api/config', '/common/dom-ready.js', - '/common/requireconfig.js', '/common/sframe-common-outer.js', -], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) { - var requireConfig = RequireConfig(); +], function (nThen, ApiConfig, DomReady, SFCommonO) { // Loaded in load #2 nThen(function (waitFor) { DomReady.onReady(waitFor()); }).nThen(function (waitFor) { - var req = { - cfg: requireConfig, - req: [ '/common/loading.js' ], - pfx: window.location.origin - }; - window.rc = requireConfig; - window.apiconf = ApiConfig; - document.getElementById('sbox-iframe').setAttribute('src', - ApiConfig.httpSafeOrigin + '/notifications/inner.html?' + requireConfig.urlArgs + - '#' + encodeURIComponent(JSON.stringify(req))); - - // This is a cheap trick to avoid loading sframe-channel in parallel with the - // loading screen setup. - var done = waitFor(); - var onMsg = function (msg) { - var data = JSON.parse(msg.data); - if (data.q !== 'READY') { return; } - window.removeEventListener('message', onMsg); - var _done = done; - done = function () { }; - _done(); - }; - window.addEventListener('message', onMsg); + SFCommonO.initIframe(waitFor); }).nThen(function (/*waitFor*/) { var category; if (window.location.hash) { diff --git a/www/oodoc/inner.html b/www/oodoc/inner.html index 529c5a8d9..884ae5a00 100644 --- a/www/oodoc/inner.html +++ b/www/oodoc/inner.html @@ -2,7 +2,7 @@ - + diff --git a/www/ooslide/inner.html b/www/ooslide/inner.html index d06820db2..e7c4e111f 100644 --- a/www/ooslide/inner.html +++ b/www/ooslide/inner.html @@ -2,7 +2,7 @@ - + diff --git a/www/pad/inner.html b/www/pad/inner.html index e4dbcdf95..17bfec308 100644 --- a/www/pad/inner.html +++ b/www/pad/inner.html @@ -2,7 +2,7 @@ - + diff --git a/www/profile/main.js b/www/profile/main.js index 92b24b3fc..b041d926a 100644 --- a/www/profile/main.js +++ b/www/profile/main.js @@ -3,38 +3,14 @@ define([ '/bower_components/nthen/index.js', '/api/config', '/common/dom-ready.js', - '/common/requireconfig.js', '/common/sframe-common-outer.js', -], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) { - var requireConfig = RequireConfig(); +], function (nThen, ApiConfig, DomReady, SFCommonO) { // Loaded in load #2 nThen(function (waitFor) { DomReady.onReady(waitFor()); }).nThen(function (waitFor) { - var req = { - cfg: requireConfig, - req: [ '/common/loading.js' ], - pfx: window.location.origin - }; - window.rc = requireConfig; - window.apiconf = ApiConfig; - document.getElementById('sbox-iframe').setAttribute('src', - ApiConfig.httpSafeOrigin + '/profile/inner.html?' + requireConfig.urlArgs + - '#' + encodeURIComponent(JSON.stringify(req))); - - // This is a cheap trick to avoid loading sframe-channel in parallel with the - // loading screen setup. - var done = waitFor(); - var onMsg = function (msg) { - var data = JSON.parse(msg.data); - if (data.q !== 'READY') { return; } - window.removeEventListener('message', onMsg); - var _done = done; - done = function () { }; - _done(); - }; - window.addEventListener('message', onMsg); + SFCommonO.initIframe(waitFor); }).nThen(function (/*waitFor*/) { var getSecrets = function (Cryptpad, Utils, cb) { var Hash = Utils.Hash; diff --git a/www/secureiframe/inner.html b/www/secureiframe/inner.html index 29c3cf797..97bfb3930 100644 --- a/www/secureiframe/inner.html +++ b/www/secureiframe/inner.html @@ -2,7 +2,7 @@ - + diff --git a/www/settings/main.js b/www/settings/main.js index bbc0f87d3..750423a1d 100644 --- a/www/settings/main.js +++ b/www/settings/main.js @@ -3,38 +3,14 @@ define([ '/bower_components/nthen/index.js', '/api/config', '/common/dom-ready.js', - '/common/requireconfig.js', '/common/sframe-common-outer.js' -], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) { - var requireConfig = RequireConfig(); +], function (nThen, ApiConfig, DomReady, SFCommonO) { // Loaded in load #2 nThen(function (waitFor) { DomReady.onReady(waitFor()); }).nThen(function (waitFor) { - var req = { - cfg: requireConfig, - req: [ '/common/loading.js' ], - pfx: window.location.origin - }; - window.rc = requireConfig; - window.apiconf = ApiConfig; - document.getElementById('sbox-iframe').setAttribute('src', - ApiConfig.httpSafeOrigin + '/settings/inner.html?' + requireConfig.urlArgs + - '#' + encodeURIComponent(JSON.stringify(req))); - - // This is a cheap trick to avoid loading sframe-channel in parallel with the - // loading screen setup. - var done = waitFor(); - var onMsg = function (msg) { - var data = JSON.parse(msg.data); - if (data.q !== 'READY') { return; } - window.removeEventListener('message', onMsg); - var _done = done; - done = function () { }; - _done(); - }; - window.addEventListener('message', onMsg); + SFCommonO.initIframe(waitFor); }).nThen(function (/*waitFor*/) { var addRpc = function (sframeChan, Cryptpad, Utils) { sframeChan.on('Q_THUMBNAIL_CLEAR', function (d, cb) { diff --git a/www/sheet/inner.html b/www/sheet/inner.html index 07d21904d..68949568f 100644 --- a/www/sheet/inner.html +++ b/www/sheet/inner.html @@ -2,7 +2,7 @@ - + diff --git a/www/slide/inner.html b/www/slide/inner.html index c04091cf7..f067e8a2f 100644 --- a/www/slide/inner.html +++ b/www/slide/inner.html @@ -2,7 +2,7 @@ - + diff --git a/www/support/main.js b/www/support/main.js index b5ca65126..1dc8c0e56 100644 --- a/www/support/main.js +++ b/www/support/main.js @@ -3,40 +3,16 @@ define([ '/bower_components/nthen/index.js', '/api/config', '/common/dom-ready.js', - '/common/requireconfig.js', '/common/sframe-common-outer.js', '/common/outer/local-store.js', '/common/outer/login-block.js', -], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO, LocalStore, Block) { - var requireConfig = RequireConfig(); +], function (nThen, ApiConfig, DomReady, SFCommonO, LocalStore, Block) { // Loaded in load #2 nThen(function (waitFor) { DomReady.onReady(waitFor()); }).nThen(function (waitFor) { - var req = { - cfg: requireConfig, - req: [ '/common/loading.js' ], - pfx: window.location.origin - }; - window.rc = requireConfig; - window.apiconf = ApiConfig; - document.getElementById('sbox-iframe').setAttribute('src', - ApiConfig.httpSafeOrigin + '/support/inner.html?' + requireConfig.urlArgs + - '#' + encodeURIComponent(JSON.stringify(req))); - - // This is a cheap trick to avoid loading sframe-channel in parallel with the - // loading screen setup. - var done = waitFor(); - var onMsg = function (msg) { - var data = JSON.parse(msg.data); - if (data.q !== 'READY') { return; } - window.removeEventListener('message', onMsg); - var _done = done; - done = function () { }; - _done(); - }; - window.addEventListener('message', onMsg); + SFCommonO.initIframe(waitFor); }).nThen(function (/*waitFor*/) { var category; if (window.location.hash) { diff --git a/www/teams/inner.html b/www/teams/inner.html index 243a74edf..5ec12c287 100644 --- a/www/teams/inner.html +++ b/www/teams/inner.html @@ -2,7 +2,7 @@ - + diff --git a/www/whiteboard/inner.html b/www/whiteboard/inner.html index 533b4568f..4b56440e3 100644 --- a/www/whiteboard/inner.html +++ b/www/whiteboard/inner.html @@ -2,7 +2,7 @@ - + diff --git a/www/worker/main.js b/www/worker/main.js index 04dffa748..633982146 100644 --- a/www/worker/main.js +++ b/www/worker/main.js @@ -3,38 +3,14 @@ define([ '/bower_components/nthen/index.js', '/api/config', '/common/dom-ready.js', - '/common/requireconfig.js', '/common/sframe-common-outer.js' -], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) { - var requireConfig = RequireConfig(); +], function (nThen, ApiConfig, DomReady, SFCommonO) { // Loaded in load #2 nThen(function (waitFor) { DomReady.onReady(waitFor()); }).nThen(function (waitFor) { - var req = { - cfg: requireConfig, - req: [ '/common/loading.js' ], - pfx: window.location.origin - }; - window.rc = requireConfig; - window.apiconf = ApiConfig; - document.getElementById('sbox-iframe').setAttribute('src', - ApiConfig.httpSafeOrigin + '/worker/inner.html?' + requireConfig.urlArgs + - '#' + encodeURIComponent(JSON.stringify(req))); - - // This is a cheap trick to avoid loading sframe-channel in parallel with the - // loading screen setup. - var done = waitFor(); - var onMsg = function (msg) { - var data = JSON.parse(msg.data); - if (data.q !== 'READY') { return; } - window.removeEventListener('message', onMsg); - var _done = done; - done = function () { }; - _done(); - }; - window.addEventListener('message', onMsg); + SFCommonO.initIframe(waitFor); }).nThen(function (/*waitFor*/) { SFCommonO.start({ noRealtime: true, From 2844505593249b66ad8ef8dcdfe9558e5f02346e Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 4 Nov 2020 13:09:49 +0100 Subject: [PATCH 50/84] Remove double slash in iframe URL --- www/common/sframe-common-outer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 24496dd53..f6f24c5bf 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -31,7 +31,7 @@ define([ } document.getElementById('sbox-iframe').setAttribute('src', - ApiConfig.httpSafeOrigin + window.location.pathname + '/inner.html?' + + ApiConfig.httpSafeOrigin + window.location.pathname + 'inner.html?' + requireConfig.urlArgs + '#' + encodeURIComponent(JSON.stringify(req))); // This is a cheap trick to avoid loading sframe-channel in parallel with the From a63ddb646cdc0f3e923c7daca5664c971d5612fb Mon Sep 17 00:00:00 2001 From: ansuz Date: Thu, 5 Nov 2020 14:46:46 +0530 Subject: [PATCH 51/84] include anchors in rich text table of contents --- www/pad/inner.js | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/www/pad/inner.js b/www/pad/inner.js index eeabbb337..235b4fafa 100644 --- a/www/pad/inner.js +++ b/www/pad/inner.js @@ -650,9 +650,25 @@ define([ }, 500); // 500ms to make sure it is sent after chainpad sync }; + var isAnchor = function (el) { return el.nodeName === 'A'; }; + var getAnchorName = function (el) { + return el.getAttribute('id') || + el.getAttribute('data-cke-saved-name') || + el.getAttribute('name') || + Util.stripTags($(el).text()); + }; + var updateTOC = Util.throttle(function () { var toc = []; - $inner.find('h1, h2, h3').each(function (i, el) { + $inner.find('h1, h2, h3, a[id][data-cke-saved-name]').each(function (i, el) { + if (isAnchor(el)) { + return void toc.push({ + level: 2, + el: el, + title: getAnchorName(el), + }); + } + toc.push({ level: Number(el.tagName.slice(1)), el: el, @@ -661,6 +677,8 @@ define([ }); var content = [h('h2', Messages.markdown_toc)]; toc.forEach(function (obj) { + var title = (obj.title || "").trim(); + if (!title) { return; } // Only include level 2 headings var level = obj.level; var a = h('a.cp-pad-toc-link', { @@ -672,7 +690,7 @@ define([ if (!obj.el || UIElements.isVisible(obj.el, $inner)) { return; } obj.el.scrollIntoView(); }); - a.innerHTML = obj.title; + a.innerHTML = title; content.push(h('p.cp-pad-toc-'+level, a)); }); $toc.html('').append(content); @@ -1098,7 +1116,7 @@ define([ */ Ckeditor.dom.element.prototype.setHtml = function(a){ if (/callFunction/.test(a)) { - a = a.replace(/on(mousedown|blur|keydown|focus|click|dragstart)/g, function (value) { + a = a.replace(/on(mousedown|blur|keydown|focus|click|dragstart|mouseover|mouseout)/g, function (value) { return 'o' + value; }); } From 830739c901aa2623ee4aa0831cebc9443673b4ac Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 5 Nov 2020 11:28:20 +0100 Subject: [PATCH 52/84] Fix teams APP issue after login redirect --- www/common/sframe-common-outer.js | 3 ++- www/teams/main.js | 9 ++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index f6f24c5bf..ad128db33 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -121,6 +121,7 @@ define([ Utils.Feedback = _Feedback; Utils.LocalStore = _LocalStore; Utils.UserObject = _UserObject; + Utils.currentPad = currentPad; AppConfig = _AppConfig; Test = _Test; @@ -604,7 +605,7 @@ define([ for (var k in additionalPriv) { metaObj.priv[k] = additionalPriv[k]; } if (cfg.addData) { - cfg.addData(metaObj.priv, Cryptpad, metaObj.user); + cfg.addData(metaObj.priv, Cryptpad, metaObj.user, Utils); } sframeChan.event('EV_METADATA_UPDATE', metaObj); diff --git a/www/teams/main.js b/www/teams/main.js index 722cd7c1b..044419dfc 100644 --- a/www/teams/main.js +++ b/www/teams/main.js @@ -97,9 +97,12 @@ define([ var secret = Hash.getSecrets('team', hash); cb(null, secret); }; - var addData = function (meta) { - if (!hash) { return; } - meta.teamInviteHash = hash.slice(1); + var addData = function (meta, Cryptpad, user, Utils) { + if (!Utils.currentPad.hash) { return; } + var _hash = Utils.currentPad.hash.replace(/^#/, ''); + var parsed = Utils.Hash.parseTypeHash('invite', _hash); + if (parsed.app !== 'invite') { return; } + meta.teamInviteHash = _hash; }; SFCommonO.start({ getSecrets: getSecrets, From d5b348a366f97b1f5d521c2aa30a183dbb556a1a Mon Sep 17 00:00:00 2001 From: Weblate Date: Wed, 4 Nov 2020 15:25:59 +0100 Subject: [PATCH 53/84] Translated using Weblate (German) Currently translated at 100.0% (1372 of 1372 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/de/ --- www/common/translations/messages.de.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/www/common/translations/messages.de.json b/www/common/translations/messages.de.json index 6b5046aa0..b8aa108ed 100644 --- a/www/common/translations/messages.de.json +++ b/www/common/translations/messages.de.json @@ -1467,5 +1467,6 @@ "loading_state_2": "Inhalte aktualisieren", "loading_state_1": "Drive laden", "loading_state_0": "Oberfläche vorbereiten", - "loading_state_5": "Dokument rekonstruieren" + "loading_state_5": "Dokument rekonstruieren", + "error_unhelpfulScriptError": "Skriptfehler: Siehe Konsole im Browser für Details" } From 0e08abecb39573c10dbff2ac15fb7fb9e513cacf Mon Sep 17 00:00:00 2001 From: ansuz Date: Thu, 5 Nov 2020 18:33:17 +0530 Subject: [PATCH 54/84] update changelog for 3.24.0 --- CHANGELOG.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f649883d..3330eb35b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,40 @@ +# YunnanLakeNewt (3.24.0) + +## Goals + +We are once again working to develop some significant new features. This release is fairly small but includes some significant changes to detect and handle a variety of errors. + +## Update notes + +This release includes some minor corrections the recommended NGINX configuration supplied in `cryptpad/docs/example.nginx.conf`. + +To update from 3.23.2 to 3.24.0: + +1. Update your NGINX config to replicate the most recent changes and reload NGINX to apply them. +2. Stop the nodejs server. +3. Pull the latest code from the `3.24.0` tag or the `main` branch using `git`. +4. Ensure you have the latest clientside and serverside dependencies with `bower update` and `npm install`. +5. Restart the nodejs server. + +## Features + +* A variety of CryptPad's pages now feature a much-improved loading screen which provides a more informative account of what is being loaded. It also implements some generic error handling to detect and report when something has failed in a catastrophic way. This is intended to both inform users that the page is in a broken state as well as to improve the quality of the debugging information they can provide to us so that we can fix the underlying cause. +* It is now possible to create spreadsheets from templates. Template functionality has existed for a long time in our other editors, however, OnlyOffice's architecture differs significantly and required the implementation of a wholly different system. +* One user reported some confusion regarding the use of the Kanban app's _tag_ functionality. We've updated the UI to be a little more informative. +* The "table of contents" in rich text pads now includes "anchors" created via the editor's toolbar. + +## Bug fixes + +* Recent changes to CryptPad's recommended CSP headers enabled Firefox to export spreadsheets to XLSX format, but they also triggered some regressions due to a number of incompatible APIs. + * Our usage of the `sessionStorage` for the purpose of passing important information to editors opened in a new tab stopped working. This meant that when you created a document in a folder, the resulting new tab would not receive the argument describing where it should be stored, and would instead save it to the default location. We've addressed this by replacing our usage of sessionStorage with a new format for passing the same arguments via the hash in the new document's URL. + * The `window.print` API also failed in a variety of cases. We've updated the relevant CSP headers to only be applied on the sheet editor (to support XSLX export) but allow printing elsewhere. We've also updated some print styles to provide more appealing results. +* The table of contents available in rich text pads failed to scroll when there were a sufficient number of heading to flow beyond the length of the page. Now a scrollbar appears when necessary. +* We discovered a number of cases where the presence of an allow list prevented some valid behaviour due to the server incorrectly concluding that users were not authenticated. We've improved the client's ability to detect these cases and re-authenticate when necessary. +* We also found that when the server was under very heavy load some database queries were timing out because they were slow (but not stopped). We've addressed this to only terminate such queries if they have been entirely inactive for several minutes. +* It was possible for "safe links" to include a mode ("edit" or "view") which did not match the rights of the user opening them. For example, if a user loaded a safe link with edit rights though they only had read-only access via their "viewer" role in a team. CryptPad will now recover from such cases and open the document with the closest set of access rights that they possess. +* We found that the server query `"IS_NEW_PAD"` could return an error but that clients would incorrectly interpret such a response as a `false`. This has been corrected. +* Finally, we've modified the "trash" UI for user and team drives such that when users attempt to empty their trash of owned shared folders they are prompted to remove the items or delete them from the server entirely, as they would be with other owned assets. + # XerusDaamsi reloaded (3.23.2) A number of instance administrators reported issues following our 3.23.1 release. We suspect the issues were caused by applying the recommended update steps out of order which would result in the incorrect HTTP header values getting cached for the most recent version of a file. Since the most recently updated headers modified some security settings, this caused a catastrophic error on clients receiving the incorrect headers which caused them to fail to load under certain circumstances. From bc2387256f61b63a4ac85104e34da42e56c78c1d Mon Sep 17 00:00:00 2001 From: Christian Pietsch Date: Thu, 5 Nov 2020 19:23:08 +0100 Subject: [PATCH 55/84] Set reasonable value for $PWD /home/cryptpad/cryptpad/cryptpad seems one cryptpad too many, and it does not match the sample value of WorkingDirectory above --- docs/cryptpad.service | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cryptpad.service b/docs/cryptpad.service index eee8b2af5..43d8652f6 100644 --- a/docs/cryptpad.service +++ b/docs/cryptpad.service @@ -17,7 +17,7 @@ SyslogIdentifier=cryptpad User=cryptpad Group=cryptpad # modify to match your working directory -Environment='PWD="/home/cryptpad/cryptpad/cryptpad"' +Environment='PWD="/home/cryptpad/cryptpad"' # systemd sets the open file limit to 4000 unless you override it # cryptpad stores its data with the filesystem, so you should increase this to match the value of `ulimit -n` From 7fe1ec14149b153e1d5a6b68f9ad21c4e84f6e62 Mon Sep 17 00:00:00 2001 From: Weblate Date: Thu, 5 Nov 2020 19:52:00 +0100 Subject: [PATCH 56/84] Translated using Weblate (Finnish) Currently translated at 100.0% (1372 of 1372 strings) Translation: CryptPad/App Translate-URL: http://weblate.cryptpad.fr/projects/cryptpad/app/fi/ --- www/common/translations/messages.fi.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/www/common/translations/messages.fi.json b/www/common/translations/messages.fi.json index fe4ee1f64..ac5ace154 100644 --- a/www/common/translations/messages.fi.json +++ b/www/common/translations/messages.fi.json @@ -1459,5 +1459,14 @@ "history_cantRestore": "Palauttaminen epäonnistui. Yhteytesi on katkennut.", "history_close": "Sulje", "history_restore": "Palauta", - "share_bar": "Luo linkki" + "share_bar": "Luo linkki", + "error_unhelpfulScriptError": "Skriptivirhe: Lisätietoja selaimen kehittäjäkonsolissa", + "tag_edit": "Muokkaa", + "tag_add": "Lisää", + "loading_state_5": "Uudelleenrakenna asiakirja", + "loading_state_4": "Lataa Teams", + "loading_state_3": "Lataa jaetut kansiot", + "loading_state_2": "Päivitä sisältö", + "loading_state_1": "Lataa Drive", + "loading_state_0": "Rakenna käyttöliittymä" } From 681d2caca7cab9380fc0d4aadfcc46224db210f0 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 6 Nov 2020 19:19:18 +0530 Subject: [PATCH 57/84] minimal patch for iOS firefox --- www/common/boot2.js | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/www/common/boot2.js b/www/common/boot2.js index 30f776a25..87bba4a43 100644 --- a/www/common/boot2.js +++ b/www/common/boot2.js @@ -37,6 +37,42 @@ define([ window.alert("CryptPad needs localStorage to work. Try changing your cookie permissions, or using a different browser"); }; + + var getLogElement = function () { + var logger = document.querySelector('#cp-logger'); + if (logger) { return logger; } + logger = document.createElement('div'); + logger.setAttribute('id', 'cp-logger'); + document.body.appendChild(logger); + var css = function(){/* #cp-logger { display: none; } */}.toString().slice(14, -3); + var style = document.createElement('style'); + style.type = 'text/css'; + style.appendChild(document.createTextNode(css)); + document.head.appendChild(style); + return logger; + }; + + + var logToDom = function () { + var pre = document.createElement('pre'); + pre.innerText = 'x'; + getLogElement().appendChild(pre); + }; + + if (window.Proxy) { + var c = console; + window.console = new Proxy(c, { + get: function (o, k) { + if (k !== 'error') { return o[k]; } + return function () { + var args = Array.prototype.slice.call(arguments); + c.error.apply(null, args); + logToDom(); + }; + }, + }); + } + window.onerror = function (e) { if (/requirejs\.org/.test(e)) { console.log(); From 760356c4ffc56a3c622e4a4ac4b7a73f74132a83 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 6 Nov 2020 19:28:36 +0530 Subject: [PATCH 58/84] patch loading.js --- customize.dist/loading.js | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/customize.dist/loading.js b/customize.dist/loading.js index edb66ef0a..1db126fa5 100644 --- a/customize.dist/loading.js +++ b/customize.dist/loading.js @@ -312,15 +312,24 @@ button.primary:hover{ return bar; }; + var hideSpinner = function () { + try { + document.querySelector('.cp-loading-spinner-container').style.display = 'none'; + document.querySelector('.cp-loading-spinner-container').setAttribute('style', 'display:none;'); + } catch (err) { return; } + }; + var hasErrored = false; var updateLoadingProgress = function (data) { if (!built || !data) { return; } var c = types.indexOf(data.type); if (c < current) { return console.error(data); } try { - document.querySelector('.cp-loading-spinner-container').style.display = 'none'; - document.querySelector('.cp-loading-progress-list').innerHTML = makeList(data); - document.querySelector('.cp-loading-progress-container').innerHTML = makeBar(data); + hideSpinner(); + var list = document.querySelector('.cp-loading-progress-list'); + list && (list.innerHTML = makeList(data)); + var container = document.querySelector('.cp-loading-progress-container'); + container && (container.innerHTML = makeBar(data)); } catch (e) { if (!hasErrored) { console.error(e); } } @@ -329,6 +338,7 @@ button.primary:hover{ window.CryptPad_loadingError = function (err) { if (!built) { return; } + console.error(err); hasErrored = true; var err2; if (err === 'Script error.') { @@ -339,10 +349,12 @@ button.primary:hover{ var node = document.querySelector('.cp-loading-progress'); if (!node) { return; } if (node.parentNode) { node.parentNode.removeChild(node); } - document.querySelector('.cp-loading-spinner-container').setAttribute('style', 'display:none;'); + hideSpinner(); document.querySelector('#cp-loading-message').setAttribute('style', 'display:block;'); document.querySelector('#cp-loading-message').innerText = err2 || err; - } catch (e) { console.error(e); } + } catch (e) { + console.error(e); + } }; return function () { built = true; From 46703d627b36144972e201e0f27d857e61a10d94 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 6 Nov 2020 19:30:57 +0530 Subject: [PATCH 59/84] . --- www/common/cryptpad-common.js | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index ca65ce77b..c4be384b8 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -2056,6 +2056,8 @@ define([ }; var userHash; + console.error("pewpew"); + //console.error('pewpew'); Nthen(function (waitFor) { if (AppConfig.beforeLogin) { @@ -2108,6 +2110,7 @@ define([ // FIXME Backward compatibility if (sessionStorage.newPadFileData) { + /* common.fromFileData = JSON.parse(sessionStorage.newPadFileData); var _parsed1 = Hash.parsePadUrl(common.fromFileData.href); var _parsed2 = Hash.parsePadUrl(window.location.href); @@ -2115,6 +2118,7 @@ define([ if (_parsed1.type !== _parsed2.type) { delete common.fromFileData; } } delete sessionStorage.newPadFileData; + */ } if (sessionStorage.newPadPath) { @@ -2134,11 +2138,12 @@ define([ var postMsg, worker; var noWorker = AppConfig.disableWorkers || false; var noSharedWorker = false; - if (localStorage.CryptPad_noWorkers) { + if (localStorage.CryptPad_noWorkers || true) { noWorker = localStorage.CryptPad_noWorkers === '1'; - console.error('WebWorker/SharedWorker state forced to ' + !noWorker); + //console.error('WebWorker/SharedWorker state forced to ' + !noWorker); } Nthen(function (waitFor2) { + return; if (Worker) { var w = waitFor2(); try { @@ -2161,6 +2166,7 @@ define([ w(); } } + return; if (typeof(SharedWorker) !== "undefined") { try { new SharedWorker(''); @@ -2170,6 +2176,18 @@ define([ } } }).nThen(function (waitFor2) { + // Use the async store in the main thread if workers are not available + require(['/common/outer/noworker.js'], waitFor2(function (NoWorker) { + NoWorker.onMessage(function (data) { + msgEv.fire({data: data}); + }); + postMsg = function (d) { setTimeout(function () { NoWorker.query(d); }); }; + NoWorker.create(); + })); + return; + + + if (!noWorker && !noSharedWorker && typeof(SharedWorker) !== "undefined") { worker = new SharedWorker('/common/outer/sharedworker.js?' + urlArgs); worker.onerror = function (e) { @@ -2337,6 +2355,8 @@ define([ } if (parsedNew.hashData) { oldHref = newHref; } }; + + /* // Listen for login/logout in other tabs window.addEventListener('storage', function (e) { if (e.key !== Constants.userHashKey) { return; } @@ -2347,7 +2367,7 @@ define([ } else if (o && !n) { LocalStore.logout(); } - }); + });*/ LocalStore.onLogout(function () { console.log('onLogout: disconnect'); postMessage("DISCONNECT"); @@ -2367,6 +2387,7 @@ define([ common.mergeAnonDrive(waitFor()); } }).nThen(function (waitFor) { + return; if (AppConfig.afterLogin) { AppConfig.afterLogin(common, waitFor()); } From 3468bb33a2b7bfa0740072bdd640045f35846619 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 12 Nov 2020 11:22:46 +0100 Subject: [PATCH 60/84] Test notification prompt --- www/common/sframe-common-outer.js | 9 +++++++-- www/common/toolbar.js | 22 ++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index ad128db33..d17924cf0 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -569,6 +569,7 @@ define([ isPresent: parsed.hashData && parsed.hashData.present, isEmbed: parsed.hashData && parsed.hashData.embed, isHistoryVersion: parsed.hashData && parsed.hashData.versionHash, + notifications: Notification && Notification.permission === "granted", accounts: { donateURL: Cryptpad.donateURL, upgradeURL: Cryptpad.upgradeURL @@ -1569,9 +1570,13 @@ define([ }); }); - if (cfg.messaging) { - Notifier.getPermission(); + sframeChan.on('Q_ASK_NOTIFICATION', function (data, cb) { + Notification.requestPermission(function (s) { + cb(s === "granted"); + }); + }); + if (cfg.messaging) { sframeChan.on('Q_CHAT_OPENPADCHAT', function (data, cb) { Cryptpad.universal.execCommand({ type: 'messenger', diff --git a/www/common/toolbar.js b/www/common/toolbar.js index 78e4e91f3..709d659b5 100644 --- a/www/common/toolbar.js +++ b/www/common/toolbar.js @@ -990,6 +990,28 @@ MessengerUI, Messages) { h('div.cp-notifications-empty', Messages.notifications_empty) ]); var pads_options = [div]; + + var metadataMgr = config.metadataMgr; + var privateData = metadataMgr.getPrivateData(); + if (!privateData.notifications) { + Messages.allowNotifications = "Allow notifications"; // XXX + var allowNotif = h('div.cp-notifications-gotoapp', h('p', Messages.allowNotifications)); + pads_options.unshift(h("hr")); + pads_options.unshift(allowNotif); + var $allow = $(allowNotif).click(function () { + Common.getSframeChannel().event('Q_ASK_NOTIFICATION', null, function (granted) { + if (!granted) { return; } + $(allowNotif).remove(); + }); + }); + metadataMgr.onChange(function () { + var privateData = metadataMgr.getPrivateData(); + if (!privateData.notifications) { return; } + $allow.remove(); + }); + } + + if (Common.isLoggedIn()) { pads_options.unshift(h("hr")); pads_options.unshift(openNotifsApp); From ca065e557e04ae7efce30351dda1a410e9b55db3 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 12 Nov 2020 11:29:05 +0100 Subject: [PATCH 61/84] Improve notification prompt --- www/common/toolbar.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/www/common/toolbar.js b/www/common/toolbar.js index 709d659b5..5d453f061 100644 --- a/www/common/toolbar.js +++ b/www/common/toolbar.js @@ -999,16 +999,18 @@ MessengerUI, Messages) { pads_options.unshift(h("hr")); pads_options.unshift(allowNotif); var $allow = $(allowNotif).click(function () { - Common.getSframeChannel().event('Q_ASK_NOTIFICATION', null, function (granted) { - if (!granted) { return; } + Common.getSframeChannel().event('Q_ASK_NOTIFICATION', null, function (e, allow) { + if (!allow) { return; } $(allowNotif).remove(); }); }); - metadataMgr.onChange(function () { + var onChange = function () { var privateData = metadataMgr.getPrivateData(); if (!privateData.notifications) { return; } $allow.remove(); - }); + metadataMgr.off('change', onChange); + }; + metadataMgr.onChange(onChange); } From 59f427f1b530fbafe8fc2b43d2e3ca9c7b75fc78 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 12 Nov 2020 17:20:48 +0100 Subject: [PATCH 62/84] Don't notify for old mailbox messages --- www/common/outer/mailbox.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/www/common/outer/mailbox.js b/www/common/outer/mailbox.js index 11c912a44..803d8bc93 100644 --- a/www/common/outer/mailbox.js +++ b/www/common/outer/mailbox.js @@ -297,6 +297,7 @@ proxy.mailboxes = { msg: msg, hash: hash }; + var notify = box.ready; Handlers.add(ctx, box, message, function (dismissed, toDismiss) { if (toDismiss) { // List of other messages to remove dismiss(ctx, toDismiss, '', function () { @@ -314,8 +315,7 @@ proxy.mailboxes = { } box.content[hash] = msg; showMessage(ctx, type, message, null, function (obj) { - if (!box.ready) { return; } - if (!obj || !obj.msg) { return; } + if (!obj || !obj.msg || !notify) { return; } Notify.system(undefined, obj.msg); }); }); From b155f7b2911aefdddd327b81152978d42babdebc Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 17 Nov 2020 16:09:47 +0530 Subject: [PATCH 63/84] increase child-process timeout and improve error handling --- lib/workers/db-worker.js | 4 ++-- lib/workers/index.js | 4 +++- www/common/common-util.js | 13 +++++++++++-- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/lib/workers/db-worker.js b/lib/workers/db-worker.js index 42c75fdca..0b4c4d03d 100644 --- a/lib/workers/db-worker.js +++ b/lib/workers/db-worker.js @@ -568,7 +568,7 @@ process.on('message', function (data) { const cb = function (err, value) { process.send({ - error: err, + error: Util.serializeError(err), txid: data.txid, pid: data.pid, value: value, @@ -577,7 +577,7 @@ process.on('message', function (data) { if (!ready) { return void init(data.config, function (err) { - if (err) { return void cb(err); } + if (err) { return void cb(Util.serializeError(err)); } ready = true; cb(); }); diff --git a/lib/workers/index.js b/lib/workers/index.js index 6e9f57e88..522339812 100644 --- a/lib/workers/index.js +++ b/lib/workers/index.js @@ -9,6 +9,7 @@ const PID = process.pid; const DB_PATH = 'lib/workers/db-worker'; const MAX_JOBS = 16; +const DEFAULT_QUERY_TIMEOUT = 60000 * 15; // increased from three to fifteen minutes because queries for very large files were taking as long as seven minutes Workers.initialize = function (Env, config, _cb) { var cb = Util.once(Util.mkAsync(_cb)); @@ -113,6 +114,7 @@ Workers.initialize = function (Env, config, _cb) { const txid = guid(); var cb = Util.once(Util.mkAsync(Util.both(_cb, function (err /*, value */) { if (err !== 'TIMEOUT') { return; } + Log.debug("WORKER_TIMEOUT_CAUSE", msg); // in the event of a timeout the user will receive an error // but the state used to resend a query in the event of a worker crash // won't be cleared. This also leaks a slot that could be used to keep @@ -132,7 +134,7 @@ Workers.initialize = function (Env, config, _cb) { state.tasks[txid] = msg; // default to timing out affter 180s if no explicit timeout is passed - var timeout = typeof(opt.timeout) !== 'undefined'? opt.timeout: 180000; + var timeout = typeof(opt.timeout) !== 'undefined'? opt.timeout: DEFAULT_QUERY_TIMEOUT; response.expect(txid, cb, timeout); state.worker.send(msg); }; diff --git a/www/common/common-util.js b/www/common/common-util.js index 41797e7c8..dfc6e12d7 100644 --- a/www/common/common-util.js +++ b/www/common/common-util.js @@ -30,6 +30,15 @@ return JSON.parse(JSON.stringify(o)); }; + Util.serializeError = function (err) { + if (!(err instanceof Error)) { return err; } + var ser = {}; + Object.getOwnPropertyNames(err).forEach(function (key) { + ser[key] = err[key]; + }); + return ser; + }; + Util.tryParse = function (s) { try { return JSON.parse(s); } catch (e) { return;} }; @@ -113,13 +122,13 @@ var handle = function (id, args) { var fn = pending[id]; if (typeof(fn) !== 'function') { - errorHandler("MISSING_CALLBACK", { + return void errorHandler("MISSING_CALLBACK", { id: id, args: args, }); } try { - pending[id].apply(null, Array.isArray(args)? args : [args]); + fn.apply(null, Array.isArray(args)? args : [args]); } catch (err) { errorHandler('HANDLER_ERROR', { error: err, From 0e0c0fd6556d082d265456959e80ee9077fa790b Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 17 Nov 2020 17:45:02 +0530 Subject: [PATCH 64/84] guard against typeError for unsupported notification APIs --- www/common/notify.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/www/common/notify.js b/www/common/notify.js index a3c8cd2b8..81dee662d 100644 --- a/www/common/notify.js +++ b/www/common/notify.js @@ -16,6 +16,8 @@ define(['/api/config'], function (ApiConfig) { var getPermission = Module.getPermission = function (f) { f = f || function () {}; + // "Notification.requestPermission is not a function" on Firefox 68.11.0esr + if (!Notification || typeof(Notification.requestPermission) !== 'function') { return void f(false); } Notification.requestPermission(function (permission) { if (permission === "granted") { f(true); } else { f(false); } From ebc394c4b31f81866162f662e8487d4c14554c24 Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 17 Nov 2020 18:09:34 +0530 Subject: [PATCH 65/84] expose defaultStorageLimit via /api/config --- server.js | 1 + 1 file changed, 1 insertion(+) diff --git a/server.js b/server.js index 60247f47a..eb2787475 100644 --- a/server.js +++ b/server.js @@ -202,6 +202,7 @@ var serveConfig = (function () { adminKeys: Env.admins, inactiveTime: Env.inactiveTime, supportMailbox: Env.supportMailbox, + defaultStorageLimit: Env.defaultStorageLimit, maxUploadSize: Env.maxUploadSize, premiumUploadSize: Env.premiumUploadSize, }, null, '\t'), From c9ce6da49628166a05518a076c0c459623aeeaec Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 17 Nov 2020 19:05:05 +0530 Subject: [PATCH 66/84] . --- www/common/cryptpad-common.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index c4be384b8..cb312892b 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -2056,8 +2056,6 @@ define([ }; var userHash; - console.error("pewpew"); - //console.error('pewpew'); Nthen(function (waitFor) { if (AppConfig.beforeLogin) { @@ -2355,8 +2353,6 @@ define([ } if (parsedNew.hashData) { oldHref = newHref; } }; - - /* // Listen for login/logout in other tabs window.addEventListener('storage', function (e) { if (e.key !== Constants.userHashKey) { return; } @@ -2367,7 +2363,7 @@ define([ } else if (o && !n) { LocalStore.logout(); } - });*/ + }); LocalStore.onLogout(function () { console.log('onLogout: disconnect'); postMessage("DISCONNECT"); @@ -2387,7 +2383,6 @@ define([ common.mergeAnonDrive(waitFor()); } }).nThen(function (waitFor) { - return; if (AppConfig.afterLogin) { AppConfig.afterLogin(common, waitFor()); } From b37fd224b0381d4f5ab9f3a61b3302130c058d26 Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 17 Nov 2020 19:19:55 +0530 Subject: [PATCH 67/84] continue bisecting --- www/common/cryptpad-common.js | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index cb312892b..2d7440087 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -2056,6 +2056,7 @@ define([ }; var userHash; + console.error('without this error statement Firefox on iOS throws a script error...'); Nthen(function (waitFor) { if (AppConfig.beforeLogin) { @@ -2108,7 +2109,6 @@ define([ // FIXME Backward compatibility if (sessionStorage.newPadFileData) { - /* common.fromFileData = JSON.parse(sessionStorage.newPadFileData); var _parsed1 = Hash.parsePadUrl(common.fromFileData.href); var _parsed2 = Hash.parsePadUrl(window.location.href); @@ -2116,7 +2116,6 @@ define([ if (_parsed1.type !== _parsed2.type) { delete common.fromFileData; } } delete sessionStorage.newPadFileData; - */ } if (sessionStorage.newPadPath) { @@ -2136,12 +2135,11 @@ define([ var postMsg, worker; var noWorker = AppConfig.disableWorkers || false; var noSharedWorker = false; - if (localStorage.CryptPad_noWorkers || true) { + if (localStorage.CryptPad_noWorkers) { noWorker = localStorage.CryptPad_noWorkers === '1'; - //console.error('WebWorker/SharedWorker state forced to ' + !noWorker); + console.error('WebWorker/SharedWorker state forced to ' + !noWorker); } Nthen(function (waitFor2) { - return; if (Worker) { var w = waitFor2(); try { @@ -2164,7 +2162,6 @@ define([ w(); } } - return; if (typeof(SharedWorker) !== "undefined") { try { new SharedWorker(''); @@ -2174,18 +2171,6 @@ define([ } } }).nThen(function (waitFor2) { - // Use the async store in the main thread if workers are not available - require(['/common/outer/noworker.js'], waitFor2(function (NoWorker) { - NoWorker.onMessage(function (data) { - msgEv.fire({data: data}); - }); - postMsg = function (d) { setTimeout(function () { NoWorker.query(d); }); }; - NoWorker.create(); - })); - return; - - - if (!noWorker && !noSharedWorker && typeof(SharedWorker) !== "undefined") { worker = new SharedWorker('/common/outer/sharedworker.js?' + urlArgs); worker.onerror = function (e) { From 30e1c448d39a6c294f0911d26d5d5d59d5c388d9 Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 17 Nov 2020 19:35:09 +0530 Subject: [PATCH 68/84] cleanup and lint compliance --- customize.dist/loading.js | 15 ++++++++++----- www/common/boot2.js | 2 +- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/customize.dist/loading.js b/customize.dist/loading.js index 1db126fa5..e3f5c24f7 100644 --- a/customize.dist/loading.js +++ b/customize.dist/loading.js @@ -315,10 +315,17 @@ button.primary:hover{ var hideSpinner = function () { try { document.querySelector('.cp-loading-spinner-container').style.display = 'none'; - document.querySelector('.cp-loading-spinner-container').setAttribute('style', 'display:none;'); } catch (err) { return; } }; + var getList = function () { + return document.querySelector('.cp-loading-progress-list') || {}; + }; + + var getProgressBar = function () { + return document.querySelector('.cp-loading-progress-container') || {}; + }; + var hasErrored = false; var updateLoadingProgress = function (data) { if (!built || !data) { return; } @@ -326,10 +333,8 @@ button.primary:hover{ if (c < current) { return console.error(data); } try { hideSpinner(); - var list = document.querySelector('.cp-loading-progress-list'); - list && (list.innerHTML = makeList(data)); - var container = document.querySelector('.cp-loading-progress-container'); - container && (container.innerHTML = makeBar(data)); + getList().innerHTML = makeList(data); + getProgressBar().innerHTML = makeBar(data); } catch (e) { if (!hasErrored) { console.error(e); } } diff --git a/www/common/boot2.js b/www/common/boot2.js index 87bba4a43..0d879b32f 100644 --- a/www/common/boot2.js +++ b/www/common/boot2.js @@ -61,7 +61,7 @@ define([ if (window.Proxy) { var c = console; - window.console = new Proxy(c, { + window.console = new window.Proxy(c, { get: function (o, k) { if (k !== 'error') { return o[k]; } return function () { From 713516f7517ff135ebc53ee64166db78124eaf9b Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 17 Nov 2020 19:52:37 +0530 Subject: [PATCH 69/84] keep bisecting to narrow down on the Firefox iOS error --- customize.dist/loading.js | 27 +++++--------------------- www/common/boot2.js | 36 ----------------------------------- www/common/cryptpad-common.js | 22 +++++++++++++++++++-- 3 files changed, 25 insertions(+), 60 deletions(-) diff --git a/customize.dist/loading.js b/customize.dist/loading.js index e3f5c24f7..edb66ef0a 100644 --- a/customize.dist/loading.js +++ b/customize.dist/loading.js @@ -312,29 +312,15 @@ button.primary:hover{ return bar; }; - var hideSpinner = function () { - try { - document.querySelector('.cp-loading-spinner-container').style.display = 'none'; - } catch (err) { return; } - }; - - var getList = function () { - return document.querySelector('.cp-loading-progress-list') || {}; - }; - - var getProgressBar = function () { - return document.querySelector('.cp-loading-progress-container') || {}; - }; - var hasErrored = false; var updateLoadingProgress = function (data) { if (!built || !data) { return; } var c = types.indexOf(data.type); if (c < current) { return console.error(data); } try { - hideSpinner(); - getList().innerHTML = makeList(data); - getProgressBar().innerHTML = makeBar(data); + document.querySelector('.cp-loading-spinner-container').style.display = 'none'; + document.querySelector('.cp-loading-progress-list').innerHTML = makeList(data); + document.querySelector('.cp-loading-progress-container').innerHTML = makeBar(data); } catch (e) { if (!hasErrored) { console.error(e); } } @@ -343,7 +329,6 @@ button.primary:hover{ window.CryptPad_loadingError = function (err) { if (!built) { return; } - console.error(err); hasErrored = true; var err2; if (err === 'Script error.') { @@ -354,12 +339,10 @@ button.primary:hover{ var node = document.querySelector('.cp-loading-progress'); if (!node) { return; } if (node.parentNode) { node.parentNode.removeChild(node); } - hideSpinner(); + document.querySelector('.cp-loading-spinner-container').setAttribute('style', 'display:none;'); document.querySelector('#cp-loading-message').setAttribute('style', 'display:block;'); document.querySelector('#cp-loading-message').innerText = err2 || err; - } catch (e) { - console.error(e); - } + } catch (e) { console.error(e); } }; return function () { built = true; diff --git a/www/common/boot2.js b/www/common/boot2.js index 0d879b32f..30f776a25 100644 --- a/www/common/boot2.js +++ b/www/common/boot2.js @@ -37,42 +37,6 @@ define([ window.alert("CryptPad needs localStorage to work. Try changing your cookie permissions, or using a different browser"); }; - - var getLogElement = function () { - var logger = document.querySelector('#cp-logger'); - if (logger) { return logger; } - logger = document.createElement('div'); - logger.setAttribute('id', 'cp-logger'); - document.body.appendChild(logger); - var css = function(){/* #cp-logger { display: none; } */}.toString().slice(14, -3); - var style = document.createElement('style'); - style.type = 'text/css'; - style.appendChild(document.createTextNode(css)); - document.head.appendChild(style); - return logger; - }; - - - var logToDom = function () { - var pre = document.createElement('pre'); - pre.innerText = 'x'; - getLogElement().appendChild(pre); - }; - - if (window.Proxy) { - var c = console; - window.console = new window.Proxy(c, { - get: function (o, k) { - if (k !== 'error') { return o[k]; } - return function () { - var args = Array.prototype.slice.call(arguments); - c.error.apply(null, args); - logToDom(); - }; - }, - }); - } - window.onerror = function (e) { if (/requirejs\.org/.test(e)) { console.log(); diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 2d7440087..2aaa50796 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -2056,9 +2056,27 @@ define([ }; var userHash; - console.error('without this error statement Firefox on iOS throws a script error...'); - Nthen(function (waitFor) { + Nthen(function () { + var getLogElement = function () { + var logger = document.createElement('div'); + logger.setAttribute('id', 'cp-logger'); + document.body.appendChild(logger); + var css = function(){/* #cp-logger { display: none; } */}.toString().slice(14, -3); + var style = document.createElement('style'); + style.type = 'text/css'; + style.appendChild(document.createTextNode(css)); + document.head.appendChild(style); + return logger; + }; + var logToDom = function () { + var pre = document.createElement('pre'); + pre.innerText = 'x'; + getLogElement(); + getLogElement().appendChild(pre); + }; + logToDom(); + }).nThen(function (waitFor) { if (AppConfig.beforeLogin) { AppConfig.beforeLogin(LocalStore.isLoggedIn(), waitFor()); } From 700ea4c282fecd92781fcd5c659e53c054e9dc2d Mon Sep 17 00:00:00 2001 From: ansuz Date: Tue, 17 Nov 2020 20:25:12 +0530 Subject: [PATCH 70/84] minimal patch and an ominous warning comment --- www/common/cryptpad-common.js | 42 +++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 2aaa50796..9a9807e7e 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -2057,26 +2057,34 @@ define([ var userHash; - Nthen(function () { - var getLogElement = function () { - var logger = document.createElement('div'); - logger.setAttribute('id', 'cp-logger'); - document.body.appendChild(logger); - var css = function(){/* #cp-logger { display: none; } */}.toString().slice(14, -3); + (function iOSFirefoxFix () { +/* + For some bizarre reason Firefox on iOS throws an error during the + loading process unless we call this function. Drawing these elements + to the DOM presumably causes the JS engine to wait just a little bit longer + until some APIs we need are ready. This occurs despite all this code being + run after the usual dom-ready events. This fix was discovered while trying + to log the error messages to the DOM because it's extremely difficult + to debug Firefox iOS in the usual ways. In summary, computers are terrible. +*/ + try { var style = document.createElement('style'); - style.type = 'text/css'; - style.appendChild(document.createTextNode(css)); + style.type = 'text/css'; + style.appendChild(document.createTextNode('#cp-logger { display: none; }')); document.head.appendChild(style); - return logger; - }; - var logToDom = function () { + + var logger = document.createElement('div'); + logger.setAttribute('id', 'cp-logger'); + document.body.appendChild(logger); + var pre = document.createElement('pre'); - pre.innerText = 'x'; - getLogElement(); - getLogElement().appendChild(pre); - }; - logToDom(); - }).nThen(function (waitFor) { + pre.innerText = 'x'; + pre.style.display = 'none'; + logger.appendChild(pre); + } catch (err) { console.error(err); } + }()); + + Nthen(function (waitFor) { if (AppConfig.beforeLogin) { AppConfig.beforeLogin(LocalStore.isLoggedIn(), waitFor()); } From 7bbb46c2ef43f73d25e92ad117163111f48747ed Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 17 Nov 2020 16:02:06 +0100 Subject: [PATCH 71/84] Trim history prompt --- www/common/common-ui-elements.js | 39 +++++++++++++++++++ www/common/onlyoffice/inner.js | 10 +++++ www/common/sframe-app-framework.js | 2 + www/common/sframe-common.js | 60 ++++++++++++++++++++++++++++++ www/drive/inner.js | 4 ++ www/poll/inner.js | 2 + 6 files changed, 117 insertions(+) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index e977f1f49..3cb044231 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -2684,6 +2684,45 @@ define([ }; + Messages.history_trimPrompt = "This document's history is very large ({0}) and it may impact the loading time. You can delete the unnecessary history."; + UIElements.displayTrimHistoryPrompt = function (common, data) { + var mb = Util.bytesToMegabytes(data.size); + var text = Messages._getKey('history_trimPrompt', [ + Messages._getKey('formattedMB', [mb]) + ]); + var yes = h('button.cp-corner-primary', [ + h('span.fa.fa-trash-o'), + Messages.trimHistory_button + ]); + var no = h('button.cp-corner-cancel', Messages.crowdfunding_popup_no); // Not now + var actions = h('div', [no, yes]); + + var dontShowAgain = function () { + var until = (+new Date()) + (7 * 24 * 3600 * 1000); // 7 days from now + until = (+new Date()) + 30000; // XXX 30s from now + if (data.drive) { + common.setAttribute(['drive', 'trim'], until); + return; + } + common.setPadAttribute('trim', until); + }; + + var modal = UI.cornerPopup(text, actions, '', {}); + + $(yes).click(function () { + modal.delete(); + if (data.drive) { + common.openURL('/settings/#drive'); + return; + } + common.getSframeChannel().event('EV_PROPERTIES_OPEN'); + }); + $(no).click(function () { + dontShowAgain(); + modal.delete(); + }); + }; + UIElements.displayFriendRequestModal = function (common, data) { var msg = data.content.msg; var userData = msg.content.user; diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index 28d06dd32..c9efb1eda 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -1310,6 +1310,16 @@ define([ if (APP.migrate && !readOnly) { onMigrateRdy.fire(); } + + // Check if history can/should be trimmed + var cp = getLastCp(); + if (cp && cp.file && cp.hash) { + var channels = [{ + channel: content.channel, + lastKnownHash: cp.hash + }]; + common.checkTrimHistory(channels); + } } } }; diff --git a/www/common/sframe-app-framework.js b/www/common/sframe-app-framework.js index 3097cf6d2..5f28373c2 100644 --- a/www/common/sframe-app-framework.js +++ b/www/common/sframe-app-framework.js @@ -551,6 +551,8 @@ define([ Thumb.initPadThumbnails(common, options.thumbnail); } } + + common.checkTrimHistory(); }); }; var onConnectionChange = function (info) { diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index 3eb86ff93..4198a649a 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -264,6 +264,66 @@ define([ return teamChatChannel; }; + // When opening a pad, if were an owner check the history size and prompt for trimming if + // necessary + funcs.checkTrimHistory = function (channels, isDrive) { + channels = channels || []; + var priv = ctx.metadataMgr.getPrivateData(); + + var limit = 100 * 1024 * 1024; // 100MB + limit = 100 * 1024; // XXX 100KB + + var owned; + nThen(function (w) { + if (isDrive) { + funcs.getAttribute(['drive', 'trim'], w(function (err, val) { + if (err || typeof(val) !== "number") { return; } + if (val < (+new Date())) { return; } + w.abort(); + })); + return; + } + funcs.getPadAttribute('trim', w(function (err, val) { + if (err || typeof(val) !== "number") { return; } + if (val < (+new Date())) { return; } + w.abort(); + })); + }).nThen(function (w) { + // Check ownership + // DRIVE + if (isDrive) { + if (!priv.isDriveOwned) { return void w.abort(); } + return; + } + // PAD + channels.push({ channel: priv.channel }); + funcs.getPadMetadata({ + channel: priv.channel + }, w(function (md) { + if (md && md.error) { return void w.abort(); } + var owners = md.owners; + owned = funcs.isOwned(owners); + if (!owned) { return void w.abort(); } + })); + }).nThen(function () { + // We're an owner: check the history size + var history = funcs.makeUniversal('history'); + history.execCommand('GET_HISTORY_SIZE', { + account: isDrive, + pad: !isDrive, + channels: channels, + teamId: typeof(owned) === "number" && owned + }, function (obj) { + if (obj && obj.error) { return; } // can't get history size: abort + var bytes = obj.size; + if (!bytes || typeof(bytes) !== "number") { return; } // no history: abort + if (bytes < limit) { return; } + obj.drive = isDrive; + UIElements.displayTrimHistoryPrompt(funcs, obj); + }); + }); + }; + var cursorChannel; // common-ui-elements needs to be able to get the cursor channel to put it in metadata when // importing a template diff --git a/www/drive/inner.js b/www/drive/inner.js index f85819083..2be9e06e6 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -310,6 +310,10 @@ define([ onReconnect(); }); common.onLogout(function () { setEditable(false); }); + + // Check if our drive history needs to be trimmed + common.checkTrimHistory(null, true); + }); }; main(); diff --git a/www/poll/inner.js b/www/poll/inner.js index b36466a13..bd40bc678 100644 --- a/www/poll/inner.js +++ b/www/poll/inner.js @@ -1087,6 +1087,8 @@ define([ common.openPadChat(function () {}); UI.removeLoadingScreen(); + + common.checkTrimHistory(); }; var onError = function (info) { From 62930f65b4910681d876285a03ebe98e263f86bb Mon Sep 17 00:00:00 2001 From: ansuz Date: Wed, 18 Nov 2020 12:41:47 +0530 Subject: [PATCH 72/84] fix the iOS Firefox fix --- www/common/cryptpad-common.js | 1 - 1 file changed, 1 deletion(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 9a9807e7e..9f88e85a9 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -2079,7 +2079,6 @@ define([ var pre = document.createElement('pre'); pre.innerText = 'x'; - pre.style.display = 'none'; logger.appendChild(pre); } catch (err) { console.error(err); } }()); From e1850088461490437b46d100d7d4baa035d632b8 Mon Sep 17 00:00:00 2001 From: ansuz Date: Wed, 18 Nov 2020 18:26:10 +0530 Subject: [PATCH 73/84] complete uploads in child processes also fix a nasty race condition for unowned file uploads --- lib/commands/upload.js | 22 ++++------------------ lib/storage/blob.js | 39 +++++++++++++++++++-------------------- lib/workers/db-worker.js | 33 +++++++++++++++++++++++++++++++++ lib/workers/index.js | 9 +++++++++ 4 files changed, 65 insertions(+), 38 deletions(-) diff --git a/lib/commands/upload.js b/lib/commands/upload.js index 7286caa93..6dc0aa911 100644 --- a/lib/commands/upload.js +++ b/lib/commands/upload.js @@ -75,21 +75,9 @@ Upload.upload = function (Env, safeKey, chunk, cb) { Env.blobStore.upload(safeKey, chunk, cb); }; -var reportStatus = function (Env, label, safeKey, err, id) { - var data = { - safeKey: safeKey, - err: err && err.message || err, - id: id, - }; - var method = err? 'error': 'info'; - Env.Log[method](label, data); -}; - Upload.complete = function (Env, safeKey, arg, cb) { - Env.blobStore.complete(safeKey, arg, function (err, id) { - reportStatus(Env, 'UPLOAD_COMPLETE', safeKey, err, id); - cb(err, id); - }); + Env.blobStore.closeBlobstage(safeKey); + Env.completeUpload(safeKey, arg, false, cb); }; Upload.cancel = function (Env, safeKey, arg, cb) { @@ -97,9 +85,7 @@ Upload.cancel = function (Env, safeKey, arg, cb) { }; Upload.complete_owned = function (Env, safeKey, arg, cb) { - Env.blobStore.completeOwned(safeKey, arg, function (err, id) { - reportStatus(Env, 'UPLOAD_COMPLETE_OWNED', safeKey, err, id); - cb(err, id); - }); + Env.blobStore.closeBlobstage(safeKey); + Env.completeUpload(safeKey, arg, true, cb); }; diff --git a/lib/storage/blob.js b/lib/storage/blob.js index dfbc802b4..044eeaeaa 100644 --- a/lib/storage/blob.js +++ b/lib/storage/blob.js @@ -139,6 +139,15 @@ var upload = function (Env, safeKey, content, cb) { } }; +var closeBlobstage = function (Env, safeKey) { + var session = Env.getSession(safeKey); + if (!(session && session.blobstage && typeof(session.blobstage.close) === 'function')) { + return; + } + session.blobstage.close(); + delete session.blobstage; +}; + // upload_cancel var upload_cancel = function (Env, safeKey, fileSize, cb) { var session = Env.getSession(safeKey); @@ -159,27 +168,22 @@ var upload_cancel = function (Env, safeKey, fileSize, cb) { // upload_complete var upload_complete = function (Env, safeKey, id, cb) { - var session = Env.getSession(safeKey); - - if (session.blobstage && session.blobstage.close) { - session.blobstage.close(); - delete session.blobstage; - } + closeBlobstage(Env, safeKey); var oldPath = makeStagePath(Env, safeKey); var newPath = makeBlobPath(Env, id); nThen(function (w) { // make sure the path to your final location exists - Fse.mkdirp(Path.dirname(newPath), function (e) { + Fse.mkdirp(Path.dirname(newPath), w(function (e) { if (e) { w.abort(); return void cb('RENAME_ERR'); } - }); + })); }).nThen(function (w) { // make sure there's not already something in that exact location - isFile(newPath, function (e, yes) { + isFile(newPath, w(function (e, yes) { if (e) { w.abort(); return void cb(e); @@ -188,8 +192,8 @@ var upload_complete = function (Env, safeKey, id, cb) { w.abort(); return void cb('RENAME_ERR'); } - cb(void 0, newPath, id); - }); + cb(void 0, id); + })); }).nThen(function () { // finally, move the old file to the new path // FIXME we could just move and handle the EEXISTS instead of the above block @@ -217,15 +221,7 @@ var tryId = function (path, cb) { // owned_upload_complete var owned_upload_complete = function (Env, safeKey, id, cb) { - var session = Env.getSession(safeKey); - - // the file has already been uploaded to the staging area - // close the pending writestream - if (session.blobstage && session.blobstage.close) { - session.blobstage.close(); - delete session.blobstage; - } - + closeBlobstage(Env, safeKey); if (!isValidId(id)) { return void cb('EINVAL_ID'); } @@ -582,6 +578,9 @@ BlobStore.create = function (config, _cb) { }, }, + closeBlobstage: function (safeKey) { + closeBlobstage(Env, safeKey); + }, complete: function (safeKey, id, _cb) { var cb = Util.once(Util.mkAsync(_cb)); if (!isValidSafeKey(safeKey)) { return void cb('INVALID_SAFEKEY'); } diff --git a/lib/workers/db-worker.js b/lib/workers/db-worker.js index 0b4c4d03d..9d5abf386 100644 --- a/lib/workers/db-worker.js +++ b/lib/workers/db-worker.js @@ -457,6 +457,38 @@ const evictInactive = function (data, cb) { Eviction(Env, cb); }; +var reportStatus = function (Env, label, safeKey, err, id) { + var data = { + safeKey: safeKey, + err: err && err.message || err, + id: id, + }; + var method = err? 'error': 'info'; + Env.Log[method](label, data); +}; + +const completeUpload = function (data, cb) { + if (!data) { return void cb('INVALID_ARGS'); } + var owned = data.owned; + var safeKey = data.safeKey; + var arg = data.arg; + + var method; + var label; + if (owned) { + method = 'completeOwned'; + label = 'UPLOAD_COMPLETE_OWNED'; + } else { + method = 'complete'; + label = 'UPLOAD_COMPLETE'; + } + + Env.blobStore[method](safeKey, arg, function (err, id) { + reportStatus(Env, label, safeKey, err, id); + cb(err, id); + }); +}; + const COMMANDS = { COMPUTE_INDEX: computeIndex, COMPUTE_METADATA: computeMetadata, @@ -471,6 +503,7 @@ const COMMANDS = { RUN_TASKS: runTasks, WRITE_TASK: writeTask, EVICT_INACTIVE: evictInactive, + COMPLETE_UPLOAD: completeUpload, }; COMMANDS.INLINE = function (data, cb) { diff --git a/lib/workers/index.js b/lib/workers/index.js index 522339812..b5704c68b 100644 --- a/lib/workers/index.js +++ b/lib/workers/index.js @@ -424,6 +424,15 @@ Workers.initialize = function (Env, config, _cb) { }, cb); }; + Env.completeUpload = function (safeKey, arg, owned, cb) { + sendCommand({ + command: "COMPLETE_UPLOAD", + owned: owned, + safeKey: safeKey, + arg: arg, + }, cb); + }; + cb(void 0); }); }; From d60bdbfba11c4f859be9287014b1cb41f4269600 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 18 Nov 2020 15:11:34 +0100 Subject: [PATCH 74/84] Don't show XDR encoding errors --- customize.dist/loading.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/customize.dist/loading.js b/customize.dist/loading.js index edb66ef0a..b0d66981c 100644 --- a/customize.dist/loading.js +++ b/customize.dist/loading.js @@ -329,6 +329,12 @@ button.primary:hover{ window.CryptPad_loadingError = function (err) { if (!built) { return; } + + if (err === 'Error: XDR encoding failure') { + console.warn(err); + return; + } + hasErrored = true; var err2; if (err === 'Script error.') { From bd4b44476dbe387987fcb5bab052be79639a37a4 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 18 Nov 2020 16:49:40 +0100 Subject: [PATCH 75/84] Archive and restore UI in the admin panel --- lib/commands/admin-rpc.js | 17 +++-- lib/storage/file.js | 4 +- www/admin/app-admin.less | 10 +++ www/admin/inner.js | 137 ++++++++++++++++++++++++++++++++++++++ www/common/common-hash.js | 4 +- 5 files changed, 164 insertions(+), 8 deletions(-) diff --git a/lib/commands/admin-rpc.js b/lib/commands/admin-rpc.js index d7f22825d..26e574307 100644 --- a/lib/commands/admin-rpc.js +++ b/lib/commands/admin-rpc.js @@ -167,12 +167,19 @@ var archiveDocument = function (Env, Server, cb, data) { // Env.blobStore.archive.proof(userSafeKey, blobId, cb) }; -var restoreArchivedDocument = function (Env, Server, cb) { - // Env.msgStore.restoreArchivedChannel(channelName, cb) - // Env.blobStore.restore.blob(blobId, cb) - // Env.blobStore.restore.proof(userSafekey, blobId, cb) +var restoreArchivedDocument = function (Env, Server, cb, data) { + var id = Array.isArray(data) && data[1]; + if (typeof(id) !== 'string' || id.length < 32) { return void cb("EINVAL"); } - cb("NOT_IMPLEMENTED"); + switch (id.length) { + case 32: + return void Env.msgStore.restoreArchivedChannel(id, cb); + case 48: + // Env.blobStore.restore.proof(userSafekey, id, cb) // XXX .... + return void Env.blobStore.restore.blob(id, cb); + default: + return void cb("INVALID_ID_LENGTH"); + } }; // CryptPad_AsyncStore.rpc.send('ADMIN', ['CLEAR_CACHED_CHANNEL_INDEX', documentID], console.log) diff --git a/lib/storage/file.js b/lib/storage/file.js index b1ccfde0f..03bbaa8b4 100644 --- a/lib/storage/file.js +++ b/lib/storage/file.js @@ -681,9 +681,9 @@ var unarchiveChannel = function (env, channelName, cb) { // restore the metadata log Fse.move(archiveMetadataPath, metadataPath, w(function (err) { // if there's nothing to move, you're done. - if (err && err.code === 'ENOENT') { + /*if (err && err.code === 'ENOENT') { return CB(); - } + }*/ // XXX make sure removing this part won't break anything // call back with an error if something goes wrong if (err) { w.abort(); diff --git a/www/admin/app-admin.less b/www/admin/app-admin.less index 5112fd9e2..651e9dddd 100644 --- a/www/admin/app-admin.less +++ b/www/admin/app-admin.less @@ -97,5 +97,15 @@ color: @colortheme_logo-2; } } + + input.cp-admin-inval { + border-color: red !important; + } + .cp-admin-nopassword { + .cp-admin-pw { + display: none !important; + } + } + } diff --git a/www/admin/inner.js b/www/admin/inner.js index db5afca54..d9bebc097 100644 --- a/www/admin/inner.js +++ b/www/admin/inner.js @@ -43,6 +43,8 @@ define([ 'general': [ 'cp-admin-flush-cache', 'cp-admin-update-limit', + 'cp-admin-archive', + 'cp-admin-unarchive', // 'cp-admin-registration', ], 'quota': [ @@ -107,6 +109,141 @@ define([ }); return $div; }; + Messages.admin_archiveTitle = "Archive documents"; // XXX + Messages.admin_archiveHint = "Make a document unavailable without deleting it permanently. It will be placed in an 'archive' directory and deleted after a few days (configurable in the server configuration file)."; // XXX + Messages.admin_archiveButton = "Archive"; + + Messages.admin_unarchiveTitle = "Restore archived documents"; // XXX + Messages.admin_unarchiveHint = "Restore a document that has previously been archived"; + Messages.admin_unarchiveButton = "Restore"; + + Messages.admin_archiveInput = "Document URL"; + Messages.admin_archiveInput2 = "Document password"; + Messages.admin_archiveInval = "Invalid document"; + Messages.restoredFromServer = "Pad restored"; + + var archiveForm = function (archive, $div, $button) { + var label = h('label', { for: 'cp-admin-archive' }, Messages.admin_archiveInput); + var input = h('input#cp-admin-archive', { + type: 'text' + }); + + var label2 = h('label.cp-admin-pw', { + for: 'cp-admin-archive-pw' + }, Messages.admin_archiveInput2); + var input2 = UI.passwordInput({ + id: 'cp-admin-archive-pw', + placeholder: Messages.login_password + }); + var $pw = $(input2); + $pw.addClass('cp-admin-pw'); + var $pwInput = $pw.find('input'); + + + $button.before(h('div.cp-admin-setlimit-form', [ + label, + input, + label2, + input2 + ])); + + $div.addClass('cp-admin-nopassword'); + + var parsed; + var $input = $(input).on('keypress change paste', function () { + setTimeout(function () { + $input.removeClass('cp-admin-inval'); + var val = $input.val().trim(); + if (!val) { + $div.toggleClass('cp-admin-nopassword', true); + return; + } + + parsed = Hash.isValidHref(val); + $pwInput.val(''); + + if (!parsed || !parsed.hashData) { + $div.toggleClass('cp-admin-nopassword', true); + return void $input.addClass('cp-admin-inval'); + } + + var pw = parsed.hashData.version !== 3 && parsed.hashData.password; + $div.toggleClass('cp-admin-nopassword', !pw); + }); + }); + $pw.on('keypress change', function () { + setTimeout(function () { + $pw.toggleClass('cp-admin-inval', !$pwInput.val()); + }); + }); + + var clicked = false; + $button.click(function () { + if (!parsed || !parsed.hashData) { + UI.warn(Messages.admin_archiveInval); + return; + } + var pw = parsed.hashData.password ? $pwInput.val() : undefined; + var channel; + if (parsed.hashData.version === 3) { + channel = parsed.hashData.channel; + } else { + var secret = Hash.getSecrets(parsed.type, parsed.hash, pw); + channel = secret && secret.channel; + } + + if (!channel) { + UI.warn(Messages.admin_archiveInval); + return; + } + + if (clicked) { return; } + clicked = true; + + nThen(function (waitFor) { + if (!archive) { return; } + common.getFileSize(channel, waitFor(function (err, size) { + if (!err && size === 0) { + clicked = false; + waitFor.abort(); + return void UI.warn(Messages.admin_archiveInval); + } + })); + }).nThen(function () { + sFrameChan.query('Q_ADMIN_RPC', { + cmd: archive ? 'ARCHIVE_DOCUMENT' : 'RESTORE_ARCHIVED_DOCUMENT', + data: channel + }, function (err, obj) { + var e = err || (obj && obj.error); + clicked = false; + if (e) { + UI.warn(Messages.error); + console.error(e); + return; + } + UI.log(archive ? Messages.deletedFromServer : Messages.restoredFromServer); + $input.val(''); + $pwInput.val(''); + }); + }); + }); + }; + + create['archive'] = function () { + var key = 'archive'; + var $div = makeBlock(key, true); + var $button = $div.find('button'); + archiveForm(true, $div, $button); + return $div; + }; + create['unarchive'] = function () { + var key = 'unarchive'; + var $div = makeBlock(key, true); + var $button = $div.find('button'); + archiveForm(false, $div, $button); + return $div; + }; + create['registration'] = function () { var key = 'registration'; var $div = makeBlock(key, true); diff --git a/www/common/common-hash.js b/www/common/common-hash.js index 199c2cb54..4f7290b81 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -464,6 +464,8 @@ Version 4: Data URL when not a realtime link yet (new pad or "static" app) }; if (!/^https*:\/\//.test(href)) { + // If it doesn't start with http(s), it should be a relative href + if (!/^\//.test(href)) { return ret; } idx = href.indexOf('/#'); ret.type = href.slice(1, idx); if (idx === -1) { return ret; } @@ -661,7 +663,7 @@ Version 4: Data URL when not a realtime link yet (new pad or "static" app) if (parsed.hashData.key && !/^[a-zA-Z0-9+-/=]+$/.test(parsed.hashData.key)) { return; } } } - return true; + return parsed; }; Hash.decodeDataOptions = function (opts) { From faa84bcbd71a645c0e90b8cd36268edc61f850d3 Mon Sep 17 00:00:00 2001 From: ansuz Date: Thu, 19 Nov 2020 12:16:02 +0530 Subject: [PATCH 76/84] describe arguments for 'completeUpload' RPC --- lib/workers/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/workers/index.js b/lib/workers/index.js index b5704c68b..fe868e250 100644 --- a/lib/workers/index.js +++ b/lib/workers/index.js @@ -427,9 +427,9 @@ Workers.initialize = function (Env, config, _cb) { Env.completeUpload = function (safeKey, arg, owned, cb) { sendCommand({ command: "COMPLETE_UPLOAD", - owned: owned, - safeKey: safeKey, - arg: arg, + owned: owned, // Boolean + safeKey: safeKey, // String (public key) + arg: arg, // String (file id) }, cb); }; From 9bbc3acf3ab688617d9ac3f17fd4a146835e19ae Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 18 Nov 2020 15:11:34 +0100 Subject: [PATCH 77/84] Don't show XDR encoding errors --- customize.dist/loading.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/customize.dist/loading.js b/customize.dist/loading.js index edb66ef0a..b0d66981c 100644 --- a/customize.dist/loading.js +++ b/customize.dist/loading.js @@ -329,6 +329,12 @@ button.primary:hover{ window.CryptPad_loadingError = function (err) { if (!built) { return; } + + if (err === 'Error: XDR encoding failure') { + console.warn(err); + return; + } + hasErrored = true; var err2; if (err === 'Script error.') { From 1aa790e02d7b730bb62c20318947054744973158 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 18 Nov 2020 15:11:34 +0100 Subject: [PATCH 78/84] Don't show XDR encoding errors --- customize.dist/loading.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/customize.dist/loading.js b/customize.dist/loading.js index edb66ef0a..b0d66981c 100644 --- a/customize.dist/loading.js +++ b/customize.dist/loading.js @@ -329,6 +329,12 @@ button.primary:hover{ window.CryptPad_loadingError = function (err) { if (!built) { return; } + + if (err === 'Error: XDR encoding failure') { + console.warn(err); + return; + } + hasErrored = true; var err2; if (err === 'Script error.') { From 6a54d9cee205820fe5518d813bfda5fe86744641 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 19 Nov 2020 10:46:42 +0100 Subject: [PATCH 79/84] Fix XDR error --- www/common/common-interface.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/www/common/common-interface.js b/www/common/common-interface.js index 6d682afa7..7a39afae2 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -980,6 +980,11 @@ define([ setTimeout(cb, 750); }; UI.errorLoadingScreen = function (error, transparent, exitable) { + if (error === 'Error: XDR encoding failure') { + console.warn(err); + return; + } + var $loading = $('#' + LOADING); if (!$loading.is(':visible') || $loading.hasClass('cp-loading-hidden')) { UI.addLoadingScreen(); From 35909f10160f5ffc7cc4d56cc846b9536910dab2 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 19 Nov 2020 10:49:08 +0100 Subject: [PATCH 80/84] Fix typo --- www/common/common-interface.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/common-interface.js b/www/common/common-interface.js index 7a39afae2..9fae121dc 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -981,7 +981,7 @@ define([ }; UI.errorLoadingScreen = function (error, transparent, exitable) { if (error === 'Error: XDR encoding failure') { - console.warn(err); + console.warn(error); return; } From f62f44771139c43018eb715a84ad2eaad28f7e87 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 19 Nov 2020 17:58:40 +0100 Subject: [PATCH 81/84] Improve file app UI --- www/common/media-tag.js | 19 ++- www/file/app-file.less | 46 ++++++- www/file/inner.html | 6 +- www/file/inner.js | 294 ++++++++++++++++++++++------------------ 4 files changed, 224 insertions(+), 141 deletions(-) diff --git a/www/common/media-tag.js b/www/common/media-tag.js index 27683c54e..1ba9a1f53 100644 --- a/www/common/media-tag.js +++ b/www/common/media-tag.js @@ -127,16 +127,27 @@ // Download a blob from href - var download = function (src, _cb) { + var download = function (src, _cb, progressCb) { var cb = function (e, res) { _cb(e, res); cb = function () {}; }; + var progress = function (offset) { + progressCb(offset * 100); + }; + var xhr = new XMLHttpRequest(); xhr.open('GET', src, true); xhr.responseType = 'arraybuffer'; + xhr.addEventListener("progress", function (evt) { + if (evt.lengthComputable) { + var percentComplete = evt.loaded / evt.total; + progress(percentComplete); + } + }, false); + xhr.onerror = function () { return void cb("XHR_ERROR"); }; xhr.onload = function () { // Error? @@ -453,9 +464,13 @@ end(u8Decrypted); }, function (progress) { emit('progress', { - progress: progress + progress: 50+0.5*progress }); }); + }, function (progress) { + emit('progress', { + progress: 0.5*progress + }); }); return mediaObject; diff --git a/www/file/app-file.less b/www/file/app-file.less index 4ac94ca33..d6e8ad69c 100644 --- a/www/file/app-file.less +++ b/www/file/app-file.less @@ -64,7 +64,49 @@ } } - #cp-app-file-upload-form, #cp-app-file-download-form { + #cp-app-file-download-form { + padding: 0px; + margin: 0px; + + position: relative; + display: block; + max-width: 90vw; + height: 150px; + width: ~"min(90vw, 600px)"; + .cp-app-file-progress-container { + margin-top: 5px; + height: 40px; + font-size: 20px; + border: 1px solid @colortheme_logo-2; + background: white; + color: @cryptpad_text_col; + display: flex; + justify-content: space-between; + position: relative; + .cp-app-file-progress-dl { + border-right: 1px solid @cryptpad_text_col; + } + .cp-app-file-progress-dl, .cp-app-file-progress-dc { + width: 50%; + display: inline-flex; + align-items: center; + justify-content: center; + z-index: 2; + } + .cp-app-file-progress { + z-index: 1; + position: absolute; + top: 0; + left: 0; + bottom: 0; + background: @colortheme_logo-2; + } + } + .cp-app-file-progress-txt { + margin-left: 30px; + } + } + #cp-app-file-upload-form { padding: 0px; margin: 0px; @@ -159,4 +201,4 @@ } } -} \ No newline at end of file +} diff --git a/www/file/inner.html b/www/file/inner.html index d474ee117..a3e5070e4 100644 --- a/www/file/inner.html +++ b/www/file/inner.html @@ -16,11 +16,7 @@
      - + diff --git a/www/file/inner.js b/www/file/inner.js index b2aef53ed..8b3f12682 100644 --- a/www/file/inner.js +++ b/www/file/inner.js @@ -8,6 +8,7 @@ define([ '/common/common-util.js', '/common/common-hash.js', '/common/common-interface.js', + '/common/hyperscript.js', '/customize/messages.js', '/file/file-crypto.js', @@ -29,6 +30,7 @@ define([ Util, Hash, UI, + h, Messages, FileCrypto, MediaTag) @@ -47,8 +49,6 @@ define([ var $dlform = $('#cp-app-file-download-form'); var $dlview = $('#cp-app-file-download-view'); var $label = $form.find('label'); - var $dllabel = $dlform.find('label span'); - var $progress = $('#cp-app-file-dlprogress'); var $bar = $('.cp-toolbar-container'); var $body = $('body'); @@ -88,142 +88,172 @@ define([ var toolbar = APP.toolbar = Toolbar.create(configTb); if (!uploadMode) { - var hexFileName = secret.channel; - var src = fileHost + Hash.getBlobPathFromHex(hexFileName); - var key = secret.keys && secret.keys.cryptKey; - var cryptKey = Nacl.util.encodeBase64(key); + (function () { + Messages.download = "Download"; // XXX + Messages.decrypt = "Decrypt"; // XXX - FileCrypto.fetchDecryptedMetadata(src, key, function (e, metadata) { - if (e) { - if (e === 'XHR_ERROR') { - return void UI.errorLoadingScreen(Messages.download_resourceNotAvailable, false, function () { - common.gotoURL('/file/'); - }); - } - return void console.error(e); - } + var progress = h('div.cp-app-file-progress'); + var progressTxt = h('span.cp-app-file-progress-txt'); + var $progress = $(progress); + var $progressTxt = $(progressTxt); + var downloadEl = h('span.cp-app-file-progress-dl', Messages.download); + var decryptEl = h('span.cp-app-file-progress-dc', Messages.decrypt); + var progressContainer = h('div.cp-app-file-progress-container', [ + downloadEl, + decryptEl, + progress + ]); - // Add pad attributes when the file is saved in the drive - Title.onTitleChange(function () { - var owners = metadata.owners; - if (owners) { common.setPadAttribute('owners', owners); } - common.setPadAttribute('fileType', metadata.type); - }); - $(document).on('cpPadStored', function () { - var owners = metadata.owners; - if (owners) { common.setPadAttribute('owners', owners); } - common.setPadAttribute('fileType', metadata.type); - }); + var hexFileName = secret.channel; + var src = fileHost + Hash.getBlobPathFromHex(hexFileName); + var key = secret.keys && secret.keys.cryptKey; + var cryptKey = Nacl.util.encodeBase64(key); - // Save to the drive or update the acces time - var title = document.title = metadata.name; - Title.updateTitle(title || Title.defaultTitle); - - var owners = metadata.owners; - if (owners) { - common.setPadAttribute('owners', owners); - } - if (metadata.type) { - common.setPadAttribute('fileType', metadata.type); - } - - toolbar.addElement(['pageTitle'], { - pageTitle: title, - title: Title.getTitleConfig(), - }); - toolbar.$drawer.append(common.createButton('forget', true)); - toolbar.$drawer.append(common.createButton('properties', true)); - if (common.isLoggedIn()) { - toolbar.$drawer.append(common.createButton('hashtag', true)); - } - toolbar.$file.show(); - - var displayFile = function (ev, sizeMb, CB) { - var called_back; - var cb = function (e) { - if (called_back) { return; } - called_back = true; - if (CB) { CB(e); } - }; - - var $mt = $dlview.find('media-tag'); - $mt.attr('src', src); - $mt.attr('data-crypto-key', 'cryptpad:'+cryptKey); - - var rightsideDisplayed = false; - - MediaTag($mt[0]).on('complete', function (decrypted) { - $dlview.show(); - $dlform.hide(); - var $dlButton = $dlview.find('media-tag button'); - if (ev) { $dlButton.click(); } - - if (!rightsideDisplayed) { - toolbar.$drawer - .append(common.createButton('export', true, {}, function () { - saveAs(decrypted.content, decrypted.metadata.name); - })); - rightsideDisplayed = true; - } - - // make pdfs big - var toolbarHeight = $('#cp-toolbar').height(); - var $another_iframe = $('media-tag iframe').css({ - 'height': 'calc(100vh - ' + toolbarHeight + 'px)', - 'width': '100vw', - 'position': 'absolute', - 'bottom': 0, - 'left': 0, - 'border': 0 - }); - - if ($another_iframe.length) { - $another_iframe.load(function () { - cb(); - }); - } else { - cb(); - } - }).on('progress', function (data) { - var p = data.progress +'%'; - $progress.width(p); - }).on('error', function (err) { - console.error(err); - }); - }; - - var todoBigFile = function (sizeMb) { - $dlform.show(); - UI.removeLoadingScreen(); - $dllabel.append($('
      ')); - $dllabel.append(Util.fixHTML(metadata.name)); - - // don't display the size if you don't know it. - if (typeof(sizeM) === 'number') { - $dllabel.append($('
      ')); - $dllabel.append(Messages._getKey('formattedMB', [sizeMb])); - } - var decrypting = false; - var onClick = function (ev) { - if (decrypting) { return; } - decrypting = true; - displayFile(ev, sizeMb, function (err) { - $appContainer.css('background-color', - common.getAppConfig().appBackgroundColor); - if (err) { UI.alert(err); } - }); - }; - if (typeof(sizeMb) === 'number' && sizeMb < 5) { return void onClick(); } - $dlform.find('#cp-app-file-dlfile, #cp-app-file-dlprogress').click(onClick); - }; - common.getFileSize(hexFileName, function (e, data) { + FileCrypto.fetchDecryptedMetadata(src, key, function (e, metadata) { if (e) { - return void UI.errorLoadingScreen(e); + if (e === 'XHR_ERROR') { + return void UI.errorLoadingScreen(Messages.download_resourceNotAvailable, false, function () { + common.gotoURL('/file/'); + }); + } + return void console.error(e); } - var size = Util.bytesToMegabytes(data); - return void todoBigFile(size); + + // Add pad attributes when the file is saved in the drive + Title.onTitleChange(function () { + var owners = metadata.owners; + if (owners) { common.setPadAttribute('owners', owners); } + common.setPadAttribute('fileType', metadata.type); + }); + $(document).on('cpPadStored', function () { + var owners = metadata.owners; + if (owners) { common.setPadAttribute('owners', owners); } + common.setPadAttribute('fileType', metadata.type); + }); + + // Save to the drive or update the acces time + var title = document.title = metadata.name; + Title.updateTitle(title || Title.defaultTitle); + + var owners = metadata.owners; + if (owners) { + common.setPadAttribute('owners', owners); + } + if (metadata.type) { + common.setPadAttribute('fileType', metadata.type); + } + + toolbar.addElement(['pageTitle'], { + pageTitle: title, + title: Title.getTitleConfig(), + }); + toolbar.$drawer.append(common.createButton('forget', true)); + toolbar.$drawer.append(common.createButton('properties', true)); + if (common.isLoggedIn()) { + toolbar.$drawer.append(common.createButton('hashtag', true)); + } + toolbar.$file.show(); + + var displayFile = function (ev, sizeMb, CB) { + var called_back; + var cb = function (e) { + if (called_back) { return; } + called_back = true; + if (CB) { CB(e); } + }; + + var $mt = $dlview.find('media-tag'); + $mt.attr('src', src); + $mt.attr('data-crypto-key', 'cryptpad:'+cryptKey); + + var rightsideDisplayed = false; + + MediaTag($mt[0]).on('complete', function (decrypted) { + $dlview.show(); + $dlform.hide(); + var $dlButton = $dlview.find('media-tag button'); + if (ev) { $dlButton.click(); } + + if (!rightsideDisplayed) { + toolbar.$drawer + .append(common.createButton('export', true, {}, function () { + saveAs(decrypted.content, decrypted.metadata.name); + })); + rightsideDisplayed = true; + } + + // make pdfs big + var toolbarHeight = $('#cp-toolbar').height(); + var $another_iframe = $('media-tag iframe').css({ + 'height': 'calc(100vh - ' + toolbarHeight + 'px)', + 'width': '100vw', + 'position': 'absolute', + 'bottom': 0, + 'left': 0, + 'border': 0 + }); + + if ($another_iframe.length) { + $another_iframe.load(function () { + cb(); + }); + } else { + cb(); + } + }).on('progress', function (data) { + if (data.progress > 75) { return; } + var p = data.progress +'%'; + $progress.width(p); + $progressTxt.text(Math.floor(data.progress) + '%'); + }).on('error', function (err) { + console.error(err); + }); + }; + + // XXX Update "download_button" key to use "download" first and "decrypt" second + var todoBigFile = function (sizeMb) { + $dlform.show(); + UI.removeLoadingScreen(); + var button = h('button.btn.btn-primary', { + title: Messages.download_button + }, Messages.download_button); + $dlform.append([ + h('h2', Util.fixHTML(metadata.name)), + h('div.cp-button-container', [ + button, + progressTxt + ]), + ]); + + // don't display the size if you don't know it. + if (typeof(sizeMb) === 'number') { + $dlform.find('h2').append(' - ' + + Messages._getKey('formattedMB', [sizeMb])); + } + var decrypting = false; + var onClick = function (ev) { + if (decrypting) { return; } + decrypting = true; + $(button).prop('disabled', 'disabled'); + $dlform.append(progressContainer); + displayFile(ev, sizeMb, function (err) { + $appContainer.css('background-color', + common.getAppConfig().appBackgroundColor); + if (err) { UI.alert(err); } + }); + }; + if (typeof(sizeMb) === 'number' && sizeMb < 5) { return void onClick(); } + $(button).click(onClick); + }; + common.getFileSize(hexFileName, function (e, data) { + if (e) { + return void UI.errorLoadingScreen(e); + } + var size = Util.bytesToMegabytes(data); + return void todoBigFile(size); + }); }); - }); + })(); return; } From a29b98783aca432ed0aa7ac7ca0674b2edd3c43c Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 20 Nov 2020 16:38:18 +0100 Subject: [PATCH 82/84] Improve download table --- www/common/common-util.js | 6 ++++ www/common/drive-ui.js | 11 +++++++ www/common/make-backup.js | 45 +++++++++++++++++++++-------- www/common/sframe-common-file.js | 49 ++++++++++++++++++++------------ www/file/file-crypto.js | 10 +++++++ 5 files changed, 91 insertions(+), 30 deletions(-) diff --git a/www/common/common-util.js b/www/common/common-util.js index dfc6e12d7..435a72280 100644 --- a/www/common/common-util.js +++ b/www/common/common-util.js @@ -296,6 +296,12 @@ return void CB(void 0, new Uint8Array(xhr.response)); }; xhr.send(null); + + return { + cancel: function () { + if (xhr.abort) { xhr.abort(); } + } + }; }; Util.dataURIToBlob = function (dataURI) { diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index 53e496ef4..c01bf0f54 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -4149,6 +4149,17 @@ define([ data.name = Util.fixFileName(folderName); data.folderName = Util.fixFileName(folderName) + '.zip'; + var uo = manager.user.userObject; + if (sfId && manager.folders[sfId]) { + uo = manager.folders[sfId].userObject; + } + if (uo.getFilesRecursively) { + data.list = uo.getFilesRecursively(folderElement).map(function (el) { + var d = uo.getFileData(el); + return d.channel; + }); + } + APP.FM.downloadFolder(data, function (err, obj) { console.log(err, obj); console.log('DONE'); diff --git a/www/common/make-backup.js b/www/common/make-backup.js index b718ae9c1..88602c8bb 100644 --- a/www/common/make-backup.js +++ b/www/common/make-backup.js @@ -53,9 +53,6 @@ define([ var _downloadFile = function (ctx, fData, cb, updateProgress) { var cancelled = false; - var cancel = function () { - cancelled = true; - }; var href = (fData.href && fData.href.indexOf('#') !== -1) ? fData.href : fData.roHref; var parsed = Hash.parsePadUrl(href); var hash = parsed.hash; @@ -63,10 +60,13 @@ define([ var secret = Hash.getSecrets('file', hash, fData.password); var src = (ctx.fileHost || '') + Hash.getBlobPathFromHex(secret.channel); var key = secret.keys && secret.keys.cryptKey; - Util.fetch(src, function (err, u8) { + + var fetchObj, decryptObj; + + fetchObj = Util.fetch(src, function (err, u8) { if (cancelled) { return; } if (err) { return void cb('E404'); } - FileCrypto.decrypt(u8, key, function (err, res) { + decryptObj = FileCrypto.decrypt(u8, key, function (err, res) { if (cancelled) { return; } if (err) { return void cb(err); } if (!res.content) { return void cb('EEMPTY'); } @@ -78,8 +78,25 @@ define([ content: res.content, download: dl }); - }, updateProgress && updateProgress.progress2); - }, updateProgress && updateProgress.progress); + }, function (data) { + if (cancelled) { return; } + if (updateProgress && updateProgress.progress2) { + updateProgress.progress2(data); + } + }); + }, function (data) { + if (cancelled) { return; } + if (updateProgress && updateProgress.progress) { + updateProgress.progress(data); + } + }); + + var cancel = function () { + cancelled = true; + if (fetchObj && fetchObj.cancel) { fetchObj.cancel(); } + if (decryptObj && decryptObj.cancel) { decryptObj.cancel(); } + }; + return { cancel: cancel }; @@ -162,10 +179,10 @@ define([ if (ctx.stop) { return; } if (to) { clearTimeout(to); } //setTimeout(g, 2000); - g(); - w(); ctx.done++; ctx.updateProgress('download', {max: ctx.max, current: ctx.done}); + g(); + w(); }; var error = function (err) { @@ -312,13 +329,14 @@ define([ delete ctx.zip; }; return { - stop: stop + stop: stop, + cancel: stop }; }; var _downloadFolder = function (ctx, data, cb, updateProgress) { - create(data, ctx.get, ctx.fileHost, function (blob, errors) { + return create(data, ctx.get, ctx.fileHost, function (blob, errors) { if (errors && errors.length) { console.error(errors); } // TODO show user errors var dl = function () { saveAs(blob, data.folderName); @@ -332,8 +350,11 @@ define([ if (typeof progress.current !== "number") { return; } updateProgress.folderProgress(progress.current / progress.max); } + else if (state === "compressing") { + updateProgress.folderProgress(2); + } else if (state === "done") { - updateProgress.folderProgress(1); + updateProgress.folderProgress(3); } }); }; diff --git a/www/common/sframe-common-file.js b/www/common/sframe-common-file.js index c9e2eeacd..8a5be3764 100644 --- a/www/common/sframe-common-file.js +++ b/www/common/sframe-common-file.js @@ -47,8 +47,9 @@ define([ return 'cp-fileupload-element-' + String(Math.random()).substring(2); }; + Messages.fileTableHeader = "Downloads and uploads"; // XXX var tableHeader = h('div.cp-fileupload-header', [ - h('div.cp-fileupload-header-title', h('span', Messages.fileuploadHeader || 'Uploaded files')), + h('div.cp-fileupload-header-title', h('span', Messages.fileTableHeader)), h('div.cp-fileupload-header-close', h('span.fa.fa-times')), ]); @@ -262,7 +263,8 @@ define([ // name $('').append($link).appendTo($tr); // size - $('').text(UIElements.prettySize(estimate)).appendTo($tr); + var size = estimate ? UIElements.prettySize(estimate) : ''; + $(h('td.cp-fileupload-size')).text(size).appendTo($tr); // progress $('', {'class': 'cp-fileupload-table-progress'}).append($progressContainer).appendTo($tr); // cancel @@ -590,12 +592,11 @@ define([ queue.next(); }; - /* var cancelled = function () { $row.find('.cp-fileupload-table-cancel').addClass('cancelled').html('').append(h('span.fa.fa-minus')); queue.inProgress = false; queue.next(); - };*/ + }; /** * Update progress in the download panel, for downloading a file @@ -627,8 +628,21 @@ define([ * As updateDLProgress but for folders * @param {number} progressValue Progression of download, between 0 and 1 */ + Messages.download_zip = "Building ZIP file..."; // XXX + Messages.download_zip_file = "File {0}/{1}"; // XXX var updateProgress = function (progressValue) { var text = Math.round(progressValue*100) + '%'; + if (Array.isArray(data.list)) { + text = Messages._getKey('download_zip_file', [Math.round(progressValue * data.list.length), data.list.length]); + } + if (progressValue === 2) { + text = Messages.download_zip; + progressValue = 1; + } + if (progressValue === 3) { + text = "100%"; + progressValue = 1; + } $pv.text(text); $pb.css({ width: (progressValue * 100) + '%' @@ -641,7 +655,8 @@ define([ get: common.getPad, sframeChan: sframeChan, }; - downloadFunction(ctx, data, function (err, obj) { + + var dl = downloadFunction(ctx, data, function (err, obj) { $link.prepend($('', {'class': 'fa fa-external-link'})) .attr('href', '#') .click(function (e) { @@ -657,19 +672,17 @@ define([ folderProgress: updateProgress, }); -/* - var $cancel = $('', {'class': 'cp-fileupload-table-cancel-button fa fa-times'}).click(function () { - dl.cancel(); - $cancel.remove(); - $row.find('.cp-fileupload-table-progress-value').text(Messages.upload_cancelled); - cancelled(); - }); -*/ - - $row.find('.cp-fileupload-table-cancel') - .html('') - .append(h('span.fa.fa-minus')); - //.append($cancel); + var $cancel = $row.find('.cp-fileupload-table-cancel').html(''); + if (dl && dl.cancel) { + $('', { + 'class': 'cp-fileupload-table-cancel-button fa fa-times' + }).click(function () { + dl.cancel(); + $cancel.remove(); + $row.find('.cp-fileupload-table-progress-value').text(Messages.upload_cancelled); + cancelled(); + }).appendTo($cancel); + } }; File.downloadFile = function (fData, cb) { diff --git a/www/file/file-crypto.js b/www/file/file-crypto.js index a74439492..12453bb48 100644 --- a/www/file/file-crypto.js +++ b/www/file/file-crypto.js @@ -128,6 +128,11 @@ define([ metadata: undefined, }; + var cancelled = false; + var cancel = function () { + cancelled = true; + }; + var metaBox = new Uint8Array(u8.subarray(2, 2 + metadataLength)); var metaChunk = Nacl.secretbox.open(metaBox, nonce, key); @@ -168,6 +173,7 @@ define([ var chunks = []; var again = function () { + if (cancelled) { return; } takeChunk(function (e, plaintext) { if (e) { return setTimeout(function () { @@ -188,6 +194,10 @@ define([ }; again(); + + return { + cancel: cancel + }; }; // metadata From 1fa8be7f28751349b0794bc2c5e1d86bf9ffe946 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 24 Nov 2020 10:29:38 +0100 Subject: [PATCH 83/84] LESS improvements --- customize.dist/src/less2/include/forms.less | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/customize.dist/src/less2/include/forms.less b/customize.dist/src/less2/include/forms.less index 0acbd49b4..737509130 100644 --- a/customize.dist/src/less2/include/forms.less +++ b/customize.dist/src/less2/include/forms.less @@ -2,6 +2,10 @@ @import (reference) "./variables.less"; .forms_main() { + --LessLoader_require: LessLoader_currentFile(); +} + +& { @alertify-fore: @colortheme_modal-fg; @alertify-btn-fg: @alertify-fore; @alertify-light-bg: fade(@alertify-fore, 25%); From 396eb4d263a3bf9cc69ed6f52df32a5cf19f9535 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 24 Nov 2020 16:38:31 +0100 Subject: [PATCH 84/84] Stop autodownloading big mediatags --- customize.dist/ckeditor-contents.css | 30 ++++ customize.dist/src/less2/include/forms.less | 8 + .../src/less2/include/markdown.less | 9 ++ server.js | 14 ++ www/common/diffMarked.js | 4 +- www/common/media-tag.js | 150 +++++++++++++++--- www/file/inner.js | 4 +- www/pad/inner.js | 5 +- 8 files changed, 197 insertions(+), 27 deletions(-) diff --git a/customize.dist/ckeditor-contents.css b/customize.dist/ckeditor-contents.css index 000162c00..a7939839d 100644 --- a/customize.dist/ckeditor-contents.css +++ b/customize.dist/ckeditor-contents.css @@ -213,3 +213,33 @@ media-tag * { width: 100%; height: 100%; } +media-tag button.btn { + background-color: #fff; + box-sizing: border-box; + outline: 0; + display: inline-flex; + align-items: center; + padding: 0 6px; + min-height: 36px; + line-height: 22px; + white-space: nowrap; + text-align: center; + text-transform: uppercase; + font-size: 14px; + text-decoration: none; + cursor: pointer; + border-radius: 0; + transition: none; + color: #3F4141; + border: 1px solid #3F4141; +} +media-tag button.btn:hover, media-tag button.btn:active, media-tag button.btn:focus { + background-color: #ccc; +} +media-tag button b { + margin-left: 5px; +} +media-tag button .fa { + display: inline; + margin-right: 5px; +} diff --git a/customize.dist/src/less2/include/forms.less b/customize.dist/src/less2/include/forms.less index 737509130..65eedf263 100644 --- a/customize.dist/src/less2/include/forms.less +++ b/customize.dist/src/less2/include/forms.less @@ -128,6 +128,14 @@ font-weight: bold; } + &.btn-default { + border-color: @cryptpad_text_col; + color: @cryptpad_text_col; + &:hover, &:active, &:focus { + background-color: #ccc; + } + } + &.danger, &.btn-danger { background-color: @colortheme_alertify-red; border-color: @colortheme_alertify-red-border; diff --git a/customize.dist/src/less2/include/markdown.less b/customize.dist/src/less2/include/markdown.less index 23eb056b0..d7fe13f43 100644 --- a/customize.dist/src/less2/include/markdown.less +++ b/customize.dist/src/less2/include/markdown.less @@ -94,6 +94,15 @@ height: 80vh; max-height: 90vh; } + button.btn-default { + display: inline-flex; + .fa { + margin-right: 5px; + } + b { + margin-left: 5px; + } + } } media-tag:empty { width: 100px; diff --git a/server.js b/server.js index eb2787475..3869af509 100644 --- a/server.js +++ b/server.js @@ -136,6 +136,20 @@ app.head(/^\/common\/feedback\.html/, function (req, res, next) { }); }()); +app.use('/blob', function (req, res, next) { + if (req.method === 'HEAD') { + Express.static(Path.join(__dirname, (config.blobPath || './blob')), { + setHeaders: function (res, path, stat) { + res.set('Access-Control-Allow-Origin', '*'); + res.set('Access-Control-Allow-Headers', 'Content-Length'); + res.set('Access-Control-Expose-Headers', 'Content-Length'); + } + })(req, res, next); + return; + } + next(); +}); + app.use(function (req, res, next) { if (req.method === 'OPTIONS' && /\/blob\//.test(req.url)) { res.setHeader('Access-Control-Allow-Origin', '*'); diff --git a/www/common/diffMarked.js b/www/common/diffMarked.js index 734551983..fd3fcafb3 100644 --- a/www/common/diffMarked.js +++ b/www/common/diffMarked.js @@ -668,7 +668,7 @@ define([ } return; } - MediaTag(el); + var mediaObject = MediaTag(el); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { if (mutation.type === 'childList') { @@ -676,7 +676,7 @@ define([ .map(function (el) { return el.outerHTML; }) .join(''); mediaMap[mutation.target.getAttribute('src')] = list_values; - observer.disconnect(); + if (mediaObject.complete) { observer.disconnect(); } } }); $mt.off('click dblclick preview'); diff --git a/www/common/media-tag.js b/www/common/media-tag.js index 1ba9a1f53..4318ca8f6 100644 --- a/www/common/media-tag.js +++ b/www/common/media-tag.js @@ -63,7 +63,8 @@ ], pdf: {}, download: { - text: "Download" + text: "Save", + textDl: "Load attachment" }, Plugins: { /** @@ -114,8 +115,8 @@ }, download: function (metadata, url, content, cfg, cb) { var btn = document.createElement('button'); - btn.setAttribute('class', 'btn btn-success'); - btn.innerHTML = cfg.download.text + '
      ' + + btn.setAttribute('class', 'btn btn-default'); + btn.innerHTML = '' + cfg.download.text + '
      ' + (metadata.name ? '' + fixHTML(metadata.name) + '' : ''); btn.addEventListener('click', function () { saveFile(content, url, metadata.name); @@ -125,6 +126,92 @@ } }; + var makeProgressBar = function (cfg, mediaObject) { + // XXX CSP: we'll need to add style in cryptpad's less + var style = (function(){/* +.mediatag-progress-container { + position: relative; + border: 1px solid #0087FF; + background: white; + height: 25px; + display: inline-flex; + width: 200px; + align-items: center; + justify-content: center; + box-sizing: border-box; + vertical-align: top; +} +.mediatag-progress-bar { + position: absolute; + left: 0; + top: 0; + bottom: 0; + background: #0087FF; + width: 0%; +} +.mediatag-progress-text { + height: 25px; + margin-left: 5px; + line-height: 25px; + vertical-align: top; + width: auto; + display: inline-block; + color: #3F4141; + font-weight: bold; +} +*/}).toString().slice(14, -3); + var container = document.createElement('div'); + container.classList.add('mediatag-progress-container'); + var bar = document.createElement('div'); + bar.classList.add('mediatag-progress-bar'); + container.appendChild(bar); + + var text = document.createElement('span'); + text.classList.add('mediatag-progress-text'); + text.innerText = '0%'; + + mediaObject.on('progress', function (obj) { + var percent = obj.progress; + text.innerText = (Math.round(percent*10))/10+'%'; + bar.setAttribute('style', 'width:'+percent+'%;'); + }); + + mediaObject.tag.innerHTML = ''; + mediaObject.tag.appendChild(container); + mediaObject.tag.appendChild(text); + }; + var makeDownloadButton = function (cfg, mediaObject, size, cb) { + var btn = document.createElement('button'); + btn.setAttribute('class', 'btn btn-default'); + btn.innerHTML = '' + + cfg.download.textDl + ' (' + size + 'MB)'; + btn.addEventListener('click', function () { + makeProgressBar(cfg, mediaObject); + cb(); + }); + mediaObject.tag.innerHTML = ''; + mediaObject.tag.appendChild(btn); + }; + + var getFileSize = function (src, _cb) { + var cb = function (e, res) { + _cb(e, res); + cb = function () {}; + }; + // XXX Cache + var xhr = new XMLHttpRequest(); + xhr.open("HEAD", src); + xhr.onerror = function () { return void cb("XHR_ERROR"); }; + xhr.onreadystatechange = function() { + if (this.readyState === this.DONE) { + cb(null, Number(xhr.getResponseHeader("Content-Length"))); + } + }; + xhr.onload = function () { + if (/^4/.test('' + this.status)) { return void cb("XHR_ERROR " + this.status); } + }; + xhr.send(); + }; // Download a blob from href var download = function (src, _cb, progressCb) { @@ -433,6 +520,7 @@ // End media-tag rendering: display the tag and emit the event var end = function (decrypted) { + mediaObject.complete = true; process(mediaObject, decrypted, cfg, function (err) { if (err) { return void emit('error', err); } mediaObject._blob = decrypted; @@ -441,36 +529,54 @@ }; // If we have the blob in our cache, don't download & decrypt it again, just display + // XXX Store in the cache the pending mediaobject: make sure we don't download and decrypt twice the same element at the same time if (cache[uid]) { end(cache[uid]); return mediaObject; } - // Download the encrypted blob - download(src, function (err, u8Encrypted) { + var dl = function () { + // Download the encrypted blob + download(src, function (err, u8Encrypted) { + if (err) { + if (err === "XHR_ERROR 404") { + mediaObject.tag.innerHTML = ''; + } + return void emit('error', err); + } + // Decrypt the blob + decrypt(u8Encrypted, strKey, function (errDecryption, u8Decrypted) { + if (errDecryption) { + return void emit('error', errDecryption); + } + // Cache and display the decrypted blob + cache[uid] = u8Decrypted; + end(u8Decrypted); + }, function (progress) { + emit('progress', { + progress: 50+0.5*progress + }); + }); + }, function (progress) { + emit('progress', { + progress: 0.5*progress + }); + }); + }; + + if (cfg.force) { dl(); return mediaObject; } + + var maxSize = 5 * 1024 * 1024; + getFileSize(src, function (err, size) { if (err) { if (err === "XHR_ERROR 404") { mediaObject.tag.innerHTML = ''; } return void emit('error', err); } - // Decrypt the blob - decrypt(u8Encrypted, strKey, function (errDecryption, u8Decrypted) { - if (errDecryption) { - return void emit('error', errDecryption); - } - // Cache and display the decrypted blob - cache[uid] = u8Decrypted; - end(u8Decrypted); - }, function (progress) { - emit('progress', { - progress: 50+0.5*progress - }); - }); - }, function (progress) { - emit('progress', { - progress: 0.5*progress - }); + if (!size || size < maxSize) { return void dl(); } + var sizeMb = Math.round(10 * size / 1024 / 1024) / 10; + makeDownloadButton(cfg, mediaObject, sizeMb, dl); }); return mediaObject; diff --git a/www/file/inner.js b/www/file/inner.js index 8b3f12682..c29657672 100644 --- a/www/file/inner.js +++ b/www/file/inner.js @@ -168,7 +168,9 @@ define([ var rightsideDisplayed = false; - MediaTag($mt[0]).on('complete', function (decrypted) { + MediaTag($mt[0], { + force: true // Download starts automatically + }).on('complete', function (decrypted) { $dlview.show(); $dlform.hide(); var $dlButton = $dlview.find('media-tag button'); diff --git a/www/pad/inner.js b/www/pad/inner.js index 235b4fafa..5139197e0 100644 --- a/www/pad/inner.js +++ b/www/pad/inner.js @@ -462,7 +462,7 @@ define([ setTimeout(function() { // Just in case var tags = dom.querySelectorAll('media-tag:empty'); Array.prototype.slice.call(tags).forEach(function(el) { - MediaTag(el); + var mediaObject = MediaTag(el); $(el).on('keydown', function(e) { if ([8, 46].indexOf(e.which) !== -1) { $(el).remove(); @@ -474,6 +474,7 @@ define([ if (mutation.type === 'childList') { var list_values = [].slice.call(el.children); mediaTagMap[el.getAttribute('src')] = list_values; + if (mediaObject.complete) { observer.disconnect(); } } }); }); @@ -492,7 +493,7 @@ define([ var src = tag.getAttribute('src'); if (mediaTagMap[src]) { mediaTagMap[src].forEach(function(n) { - tag.appendChild(n.cloneNode()); + tag.appendChild(n.cloneNode(true)); }); } });