From d16cc2472dfef812f95f399404903b1ba86b9545 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 3 Nov 2020 10:49:13 +0100 Subject: [PATCH 01/51] First prototype of an offline cache --- lib/hk-util.js | 13 ++++++-- www/common/outer/async-store.js | 5 ++- www/common/outer/cache-store.js | 55 ++++++++++++++++++++++++++++++++ www/common/outer/sharedfolder.js | 4 ++- www/common/outer/team.js | 5 ++- 5 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 www/common/outer/cache-store.js diff --git a/lib/hk-util.js b/lib/hk-util.js index 14263e481..4fa2140f9 100644 --- a/lib/hk-util.js +++ b/lib/hk-util.js @@ -419,9 +419,11 @@ const getHistoryOffset = (Env, channelName, lastKnownHash, _cb) => { // fall through to the next block if the offset of the hash in question is not in memory if (lastKnownHash && typeof(lkh) !== "number") { return; } + // If we have a lastKnownHash or we didn't ask for one, we don't need the next blocks + waitFor.abort(); + // Since last 2 checkpoints if (!lastKnownHash) { - waitFor.abort(); // Less than 2 checkpoints in the history: return everything if (index.cpIndex.length < 2) { return void cb(null, 0); } // Otherwise return the second last checkpoint's index @@ -436,7 +438,14 @@ const getHistoryOffset = (Env, channelName, lastKnownHash, _cb) => { to reconcile their differences. */ } - offset = lkh; + // If our lastKnownHash is older than the 2nd to last checkpoint, + // only send the last 2 checkpoints and ignore "lkh" + if (lkh && index.cpIndex.length >= 2 && lkh < index.cpIndex[0].offset) { + return void cb(null, index.cpIndex[0].offset); + } + + // Otherwise use our lastKnownHash + cb(null, lkh); })); }).nThen((w) => { // skip past this block if the offset is anything other than -1 diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 62e078050..a39734dc4 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -10,6 +10,7 @@ define([ '/common/common-realtime.js', '/common/common-messaging.js', '/common/pinpad.js', + '/common/outer/cache-store.js', '/common/outer/sharedfolder.js', '/common/outer/cursor.js', '/common/outer/onlyoffice.js', @@ -28,7 +29,7 @@ define([ '/bower_components/nthen/index.js', '/bower_components/saferphore/index.js', ], function (Sortify, UserObject, ProxyManager, Migrate, Hash, Util, Constants, Feedback, - Realtime, Messaging, Pinpad, + Realtime, Messaging, Pinpad, Cache, SF, Cursor, OnlyOffice, Mailbox, Profile, Team, Messenger, History, NetConfig, AppConfig, Crypto, ChainPad, CpNetflux, Listmap, nThen, Saferphore) { @@ -1590,6 +1591,7 @@ define([ Store.leavePad(null, data, function () {}); }; var conf = { + Cache: Cache, onReady: function (pad) { var padData = pad.metadata || {}; channel.data = padData; @@ -2640,6 +2642,7 @@ define([ readOnly: false, validateKey: secret.keys.validateKey || undefined, crypto: Crypto.createEncryptor(secret.keys), + Cache: Cache, userName: 'fs', logLevel: 1, ChainPad: ChainPad, diff --git a/www/common/outer/cache-store.js b/www/common/outer/cache-store.js new file mode 100644 index 000000000..139501872 --- /dev/null +++ b/www/common/outer/cache-store.js @@ -0,0 +1,55 @@ +define([ + '/bower_components/localforage/dist/localforage.min.js', +], function (localForage) { + var S = {}; + + var cache = localForage.createInstance({ + name: "cp_cache" + }); + + // id: channel ID or blob ID + // returns array of messages + S.getChannelCache = function (id, cb) { + cache.getItem(id, function (err, obj) { + if (err || !obj || !Array.isArray(obj.c)) { + return void cb(err || 'EINVAL'); + } + cb(null, obj); + }); + }; + + var checkCheckpoints = function (array) { + if (!Array.isArray(array)) { return; } + var cp = 0; + for (var i = array.length - 1; i >= 0; i--) { + if (array[i].isCheckpoint) { cp++; } + if (cp === 2) { + array.splice(0, i); + break; + } + } + }; + + S.storeCache = function (id, validateKey, val, cb) { + cb = cb || function () {}; + if (!Array.isArray(val) || !validateKey) { return void cb('EINVAL'); } + checkCheckpoints(val); + cache.setItem(id, { + k: validateKey, + c: val + }, function (err) { + cb(err); + }); + }; + + S.clearChannel = function (id, cb) { + cb = cb || function () {}; + cache.removeItem(id, cb); + }; + + S.clear = function () { + cache.clear(); + }; + + return S; +}); diff --git a/www/common/outer/sharedfolder.js b/www/common/outer/sharedfolder.js index e34cd64df..3c1c8632d 100644 --- a/www/common/outer/sharedfolder.js +++ b/www/common/outer/sharedfolder.js @@ -2,12 +2,13 @@ define([ '/common/common-hash.js', '/common/common-util.js', '/common/userObject.js', + '/common/outer/cache-store.js', '/bower_components/nthen/index.js', '/bower_components/chainpad-crypto/crypto.js', '/bower_components/chainpad-listmap/chainpad-listmap.js', '/bower_components/chainpad/chainpad.dist.js', -], function (Hash, Util, UserObject, +], function (Hash, Util, UserObject, Cache, nThen, Crypto, Listmap, ChainPad) { var SF = {}; @@ -174,6 +175,7 @@ define([ ChainPad: ChainPad, classic: true, network: network, + Cache: Cache, metadata: { validateKey: secret.keys.validateKey || undefined, owners: owners diff --git a/www/common/outer/team.js b/www/common/outer/team.js index de9206511..dc078a2f4 100644 --- a/www/common/outer/team.js +++ b/www/common/outer/team.js @@ -12,6 +12,7 @@ define([ '/common/common-feedback.js', '/common/outer/invitation.js', '/common/cryptget.js', + '/common/outer/cache-store.js', '/bower_components/chainpad-listmap/chainpad-listmap.js', '/bower_components/chainpad-crypto/crypto.js', @@ -21,7 +22,7 @@ define([ '/bower_components/saferphore/index.js', '/bower_components/tweetnacl/nacl-fast.min.js', ], function (Util, Hash, Constants, Realtime, - ProxyManager, UserObject, SF, Roster, Messaging, Feedback, Invite, Crypt, + ProxyManager, UserObject, SF, Roster, Messaging, Feedback, Invite, Crypt, Cache, Listmap, Crypto, CpNetflux, ChainPad, nThen, Saferphore) { var Team = {}; @@ -426,6 +427,7 @@ define([ channel: secret.channel, crypto: crypto, ChainPad: ChainPad, + Cache: Cache, metadata: { validateKey: secret.keys.validateKey || undefined, }, @@ -573,6 +575,7 @@ define([ logLevel: 1, classic: true, ChainPad: ChainPad, + Cache: Cache, owners: [ctx.store.proxy.edPublic] }; nThen(function (waitFor) { From 4126ae5d8b56b0bd846606fcfe8904b99d9e28dc Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 3 Nov 2020 13:48:59 +0100 Subject: [PATCH 02/51] Display pad content from cache before joining the network/channel --- www/common/cryptpad-common.js | 4 +++ www/common/outer/async-store.js | 6 ++++ www/common/sframe-app-framework.js | 38 +++++++++++++++++++++ www/common/sframe-chainpad-netflux-inner.js | 10 ++++++ www/common/sframe-chainpad-netflux-outer.js | 13 ++++++- www/common/toolbar.js | 12 +++++++ 6 files changed, 82 insertions(+), 1 deletion(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 8874871d8..82ea2991f 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -992,6 +992,8 @@ define([ pad.onJoinEvent = Util.mkEvent(); pad.onLeaveEvent = Util.mkEvent(); pad.onDisconnectEvent = Util.mkEvent(); + pad.onCacheEvent = Util.mkEvent(); + pad.onCacheReadyEvent = Util.mkEvent(); pad.onConnectEvent = Util.mkEvent(); pad.onErrorEvent = Util.mkEvent(); pad.onMetadataEvent = Util.mkEvent(); @@ -1957,6 +1959,8 @@ define([ PAD_JOIN: common.padRpc.onJoinEvent.fire, PAD_LEAVE: common.padRpc.onLeaveEvent.fire, PAD_DISCONNECT: common.padRpc.onDisconnectEvent.fire, + PAD_CACHE: common.padRpc.onCacheEvent.fire, + PAD_CACHE_READY: common.padRpc.onCacheReadyEvent.fire, PAD_CONNECT: common.padRpc.onConnectEvent.fire, PAD_ERROR: common.padRpc.onErrorEvent.fire, PAD_METADATA: common.padRpc.onMetadataEvent.fire, diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index a39734dc4..599d1d3b3 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -1592,6 +1592,12 @@ define([ }; var conf = { Cache: Cache, + onCacheStart: function () { + postMessage(clientId, "PAD_CACHE"); + }, + onCacheReady: function (info) { + postMessage(clientId, "PAD_CACHE_READY"); + }, onReady: function (pad) { var padData = pad.metadata || {}; channel.data = padData; diff --git a/www/common/sframe-app-framework.js b/www/common/sframe-app-framework.js index 3097cf6d2..81ab18d45 100644 --- a/www/common/sframe-app-framework.js +++ b/www/common/sframe-app-framework.js @@ -467,7 +467,44 @@ define([ }); }; + var onCacheReady = function () { + stateChange(STATE.DISCONNECTED); + toolbar.offline(true); + var newContentStr = cpNfInner.chainpad.getUserDoc(); + if (toolbar) { + // Check if we have a new chainpad instance + toolbar.resetChainpad(cpNfInner.chainpad); + } +console.log(newContentStr); + + // Invalid cache? abort + // XXX tell outer/worker to invalidate cache + if (newContentStr === '') { return; } + + var privateDat = cpNfInner.metadataMgr.getPrivateData(); + var type = privateDat.app; + + var newContent = JSON.parse(newContentStr); + var metadata = extractMetadata(newContent); +console.log('OKOK'); + + // Make sure we're using the correct app for this cache + if (metadata && typeof(metadata.type) !== 'undefined' && metadata.type !== type) { + console.error('return'); + return; + } + + cpNfInner.metadataMgr.updateMetadata(metadata); + newContent = normalize(newContent); + if (!unsyncMode) { + contentUpdate(newContent, function () { return function () {}}); + } + + UI.removeLoadingScreen(emitResize); + }; var onReady = function () { + toolbar.offline(false); + console.error('READY'); var newContentStr = cpNfInner.chainpad.getUserDoc(); if (state === STATE.DELETED) { return; } @@ -732,6 +769,7 @@ define([ onRemote: onRemote, onLocal: onLocal, onInit: onInit, + onCacheReady: onCacheReady, onReady: function () { evStart.reg(onReady); }, onConnectionChange: onConnectionChange, onError: onError, diff --git a/www/common/sframe-chainpad-netflux-inner.js b/www/common/sframe-chainpad-netflux-inner.js index abb1cebdf..57689b7ea 100644 --- a/www/common/sframe-chainpad-netflux-inner.js +++ b/www/common/sframe-chainpad-netflux-inner.js @@ -34,6 +34,7 @@ define([ var onLocal = config.onLocal || function () { }; var setMyID = config.setMyID || function () { }; var onReady = config.onReady || function () { }; + var onCacheReady = config.onCacheReady || function () { }; var onError = config.onError || function () { }; var userName = config.userName; var initialState = config.initialState; @@ -93,6 +94,14 @@ define([ evInfiniteSpinner.fire(); }, 2000); + sframeChan.on('EV_RT_CACHE', function (isPermanent) { + // XXX + }); + sframeChan.on('EV_RT_CACHE_READY', function (isPermanent) { + // XXX + onCacheReady({realtime: chainpad}); + console.error('PEWPEWPEW'); + }); sframeChan.on('EV_RT_DISCONNECT', function (isPermanent) { isReady = false; chainpad.abort(); @@ -134,6 +143,7 @@ define([ evConnected.fire(); }); sframeChan.on('Q_RT_MESSAGE', function (content, cb) { + console.log(content); if (isReady) { onLocal(true); // should be onBeforeMessage } diff --git a/www/common/sframe-chainpad-netflux-outer.js b/www/common/sframe-chainpad-netflux-outer.js index 72cfdef9a..056b9cd28 100644 --- a/www/common/sframe-chainpad-netflux-outer.js +++ b/www/common/sframe-chainpad-netflux-outer.js @@ -89,6 +89,7 @@ define([], function () { validateKey = msgObj.validateKey; } var message = msgIn(msgObj.user, msgObj.msg); + console.log(message); if (!message) { return; } lastTime = msgObj.time; @@ -114,16 +115,26 @@ define([], function () { if (firstConnection) { firstConnection = false; // Add the handlers to the WebChannel - padRpc.onMessageEvent.reg(function (msg) { onMessage(msg); }); padRpc.onJoinEvent.reg(function (m) { sframeChan.event('EV_RT_JOIN', m); }); padRpc.onLeaveEvent.reg(function (m) { sframeChan.event('EV_RT_LEAVE', m); }); } }; + padRpc.onMessageEvent.reg(function (msg) { onMessage(msg); }); + padRpc.onDisconnectEvent.reg(function (permanent) { sframeChan.event('EV_RT_DISCONNECT', permanent); }); + padRpc.onCacheReadyEvent.reg(function () { + console.log('ONCACHEREADY'); + sframeChan.event('EV_RT_CACHE_READY'); + }); + + padRpc.onCacheEvent.reg(function () { + sframeChan.event('EV_RT_CACHE'); + }); + padRpc.onConnectEvent.reg(function (data) { onOpen(data); }); diff --git a/www/common/toolbar.js b/www/common/toolbar.js index 78e4e91f3..0927cde21 100644 --- a/www/common/toolbar.js +++ b/www/common/toolbar.js @@ -1365,6 +1365,18 @@ MessengerUI, Messages) { } }; + toolbar.offline = function (bool) { + toolbar.connected = !bool; // Can't edit title + toolbar.history = bool; // Stop "Initializing" state + toolbar.isErrorState = bool; // Stop kickSpinner + toolbar.title.toggleClass('cp-toolbar-unsync', bool); // "read only" next to the title + if (bool && toolbar.spinner) { + toolbar.spinner.text("OFFLINE"); // XXX + } else { + kickSpinner(toolbar, config); + } + }; + // On log out, remove permanently the realtime elements of the toolbar Common.onLogout(function () { failed(); From 8d5d85c7f8d3ad0bdc7fc3fba094670bf7031644 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 3 Nov 2020 15:35:14 +0100 Subject: [PATCH 03/51] Add XXX --- www/common/outer/cache-store.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/www/common/outer/cache-store.js b/www/common/outer/cache-store.js index 139501872..aed480860 100644 --- a/www/common/outer/cache-store.js +++ b/www/common/outer/cache-store.js @@ -1,4 +1,5 @@ define([ + // XXX Load util and use mkAsync '/bower_components/localforage/dist/localforage.min.js', ], function (localForage) { var S = {}; @@ -21,6 +22,7 @@ define([ var checkCheckpoints = function (array) { if (!Array.isArray(array)) { return; } var cp = 0; + // XXX check sliceCpIndex from hk-util: use the same logic for forks for (var i = array.length - 1; i >= 0; i--) { if (array[i].isCheckpoint) { cp++; } if (cp === 2) { @@ -37,6 +39,8 @@ define([ cache.setItem(id, { k: validateKey, c: val + // XXX add "time" here +new Date() ==> we want to store the time when it was updated in our indexeddb in case we want to remove inactive entries from indexeddb later + // XXX we would also need to update the time when we "getChannelCache" }, function (err) { cb(err); }); From ebd007fdb95cfed508e52a52a2b42d7da422cffb Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 3 Nov 2020 17:17:20 +0100 Subject: [PATCH 04/51] Use offline state per team and not global state --- www/common/outer/team.js | 23 +++++++++++------ www/teams/inner.js | 53 ++++++++++++++++++++++++++++++++-------- www/teams/main.js | 4 +-- 3 files changed, 61 insertions(+), 19 deletions(-) diff --git a/www/common/outer/team.js b/www/common/outer/team.js index de9206511..bb2b8fbf5 100644 --- a/www/common/outer/team.js +++ b/www/common/outer/team.js @@ -57,11 +57,11 @@ define([ }); proxy.on('disconnect', function () { team.offline = true; - team.sendEvent('NETWORK_DISCONNECT'); + team.sendEvent('NETWORK_DISCONNECT', team.id); }); proxy.on('reconnect', function () { team.offline = false; - team.sendEvent('NETWORK_RECONNECT'); + team.sendEvent('NETWORK_RECONNECT', team.id); }); } proxy.on('change', [], function (o, n, p) { @@ -931,7 +931,9 @@ define([ if (!team) { return void cb ({error: 'ENOENT'}); } if (!team.roster) { return void cb({error: 'NO_ROSTER'}); } var state = team.roster.getState() || {}; - cb(state.metadata || {}); + var md = state.metadata || {}; + md.offline = team.offline; + cb(md); }; var setTeamMetadata = function (ctx, data, cId, cb) { @@ -1879,15 +1881,15 @@ define([ var t = Util.clone(teams); Object.keys(t).forEach(function (id) { // If failure to load the team, don't send it - if (ctx.teams[id]) { return; } + if (ctx.teams[id]) { + t[id].offline = ctx.teams[id].offline; + return; + } t[id].error = true; }); cb(t); }; team.execCommand = function (clientId, obj, cb) { - if (ctx.store.offline) { - return void cb({ error: 'OFFLINE' }); - } var cmd = obj.cmd; var data = obj.data; @@ -1911,30 +1913,36 @@ define([ return void setTeamMetadata(ctx, data, clientId, cb); } if (cmd === 'OFFER_OWNERSHIP') { + if (ctx.store.offline) { return void cb({ error: 'OFFLINE' }); } return void offerOwnership(ctx, data, clientId, cb); } if (cmd === 'ANSWER_OWNERSHIP') { + if (ctx.store.offline) { return void cb({ error: 'OFFLINE' }); } return void answerOwnership(ctx, data, clientId, cb); } if (cmd === 'DESCRIBE_USER') { return void describeUser(ctx, data, clientId, cb); } if (cmd === 'INVITE_TO_TEAM') { + if (ctx.store.offline) { return void cb({ error: 'OFFLINE' }); } return void inviteToTeam(ctx, data, clientId, cb); } if (cmd === 'LEAVE_TEAM') { return void leaveTeam(ctx, data, clientId, cb); } if (cmd === 'JOIN_TEAM') { + if (ctx.store.offline) { return void cb({ error: 'OFFLINE' }); } return void joinTeam(ctx, data, clientId, cb); } if (cmd === 'REMOVE_USER') { return void removeUser(ctx, data, clientId, cb); } if (cmd === 'DELETE_TEAM') { + if (ctx.store.offline) { return void cb({ error: 'OFFLINE' }); } return void deleteTeam(ctx, data, clientId, cb); } if (cmd === 'CREATE_TEAM') { + if (ctx.store.offline) { return void cb({ error: 'OFFLINE' }); } return void createTeam(ctx, data, clientId, cb); } if (cmd === 'GET_EDITABLE_FOLDERS') { @@ -1947,6 +1955,7 @@ define([ return void getPreviewContent(ctx, data, clientId, cb); } if (cmd === 'ACCEPT_LINK_INVITATION') { + if (ctx.store.offline) { return void cb({ error: 'OFFLINE' }); } return void acceptLinkInvitation(ctx, data, clientId, cb); } }; diff --git a/www/teams/inner.js b/www/teams/inner.js index a75ed94db..d49bf6209 100644 --- a/www/teams/inner.js +++ b/www/teams/inner.js @@ -46,7 +46,9 @@ define([ Backup, Messages) { - var APP = {}; + var APP = { + teams: {} + }; var driveAPP = {}; var saveAs = window.saveAs; //var SHARED_FOLDER_NAME = Messages.fm_sharedFolderName; @@ -211,6 +213,11 @@ define([ if (obj && obj.error) { return void UI.warn(Messages.error); } + + // Refresh offline state + APP.teams[APP.team] = APP.teams[APP.team] || {}; + APP.teams[APP.team].offline = obj.offline; + common.displayAvatar($avatar, obj.avatar, obj.name); $category.append($avatar); $avatar.append(h('span.cp-sidebarlayout-category-name', obj.name)); @@ -333,6 +340,11 @@ define([ }); APP.drive = drive; driveAPP.refresh = drive.refresh; + + if (APP.teams[id] && APP.teams[id].offline) { + setEditable(false); + drive.refresh(); + } }); }; @@ -406,7 +418,18 @@ define([ content.push(h('h3', Messages.team_listTitle + ' ' + slots)); + var metadataMgr = common.getMetadataMgr(); + var privateData = metadataMgr.getPrivateData(); + var teams = privateData.teams || {}; + APP.teams = {}; + keys.forEach(function (id) { + if (!obj[id].empty) { + APP.teams[id] = { + offline: obj[id] && obj[id].offline + }; + } + var team = obj[id]; if (team.empty) { list.push(h('div.cp-team-list-team.empty', [ @@ -1433,27 +1456,37 @@ define([ } }); - var onDisconnect = function (noAlert) { + var teams = privateData.teams || {}; + + var onDisconnect = function (teamId) { + if (APP.team && teamId && APP.team !== teamId) { return; } setEditable(false); if (APP.team && driveAPP.refresh) { driveAPP.refresh(); } toolbar.failed(); - if (!noAlert) { UIElements.disconnectAlert(); } + UIElements.disconnectAlert(); }; - var onReconnect = function () { + var onReconnect = function (teamId) { + if (APP.team && teamId && APP.team !== teamId) { return; } setEditable(true); if (APP.team && driveAPP.refresh) { driveAPP.refresh(); } toolbar.reconnecting(); UIElements.reconnectAlert(); }; - sframeChan.on('EV_DRIVE_LOG', function (msg) { - UI.log(msg); + sframeChan.on('EV_DRIVE_LOG', function (data) { + UI.log(data.msg); }); - sframeChan.on('EV_NETWORK_DISCONNECT', function () { - onDisconnect(); + sframeChan.on('EV_NETWORK_DISCONNECT', function (teamId) { + onDisconnect(teamId); + if (teamId && APP.teams[teamId]) { + APP.teams[teamId].offline = true; + } }); - sframeChan.on('EV_NETWORK_RECONNECT', function () { - onReconnect(); + sframeChan.on('EV_NETWORK_RECONNECT', function (teamId) { + onReconnect(teamId); + if (teamId && APP.teams[teamId]) { + APP.teams[teamId].offline = false; + } }); common.onLogout(function () { setEditable(false); }); }); diff --git a/www/teams/main.js b/www/teams/main.js index cab84d160..0344a902f 100644 --- a/www/teams/main.js +++ b/www/teams/main.js @@ -85,10 +85,10 @@ define([ sframeChan.event('EV_'+obj.data.ev, obj.data.data); } if (obj.data.ev === 'NETWORK_RECONNECT') { - sframeChan.event('EV_NETWORK_RECONNECT'); + sframeChan.event('EV_NETWORK_RECONNECT', obj.data.data); } if (obj.data.ev === 'NETWORK_DISCONNECT') { - sframeChan.event('EV_NETWORK_DISCONNECT'); + sframeChan.event('EV_NETWORK_DISCONNECT', obj.data.data); } }); From 446cca0725452110db24d269b45292348c7cf075 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 3 Nov 2020 18:29:09 +0100 Subject: [PATCH 05/51] Use offline state per shared folder in the drive --- customize.dist/loading.js | 1 + www/common/drive-ui.js | 26 ++++++++++++++++++++++++-- www/common/outer/async-store.js | 5 ++++- www/common/proxy-manager.js | 8 ++++++++ www/drive/inner.js | 2 ++ www/teams/inner.js | 2 ++ 6 files changed, 41 insertions(+), 3 deletions(-) diff --git a/customize.dist/loading.js b/customize.dist/loading.js index 4f8b79125..733b3fff0 100644 --- a/customize.dist/loading.js +++ b/customize.dist/loading.js @@ -327,6 +327,7 @@ button.primary:hover{ if (!built) { return; } 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/www/common/drive-ui.js b/www/common/drive-ui.js index 53e496ef4..4886b213e 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -42,6 +42,7 @@ define([ var APP = window.APP = { editable: false, + online: true, mobile: function () { if (window.matchMedia) { return !window.matchMedia('(any-pointer:fine)').matches; } else { return $('body').width() <= 600; } @@ -267,13 +268,25 @@ define([ }; // Handle disconnect/reconnect - var setEditable = function (state, isHistory) { + // If isHistory and isSf are both false, update the "APP.online" flag + // If isHistory is true, update the "APP.history" flag + // isSf is used to detect offline shared folders: setEditable is called on displayDirectory + var setEditable = function (state, isHistory, isSf) { if (APP.closed || !APP.$content || !$.contains(document.documentElement, APP.$content[0])) { return; } + if (isHistory) { + APP.history = !state; + } else if (!isSf) { + APP.online = state; + } + state = APP.online && !APP.history && state; APP.editable = !APP.readOnly && state; + if (!state) { APP.$content.addClass('cp-app-drive-readonly'); - if (!isHistory) { + if (!APP.history || !APP.online) { $('#cp-app-drive-connection-state').show(); + } else { + $('#cp-app-drive-connection-state').hide(); } $('[draggable="true"]').attr('draggable', false); } @@ -3670,6 +3683,15 @@ define([ } var readOnlyFolder = false; + + // If the shared folder is offline, add the "DISCONNECTED" banner, otherwise + // use the normal "editable" behavior (based on drive offline or history mode) + if (sfId && manager.folders[sfId].offline) { + setEditable(false, false, true); + } else { + setEditable(true, false, true); + } + if (APP.readOnly) { // Read-only drive (team?) $content.prepend($readOnly.clone()); diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 62e078050..76b92feab 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -120,10 +120,13 @@ define([ Store.getSharedFolder = function (clientId, data, cb) { var s = getStore(data.teamId); var id = data.id; + var proxy; if (!s || !s.manager) { return void cb({ error: 'ENOTFOUND' }); } if (s.manager.folders[id]) { + proxy = Util.clone(s.manager.folders[id].proxy); + proxy.offline = Boolean(s.manager.folders[id].offline); // If it is loaded, return the shared folder proxy - return void cb(s.manager.folders[id].proxy); + return void cb(proxy); } else { // Otherwise, check if we know this shared folder var shared = Util.find(s.proxy, ['drive', UserObject.SHARED_FOLDERS]) || {}; diff --git a/www/common/proxy-manager.js b/www/common/proxy-manager.js index 88f4a052b..74bfcd890 100644 --- a/www/common/proxy-manager.js +++ b/www/common/proxy-manager.js @@ -40,6 +40,14 @@ define([ userObject: userObject, leave: leave }; + if (proxy.on) { + proxy.on('disconnect', function () { + Env.folders[id].offline = true; + }); + proxy.on('reconnect', function () { + Env.folders[id].online = true; + }); + } return userObject; }; diff --git a/www/drive/inner.js b/www/drive/inner.js index f85819083..9aa080dc3 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -82,6 +82,8 @@ define([ var readOnly = !secret.keys.editKeyStr; if (!manager || !manager.folders[fId]) { return; } manager.folders[fId].userObject.setReadOnly(readOnly, secret.keys.secondaryKey); + + manager.folders[fId].offline = newObj.offline; })); }); // Remove from memory folders that have been deleted from the drive remotely diff --git a/www/teams/inner.js b/www/teams/inner.js index d49bf6209..c50dde5a1 100644 --- a/www/teams/inner.js +++ b/www/teams/inner.js @@ -93,6 +93,8 @@ define([ var readOnly = !secret.keys.editKeyStr; if (!manager || !manager.folders[fId]) { return; } manager.folders[fId].userObject.setReadOnly(readOnly, secret.keys.secondaryKey); + + manager.folders[fId].offline = newObj.offline; })); }); // Remove from memory folders that have been deleted from the drive remotely From 4edf74587ef964743c2545698ea5ef4f38818836 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 3 Nov 2020 18:30:09 +0100 Subject: [PATCH 06/51] lint compliance --- www/teams/inner.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/www/teams/inner.js b/www/teams/inner.js index c50dde5a1..05f823e2d 100644 --- a/www/teams/inner.js +++ b/www/teams/inner.js @@ -420,9 +420,6 @@ define([ content.push(h('h3', Messages.team_listTitle + ' ' + slots)); - var metadataMgr = common.getMetadataMgr(); - var privateData = metadataMgr.getPrivateData(); - var teams = privateData.teams || {}; APP.teams = {}; keys.forEach(function (id) { @@ -1458,8 +1455,6 @@ define([ } }); - var teams = privateData.teams || {}; - var onDisconnect = function (teamId) { if (APP.team && teamId && APP.team !== teamId) { return; } setEditable(false); From b456fee9e9beb5285055b979d01deb2e9e6099c3 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 4 Nov 2020 12:24:57 +0100 Subject: [PATCH 07/51] Fix API error --- www/teams/inner.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/www/teams/inner.js b/www/teams/inner.js index 05f823e2d..070d11825 100644 --- a/www/teams/inner.js +++ b/www/teams/inner.js @@ -1470,8 +1470,8 @@ define([ UIElements.reconnectAlert(); }; - sframeChan.on('EV_DRIVE_LOG', function (data) { - UI.log(data.msg); + sframeChan.on('EV_DRIVE_LOG', function (msg) { + UI.log(msg); }); sframeChan.on('EV_NETWORK_DISCONNECT', function (teamId) { onDisconnect(teamId); From e998c21691aa302ce9d0523f638d54403c43168e Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 5 Nov 2020 16:19:05 +0100 Subject: [PATCH 08/51] Invalidate corrupted caches --- www/common/cryptpad-common.js | 4 +++ www/common/outer/async-store.js | 10 +++++- www/common/outer/cache-store.js | 37 ++++++++++++--------- www/common/outer/store-rpc.js | 1 + www/common/sframe-app-framework.js | 23 ++++++++----- www/common/sframe-chainpad-netflux-inner.js | 6 ---- www/common/sframe-chainpad-netflux-outer.js | 2 -- www/common/sframe-common-outer.js | 9 ++++- 8 files changed, 58 insertions(+), 34 deletions(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 82ea2991f..34272dcfa 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -1006,6 +1006,10 @@ define([ postMessage("GIVE_PAD_ACCESS", data, cb); }; + common.onCorruptedCache = function (channel) { + postMessage("CORRUPTED_CACHE", channel); + }; + common.setPadMetadata = function (data, cb) { postMessage('SET_PAD_METADATA', data, cb); }; diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 7066d3921..56e1cadfd 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -1607,7 +1607,7 @@ define([ if (padData && padData.validateKey && store.messenger) { store.messenger.storeValidateKey(data.channel, padData.validateKey); } - postMessage(clientId, "PAD_READY"); + postMessage(clientId, "PAD_READY", pad.noCache); }, onMessage: function (m, user, validateKey, isCp, hash) { channel.lastHash = hash; @@ -1738,6 +1738,14 @@ define([ channel.sendMessage(msg, clientId, cb); }; + Store.corruptedCache = function (clientId, channel) { + var chan = channels[channel]; + if (!chan || !chan.cpNf) { return; } + Cache.clearChannel(channel); + if (!chan.cpNf.resetCache) { return; } + chan.cpNf.resetCache(); + }; + // Unpin and pin the new channel in all team when changing a pad password Store.changePadPasswordPin = function (clientId, data, cb) { var oldChannel = data.oldChannel; diff --git a/www/common/outer/cache-store.js b/www/common/outer/cache-store.js index aed480860..687259ddd 100644 --- a/www/common/outer/cache-store.js +++ b/www/common/outer/cache-store.js @@ -1,7 +1,7 @@ define([ - // XXX Load util and use mkAsync + '/common/common-util.js', '/bower_components/localforage/dist/localforage.min.js', -], function (localForage) { +], function (Util, localForage) { var S = {}; var cache = localForage.createInstance({ @@ -11,43 +11,50 @@ define([ // id: channel ID or blob ID // returns array of messages S.getChannelCache = function (id, cb) { + cb = Util.once(Util.mkAsync(cb || function () {})); cache.getItem(id, function (err, obj) { if (err || !obj || !Array.isArray(obj.c)) { return void cb(err || 'EINVAL'); } cb(null, obj); + obj.t = +new Date(); + cache.setItem(id, obj); }); }; + // Keep the last two checkpoint + any checkpoint that may exist in the last 100 messages + // FIXME: duplicate system with sliceCpIndex from lib/hk-util.js var checkCheckpoints = function (array) { if (!Array.isArray(array)) { return; } - var cp = 0; - // XXX check sliceCpIndex from hk-util: use the same logic for forks - for (var i = array.length - 1; i >= 0; i--) { - if (array[i].isCheckpoint) { cp++; } - if (cp === 2) { - array.splice(0, i); - break; - } + // Keep the last 100 messages + if (array.length > 100) { + array.splice(0, array.length - 100); } + // Remove every message before the first checkpoint + var firstCpIdx; + array.some(function (el, i) { + if (!el.isCheckpoint) { return; } + firstCpIdx = i; + return true; + }); + array.splice(0, firstCpIdx); }; S.storeCache = function (id, validateKey, val, cb) { - cb = cb || function () {}; + cb = Util.once(Util.mkAsync(cb || function () {})); if (!Array.isArray(val) || !validateKey) { return void cb('EINVAL'); } checkCheckpoints(val); cache.setItem(id, { k: validateKey, - c: val - // XXX add "time" here +new Date() ==> we want to store the time when it was updated in our indexeddb in case we want to remove inactive entries from indexeddb later - // XXX we would also need to update the time when we "getChannelCache" + c: val, + t: (+new Date()) // 't' represent the "lastAccess" of this cache (get or set) }, function (err) { cb(err); }); }; S.clearChannel = function (id, cb) { - cb = cb || function () {}; + cb = Util.once(Util.mkAsync(cb || function () {})); cache.removeItem(id, cb); }; diff --git a/www/common/outer/store-rpc.js b/www/common/outer/store-rpc.js index 6ac3e52b0..767448284 100644 --- a/www/common/outer/store-rpc.js +++ b/www/common/outer/store-rpc.js @@ -88,6 +88,7 @@ define([ CHANGE_PAD_PASSWORD_PIN: Store.changePadPasswordPin, GET_LAST_HASH: Store.getLastHash, GET_SNAPSHOT: Store.getSnapshot, + CORRUPTED_CACHE: Store.corruptedCache, // Drive DRIVE_USEROBJECT: Store.userObjectCommand, // Settings, diff --git a/www/common/sframe-app-framework.js b/www/common/sframe-app-framework.js index 81ab18d45..59dd51520 100644 --- a/www/common/sframe-app-framework.js +++ b/www/common/sframe-app-framework.js @@ -467,6 +467,10 @@ define([ }); }; + var onCorruptedCache = function (cb) { + var sframeChan = common.getSframeChannel(); + sframeChan.event("Q_CORRUPTED_CACHE", cb); + }; var onCacheReady = function () { stateChange(STATE.DISCONNECTED); toolbar.offline(true); @@ -475,23 +479,19 @@ define([ // Check if we have a new chainpad instance toolbar.resetChainpad(cpNfInner.chainpad); } -console.log(newContentStr); - // Invalid cache? abort - // XXX tell outer/worker to invalidate cache - if (newContentStr === '') { return; } + // Invalid cache + if (newContentStr === '') { return void onCorruptedCache(); } var privateDat = cpNfInner.metadataMgr.getPrivateData(); var type = privateDat.app; var newContent = JSON.parse(newContentStr); var metadata = extractMetadata(newContent); -console.log('OKOK'); // Make sure we're using the correct app for this cache if (metadata && typeof(metadata.type) !== 'undefined' && metadata.type !== type) { - console.error('return'); - return; + return void onCorruptedCache(); } cpNfInner.metadataMgr.updateMetadata(metadata); @@ -504,7 +504,7 @@ console.log('OKOK'); }; var onReady = function () { toolbar.offline(false); - console.error('READY'); + var newContentStr = cpNfInner.chainpad.getUserDoc(); if (state === STATE.DELETED) { return; } @@ -545,7 +545,12 @@ console.log('OKOK'); console.log("Either this is an empty document which has not been touched"); console.log("Or else something is terribly wrong, reloading."); Feedback.send("NON_EMPTY_NEWDOC"); - setTimeout(function () { common.gotoURL(); }, 1000); + // The cache may be wrong, empty it and reload after. + waitFor.abort(); + UI.errorLoadingScreen("MAYBE CORRUPTED CACHE... RELOADING"); // XXX + onCorruptedCache(function () { + setTimeout(function () { common.gotoURL(); }, 1000); + }); return; } console.log('updating title'); diff --git a/www/common/sframe-chainpad-netflux-inner.js b/www/common/sframe-chainpad-netflux-inner.js index 57689b7ea..e70a5874e 100644 --- a/www/common/sframe-chainpad-netflux-inner.js +++ b/www/common/sframe-chainpad-netflux-inner.js @@ -94,13 +94,8 @@ define([ evInfiniteSpinner.fire(); }, 2000); - sframeChan.on('EV_RT_CACHE', function (isPermanent) { - // XXX - }); sframeChan.on('EV_RT_CACHE_READY', function (isPermanent) { - // XXX onCacheReady({realtime: chainpad}); - console.error('PEWPEWPEW'); }); sframeChan.on('EV_RT_DISCONNECT', function (isPermanent) { isReady = false; @@ -143,7 +138,6 @@ define([ evConnected.fire(); }); sframeChan.on('Q_RT_MESSAGE', function (content, cb) { - console.log(content); if (isReady) { onLocal(true); // should be onBeforeMessage } diff --git a/www/common/sframe-chainpad-netflux-outer.js b/www/common/sframe-chainpad-netflux-outer.js index 056b9cd28..c19299da2 100644 --- a/www/common/sframe-chainpad-netflux-outer.js +++ b/www/common/sframe-chainpad-netflux-outer.js @@ -89,7 +89,6 @@ define([], function () { validateKey = msgObj.validateKey; } var message = msgIn(msgObj.user, msgObj.msg); - console.log(message); if (!message) { return; } lastTime = msgObj.time; @@ -127,7 +126,6 @@ define([], function () { }); padRpc.onCacheReadyEvent.reg(function () { - console.log('ONCACHEREADY'); sframeChan.event('EV_RT_CACHE_READY'); }); diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index b7ce64d4d..8fd37db4b 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -53,12 +53,13 @@ define([ '/common/common-constants.js', '/common/common-feedback.js', '/common/outer/local-store.js', + '/common/outer/cache-store.js', '/customize/application_config.js', '/common/test.js', '/common/userObject.js', ], waitFor(function (_CpNfOuter, _Cryptpad, _Crypto, _Cryptget, _SFrameChannel, _SecureIframe, _Messaging, _Notifier, _Hash, _Util, _Realtime, - _Constants, _Feedback, _LocalStore, _AppConfig, _Test, _UserObject) { + _Constants, _Feedback, _LocalStore, _Cache, _AppConfig, _Test, _UserObject) { CpNfOuter = _CpNfOuter; Cryptpad = _Cryptpad; Crypto = Utils.Crypto = _Crypto; @@ -73,6 +74,7 @@ define([ Utils.Constants = _Constants; Utils.Feedback = _Feedback; Utils.LocalStore = _LocalStore; + Utils.Cache = _Cache; Utils.UserObject = _UserObject; AppConfig = _AppConfig; Test = _Test; @@ -1635,6 +1637,11 @@ define([ }); }; + sframeChan.on('Q_CORRUPTED_CACHE', function (cb) { + Utils.Cache.clearChannel(secret.channel, cb); + Cryptpad.onCorruptedCache(secret.channel); + }); + sframeChan.on('Q_CREATE_PAD', function (data, cb) { if (!isNewFile || rtStarted) { return; } // Create a new hash From e37cf4b59cbdefc3009f8e82062ae4d7defef3a1 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 5 Nov 2020 16:50:20 +0100 Subject: [PATCH 09/51] lint compliance --- www/common/outer/async-store.js | 2 +- www/common/outer/cache-store.js | 4 ++-- www/common/sframe-app-framework.js | 2 +- www/common/sframe-chainpad-netflux-inner.js | 2 +- www/common/sframe-common-outer.js | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 56e1cadfd..687326139 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -1598,7 +1598,7 @@ define([ onCacheStart: function () { postMessage(clientId, "PAD_CACHE"); }, - onCacheReady: function (info) { + onCacheReady: function () { postMessage(clientId, "PAD_CACHE_READY"); }, onReady: function (pad) { diff --git a/www/common/outer/cache-store.js b/www/common/outer/cache-store.js index 687259ddd..56c78a5be 100644 --- a/www/common/outer/cache-store.js +++ b/www/common/outer/cache-store.js @@ -53,8 +53,8 @@ define([ }); }; - S.clearChannel = function (id, cb) { - cb = Util.once(Util.mkAsync(cb || function () {})); + S.clearChannel = function (id, _cb) { + cb = Util.once(Util.mkAsync(_cb || function () {})); cache.removeItem(id, cb); }; diff --git a/www/common/sframe-app-framework.js b/www/common/sframe-app-framework.js index 59dd51520..6353fb2f8 100644 --- a/www/common/sframe-app-framework.js +++ b/www/common/sframe-app-framework.js @@ -497,7 +497,7 @@ define([ cpNfInner.metadataMgr.updateMetadata(metadata); newContent = normalize(newContent); if (!unsyncMode) { - contentUpdate(newContent, function () { return function () {}}); + contentUpdate(newContent, function () { return function () {}; }); } UI.removeLoadingScreen(emitResize); diff --git a/www/common/sframe-chainpad-netflux-inner.js b/www/common/sframe-chainpad-netflux-inner.js index e70a5874e..1bdcc260f 100644 --- a/www/common/sframe-chainpad-netflux-inner.js +++ b/www/common/sframe-chainpad-netflux-inner.js @@ -94,7 +94,7 @@ define([ evInfiniteSpinner.fire(); }, 2000); - sframeChan.on('EV_RT_CACHE_READY', function (isPermanent) { + sframeChan.on('EV_RT_CACHE_READY', function () { onCacheReady({realtime: chainpad}); }); sframeChan.on('EV_RT_DISCONNECT', function (isPermanent) { diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 8fd37db4b..4c448e822 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -1637,7 +1637,7 @@ define([ }); }; - sframeChan.on('Q_CORRUPTED_CACHE', function (cb) { + sframeChan.on('Q_CORRUPTED_CACHE', function (data, cb) { Utils.Cache.clearChannel(secret.channel, cb); Cryptpad.onCorruptedCache(secret.channel); }); From 517492f7d36cf22d3fef158045f15dee87781e4e Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 5 Nov 2020 17:30:35 +0100 Subject: [PATCH 10/51] Fix loading errors --- customize.dist/loading.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/customize.dist/loading.js b/customize.dist/loading.js index 733b3fff0..344c61942 100644 --- a/customize.dist/loading.js +++ b/customize.dist/loading.js @@ -317,9 +317,12 @@ 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); + var el1 = document.querySelector('.cp-loading-spinner-container'); + if (el1) { el1.style.display = 'none'; } + var el2 = document.querySelector('.cp-loading-progress-list'); + if (el2) { el2.innerHTML = makeList(data); } + var el3 = document.querySelector('.cp-loading-progress-container'); + if (el3) { el3.innerHTML = makeBar(data); } } catch (e) { console.error(e); } }; window.CryptPad_updateLoadingProgress = updateLoadingProgress; From 5946b10d0bd97488544ceb69c5b1782e2ed879a6 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 6 Nov 2020 15:00:58 +0100 Subject: [PATCH 11/51] No page reload when the cache is corrupted onReady --- lib/hk-util.js | 6 ++++-- www/common/outer/cache-store.js | 4 +++- www/common/sframe-app-framework.js | 21 +++++++++++++-------- www/common/sframe-common-outer.js | 3 +-- 4 files changed, 21 insertions(+), 13 deletions(-) diff --git a/lib/hk-util.js b/lib/hk-util.js index 4fa2140f9..20e0ce5d4 100644 --- a/lib/hk-util.js +++ b/lib/hk-util.js @@ -440,9 +440,11 @@ const getHistoryOffset = (Env, channelName, lastKnownHash, _cb) => { // If our lastKnownHash is older than the 2nd to last checkpoint, // only send the last 2 checkpoints and ignore "lkh" - if (lkh && index.cpIndex.length >= 2 && lkh < index.cpIndex[0].offset) { + // XXX XXX this is probably wrong! ChainPad may not accept checkpoints that are not connected to root + // XXX We probably need to send an EUNKNOWN here so that the client can recreate a new chainpad + /*if (lkh && index.cpIndex.length >= 2 && lkh < index.cpIndex[0].offset) { return void cb(null, index.cpIndex[0].offset); - } + }*/ // Otherwise use our lastKnownHash cb(null, lkh); diff --git a/www/common/outer/cache-store.js b/www/common/outer/cache-store.js index 56c78a5be..ff2a40e93 100644 --- a/www/common/outer/cache-store.js +++ b/www/common/outer/cache-store.js @@ -55,7 +55,9 @@ define([ S.clearChannel = function (id, _cb) { cb = Util.once(Util.mkAsync(_cb || function () {})); - cache.removeItem(id, cb); + cache.removeItem(id, function () { + cb(); + }); }; S.clear = function () { diff --git a/www/common/sframe-app-framework.js b/www/common/sframe-app-framework.js index 6353fb2f8..50565cc0b 100644 --- a/www/common/sframe-app-framework.js +++ b/www/common/sframe-app-framework.js @@ -467,9 +467,14 @@ define([ }); }; - var onCorruptedCache = function (cb) { - var sframeChan = common.getSframeChannel(); - sframeChan.event("Q_CORRUPTED_CACHE", cb); + var noCache = false; // Prevent reload loops + var onCorruptedCache = function () { + if (noCache) { + // XXX translation key + return UI.errorLoadingScreen("Reload loop: empty chainpad for a non-empty channel"); + } + noCache = true; + sframeChan.event("Q_CORRUPTED_CACHE"); }; var onCacheReady = function () { stateChange(STATE.DISCONNECTED); @@ -547,17 +552,17 @@ define([ Feedback.send("NON_EMPTY_NEWDOC"); // The cache may be wrong, empty it and reload after. waitFor.abort(); - UI.errorLoadingScreen("MAYBE CORRUPTED CACHE... RELOADING"); // XXX - onCorruptedCache(function () { - setTimeout(function () { common.gotoURL(); }, 1000); - }); + onCorruptedCache(); return; } - console.log('updating title'); title.updateTitle(title.defaultTitle); evOnDefaultContentNeeded.fire(); } }).nThen(function () { + // We have a valid chainpad, reenable cache fix in case with reconnect with + // a corrupted cache + noCache = false; + stateChange(STATE.READY); firstConnection = false; diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 4c448e822..a53171150 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -1637,8 +1637,7 @@ define([ }); }; - sframeChan.on('Q_CORRUPTED_CACHE', function (data, cb) { - Utils.Cache.clearChannel(secret.channel, cb); + sframeChan.on('EV_CORRUPTED_CACHE', function () { Cryptpad.onCorruptedCache(secret.channel); }); From 11abd6170b7affc1b3217e27a1df31e1ccf17e24 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 6 Nov 2020 15:30:56 +0100 Subject: [PATCH 12/51] Use indexeddb cache for blobs --- www/common/media-tag.js | 70 +++++++++++++++++++++++++-------- www/common/outer/cache-store.js | 22 +++++++++++ 2 files changed, 75 insertions(+), 17 deletions(-) diff --git a/www/common/media-tag.js b/www/common/media-tag.js index 27683c54e..1d7cd0c61 100644 --- a/www/common/media-tag.js +++ b/www/common/media-tag.js @@ -1,8 +1,5 @@ -(function(name, definition) { - if (typeof module !== 'undefined') { module.exports = definition(); } - else if (typeof define === 'function' && typeof define.amd === 'object') { define(definition); } - else { this[name] = definition(); } -}('MediaTag', function() { +(function (window) { +var factory = function (Cache) { var cache; var cypherChunkLength = 131088; @@ -133,20 +130,44 @@ cb = function () {}; }; - var xhr = new XMLHttpRequest(); - xhr.open('GET', src, true); - xhr.responseType = 'arraybuffer'; - - xhr.onerror = function () { return void cb("XHR_ERROR"); }; - xhr.onload = function () { - // Error? - if (/^4/.test('' + this.status)) { return void cb("XHR_ERROR " + this.status); } + var _src = src.replace(/(\/)*$/, ''); // Remove trailing slashes + var idx = _src.lastIndexOf('/'); + var cacheKey = _src.slice(idx+1); + if (!/^[a-f0-9]{48}$/.test(cacheKey)) { cacheKey = undefined; } + + var fetch = function () { + var xhr = new XMLHttpRequest(); + xhr.open('GET', src, true); + xhr.responseType = 'arraybuffer'; + + xhr.onerror = function () { return void cb("XHR_ERROR"); }; + xhr.onload = function () { + // Error? + if (/^4/.test('' + this.status)) { return void cb("XHR_ERROR " + this.status); } + + var arrayBuffer = xhr.response; + if (arrayBuffer) { + var u8 = new Uint8Array(arrayBuffer); + if (cacheKey) { + return void Cache.setBlobCache(cacheKey, u8, function (err) { + cb(null, u8); + }); + } + cb(null, u8); + } + }; - var arrayBuffer = xhr.response; - if (arrayBuffer) { cb(null, new Uint8Array(arrayBuffer)); } + xhr.send(null); }; - xhr.send(null); + if (!cacheKey) { return void fetch(); } + + Cache.getBlobCache(cacheKey, function (err, u8) { + if (err || !u8) { return void fetch(); } + console.error('using cache', cacheKey); + cb(null, u8); + }); + }; // Decryption tools @@ -469,4 +490,19 @@ }; return init; -})); +}; + + if (typeof(module) !== 'undefined' && module.exports) { + module.exports = factory( + require("./outer/cache-store.js") + ); + } else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { + define([ + '/common/outer/cache-store.js', + ], function (Cache) { + return factory(Cache); + }); + } else { + // unsupported initialization + } +}(typeof(window) !== 'undefined'? window : {})); diff --git a/www/common/outer/cache-store.js b/www/common/outer/cache-store.js index ff2a40e93..fe3b541b2 100644 --- a/www/common/outer/cache-store.js +++ b/www/common/outer/cache-store.js @@ -8,6 +8,28 @@ define([ name: "cp_cache" }); + S.getBlobCache = function (id, cb) { + cb = Util.once(Util.mkAsync(cb || function () {})); + cache.getItem(id, function (err, obj) { + if (err || !obj || !obj.c) { + return void cb(err || 'EINVAL'); + } + cb(null, obj.c); + obj.t = +new Date(); + cache.setItem(id, obj); + }); + }; + S.setBlobCache = function (id, u8, cb) { + cb = Util.once(Util.mkAsync(cb || function () {})); + if (!u8) { return void cb('EINVAL'); } + cache.setItem(id, { + c: u8, + t: (+new Date()) // 't' represent the "lastAccess" of this cache (get or set) + }, function (err) { + cb(err); + }); + }; + // id: channel ID or blob ID // returns array of messages S.getChannelCache = function (id, cb) { From f62f44771139c43018eb715a84ad2eaad28f7e87 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 19 Nov 2020 17:58:40 +0100 Subject: [PATCH 13/51] 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 | 282 ++++++++++++++++++++++------------------ 4 files changed, 218 insertions(+), 135 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); - - 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/'); - }); + (function () { + Messages.download = "Download"; // XXX + Messages.decrypt = "Decrypt"; // XXX + + 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 + ]); + + var hexFileName = secret.channel; + var src = fileHost + Hash.getBlobPathFromHex(hexFileName); + var key = secret.keys && secret.keys.cryptKey; + var cryptKey = Nacl.util.encodeBase64(key); + + 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); } - return void console.error(e); - } - - // 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); + // 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 rightsideDisplayed = false; + // Save to the drive or update the acces time + var title = document.title = metadata.name; + Title.updateTitle(title || Title.defaultTitle); - MediaTag($mt[0]).on('complete', function (decrypted) { - $dlview.show(); - $dlform.hide(); - var $dlButton = $dlview.find('media-tag button'); - if (ev) { $dlButton.click(); } + var owners = metadata.owners; + if (owners) { + common.setPadAttribute('owners', owners); + } + if (metadata.type) { + common.setPadAttribute('fileType', metadata.type); + } - if (!rightsideDisplayed) { - toolbar.$drawer - .append(common.createButton('export', true, {}, function () { - saveAs(decrypted.content, decrypted.metadata.name); - })); - rightsideDisplayed = true; - } + 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 + }); - // 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); }); + }; - if ($another_iframe.length) { - $another_iframe.load(function () { - cb(); + // 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); } }); - } else { - cb(); + }; + 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); } - }).on('progress', function (data) { - var p = data.progress +'%'; - $progress.width(p); - }).on('error', function (err) { - console.error(err); + var size = Util.bytesToMegabytes(data); + return void todoBigFile(size); }); - }; - - 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) { - 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 14/51] 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 15/51] 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 16/51] 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)); }); } }); From d199da95634fd5bc6496b4dd05662df66e5cbdd3 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 24 Nov 2020 16:49:12 +0100 Subject: [PATCH 17/51] lint compliance --- www/common/media-tag.js | 2 +- www/common/outer/cache-store.js | 4 ++-- www/common/sframe-app-framework.js | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/www/common/media-tag.js b/www/common/media-tag.js index 04e48d7f5..558ac9a19 100644 --- a/www/common/media-tag.js +++ b/www/common/media-tag.js @@ -246,7 +246,7 @@ var factory = function (Cache) { if (arrayBuffer) { var u8 = new Uint8Array(arrayBuffer); if (cacheKey) { - return void Cache.setBlobCache(cacheKey, u8, function (err) { + return void Cache.setBlobCache(cacheKey, u8, function () { cb(null, u8); }); } diff --git a/www/common/outer/cache-store.js b/www/common/outer/cache-store.js index fe3b541b2..4d5e30ed2 100644 --- a/www/common/outer/cache-store.js +++ b/www/common/outer/cache-store.js @@ -75,8 +75,8 @@ define([ }); }; - S.clearChannel = function (id, _cb) { - cb = Util.once(Util.mkAsync(_cb || function () {})); + S.clearChannel = function (id, cb) { + cb = Util.once(Util.mkAsync(cb || function () {})); cache.removeItem(id, function () { cb(); }); diff --git a/www/common/sframe-app-framework.js b/www/common/sframe-app-framework.js index f2a5996cf..d3a4bc174 100644 --- a/www/common/sframe-app-framework.js +++ b/www/common/sframe-app-framework.js @@ -474,7 +474,8 @@ define([ return UI.errorLoadingScreen("Reload loop: empty chainpad for a non-empty channel"); } noCache = true; - sframeChan.event("Q_CORRUPTED_CACHE"); + var sframeChan = common.getSframeChannel(); + sframeChan.event("EV_CORRUPTED_CACHE"); }; var onCacheReady = function () { stateChange(STATE.DISCONNECTED); From 9bb465bbcee1c49545881aeb26f634aeb6e5d600 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 24 Nov 2020 17:00:48 +0100 Subject: [PATCH 18/51] Fix style conflict --- customize.dist/loading.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/customize.dist/loading.js b/customize.dist/loading.js index b0d66981c..5b916a0e5 100644 --- a/customize.dist/loading.js +++ b/customize.dist/loading.js @@ -241,7 +241,7 @@ p.cp-password-info{ animation-timing-function: cubic-bezier(.6,0.15,0.4,0.85); } -button.primary{ +button:not(.btn).primary{ border: 1px solid #4591c4; padding: 8px 12px; text-transform: uppercase; @@ -250,7 +250,7 @@ button.primary{ font-weight: bold; } -button.primary:hover{ +button:not(.btn).primary:hover{ background-color: rgb(52, 118, 162); } From d027005c878b95514f54c27352144c86d6765e52 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 24 Nov 2020 17:49:45 +0100 Subject: [PATCH 19/51] Automatically download cached media-tags and improve duplicates --- www/common/media-tag.js | 107 +++++++++++++++++++++++----------------- 1 file changed, 62 insertions(+), 45 deletions(-) diff --git a/www/common/media-tag.js b/www/common/media-tag.js index 558ac9a19..bab444df3 100644 --- a/www/common/media-tag.js +++ b/www/common/media-tag.js @@ -1,5 +1,6 @@ (function (window) { var factory = function (Cache) { + var Promise = window.Promise; var cache; var cypherChunkLength = 131088; @@ -190,24 +191,43 @@ var factory = function (Cache) { mediaObject.tag.appendChild(btn); }; + var getCacheKey = function (src) { + var _src = src.replace(/(\/)*$/, ''); // Remove trailing slashes + var idx = _src.lastIndexOf('/'); + var cacheKey = _src.slice(idx+1); + if (!/^[a-f0-9]{48}$/.test(cacheKey)) { cacheKey = undefined; } + return cacheKey; + }; + 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); } + + var cacheKey = getCacheKey(src); + + var check = function () { + 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(); }; - xhr.send(); + + if (!cacheKey) { return void check(); } + + Cache.getBlobCache(cacheKey, function (err, u8) { + if (err || !u8) { return void check(); } + cb(null, 0); + }); }; // Download a blob from href @@ -217,10 +237,7 @@ var factory = function (Cache) { cb = function () {}; }; - var _src = src.replace(/(\/)*$/, ''); // Remove trailing slashes - var idx = _src.lastIndexOf('/'); - var cacheKey = _src.slice(idx+1); - if (!/^[a-f0-9]{48}$/.test(cacheKey)) { cacheKey = undefined; } + var cacheKey = getCacheKey(src); var fetch = function () { var xhr = new XMLHttpRequest(); @@ -547,39 +564,41 @@ var factory = function (Cache) { }); }; - // 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; - } + var error = function (err) { + mediaObject.tag.innerHTML = ''; + emit('error', err); + }; 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[uid] = cache[uid] || new Promise(function (resolve, reject) { + download(src, function (err, u8Encrypted) { + if (err) { + return void reject(err); } - // Cache and display the decrypted blob - cache[uid] = u8Decrypted; - end(u8Decrypted); + // Decrypt the blob + decrypt(u8Encrypted, strKey, function (errDecryption, u8Decrypted) { + if (errDecryption) { + return void reject(errDecryption); + } + // Cache and display the decrypted blob + cache[uid] = u8Decrypted; + resolve(u8Decrypted); + }, function (progress) { + emit('progress', { + progress: 50+0.5*progress + }); + }); }, function (progress) { emit('progress', { - progress: 50+0.5*progress + progress: 0.5*progress }); }); - }, function (progress) { - emit('progress', { - progress: 0.5*progress - }); + }); + cache[uid].then(function (u8) { + end(u8); + }, function (err) { + error(err); }); }; @@ -588,10 +607,7 @@ var factory = function (Cache) { 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); + return void error(err); } if (!size || size < maxSize) { return void dl(); } var sizeMb = Math.round(10 * size / 1024 / 1024) / 10; @@ -618,6 +634,7 @@ var factory = function (Cache) { } else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { define([ '/common/outer/cache-store.js', + '/bower_components/es6-promise/es6-promise.min.js' ], function (Cache) { return factory(Cache); }); From 418a170dc9e1cfdce0f237ee94e4aad6d41ca011 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 25 Nov 2020 12:13:11 +0100 Subject: [PATCH 20/51] Fix type error --- www/common/media-tag.js | 1 - 1 file changed, 1 deletion(-) diff --git a/www/common/media-tag.js b/www/common/media-tag.js index bab444df3..e86fd6071 100644 --- a/www/common/media-tag.js +++ b/www/common/media-tag.js @@ -582,7 +582,6 @@ var factory = function (Cache) { return void reject(errDecryption); } // Cache and display the decrypted blob - cache[uid] = u8Decrypted; resolve(u8Decrypted); }, function (progress) { emit('progress', { From 3f88e29f30b7d168e0afea1ab7564d9a99987580 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 25 Nov 2020 14:53:48 +0100 Subject: [PATCH 21/51] Add progress when creating a pad from a file --- customize.dist/loading.js | 11 ++++++++--- www/common/cryptpad-common.js | 10 +++++++--- www/common/sframe-common-outer.js | 7 ++++++- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/customize.dist/loading.js b/customize.dist/loading.js index 5b916a0e5..a335a8903 100644 --- a/customize.dist/loading.js +++ b/customize.dist/loading.js @@ -279,7 +279,7 @@ button:not(.btn).primary:hover{ var built = false; var types = ['less', 'drive', 'migrate', 'sf', 'team', 'pad', 'end']; - var current; + var current, progress; var makeList = function (data) { var c = types.indexOf(data.type); current = c; @@ -295,7 +295,7 @@ button:not(.btn).primary:hover{ }; var list = '
    '; types.forEach(function (el, i) { - if (i >= 6) { return; } + if (el === "end") { return; } list += getLi(i); }); list += '
'; @@ -303,7 +303,7 @@ button:not(.btn).primary:hover{ }; var makeBar = function (data) { var c = types.indexOf(data.type); - var l = types.length; + var l = types.length - 1; // don't count "end" as a type var progress = Math.min(data.progress, 100); var p = (progress / l) + (100 * c / l); var bar = '
'+ @@ -315,8 +315,13 @@ button:not(.btn).primary:hover{ var hasErrored = false; var updateLoadingProgress = function (data) { if (!built || !data) { return; } + + // Make sure progress doesn't go backward var c = types.indexOf(data.type); if (c < current) { return console.error(data); } + if (c === current && progress > data.progress) { return console.error(data); } + progress = data.progress; + try { document.querySelector('.cp-loading-spinner-container').style.display = 'none'; document.querySelector('.cp-loading-progress-list').innerHTML = makeList(data); diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 9f88e85a9..714598ca4 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -701,7 +701,7 @@ define([ }); }; - common.useFile = function (Crypt, cb, optsPut) { + common.useFile = function (Crypt, cb, optsPut, onProgress) { var fileHost = Config.fileHost || window.location.origin; var data = common.fromFileData; var parsed = Hash.parsePadUrl(data.href); @@ -758,7 +758,9 @@ define([ return void cb(err); } u8 = _u8; - })); + }), function (progress) { + onProgress(progress * 50); + }); }).nThen(function (waitFor) { require(["/file/file-crypto.js"], waitFor(function (FileCrypto) { FileCrypto.decrypt(u8, key, waitFor(function (err, _res) { @@ -767,7 +769,9 @@ define([ return void cb(err); } res = _res; - })); + }), function (progress) { + onProgress(50 + progress * 50); + }); })); }).nThen(function (waitFor) { var ext = Util.parseFilename(data.title).ext; diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index d17924cf0..768ca4db2 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -1802,7 +1802,12 @@ define([ } startRealtime(); cb(); - }, cryptputCfg); + }, cryptputCfg, function (progress) { + sframeChan.event('EV_LOADING_INFO', { + type: 'pad', + progress: progress + }); + }); return; } // Start realtime outside the iframe and callback From 4027e48fd20ab09dd8520606e7f451a7a17b0b20 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 25 Nov 2020 15:51:22 +0100 Subject: [PATCH 22/51] Open or share a mediatag from a pad --- www/common/inner/common-mediatag.js | 26 ++++++++++++++++++++++++++ www/common/sframe-common.js | 23 ++++++++++++++++++++--- www/secureiframe/inner.js | 12 ++++++------ 3 files changed, 52 insertions(+), 9 deletions(-) diff --git a/www/common/inner/common-mediatag.js b/www/common/inner/common-mediatag.js index 3d28d126a..e60f734d0 100644 --- a/www/common/inner/common-mediatag.js +++ b/www/common/inner/common-mediatag.js @@ -362,6 +362,9 @@ define([ }); }; + Messages.pad_mediatagShare = "Share file"; // XXX + Messages.pad_mediatagOpen = "Open file"; // XXX + var mediatagContextMenu; MT.importMediaTagMenu = function (common) { if (mediatagContextMenu) { return mediatagContextMenu; } @@ -377,6 +380,14 @@ define([ 'tabindex': '-1', 'data-icon': "fa-eye", }, Messages.pad_mediatagPreview)), + h('li.cp-svg', h('a.cp-app-code-context-openin.dropdown-item', { + 'tabindex': '-1', + 'data-icon': "fa-external-link", + }, Messages.pad_mediatagOpen)), + h('li.cp-svg', h('a.cp-app-code-context-share.dropdown-item', { + 'tabindex': '-1', + 'data-icon': "fa-shhare-alt", + }, Messages.pad_mediatagShare)), h('li', h('a.cp-app-code-context-saveindrive.dropdown-item', { 'tabindex': '-1', 'data-icon': "fa-cloud-upload", @@ -419,6 +430,21 @@ define([ else if ($this.hasClass("cp-app-code-context-open")) { $mt.trigger('preview'); } + else if ($this.hasClass("cp-app-code-context-openin")) { + var hash = common.getHashFromMediaTag($mt); + common.openURL(Hash.hashToHref(hash, 'file')); + } + else if ($this.hasClass("cp-app-code-context-share")) { + var data = { + file: true, + pathname: '/file/', + hashes: { + fileHash: common.getHashFromMediaTag($mt) + }, + title: Util.find($mt[0], ['_mediaObject', 'name']) || '' + }; + common.getSframeChannel().event('EV_SHARE_OPEN', data); + } }); return m; diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index 4198a649a..7a00516a5 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -142,7 +142,7 @@ define([ } return; }; - funcs.importMediaTag = function ($mt) { + var getMtData = function ($mt) { if (!$mt || !$mt.is('media-tag')) { return; } var chanStr = $mt.attr('src'); var keyStr = $mt.attr('data-crypto-key'); @@ -154,10 +154,27 @@ define([ var channel = src.replace(/\/blob\/[0-9a-f]{2}\//i, ''); // Get key var key = keyStr.replace(/cryptpad:/i, ''); + return { + channel: channel, + key: key + }; + }; + funcs.getHashFromMediaTag = function ($mt) { + var data = getMtData($mt); + if (!data) { return; } + return Hash.getFileHashFromKeys({ + version: 1, + channel: data.channel, + keys: { fileKeyStr: data.key } + }); + }; + funcs.importMediaTag = function ($mt) { + var data = getMtData($mt); + if (!data) { return; } var metadata = $mt[0]._mediaObject._blob.metadata; ctx.sframeChan.query('Q_IMPORT_MEDIATAG', { - channel: channel, - key: key, + channel: data.channel, + key: data.key, name: metadata.name, type: metadata.type, owners: metadata.owners diff --git a/www/secureiframe/inner.js b/www/secureiframe/inner.js index c20d53a80..4d5b8ee4f 100644 --- a/www/secureiframe/inner.js +++ b/www/secureiframe/inner.js @@ -52,10 +52,10 @@ define([ : Share.getShareModal; f(common, { origin: priv.origin, - pathname: priv.pathname, - password: priv.password, - isTemplate: priv.isTemplate, - hashes: priv.hashes, + pathname: data.pathname || priv.pathname, + password: data.hashes ? '' : priv.password, + isTemplate: data.hashes ? false : priv.isTemplate, + hashes: data.hashes || priv.hashes, common: common, title: data.title, versionHash: data.versionHash, @@ -64,8 +64,8 @@ define([ hideIframe(); }, fileData: { - hash: priv.hashes.fileHash, - password: priv.password + hash: (data.hashes && data.hashes.fileHash) || priv.hashes.fileHash, + password: data.hashes ? '' : priv.password } }, function (e, modal) { if (e) { console.error(e); } From 3de5ca75a6164e34adc6f85e23a141e2ab5f1a7b Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 25 Nov 2020 16:19:10 +0100 Subject: [PATCH 23/51] Add new mediatag options in rich text pads --- www/pad/inner.js | 24 ++++++++++++++++++++ www/pad/mediatag-plugin.js | 45 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/www/pad/inner.js b/www/pad/inner.js index 5139197e0..2e5b2ad91 100644 --- a/www/pad/inner.js +++ b/www/pad/inner.js @@ -46,6 +46,7 @@ define([ '/common/test.js', '/bower_components/diff-dom/diffDOM.js', + '/bower_components/file-saver/FileSaver.min.js', 'css!/customize/src/print.css', 'css!/bower_components/bootstrap/dist/css/bootstrap.min.css', @@ -1085,6 +1086,9 @@ define([ border: Messages.pad_mediatagBorder, preview: Messages.pad_mediatagPreview, 'import': Messages.pad_mediatagImport, + download: Messages.download_mt_button, + share: Messages.pad_mediatagShare, + open: Messages.pad_mediatagOpen, options: Messages.pad_mediatagOptions }; Ckeditor._commentsTranslations = { @@ -1165,6 +1169,26 @@ define([ editor.plugins.mediatag.import = function($mt) { framework._.sfCommon.importMediaTag($mt); }; + editor.plugins.mediatag.download = function($mt) { + var media = Util.find($mt, [0, '_mediaObject']); + if (!(media && media._blob)) { return void console.error($mt); } + window.saveAs(media._blob.content, media.name); + }; + editor.plugins.mediatag.open = function($mt) { + var hash = framework._.sfCommon.getHashFromMediaTag($mt); + framework._.sfCommon.openURL(Hash.hashToHref(hash, 'file')); + }; + editor.plugins.mediatag.share = function($mt) { + var data = { + file: true, + pathname: '/file/', + hashes: { + fileHash: framework._.sfCommon.getHashFromMediaTag($mt) + }, + title: Util.find($mt[0], ['_mediaObject', 'name']) || '' + }; + framework._.sfCommon.getSframeChannel().event('EV_SHARE_OPEN', data); + }; Links.init(Ckeditor, editor); }).nThen(function() { // Move ckeditor parts to have a structure like the other apps diff --git a/www/pad/mediatag-plugin.js b/www/pad/mediatag-plugin.js index 46747b18a..22a732748 100644 --- a/www/pad/mediatag-plugin.js +++ b/www/pad/mediatag-plugin.js @@ -53,15 +53,57 @@ editor.plugins.mediatag.import($mt); } }); + editor.addCommand('downloadMT', { + exec: function (editor) { + var w = targetWidget; + targetWidget = undefined; + var $mt = $(w.$).find('media-tag'); + editor.plugins.mediatag.download($mt); + } + }); + editor.addCommand('openMT', { + exec: function (editor) { + var w = targetWidget; + targetWidget = undefined; + var $mt = $(w.$).find('media-tag'); + editor.plugins.mediatag.open($mt); + } + }); + editor.addCommand('shareMT', { + exec: function (editor) { + var w = targetWidget; + targetWidget = undefined; + var $mt = $(w.$).find('media-tag'); + editor.plugins.mediatag.share($mt); + } + }); if (editor.addMenuItems) { editor.addMenuGroup('mediatag'); + editor.addMenuItem('open', { + label: Messages.open, + icon: 'iframe', + command: 'openMT', + group: 'mediatag' + }); + editor.addMenuItem('share', { + label: Messages.share, + icon: 'link', + command: 'shareMT', + group: 'mediatag' + }); editor.addMenuItem('importMediatag', { label: Messages.import, icon: 'save', command: 'importMediatag', group: 'mediatag' }); + editor.addMenuItem('download', { + label: Messages.download, + icon: 'save', + command: 'downloadMT', + group: 'mediatag' + }); editor.addMenuItem('mediatag', { label: Messages.options, icon: 'image', @@ -76,6 +118,9 @@ targetWidget = element; return { mediatag: CKEDITOR.TRISTATE_OFF, + open: CKEDITOR.TRISTATE_OFF, + share: CKEDITOR.TRISTATE_OFF, + download: CKEDITOR.TRISTATE_OFF, importMediatag: CKEDITOR.TRISTATE_OFF, }; } From ff0a5700349e57ec53c90110ff287893eedd9da7 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 25 Nov 2020 16:36:48 +0100 Subject: [PATCH 24/51] Fix keybinding in upload modal --- www/common/common-interface.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/www/common/common-interface.js b/www/common/common-interface.js index 9fae121dc..3e9d848cd 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -65,12 +65,13 @@ define([ switch (e.which) { case 27: // cancel if (typeof(no) === 'function') { no(e); } + $(el || window).off('keydown', handler); break; case 13: // enter if (typeof(yes) === 'function') { yes(e); } + $(el || window).off('keydown', handler); break; } - $(el || window).off('keydown', handler); }; $(el || window).keydown(handler); From 76d527d0b23efc1044842d5da3e34ecb4cae8b0e Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 25 Nov 2020 16:40:47 +0100 Subject: [PATCH 25/51] Don't store support attachments into the drive --- www/support/ui.js | 1 + 1 file changed, 1 insertion(+) diff --git a/www/support/ui.js b/www/support/ui.js index 9b86d6f20..a58fdf170 100644 --- a/www/support/ui.js +++ b/www/support/ui.js @@ -397,6 +397,7 @@ define([ var fmConfig = { body: $('body'), + noStore: true, // Don't store attachments into our drive onUploaded: function (ev, data) { if (ev.callback) { ev.callback(data); From 41bf79232da74ef6a2c87c5e56cd55c8112dc348 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 25 Nov 2020 16:49:43 +0100 Subject: [PATCH 26/51] Add max image size in the upload error message in whiteboard #563 --- www/whiteboard/inner.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/www/whiteboard/inner.js b/www/whiteboard/inner.js index 44efd1421..bab1603dc 100644 --- a/www/whiteboard/inner.js +++ b/www/whiteboard/inner.js @@ -331,10 +331,11 @@ define([ APP.FM.handleFile(blob); }); }; + var MAX_IMAGE_SIZE = 1 * 1024 * 1024; // 1 MB + var maxSizeStr = Messages._getKey('formattedMB', [Util.bytesToMegabytes(MAX_IMAGE_SIZE)]); var addImageToCanvas = function (img) { - // 1 MB maximum - if (img.src && img.src.length > 1 * 1024 * 1024) { - UI.warn(Messages.upload_tooLargeBrief); + if (img.src && img.src.length > MAX_IMAGE_SIZE) { + UI.warn(Messages._getKey('upload_tooLargeBrief', [maxSizeStr])); // XXX update key return; } var w = img.width; @@ -356,8 +357,8 @@ define([ var file = e.target.files[0]; var reader = new FileReader(); // 1 MB maximum - if (file.size > 1 * 1024 * 1024) { - UI.warn(Messages.upload_tooLargeBrief); + if (file.size > MAX_IMAGE_SIZE) { + UI.warn(Messages._getKey('upload_tooLargeBrief', [maxSizeStr])); return; } reader.onload = function () { From 781261111bfa4278598ea8cc02dadb26ab5512f3 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 26 Nov 2020 12:49:40 +0100 Subject: [PATCH 27/51] Remove debugging code --- www/file/inner.js | 1 - 1 file changed, 1 deletion(-) diff --git a/www/file/inner.js b/www/file/inner.js index c29657672..137ca54b1 100644 --- a/www/file/inner.js +++ b/www/file/inner.js @@ -203,7 +203,6 @@ define([ cb(); } }).on('progress', function (data) { - if (data.progress > 75) { return; } var p = data.progress +'%'; $progress.width(p); $progressTxt.text(Math.floor(data.progress) + '%'); From 7448fa93df6c2ba9f4fffecb605cd675ddcabbf5 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 26 Nov 2020 16:27:12 +0100 Subject: [PATCH 28/51] Fix download from contextmenu when mediatag is not ready --- www/common/inner/common-mediatag.js | 3 +++ www/pad/inner.js | 2 ++ 2 files changed, 5 insertions(+) diff --git a/www/common/inner/common-mediatag.js b/www/common/inner/common-mediatag.js index e60f734d0..741529de0 100644 --- a/www/common/inner/common-mediatag.js +++ b/www/common/inner/common-mediatag.js @@ -364,6 +364,7 @@ define([ Messages.pad_mediatagShare = "Share file"; // XXX Messages.pad_mediatagOpen = "Open file"; // XXX + Messages.mediatag_notReady = "Not ready"; // XXX var mediatagContextMenu; MT.importMediaTagMenu = function (common) { @@ -424,6 +425,8 @@ define([ } else if ($this.hasClass("cp-app-code-context-download")) { var media = Util.find($mt, [0, '_mediaObject']); + if (!media) { return void console.error('no media'); } + if (!media.complete) { return void UI.warn(Messages.mediatag_notReady); } if (!(media && media._blob)) { return void console.error($mt); } window.saveAs(media._blob.content, media.name); } diff --git a/www/pad/inner.js b/www/pad/inner.js index 2e5b2ad91..8a99b6101 100644 --- a/www/pad/inner.js +++ b/www/pad/inner.js @@ -1171,6 +1171,8 @@ define([ }; editor.plugins.mediatag.download = function($mt) { var media = Util.find($mt, [0, '_mediaObject']); + if (!media) { return void console.error('no media'); } + if (!media.complete) { return void UI.warn(Messages.mediatag_notReady); } if (!(media && media._blob)) { return void console.error($mt); } window.saveAs(media._blob.content, media.name); }; From 5d6ebdfee6174b5f7670098205a3d6d427cd92af Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 26 Nov 2020 18:15:36 +0100 Subject: [PATCH 29/51] Make autodownload size for mediatags configurable --- .../src/less2/include/sidebar-layout.less | 2 +- www/common/inner/common-mediatag.js | 9 ++- www/common/media-tag.js | 3 +- www/common/sframe-common.js | 6 ++ www/file/inner.js | 3 - www/settings/app-settings.less | 4 ++ www/settings/inner.js | 56 ++++++++++++++++++- 7 files changed, 76 insertions(+), 7 deletions(-) diff --git a/customize.dist/src/less2/include/sidebar-layout.less b/customize.dist/src/less2/include/sidebar-layout.less index ace7350df..4273b0b9a 100644 --- a/customize.dist/src/less2/include/sidebar-layout.less +++ b/customize.dist/src/less2/include/sidebar-layout.less @@ -118,7 +118,7 @@ //border-radius: 0 0.25em 0.25em 0; //border: 1px solid #adadad; border-left: 0px; - height: @variables_input-height; + height: 40px; margin: 0 !important; } } diff --git a/www/common/inner/common-mediatag.js b/www/common/inner/common-mediatag.js index 741529de0..86900f5bb 100644 --- a/www/common/inner/common-mediatag.js +++ b/www/common/inner/common-mediatag.js @@ -17,11 +17,18 @@ define([ var Nacl = window.nacl; // Configure MediaTags to use our local viewer + // This file is loaded by sframe-common so the following config is used in all the inner apps if (MediaTag) { MediaTag.setDefaultConfig('pdf', { viewer: '/common/pdfjs/web/viewer.html' }); + Messages.mediatag_saveButton = "Save"; // XXX + MediaTag.setDefaultConfig('download', { + text: Messages.download_mt_button, + textDl: Messages.mediatag_saveButton + }); } + MT.MediaTag = MediaTag; // Cache of the avatars outer html (including ) var avatars = {}; @@ -68,7 +75,7 @@ define([ childList: true, characterData: false }); - MediaTag($tag[0]).on('error', function (data) { + MediaTag($tag[0], {force: true}).on('error', function (data) { console.error(data); }); }; diff --git a/www/common/media-tag.js b/www/common/media-tag.js index 4318ca8f6..56e0f6bc6 100644 --- a/www/common/media-tag.js +++ b/www/common/media-tag.js @@ -566,7 +566,8 @@ if (cfg.force) { dl(); return mediaObject; } - var maxSize = 5 * 1024 * 1024; + var maxSize = typeof(config.maxDownloadSize) === "number" ? config.maxDownloadSize + : (5 * 1024 * 1024); getFileSize(src, function (err, size) { if (err) { if (err === "XHR_ERROR 404") { diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index 7a00516a5..328bd70c8 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -809,6 +809,12 @@ define([ var privateData = ctx.metadataMgr.getPrivateData(); funcs.addShortcuts(window, Boolean(privateData.app)); + var mt = Util.find(privateData, ['settings', 'general', 'mediatag-size']); + if (MT.MediaTag && typeof(mt) === "number") { + var maxMtSize = mt === -1 ? Infinity : mt * 1024 * 1024; + MT.MediaTag.setDefaultConfig('maxDownloadSize', maxMtSize); + } + try { var feedback = privateData.feedbackAllowed; Feedback.init(feedback); diff --git a/www/file/inner.js b/www/file/inner.js index 137ca54b1..6cc129de5 100644 --- a/www/file/inner.js +++ b/www/file/inner.js @@ -39,9 +39,6 @@ define([ var Nacl = window.nacl; var APP = window.APP = {}; - MediaTag.setDefaultConfig('download', { - text: Messages.download_mt_button - }); var andThen = function (common) { var $appContainer = $('#cp-app-file-content'); diff --git a/www/settings/app-settings.less b/www/settings/app-settings.less index 1f0da1263..0ac3d10f2 100644 --- a/www/settings/app-settings.less +++ b/www/settings/app-settings.less @@ -74,6 +74,10 @@ margin-right: 100%; } } + & > .fa { + align-self: center; + margin-right: -16px; + } } .cp-settings-info-block { [type="text"] { diff --git a/www/settings/inner.js b/www/settings/inner.js index 8882f5861..9ff57aae7 100644 --- a/www/settings/inner.js +++ b/www/settings/inner.js @@ -51,7 +51,7 @@ define([ 'cp-settings-info-block', 'cp-settings-displayname', 'cp-settings-language-selector', - 'cp-settings-resettips', + 'cp-settings-mediatag-size', 'cp-settings-change-password', 'cp-settings-delete' ], @@ -62,6 +62,7 @@ define([ 'cp-settings-userfeedback', ], 'drive': [ + 'cp-settings-resettips', 'cp-settings-drive-duplicate', 'cp-settings-thumbnails', 'cp-settings-drive-backup', @@ -576,6 +577,59 @@ define([ cb(form); }, true); + Messages.settings_mediatagSizeTitle = "Autodownload size in MegaBytes (MB)"; // XXX + Messages.settings_mediatagSizeHint = 'Maximum size for automatically loading media elements (images, videos, pdf) embedded into the pads. Elements bigger than the specified size can be loaded manually. Use "-1" to always load the media elements automatically.'; // XXX + makeBlock('mediatag-size', function(cb) { + var $inputBlock = $('
', { + 'class': 'cp-sidebarlayout-input-block', + }); + + var spinner; + var $input = $('', { + 'min': -1, + 'max': 1000, + type: 'number', + }).appendTo($inputBlock); + + var oldVal; + + var todo = function () { + var val = parseInt($input.val()); + if (val === oldVal) { return; } + if (typeof(val) !== 'number') { return UI.warn(Messages.error); } + spinner.spin(); + common.setAttribute(['general', 'mediatag-size'], val, function (err) { + if (err) { + spinner.hide(); + console.error(err); + return UI.warn(Messages.error); + } + spinner.done(); + UI.log(Messages.saved); + }); + }; + var $save = $(h('button.btn.btn-primary', Messages.settings_save)).appendTo($inputBlock); + spinner = UI.makeSpinner($inputBlock); + + $save.click(todo); + $input.on('keyup', function(e) { + if (e.which === 13) { todo(); } + }); + + common.getAttribute(['general', 'mediatag-size'], function(e, val) { + if (e) { return void console.error(e); } + if (typeof(val) !== 'number') { + oldVal = 5; + $input.val(5); + } else { + oldVal = val; + $input.val(val); + } + }); + + cb($inputBlock); + }, true); + // Security makeBlock('safe-links', function(cb) { From 58cdf21defb492b28cf90ddf7c2cdf90ff74d46b Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 27 Nov 2020 17:11:12 +0100 Subject: [PATCH 30/51] Store the blob cache in the outer domain --- www/common/inner/cache.js | 35 +++++++++++++++++++++++++++++++ www/common/media-tag.js | 30 +++++++++++++++++--------- www/common/sframe-common-outer.js | 16 ++++++++++++++ www/common/sframe-common.js | 7 +++++++ 4 files changed, 78 insertions(+), 10 deletions(-) create mode 100644 www/common/inner/cache.js diff --git a/www/common/inner/cache.js b/www/common/inner/cache.js new file mode 100644 index 000000000..f14890797 --- /dev/null +++ b/www/common/inner/cache.js @@ -0,0 +1,35 @@ +define([ +], function () { + var S = {}; + + S.create = function (sframeChan) { + var getBlobCache = function (id, cb) { + sframeChan.query('Q_GET_BLOB_CACHE', {id:id}, function (err, data) { + var e = err || (data && data.error); + if (e) { return void cb(e); } + if (!data || typeof(data) !== "object") { return void cb('EINVAL'); } + var arr = Object.keys(data).map(function (i) { return data[i]; }); + var u8 = Uint8Array.from(arr); + cb(null, u8); + }); + }; + var setBlobCache = function (id, u8, cb) { + sframeChan.query('Q_SET_BLOB_CACHE', { + id: id, + u8: u8 + }, function (err, data) { + var e = err || (data && data.error) || undefined; + cb(e); + }); + }; + + + return { + getBlobCache: getBlobCache, + setBlobCache: setBlobCache + }; + }; + + return S; +}); + diff --git a/www/common/media-tag.js b/www/common/media-tag.js index e4ec9ce2a..b58375302 100644 --- a/www/common/media-tag.js +++ b/www/common/media-tag.js @@ -1,5 +1,5 @@ (function (window) { -var factory = function (Cache) { +var factory = function () { var Promise = window.Promise; var cache; var cypherChunkLength = 131088; @@ -199,6 +199,19 @@ var factory = function (Cache) { return cacheKey; }; + var getBlobCache = function (id, cb) { + if (!config.Cache || typeof(config.Cache.getBlobCache) !== "function") { + return void cb('EINVAL'); + } + config.Cache.getBlobCache(id, cb); + }; + var setBlobCache = function (id, u8, cb) { + if (!config.Cache || typeof(config.Cache.setBlobCache) !== "function") { + return void cb('EINVAL'); + } + config.Cache.setBlobCache(id, u8, cb); + }; + var getFileSize = function (src, _cb) { var cb = function (e, res) { _cb(e, res); @@ -224,7 +237,7 @@ var factory = function (Cache) { if (!cacheKey) { return void check(); } - Cache.getBlobCache(cacheKey, function (err, u8) { + getBlobCache(cacheKey, function (err, u8) { if (err || !u8) { return void check(); } cb(null, 0); }); @@ -263,7 +276,7 @@ var factory = function (Cache) { if (arrayBuffer) { var u8 = new Uint8Array(arrayBuffer); if (cacheKey) { - return void Cache.setBlobCache(cacheKey, u8, function () { + return void setBlobCache(cacheKey, u8, function () { cb(null, u8); }); } @@ -276,7 +289,7 @@ var factory = function (Cache) { if (!cacheKey) { return void fetch(); } - Cache.getBlobCache(cacheKey, function (err, u8) { + getBlobCache(cacheKey, function (err, u8) { if (err || !u8) { return void fetch(); } cb(null, u8); }); @@ -628,15 +641,12 @@ var factory = function (Cache) { }; if (typeof(module) !== 'undefined' && module.exports) { - module.exports = factory( - require("./outer/cache-store.js") - ); + module.exports = factory(); } else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { define([ - '/common/outer/cache-store.js', '/bower_components/es6-promise/es6-promise.min.js' - ], function (Cache) { - return factory(Cache); + ], function () { + return factory(); }); } else { // unsupported initialization diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index a46a94837..12327c720 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -680,6 +680,22 @@ define([ }); }); + sframeChan.on('Q_GET_BLOB_CACHE', function (data, cb) { + Utils.Cache.getBlobCache(data.id, function (err, obj) { + if (err) { return void cb({error: err}); } + cb(obj); + }); + }); + sframeChan.on('Q_SET_BLOB_CACHE', function (data, cb) { + if (!data || !data.u8 || typeof(data.u8) !== "object") { return void cb({error: 'EINVAL'}); } + var arr = Object.keys(data.u8).map(function (i) { return data.u8[i]; }); + var u8 = Uint8Array.from(arr); + Utils.Cache.setBlobCache(data.id, u8, function (err) { + if (err) { return void cb({error: err}); } + cb(); + }); + }); + sframeChan.on('Q_GET_ATTRIBUTE', function (data, cb) { Cryptpad.getAttribute(data.key, function (e, data) { cb({ diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index 328bd70c8..67ede0d64 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -11,6 +11,7 @@ define([ '/common/sframe-common-codemirror.js', '/common/sframe-common-cursor.js', '/common/sframe-common-mailbox.js', + '/common/inner/cache.js', '/common/inner/common-mediatag.js', '/common/metadata-manager.js', @@ -36,6 +37,7 @@ define([ CodeMirror, Cursor, Mailbox, + Cache, MT, MetadataMgr, AppConfig, @@ -815,6 +817,11 @@ define([ MT.MediaTag.setDefaultConfig('maxDownloadSize', maxMtSize); } + if (MT.MediaTag && Cache) { + var cache = Cache.create(ctx.sframeChan); + MT.MediaTag.setDefaultConfig('Cache', cache); + } + try { var feedback = privateData.feedbackAllowed; Feedback.init(feedback); From 775630696680233a913d277ac3f4a85cc69229b4 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 27 Nov 2020 17:19:03 +0100 Subject: [PATCH 31/51] Clear cache on logout --- www/common/outer/cache-store.js | 5 +++-- www/common/outer/local-store.js | 12 ++++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/www/common/outer/cache-store.js b/www/common/outer/cache-store.js index 4d5e30ed2..6b6c8604e 100644 --- a/www/common/outer/cache-store.js +++ b/www/common/outer/cache-store.js @@ -82,8 +82,9 @@ define([ }); }; - S.clear = function () { - cache.clear(); + S.clear = function (cb) { + cb = Util.once(Util.mkAsync(cb || function () {})); + cache.clear(cb); }; return S; diff --git a/www/common/outer/local-store.js b/www/common/outer/local-store.js index 0aff1f9ce..645493de5 100644 --- a/www/common/outer/local-store.js +++ b/www/common/outer/local-store.js @@ -1,9 +1,10 @@ define([ '/common/common-constants.js', '/common/common-hash.js', + '/common/outer/cache-store.js', '/bower_components/localforage/dist/localforage.min.js', '/customize/application_config.js', -], function (Constants, Hash, localForage, AppConfig) { +], function (Constants, Hash, Cache, localForage, AppConfig) { var LocalStore = {}; LocalStore.setThumbnail = function (key, value, cb) { @@ -119,7 +120,14 @@ define([ return void AppConfig.customizeLogout(cb); } - if (cb) { cb(); } + cb = cb || function () {}; + + try { + Cache.clear(cb); + } catch (e) { + console.error(e); + cb(); + } }; var loginHandlers = []; LocalStore.loginReload = function () { From 82c869f4cd494c255cec5e86ce30dde8b2042bf9 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 30 Nov 2020 12:07:03 +0100 Subject: [PATCH 32/51] Add cryptget and blob cache --- www/common/common-util.js | 79 ++++++++++++++++++++++++-------- www/common/cryptget.js | 8 +++- www/common/cryptpad-common.js | 5 +- www/common/make-backup.js | 14 +++--- www/common/onlyoffice/inner.js | 2 +- www/common/sframe-common-file.js | 1 + www/common/sframe-common.js | 11 +++-- www/settings/inner.js | 2 +- www/teams/inner.js | 2 +- 9 files changed, 88 insertions(+), 36 deletions(-) diff --git a/www/common/common-util.js b/www/common/common-util.js index 435a72280..603e38a30 100644 --- a/www/common/common-util.js +++ b/www/common/common-util.js @@ -274,32 +274,71 @@ // given a path, asynchronously return an arraybuffer - Util.fetch = function (src, cb, progress) { - var CB = Util.once(cb); + var getCacheKey = function (src) { + var _src = src.replace(/(\/)*$/, ''); // Remove trailing slashes + var idx = _src.lastIndexOf('/'); + var cacheKey = _src.slice(idx+1); + if (!/^[a-f0-9]{48}$/.test(cacheKey)) { cacheKey = undefined; } + return cacheKey; + }; + Util.fetch = function (src, cb, progress, cache) { + var CB = Util.once(Util.mkAsync(cb)); + + var cacheKey = getCacheKey(src); + var getBlobCache = function (id, cb) { + if (!cache || typeof(cache.getBlobCache) !== "function") { return void cb('EINVAL'); } + cache.getBlobCache(id, cb); + }; + var setBlobCache = function (id, u8, cb) { + if (!cache || typeof(cache.setBlobCache) !== "function") { return void cb('EINVAL'); } + cache.setBlobCache(id, u8, cb); + }; - var xhr = new XMLHttpRequest(); - xhr.open("GET", src, true); - if (progress) { - xhr.addEventListener("progress", function (evt) { - if (evt.lengthComputable) { - var percentComplete = evt.loaded / evt.total; - progress(percentComplete); - } - }, false); - } - xhr.responseType = "arraybuffer"; - xhr.onerror = function (err) { CB(err); }; - xhr.onload = function () { - if (/^4/.test(''+this.status)) { - return CB('XHR_ERROR'); + var xhr; + + var fetch = function () { + xhr = new XMLHttpRequest(); + xhr.open("GET", src, true); + if (progress) { + xhr.addEventListener("progress", function (evt) { + if (evt.lengthComputable) { + var percentComplete = evt.loaded / evt.total; + progress(percentComplete); + } + }, false); } - return void CB(void 0, new Uint8Array(xhr.response)); + xhr.responseType = "arraybuffer"; + xhr.onerror = function (err) { CB(err); }; + xhr.onload = function () { + if (/^4/.test(''+this.status)) { + return CB('XHR_ERROR'); + } + + var arrayBuffer = xhr.response; + if (arrayBuffer) { + var u8 = new Uint8Array(arrayBuffer); + if (cacheKey) { + return void setBlobCache(cacheKey, u8, function () { + CB(null, u8); + }); + } + return void CB(void 0, u8); + } + CB('ENOENT'); + }; + xhr.send(null); }; - xhr.send(null); + + if (!cacheKey) { return void fetch(); } + + getBlobCache(cacheKey, function (err, u8) { + if (err || !u8) { return void fetch(); } + CB(void 0, u8); + }); return { cancel: function () { - if (xhr.abort) { xhr.abort(); } + if (xhr && xhr.abort) { xhr.abort(); } } }; }; diff --git a/www/common/cryptget.js b/www/common/cryptget.js index e394788d7..1cbd5056e 100644 --- a/www/common/cryptget.js +++ b/www/common/cryptget.js @@ -6,10 +6,11 @@ define([ '/common/common-hash.js', '/common/common-realtime.js', '/common/outer/network-config.js', + '/common/outer/cache-store.js', '/common/pinpad.js', '/bower_components/nthen/index.js', '/bower_components/chainpad/chainpad.dist.js', -], function (Crypto, CPNetflux, Netflux, Util, Hash, Realtime, NetConfig, Pinpad, nThen) { +], function (Crypto, CPNetflux, Netflux, Util, Hash, Realtime, NetConfig, Cache, Pinpad, nThen) { var finish = function (S, err, doc) { if (S.done) { return; } S.cb(err, doc); @@ -92,7 +93,8 @@ define([ validateKey: secret.keys.validateKey || undefined, crypto: Crypto.createEncryptor(secret.keys), logLevel: 0, - initialState: opt.initialState + initialState: opt.initialState, + Cache: Cache }; return config; }; @@ -132,9 +134,11 @@ define([ }; config.onError = function (info) { + console.warn(info); finish(Session, info.error); }; config.onChannelError = function (info) { + console.error(info); finish(Session, info.error); }; diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 5dd029def..46f569a3e 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -3,6 +3,7 @@ define([ '/customize/messages.js', '/common/common-util.js', '/common/common-hash.js', + '/common/outer/cache-store.js', '/common/common-messaging.js', '/common/common-constants.js', '/common/common-feedback.js', @@ -14,7 +15,7 @@ define([ '/customize/application_config.js', '/bower_components/nthen/index.js', -], function (Config, Messages, Util, Hash, +], function (Config, Messages, Util, Hash, Cache, Messaging, Constants, Feedback, Visible, UserObject, LocalStore, Channel, Block, AppConfig, Nthen) { @@ -760,7 +761,7 @@ define([ u8 = _u8; }), function (progress) { onProgress(progress * 50); - }); + }, Cache); }).nThen(function (waitFor) { require(["/file/file-crypto.js"], waitFor(function (FileCrypto) { FileCrypto.decrypt(u8, key, waitFor(function (err, _res) { diff --git a/www/common/make-backup.js b/www/common/make-backup.js index 88602c8bb..9ba925fd7 100644 --- a/www/common/make-backup.js +++ b/www/common/make-backup.js @@ -1,17 +1,18 @@ define([ 'jquery', - '/common/cryptget.js', '/file/file-crypto.js', '/common/common-hash.js', '/common/common-util.js', '/common/common-interface.js', '/common/hyperscript.js', '/common/common-feedback.js', + '/common/inner/cache.js', '/customize/messages.js', '/bower_components/nthen/index.js', '/bower_components/saferphore/index.js', '/bower_components/jszip/dist/jszip.min.js', -], function ($, Crypt, FileCrypto, Hash, Util, UI, h, Feedback, Messages, nThen, Saferphore, JsZip) { +], function ($, FileCrypto, Hash, Util, UI, h, Feedback, + Cache, Messages, nThen, Saferphore, JsZip) { var saveAs = window.saveAs; var sanitize = function (str) { @@ -89,7 +90,7 @@ define([ if (updateProgress && updateProgress.progress) { updateProgress.progress(data); } - }); + }, ctx.cache); var cancel = function () { cancelled = true; @@ -291,7 +292,7 @@ define([ }; // Main function. Create the empty zip and fill it starting from drive.root - var create = function (data, getPad, fileHost, cb, progress) { + var create = function (data, getPad, fileHost, cb, progress, cache) { if (!data || !data.uo || !data.uo.drive) { return void cb('EEMPTY'); } var sem = Saferphore.create(5); var ctx = { @@ -305,7 +306,8 @@ define([ sem: sem, updateProgress: progress, max: 0, - done: 0 + done: 0, + cache: cache }; var filesData = data.sharedFolderId && ctx.sf[data.sharedFolderId] ? ctx.sf[data.sharedFolderId].filesData : ctx.data.filesData; progress('reading', -1); @@ -356,7 +358,7 @@ define([ else if (state === "done") { updateProgress.folderProgress(3); } - }); + }, ctx.cache); }; var createExportUI = function (origin) { diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index c9efb1eda..976daa887 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -1419,7 +1419,7 @@ define([ console.error(e); callback(""); } - }); + }, void 0, common.getCache()); }; APP.docEditor = new window.DocsAPI.DocEditor("cp-app-oo-placeholder-a", APP.ooconfig); diff --git a/www/common/sframe-common-file.js b/www/common/sframe-common-file.js index 8a5be3764..13fea31b4 100644 --- a/www/common/sframe-common-file.js +++ b/www/common/sframe-common-file.js @@ -654,6 +654,7 @@ define([ fileHost: privateData.fileHost, get: common.getPad, sframeChan: sframeChan, + cache: common.getCache() }; var dl = downloadFunction(ctx, data, function (err, obj) { diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index 67ede0d64..9329b467a 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -607,6 +607,10 @@ define([ }); }; + funcs.getCache = function () { + return ctx.cache; + }; + /* funcs.storeLinkToClipboard = function (readOnly, cb) { ctx.sframeChan.query('Q_STORE_LINK_TO_CLIPBOARD', readOnly, function (err) { if (cb) { cb(err); } @@ -805,6 +809,8 @@ define([ modules[type].onEvent(obj.data); }); + ctx.cache = Cache.create(ctx.sframeChan); + ctx.metadataMgr.onReady(waitFor()); }).nThen(function () { @@ -817,9 +823,8 @@ define([ MT.MediaTag.setDefaultConfig('maxDownloadSize', maxMtSize); } - if (MT.MediaTag && Cache) { - var cache = Cache.create(ctx.sframeChan); - MT.MediaTag.setDefaultConfig('Cache', cache); + if (MT.MediaTag && ctx.cache) { + MT.MediaTag.setDefaultConfig('Cache', ctx.cache); } try { diff --git a/www/settings/inner.js b/www/settings/inner.js index 9ff57aae7..9051d3c34 100644 --- a/www/settings/inner.js +++ b/www/settings/inner.js @@ -831,7 +831,7 @@ define([ Feedback.send('FULL_DRIVE_EXPORT_COMPLETE'); saveAs(blob, filename); }, errors); - }, ui.update); + }, ui.update, common.getCache()); ui.onCancel(function() { ui.close(); bu.stop(); diff --git a/www/teams/inner.js b/www/teams/inner.js index 070d11825..7af3d7afc 100644 --- a/www/teams/inner.js +++ b/www/teams/inner.js @@ -1071,7 +1071,7 @@ define([ Feedback.send('FULL_TEAMDRIVE_EXPORT_COMPLETE'); saveAs(blob, filename); }, errors); - }, ui.update); + }, ui.update, common.getCache); ui.onCancel(function() { ui.close(); bu.stop(); From d4055f6ef5276dd7eb7af4e678a2d4815a550c25 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 30 Nov 2020 14:40:07 +0100 Subject: [PATCH 33/51] Improve blob cache --- www/common/inner/cache.js | 6 +++--- www/common/inner/common-mediatag.js | 4 ++-- www/common/outer/upload.js | 14 ++++++++++---- www/common/sframe-common-outer.js | 5 +++-- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/www/common/inner/cache.js b/www/common/inner/cache.js index f14890797..7db9635b4 100644 --- a/www/common/inner/cache.js +++ b/www/common/inner/cache.js @@ -8,15 +8,15 @@ define([ var e = err || (data && data.error); if (e) { return void cb(e); } if (!data || typeof(data) !== "object") { return void cb('EINVAL'); } - var arr = Object.keys(data).map(function (i) { return data[i]; }); - var u8 = Uint8Array.from(arr); + var u8 = Uint8Array.from(data); cb(null, u8); }); }; var setBlobCache = function (id, u8, cb) { + var array = [].slice.call(u8); sframeChan.query('Q_SET_BLOB_CACHE', { id: id, - u8: u8 + u8: array }, function (err, data) { var e = err || (data && data.error) || undefined; cb(e); diff --git a/www/common/inner/common-mediatag.js b/www/common/inner/common-mediatag.js index 86900f5bb..72cb1e37b 100644 --- a/www/common/inner/common-mediatag.js +++ b/www/common/inner/common-mediatag.js @@ -24,8 +24,8 @@ define([ }); Messages.mediatag_saveButton = "Save"; // XXX MediaTag.setDefaultConfig('download', { - text: Messages.download_mt_button, - textDl: Messages.mediatag_saveButton + text: Messages.mediatag_saveButton, + textDl: Messages.download_mt_button, }); } MT.MediaTag = MediaTag; diff --git a/www/common/outer/upload.js b/www/common/outer/upload.js index 62f305612..7115b5d4e 100644 --- a/www/common/outer/upload.js +++ b/www/common/outer/upload.js @@ -1,9 +1,11 @@ define([ '/file/file-crypto.js', '/common/common-hash.js', + '/common/common-util.js', + '/common/outer/cache-store.js', '/bower_components/nthen/index.js', '/bower_components/tweetnacl/nacl-fast.min.js', -], function (FileCrypto, Hash, nThen) { +], function (FileCrypto, Hash, Util, Cache, nThen) { var Nacl = window.nacl; var module = {}; @@ -31,9 +33,11 @@ define([ }; var actual = 0; + var encryptedArr = [];; var again = function (err, box) { if (err) { onError(err); } if (box) { + encryptedArr.push(box); actual += box.length; var progressValue = (actual / estimate * 100); progressValue = Math.min(progressValue, 100); @@ -55,9 +59,11 @@ define([ var uri = ['', 'blob', id.slice(0,2), id].join('/'); console.log("encrypted blob is now available as %s", uri); - - - cb(); + var box_u8 = Util.uint8ArrayJoin(encryptedArr); + Cache.setBlobCache(id, box_u8, function (err) { + if (err) { console.warn(err); } + cb(); + }); }); }; diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 12327c720..49d52c37b 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -683,12 +683,13 @@ define([ sframeChan.on('Q_GET_BLOB_CACHE', function (data, cb) { Utils.Cache.getBlobCache(data.id, function (err, obj) { if (err) { return void cb({error: err}); } - cb(obj); + var arr = [].slice.call(obj); + cb(arr); }); }); sframeChan.on('Q_SET_BLOB_CACHE', function (data, cb) { if (!data || !data.u8 || typeof(data.u8) !== "object") { return void cb({error: 'EINVAL'}); } - var arr = Object.keys(data.u8).map(function (i) { return data.u8[i]; }); + var arr = data.u8; var u8 = Uint8Array.from(arr); Utils.Cache.setBlobCache(data.id, u8, function (err) { if (err) { return void cb({error: err}); } From 004c242f63a96dd737293edaa8288acdd8500e81 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 30 Nov 2020 17:19:13 +0100 Subject: [PATCH 34/51] Fix duplicate mediatag issues --- www/common/diffMarked.js | 7 +++-- www/common/media-tag.js | 64 ++++++++++++++++++++++++++++---------- www/common/outer/upload.js | 2 +- 3 files changed, 52 insertions(+), 21 deletions(-) diff --git a/www/common/diffMarked.js b/www/common/diffMarked.js index fd3fcafb3..64768f2cb 100644 --- a/www/common/diffMarked.js +++ b/www/common/diffMarked.js @@ -658,7 +658,7 @@ define([ $(contextMenu.menu).find('li').show(); contextMenu.show(e); }); - if ($mt.children().length) { + if ($mt.children().length && $mt[0]._mediaObject) { $mt.off('click dblclick preview'); $mt.on('preview', onPreview($mt)); if ($mt.find('img').length) { @@ -672,10 +672,10 @@ define([ var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { if (mutation.type === 'childList') { - var list_values = slice(mutation.target.children) + var list_values = slice(el.children) .map(function (el) { return el.outerHTML; }) .join(''); - mediaMap[mutation.target.getAttribute('src')] = list_values; + mediaMap[el.getAttribute('src')] = list_values; if (mediaObject.complete) { observer.disconnect(); } } }); @@ -689,6 +689,7 @@ define([ }); observer.observe(el, { attributes: false, + subtree: true, childList: true, characterData: false }); diff --git a/www/common/media-tag.js b/www/common/media-tag.js index b58375302..e391a2530 100644 --- a/www/common/media-tag.js +++ b/www/common/media-tag.js @@ -126,6 +126,8 @@ var factory = function () { var makeProgressBar = function (cfg, mediaObject) { // XXX CSP: we'll need to add style in cryptpad's less + if (mediaObject.bar) { return; } + mediaObject.bar = true; var style = (function(){/* .mediatag-progress-container { position: relative; @@ -181,10 +183,15 @@ var factory = function () { var makeDownloadButton = function (cfg, mediaObject, size, cb) { var btn = document.createElement('button'); btn.setAttribute('class', 'btn btn-default'); + btn.setAttribute('data-dl', '1'); btn.innerHTML = '' + cfg.download.textDl + ' (' + size + 'MB)'; btn.addEventListener('click', function () { makeProgressBar(cfg, mediaObject); + var a = document.querySelectorAll('media-tag[src="'+mediaObject.tag.getAttribute('src')+'"] button[data-dl]'); + for(var i = 0; i < a.length; i++) { + if (a[i] !== btn) { a[i].click(); } + } cb(); }); mediaObject.tag.innerHTML = ''; @@ -582,32 +589,50 @@ var factory = function () { emit('error', err); }; + var getCache = function () { + var c = cache[uid]; + if (!c || !c.promise || !c.mt) { console.error(uid);return; } + return c; + }; + var dl = function () { // Download the encrypted blob - cache[uid] = cache[uid] || new Promise(function (resolve, reject) { - download(src, function (err, u8Encrypted) { - if (err) { - return void reject(err); - } - // Decrypt the blob - decrypt(u8Encrypted, strKey, function (errDecryption, u8Decrypted) { - if (errDecryption) { - return void reject(errDecryption); + cache[uid] = getCache() || { + promise: new Promise(function (resolve, reject) { + download(src, function (err, u8Encrypted) { + if (err) { + return void reject(err); } - // Cache and display the decrypted blob - resolve(u8Decrypted); + // Decrypt the blob + decrypt(u8Encrypted, strKey, function (errDecryption, u8Decrypted) { + if (errDecryption) { + return void reject(errDecryption); + } + // Cache and display the decrypted blob + resolve(u8Decrypted); + }, function (progress) { + emit('progress', { + progress: 50+0.5*progress + }); + }); }, function (progress) { emit('progress', { - progress: 50+0.5*progress + progress: 0.5*progress }); }); - }, function (progress) { + }), + mt: mediaObject + }; + if (cache[uid].mt !== mediaObject) { + // Add progress for other instances of this tag + cache[uid].mt.on('progress', function (obj) { + if (!mediaObject.bar) { makeProgressBar(cfg, mediaObject); } emit('progress', { - progress: 0.5*progress + progress: obj.progress }); }); - }); - cache[uid].then(function (u8) { + } + cache[uid].promise.then(function (u8) { end(u8); }, function (err) { error(err); @@ -622,7 +647,12 @@ var factory = function () { if (err) { return void error(err); } - if (!size || size < maxSize) { return void dl(); } + // If the size is smaller than the autodownload limit, load the blob. + // If the blob is already loaded or being loaded, don't show the button. + if (!size || size < maxSize || getCache()) { + makeProgressBar(cfg, mediaObject); + return void dl(); + } var sizeMb = Math.round(10 * size / 1024 / 1024) / 10; makeDownloadButton(cfg, mediaObject, sizeMb, dl); }); diff --git a/www/common/outer/upload.js b/www/common/outer/upload.js index 7115b5d4e..0615ed7a7 100644 --- a/www/common/outer/upload.js +++ b/www/common/outer/upload.js @@ -33,7 +33,7 @@ define([ }; var actual = 0; - var encryptedArr = [];; + var encryptedArr = []; var again = function (err, box) { if (err) { onError(err); } if (box) { From 682e722d72a3a0397b952038f32f2d77888aaaab Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 30 Nov 2020 18:00:34 +0100 Subject: [PATCH 35/51] sframeChan Uint8Array without conversion --- www/common/inner/cache.js | 10 ++++------ www/common/media-tag.js | 2 +- www/common/outer/worker-channel.js | 19 +++++++++++-------- www/common/sframe-common-outer.js | 7 ++----- www/secureiframe/main.js | 2 +- 5 files changed, 19 insertions(+), 21 deletions(-) diff --git a/www/common/inner/cache.js b/www/common/inner/cache.js index 7db9635b4..be5b781c2 100644 --- a/www/common/inner/cache.js +++ b/www/common/inner/cache.js @@ -8,19 +8,17 @@ define([ var e = err || (data && data.error); if (e) { return void cb(e); } if (!data || typeof(data) !== "object") { return void cb('EINVAL'); } - var u8 = Uint8Array.from(data); - cb(null, u8); - }); + cb(null, data); + }, { raw: true }); }; var setBlobCache = function (id, u8, cb) { - var array = [].slice.call(u8); sframeChan.query('Q_SET_BLOB_CACHE', { id: id, - u8: array + u8: u8 }, function (err, data) { var e = err || (data && data.error) || undefined; cb(e); - }); + }, { raw: true }); }; diff --git a/www/common/media-tag.js b/www/common/media-tag.js index e391a2530..c855df328 100644 --- a/www/common/media-tag.js +++ b/www/common/media-tag.js @@ -591,7 +591,7 @@ var factory = function () { var getCache = function () { var c = cache[uid]; - if (!c || !c.promise || !c.mt) { console.error(uid);return; } + if (!c || !c.promise || !c.mt) { return; } return c; }; diff --git a/www/common/outer/worker-channel.js b/www/common/outer/worker-channel.js index 545ce435b..a359d8836 100644 --- a/www/common/outer/worker-channel.js +++ b/www/common/outer/worker-channel.js @@ -65,11 +65,13 @@ define([ cb(undefined, data.content, msg); }; evReady.reg(function () { - postMsg(JSON.stringify({ + var toSend = { txid: txid, content: content, - q: q - })); + q: q, + raw: opts.raw + }; + postMsg(opts.raw ? toSend : JSON.stringify(toSend)); }); }; @@ -84,12 +86,13 @@ define([ // 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) { - var h = function (data, msg) { + var h = function (data, msg, raw) { handler(data.content, function (replyContent) { - postMsg(JSON.stringify({ + var toSend = { txid: data.txid, content: replyContent - })); + }; + postMsg(raw ? toSend : JSON.stringify(toSend)); }, msg); }; (handlers[queryType] = handlers[queryType] || []).push(h); @@ -150,7 +153,7 @@ define([ onMsg.reg(function (msg) { if (!chanLoaded) { return; } if (!msg.data || msg.data === '_READY') { return; } - var data = JSON.parse(msg.data); + var data = typeof(msg.data) === "object" ? msg.data : JSON.parse(msg.data); if (typeof(data.ack) !== "undefined") { if (acks[data.txid]) { acks[data.txid](!data.ack); } } else if (typeof(data.q) === 'string') { @@ -163,7 +166,7 @@ define([ })); } handlers[data.q].forEach(function (f) { - f(data || JSON.parse(msg.data), msg); + f(data || JSON.parse(msg.data), msg, data && data.raw); data = undefined; }); } else { diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 49d52c37b..e43c169ed 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -683,15 +683,12 @@ define([ sframeChan.on('Q_GET_BLOB_CACHE', function (data, cb) { Utils.Cache.getBlobCache(data.id, function (err, obj) { if (err) { return void cb({error: err}); } - var arr = [].slice.call(obj); - cb(arr); + cb(obj); }); }); sframeChan.on('Q_SET_BLOB_CACHE', function (data, cb) { if (!data || !data.u8 || typeof(data.u8) !== "object") { return void cb({error: 'EINVAL'}); } - var arr = data.u8; - var u8 = Uint8Array.from(arr); - Utils.Cache.setBlobCache(data.id, u8, function (err) { + Utils.Cache.setBlobCache(data.id, data.u8, function (err) { if (err) { return void cb({error: err}); } cb(); }); diff --git a/www/secureiframe/main.js b/www/secureiframe/main.js index 1bf5e3e7b..62175c5a0 100644 --- a/www/secureiframe/main.js +++ b/www/secureiframe/main.js @@ -33,7 +33,7 @@ define([ // loading screen setup. var done = waitFor(); var onMsg = function (msg) { - var data = JSON.parse(msg.data); + var data = typeof(msg.data) === "object" ? msg.data : JSON.parse(msg.data); if (data.q !== 'READY') { return; } window.removeEventListener('message', onMsg); var _done = done; From 20cecbcfa2dae26bf4de8ed5ecf079b621ac149e Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 1 Dec 2020 18:18:16 +0100 Subject: [PATCH 36/51] Fix cached mediatags in pads --- www/common/media-tag.js | 2 +- www/common/outer/cache-store.js | 2 ++ www/pad/inner.js | 11 ++++++++--- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/www/common/media-tag.js b/www/common/media-tag.js index c855df328..8f7276446 100644 --- a/www/common/media-tag.js +++ b/www/common/media-tag.js @@ -188,7 +188,7 @@ var factory = function () { cfg.download.textDl + ' (' + size + 'MB)'; btn.addEventListener('click', function () { makeProgressBar(cfg, mediaObject); - var a = document.querySelectorAll('media-tag[src="'+mediaObject.tag.getAttribute('src')+'"] button[data-dl]'); + var a = (cfg.body || document).querySelectorAll('media-tag[src="'+mediaObject.tag.getAttribute('src')+'"] button[data-dl]'); for(var i = 0; i < a.length; i++) { if (a[i] !== btn) { a[i].click(); } } diff --git a/www/common/outer/cache-store.js b/www/common/outer/cache-store.js index 6b6c8604e..cd88de6f6 100644 --- a/www/common/outer/cache-store.js +++ b/www/common/outer/cache-store.js @@ -87,5 +87,7 @@ define([ cache.clear(cb); }; + self.CryptPad_clearIndexedDB = S.clear; + return S; }); diff --git a/www/pad/inner.js b/www/pad/inner.js index 8a99b6101..f9b5dffb9 100644 --- a/www/pad/inner.js +++ b/www/pad/inner.js @@ -463,7 +463,9 @@ define([ setTimeout(function() { // Just in case var tags = dom.querySelectorAll('media-tag:empty'); Array.prototype.slice.call(tags).forEach(function(el) { - var mediaObject = MediaTag(el); + var mediaObject = MediaTag(el, { + body: dom + }); $(el).on('keydown', function(e) { if ([8, 46].indexOf(e.which) !== -1) { $(el).remove(); @@ -473,14 +475,17 @@ define([ var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { if (mutation.type === 'childList') { - var list_values = [].slice.call(el.children); - mediaTagMap[el.getAttribute('src')] = list_values; + var list_values = slice(el.children) + .map(function (el) { return el.outerHTML; }) + .join(''); + mediaMap[el.getAttribute('src')] = list_values; if (mediaObject.complete) { observer.disconnect(); } } }); }); observer.observe(el, { attributes: false, + subtree: true, childList: true, characterData: false }); From 0deaa428a5c8dd09bd54ae5abe7e64308f8bba47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Benqu=C3=A9?= Date: Wed, 2 Dec 2020 13:28:23 +0000 Subject: [PATCH 37/51] Use existing key for empty chainpad error --- www/common/sframe-app-framework.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/www/common/sframe-app-framework.js b/www/common/sframe-app-framework.js index d3a4bc174..8776573d7 100644 --- a/www/common/sframe-app-framework.js +++ b/www/common/sframe-app-framework.js @@ -470,8 +470,9 @@ define([ var noCache = false; // Prevent reload loops var onCorruptedCache = function () { if (noCache) { - // XXX translation key - return UI.errorLoadingScreen("Reload loop: empty chainpad for a non-empty channel"); + UI.errorLoadingScreen(Messages.unableToDisplay, false, function () { + common.gotoURL(''); + }); } noCache = true; var sframeChan = common.getSframeChannel(); From 8173eb4a6fb93eef43cefee1758ec8557a3d1a83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Benqu=C3=A9?= Date: Wed, 2 Dec 2020 14:16:13 +0000 Subject: [PATCH 38/51] Add "offline" key --- www/common/toolbar.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/www/common/toolbar.js b/www/common/toolbar.js index 925f6aba3..761ecce78 100644 --- a/www/common/toolbar.js +++ b/www/common/toolbar.js @@ -1395,7 +1395,8 @@ MessengerUI, Messages) { toolbar.isErrorState = bool; // Stop kickSpinner toolbar.title.toggleClass('cp-toolbar-unsync', bool); // "read only" next to the title if (bool && toolbar.spinner) { - toolbar.spinner.text("OFFLINE"); // XXX + Messages.offline = "OFFLINE"; // XXX + toolbar.spinner.text(Messages.offline); } else { kickSpinner(toolbar, config); } From 656c81b4378689287a3d4f0a86ba9fadbaeac742 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 2 Dec 2020 17:01:43 +0100 Subject: [PATCH 39/51] Fix duplicate mediatags issues in code and slide --- www/common/diffMarked.js | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/www/common/diffMarked.js b/www/common/diffMarked.js index 64768f2cb..3d5704584 100644 --- a/www/common/diffMarked.js +++ b/www/common/diffMarked.js @@ -303,14 +303,22 @@ define([ return renderParagraph(p); }; + // Note: iframe, video and audio are used in mediatags and are allowed in rich text pads. var forbiddenTags = [ 'SCRIPT', - 'IFRAME', + //'IFRAME', 'OBJECT', 'APPLET', - 'VIDEO', // privacy implications of videos are the same as images - 'AUDIO', // same with audio + //'VIDEO', // privacy implications of videos are the same as images + //'AUDIO', // same with audio + 'SOURCE' + ]; + var restrictedTags = [ + 'IFRAME', + 'VIDEO', + 'AUDIO' ]; + var unsafeTag = function (info) { /*if (info.node && $(info.node).parents('media-tag').length) { // Do not remove elements inside a media-tag @@ -347,9 +355,16 @@ define([ parent.removeChild(node); }; + // Only allow iframe, video and audio with local source + var checkSrc = function (root) { + if (restrictedTags.indexOf(root.nodeName.toUpperCase()) === -1) { return true; } + return root.getAttribute && /^(blob\:|\/)/.test(root.getAttribute('src')); + }; + var removeForbiddenTags = function (root) { if (!root) { return; } if (forbiddenTags.indexOf(root.nodeName.toUpperCase()) !== -1) { removeNode(root); } + if (!checkSrc(root)) { removeNode(root); } slice(root.children).forEach(removeForbiddenTags); }; From 1c4ddf6e7bc2ce20908fc0f416bc5965c9a41b84 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 3 Dec 2020 10:33:43 +0100 Subject: [PATCH 40/51] MediaTag UX --- customize.dist/ckeditor-contents.css | 25 +++- .../src/less2/include/markdown.less | 16 +++ www/common/inner/common-mediatag.js | 3 +- www/common/media-tag.js | 110 +++++++++++++++++- 4 files changed, 145 insertions(+), 9 deletions(-) diff --git a/customize.dist/ckeditor-contents.css b/customize.dist/ckeditor-contents.css index a7939839d..663cd8cb9 100644 --- a/customize.dist/ckeditor-contents.css +++ b/customize.dist/ckeditor-contents.css @@ -232,14 +232,35 @@ media-tag button.btn { transition: none; color: #3F4141; border: 1px solid #3F4141; + max-width: 250px; } +media-tag button.mediatag-download-btn { + flex-flow: column; + min-height: 38px; + justify-content: center; +} +media-tag button.mediatag-download-btn > span { + display: flex; + line-height: 1.5; + align-items: center; + justify-content: center; +} +media-tag button.mediatag-download-btn * { + width: auto; +} +media-tag button.mediatag-download-btn > span.mediatag-download-name b { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + media-tag button.btn:hover, media-tag button.btn:active, media-tag button.btn:focus { background-color: #ccc; } -media-tag button b { +media-tag button.btn b { margin-left: 5px; } -media-tag button .fa { +media-tag button.btn .fa { display: inline; margin-right: 5px; } diff --git a/customize.dist/src/less2/include/markdown.less b/customize.dist/src/less2/include/markdown.less index d7fe13f43..af3ac74d8 100644 --- a/customize.dist/src/less2/include/markdown.less +++ b/customize.dist/src/less2/include/markdown.less @@ -94,8 +94,24 @@ height: 80vh; max-height: 90vh; } + button.mediatag-download-btn { + flex-flow: column; + & > span { + display: flex; + line-height: 1.5; + align-items: center; + &.mediatag-download-name b { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + } + } button.btn-default { display: inline-flex; + max-width: 250px; + min-height: 38px; + justify-content: center; .fa { margin-right: 5px; } diff --git a/www/common/inner/common-mediatag.js b/www/common/inner/common-mediatag.js index 72cb1e37b..eaf640b0d 100644 --- a/www/common/inner/common-mediatag.js +++ b/www/common/inner/common-mediatag.js @@ -23,9 +23,10 @@ define([ viewer: '/common/pdfjs/web/viewer.html' }); Messages.mediatag_saveButton = "Save"; // XXX + Messages.mediatag_loadButton = "Load attachment"; // XXX MediaTag.setDefaultConfig('download', { text: Messages.mediatag_saveButton, - textDl: Messages.download_mt_button, + textDl: Messages.mediatag_loadButton, }); } MT.MediaTag = MediaTag; diff --git a/www/common/media-tag.js b/www/common/media-tag.js index 8f7276446..446dfdf08 100644 --- a/www/common/media-tag.js +++ b/www/common/media-tag.js @@ -181,14 +181,17 @@ var factory = function () { mediaObject.tag.appendChild(text); }; var makeDownloadButton = function (cfg, mediaObject, size, cb) { + var metadata = cfg.metadata || {}; + var i = ''; + var name = metadata.name ? ''+ i +''+ + fixHTML(metadata.name)+'' : ''; var btn = document.createElement('button'); - btn.setAttribute('class', 'btn btn-default'); - btn.setAttribute('data-dl', '1'); - btn.innerHTML = '' + - cfg.download.textDl + ' (' + size + 'MB)'; + btn.setAttribute('class', 'btn btn-default mediatag-download-btn'); + btn.innerHTML = name + '' + (name ? '' : i) + + cfg.download.textDl + ' (' + size + 'MB)'; btn.addEventListener('click', function () { makeProgressBar(cfg, mediaObject); - var a = (cfg.body || document).querySelectorAll('media-tag[src="'+mediaObject.tag.getAttribute('src')+'"] button[data-dl]'); + var a = (cfg.body || document).querySelectorAll('media-tag[src="'+mediaObject.tag.getAttribute('src')+'"] button.mediatag-download-btn'); for(var i = 0; i < a.length; i++) { if (a[i] !== btn) { a[i].click(); } } @@ -346,6 +349,95 @@ var factory = function () { } }; + // The metadata size can go up to 65535 (16 bits - 2 bytes) + // The first 8 bits are stored in A[0] + // The last 8 bits are stored in A[0] + var uint8ArrayJoin = function (AA) { + var l = 0; + var i = 0; + for (; i < AA.length; i++) { l += AA[i].length; } + var C = new Uint8Array(l); + + i = 0; + for (var offset = 0; i < AA.length; i++) { + C.set(AA[i], offset); + offset += AA[i].length; + } + return C; + }; + var fetchMetadata = function (src, _cb) { + var cb = function (e, res) { + _cb(e, res); + cb = function () {}; + }; + + var cacheKey = getCacheKey(src); + + var fetch = function () { + var xhr = new XMLHttpRequest(); + xhr.open('GET', src, true); + xhr.setRequestHeader('Range', 'bytes=0-1'); + xhr.responseType = 'arraybuffer'; + + xhr.onerror = function () { return void cb("XHR_ERROR"); }; + xhr.onload = function () { + // Error? + if (/^4/.test('' + this.status)) { return void cb("XHR_ERROR " + this.status); } + var res = new Uint8Array(xhr.response); + var size = Decrypt.decodePrefix(res); + var xhr2 = new XMLHttpRequest(); + + xhr2.open("GET", src, true); + xhr2.setRequestHeader('Range', 'bytes=2-' + (size + 2)); + xhr2.responseType = 'arraybuffer'; + xhr2.onload = function () { + if (/^4/.test('' + this.status)) { return void cb("XHR_ERROR " + this.status); } + var res2 = new Uint8Array(xhr2.response); + var all = uint8ArrayJoin([res, res2]); + cb(void 0, all); + }; + xhr2.send(null); + }; + + xhr.send(null); + }; + + if (!cacheKey) { return void fetch(); } + + getBlobCache(cacheKey, function (err, u8) { + if (err || !u8) { return void fetch(); } + + var size = Decrypt.decodePrefix(u8.subarray(0,2)); + console.error(size); + + cb(null, u8.subarray(0, size+2)); + }); + }; + var decryptMetadata = function (u8, key) { + var prefix = u8.subarray(0, 2); + var metadataLength = Decrypt.decodePrefix(prefix); + + var metaBox = new Uint8Array(u8.subarray(2, 2 + metadataLength)); + var metaChunk = window.nacl.secretbox.open(metaBox, Decrypt.createNonce(), key); + + try { + return JSON.parse(window.nacl.util.encodeUTF8(metaChunk)); + } + catch (e) { return null; } + }; + var fetchDecryptedMetadata = function (src, strKey, cb) { + if (typeof(src) !== 'string') { + return window.setTimeout(function () { + cb('NO_SOURCE'); + }); + } + fetchMetadata(src, function (e, buffer) { + if (e) { return cb(e); } + var key = Decrypt.getKeyFromStr(strKey); + cb(void 0, decryptMetadata(buffer, key)); + }); + }; + // Decrypts a Uint8Array with the given key. var decrypt = function (u8, strKey, done, progressCb) { var Nacl = window.nacl; @@ -608,6 +700,7 @@ var factory = function () { if (errDecryption) { return void reject(errDecryption); } + // XXX emit 'metadata' u8Decrypted.metadata // Cache and display the decrypted blob resolve(u8Decrypted); }, function (progress) { @@ -654,7 +747,12 @@ var factory = function () { return void dl(); } var sizeMb = Math.round(10 * size / 1024 / 1024) / 10; - makeDownloadButton(cfg, mediaObject, sizeMb, dl); + fetchDecryptedMetadata(src, strKey, function (err, md) { + if (err) { return void error(err); } + cfg.metadata = md; + // XXX emit 'metadata' + makeDownloadButton(cfg, mediaObject, sizeMb, dl); + }); }); return mediaObject; From 43ad4f0a841be3ba34270e7dffa039bd2e1be4aa Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 3 Dec 2020 15:22:39 +0100 Subject: [PATCH 41/51] Deduplicate code between File and MediaTag --- .../src/less2/include/markdown.less | 45 ++--- .../src/less2/include/modals-ui-elements.less | 2 + www/common/common-thumbnail.js | 6 +- www/common/media-tag.js | 36 ++-- www/file/app-file.less | 5 + www/file/file-crypto.js | 66 -------- www/file/inner.html | 1 - www/file/inner.js | 160 ++++-------------- www/pad/inner.js | 2 +- 9 files changed, 89 insertions(+), 234 deletions(-) diff --git a/customize.dist/src/less2/include/markdown.less b/customize.dist/src/less2/include/markdown.less index af3ac74d8..f88d8ca7c 100644 --- a/customize.dist/src/less2/include/markdown.less +++ b/customize.dist/src/less2/include/markdown.less @@ -64,26 +64,7 @@ } } -.markdown_cryptpad() { - word-wrap: break-word; - - h1, h2, h3, h4, h5, h6 { - font-weight: bold; - padding-bottom: 0.3em; - border-bottom: 1px solid #eee; - } - li { - min-height: 22px; - } - .todo-list-item { - list-style: none; - position: relative; - .fa { - position: absolute; - margin-left: -17px; - margin-top: 4px; - } - } +.mediatag_cryptpad() { media-tag { cursor: pointer; * { @@ -126,6 +107,30 @@ display: inline-block; border: 1px solid #BBB; } +} + +.markdown_cryptpad() { + word-wrap: break-word; + + h1, h2, h3, h4, h5, h6 { + font-weight: bold; + padding-bottom: 0.3em; + border-bottom: 1px solid #eee; + } + li { + min-height: 22px; + } + .todo-list-item { + list-style: none; + position: relative; + .fa { + position: absolute; + margin-left: -17px; + margin-top: 4px; + } + } + + .mediatag_cryptpad(); pre.markmap { border: 1px solid #ddd; diff --git a/customize.dist/src/less2/include/modals-ui-elements.less b/customize.dist/src/less2/include/modals-ui-elements.less index 27eb233da..ff40729a6 100644 --- a/customize.dist/src/less2/include/modals-ui-elements.less +++ b/customize.dist/src/less2/include/modals-ui-elements.less @@ -1,6 +1,7 @@ @import (reference) "./colortheme-all.less"; @import (reference) "./variables.less"; @import (reference) "./browser.less"; +@import (reference) "./markdown.less"; .modals-ui-elements_main() { --LessLoader_require: LessLoader_currentFile(); @@ -214,6 +215,7 @@ flex: 1; min-width: 0; overflow: auto; + .mediatag_cryptpad(); media-tag { & > * { max-width: 100%; diff --git a/www/common/common-thumbnail.js b/www/common/common-thumbnail.js index 8c78d9d6d..012c2c9b4 100644 --- a/www/common/common-thumbnail.js +++ b/www/common/common-thumbnail.js @@ -3,9 +3,9 @@ define([ '/common/common-util.js', '/common/visible.js', '/common/common-hash.js', - '/file/file-crypto.js', + '/common/media-tag.js', '/bower_components/tweetnacl/nacl-fast.min.js', -], function ($, Util, Visible, Hash, FileCrypto) { +], function ($, Util, Visible, Hash, MediaTag) { var Nacl = window.nacl; var Thumb = { dimension: 100, @@ -314,7 +314,7 @@ define([ var hexFileName = secret.channel; var src = fileHost + Hash.getBlobPathFromHex(hexFileName); var key = secret.keys && secret.keys.cryptKey; - FileCrypto.fetchDecryptedMetadata(src, key, function (e, metadata) { + MediaTag.fetchDecryptedMetadata(src, key, function (e, metadata) { if (e) { if (e === 'XHR_ERROR') { return; } return console.error(e); diff --git a/www/common/media-tag.js b/www/common/media-tag.js index 446dfdf08..a00377ff4 100644 --- a/www/common/media-tag.js +++ b/www/common/media-tag.js @@ -125,7 +125,6 @@ var factory = function () { }; var makeProgressBar = function (cfg, mediaObject) { - // XXX CSP: we'll need to add style in cryptpad's less if (mediaObject.bar) { return; } mediaObject.bar = true; var style = (function(){/* @@ -151,10 +150,10 @@ var factory = function () { } .mediatag-progress-text { height: 25px; + width: 50px; margin-left: 5px; line-height: 25px; vertical-align: top; - width: auto; display: inline-block; color: #3F4141; font-weight: bold; @@ -618,6 +617,7 @@ var factory = function () { var handlers = cfg.handlers || { 'progress': [], 'complete': [], + 'metadata': [], 'error': [] }; @@ -700,8 +700,7 @@ var factory = function () { if (errDecryption) { return void reject(errDecryption); } - // XXX emit 'metadata' u8Decrypted.metadata - // Cache and display the decrypted blob + emit('metadata', u8Decrypted.metadata); resolve(u8Decrypted); }, function (progress) { emit('progress', { @@ -736,21 +735,18 @@ var factory = function () { var maxSize = typeof(config.maxDownloadSize) === "number" ? config.maxDownloadSize : (5 * 1024 * 1024); - getFileSize(src, function (err, size) { - if (err) { - return void error(err); - } - // If the size is smaller than the autodownload limit, load the blob. - // If the blob is already loaded or being loaded, don't show the button. - if (!size || size < maxSize || getCache()) { - makeProgressBar(cfg, mediaObject); - return void dl(); - } - var sizeMb = Math.round(10 * size / 1024 / 1024) / 10; - fetchDecryptedMetadata(src, strKey, function (err, md) { - if (err) { return void error(err); } - cfg.metadata = md; - // XXX emit 'metadata' + fetchDecryptedMetadata(src, strKey, function (err, md) { + if (err) { return void error(err); } + cfg.metadata = md; + emit('metadata', md); + getFileSize(src, function (err, size) { + // If the size is smaller than the autodownload limit, load the blob. + // If the blob is already loaded or being loaded, don't show the button. + if (!size || size < maxSize || getCache()) { + makeProgressBar(cfg, mediaObject); + return void dl(); + } + var sizeMb = Math.round(10 * size / 1024 / 1024) / 10; makeDownloadButton(cfg, mediaObject, sizeMb, dl); }); }); @@ -765,6 +761,8 @@ var factory = function () { config[key] = value; }; + init.fetchDecryptedMetadata = fetchDecryptedMetadata; + return init; }; diff --git a/www/file/app-file.less b/www/file/app-file.less index d6e8ad69c..be7e067f1 100644 --- a/www/file/app-file.less +++ b/www/file/app-file.less @@ -1,5 +1,6 @@ @import (reference) '../../customize/src/less2/include/tokenfield.less'; @import (reference) '../../customize/src/less2/include/framework.less'; +@import (reference) '../../customize/src/less2/include/markdown.less'; &.cp-app-file { @@ -47,6 +48,7 @@ z-index: -1; } + .mediatag_cryptpad(); media-tag { img { max-width: 100%; @@ -198,6 +200,9 @@ max-height: 100%; max-width: 100%; } + &:empty { + display: none !important; + } } } diff --git a/www/file/file-crypto.js b/www/file/file-crypto.js index 12453bb48..6a0c08816 100644 --- a/www/file/file-crypto.js +++ b/www/file/file-crypto.js @@ -48,69 +48,6 @@ define([ return new Blob(chunks); }; - var concatBuffer = function (a, b) { // TODO make this not so ugly - return new Uint8Array(slice(a).concat(slice(b))); - }; - - var fetchMetadata = function (src, cb) { - var done = false; - var CB = function (err, res) { - if (done) { return; } - done = true; - cb(err, res); - }; - - var xhr = new XMLHttpRequest(); - xhr.open("GET", src, true); - xhr.setRequestHeader('Range', 'bytes=0-1'); - xhr.responseType = 'arraybuffer'; - - xhr.onerror= function () { return CB('XHR_ERROR'); }; - xhr.onload = function () { - if (/^4/.test('' + this.status)) { return CB('XHR_ERROR'); } - var res = new Uint8Array(xhr.response); - var size = decodePrefix(res); - var xhr2 = new XMLHttpRequest(); - - xhr2.open("GET", src, true); - xhr2.setRequestHeader('Range', 'bytes=2-' + (size + 2)); - xhr2.responseType = 'arraybuffer'; - xhr2.onload = function () { - if (/^4/.test('' + this.status)) { return CB('XHR_ERROR'); } - var res2 = new Uint8Array(xhr2.response); - var all = concatBuffer(res, res2); - CB(void 0, all); - }; - xhr2.send(null); - }; - xhr.send(null); - }; - - var decryptMetadata = function (u8, key) { - var prefix = u8.subarray(0, 2); - var metadataLength = decodePrefix(prefix); - - var metaBox = new Uint8Array(u8.subarray(2, 2 + metadataLength)); - var metaChunk = Nacl.secretbox.open(metaBox, createNonce(), key); - - try { - return JSON.parse(Nacl.util.encodeUTF8(metaChunk)); - } - catch (e) { return null; } - }; - - var fetchDecryptedMetadata = function (src, key, cb) { - if (typeof(src) !== 'string') { - return window.setTimeout(function () { - cb('NO_SOURCE'); - }); - } - fetchMetadata(src, function (e, buffer) { - if (e) { return cb(e); } - cb(void 0, decryptMetadata(buffer, key)); - }); - }; - var decrypt = function (u8, key, done, progress) { var MAX = u8.length; var _progress = function (offset) { @@ -268,8 +205,5 @@ define([ encrypt: encrypt, joinChunks: joinChunks, computeEncryptedSize: computeEncryptedSize, - decryptMetadata: decryptMetadata, - fetchMetadata: fetchMetadata, - fetchDecryptedMetadata: fetchDecryptedMetadata, }; }); diff --git a/www/file/inner.html b/www/file/inner.html index a3e5070e4..45ba831fe 100644 --- a/www/file/inner.html +++ b/www/file/inner.html @@ -16,7 +16,6 @@
- diff --git a/www/file/inner.js b/www/file/inner.js index 6cc129de5..59291c964 100644 --- a/www/file/inner.js +++ b/www/file/inner.js @@ -43,7 +43,6 @@ define([ var andThen = function (common) { var $appContainer = $('#cp-app-file-content'); var $form = $('#cp-app-file-upload-form'); - var $dlform = $('#cp-app-file-download-form'); var $dlview = $('#cp-app-file-download-view'); var $label = $form.find('label'); var $bar = $('.cp-toolbar-container'); @@ -86,36 +85,44 @@ define([ if (!uploadMode) { (function () { - Messages.download = "Download"; // XXX - Messages.decrypt = "Decrypt"; // XXX - - 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 - ]); - var hexFileName = secret.channel; var src = fileHost + Hash.getBlobPathFromHex(hexFileName); var key = secret.keys && secret.keys.cryptKey; var cryptKey = Nacl.util.encodeBase64(key); - 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 $mt = $dlview.find('media-tag'); + $mt.attr('src', src); + $mt.attr('data-crypto-key', 'cryptpad:'+cryptKey); + $mt.css('transform', 'scale(2)'); + + var rightsideDisplayed = false; + var metadataReceived = false; + UI.removeLoadingScreen(); + $dlview.show(); + + MediaTag($mt[0]).on('complete', function (decrypted) { + $mt.css('transform', ''); + 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(); + $('media-tag iframe').css({ + 'height': 'calc(100vh - ' + toolbarHeight + 'px)', + 'width': '100vw', + 'position': 'absolute', + 'bottom': 0, + 'left': 0, + 'border': 0 + }); + }).on('metadata', function (metadata) { + if (metadataReceived) { return; } + metadataReceived = true; // Add pad attributes when the file is saved in the drive Title.onTitleChange(function () { var owners = metadata.owners; @@ -150,106 +157,11 @@ define([ 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], { - force: true // Download starts automatically - }).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); - $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); - }); + }).on('error', function (err) { + $appContainer.css('background-color', + common.getAppConfig().appBackgroundColor); + UI.warn(Messages.error); + console.error(err); }); })(); return; diff --git a/www/pad/inner.js b/www/pad/inner.js index f9b5dffb9..a22c0d81d 100644 --- a/www/pad/inner.js +++ b/www/pad/inner.js @@ -478,7 +478,7 @@ define([ var list_values = slice(el.children) .map(function (el) { return el.outerHTML; }) .join(''); - mediaMap[el.getAttribute('src')] = list_values; + mediaTagMap[el.getAttribute('src')] = list_values; if (mediaObject.complete) { observer.disconnect(); } } }); From 7b4a89be92b7c8fbe41cc212c86bf6eb74304209 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 3 Dec 2020 15:36:29 +0100 Subject: [PATCH 42/51] Handle cached mediatags in preview modal --- customize.dist/src/less2/include/markdown.less | 3 +++ www/common/inner/common-mediatag.js | 9 ++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/customize.dist/src/less2/include/markdown.less b/customize.dist/src/less2/include/markdown.less index f88d8ca7c..677ec13a3 100644 --- a/customize.dist/src/less2/include/markdown.less +++ b/customize.dist/src/less2/include/markdown.less @@ -66,6 +66,9 @@ .mediatag_cryptpad() { media-tag { + &:empty { + display: none !important; + } cursor: pointer; * { max-width: 100%; diff --git a/www/common/inner/common-mediatag.js b/www/common/inner/common-mediatag.js index eaf640b0d..bbc149d5b 100644 --- a/www/common/inner/common-mediatag.js +++ b/www/common/inner/common-mediatag.js @@ -249,7 +249,6 @@ define([ var locked = false; var show = function (_i) { if (locked) { return; } - locked = true; if (_i < 0) { i = 0; } else if (_i > tags.length -1) { i = tags.length - 1; } else { i = _i; } @@ -293,7 +292,6 @@ define([ if (_key) { key = 'cryptpad:' + Nacl.util.encodeBase64(_key); } } if (!src || !key) { - locked = false; $spinner.hide(); return void UI.log(Messages.error); } @@ -307,13 +305,18 @@ define([ locked = false; $spinner.hide(); UI.log(Messages.error); + }).on('progress', function () { + $spinner.hide(); + locked = true; + }).on('complete', function () { + locked = false; + $spinner.hide(); }); }); } var observer = new MutationObserver(function(mutations) { mutations.forEach(function() { - locked = false; $spinner.hide(); }); }); From a857a0e3d4e2f6b14cd45c251111ec4bb9a6e475 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 3 Dec 2020 15:50:20 +0100 Subject: [PATCH 43/51] Fix upload table z-index --- customize.dist/src/less2/include/fileupload.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/customize.dist/src/less2/include/fileupload.less b/customize.dist/src/less2/include/fileupload.less index 8fb1c8857..0353b06eb 100644 --- a/customize.dist/src/less2/include/fileupload.less +++ b/customize.dist/src/less2/include/fileupload.less @@ -14,7 +14,7 @@ right: 10vw; bottom: 10vh; box-sizing: border-box; - z-index: 100000; //Z file upload table container + z-index: 100001; //Z file upload table container: just above the file picker display: none; color: darken(@colortheme_drive-bg, 10%); max-height: 180px; From f816a54cd47c2e569e35262af1ac3f10d403fff2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Benqu=C3=A9?= Date: Fri, 4 Dec 2020 13:54:17 +0000 Subject: [PATCH 44/51] Remove XXX related to translation keys --- www/common/inner/common-mediatag.js | 6 ------ www/common/sframe-common-file.js | 3 --- www/common/toolbar.js | 1 - www/settings/inner.js | 2 -- www/whiteboard/inner.js | 2 +- 5 files changed, 1 insertion(+), 13 deletions(-) diff --git a/www/common/inner/common-mediatag.js b/www/common/inner/common-mediatag.js index bbc149d5b..80195c71c 100644 --- a/www/common/inner/common-mediatag.js +++ b/www/common/inner/common-mediatag.js @@ -22,8 +22,6 @@ define([ MediaTag.setDefaultConfig('pdf', { viewer: '/common/pdfjs/web/viewer.html' }); - Messages.mediatag_saveButton = "Save"; // XXX - Messages.mediatag_loadButton = "Load attachment"; // XXX MediaTag.setDefaultConfig('download', { text: Messages.mediatag_saveButton, textDl: Messages.mediatag_loadButton, @@ -373,10 +371,6 @@ define([ }); }; - Messages.pad_mediatagShare = "Share file"; // XXX - Messages.pad_mediatagOpen = "Open file"; // XXX - Messages.mediatag_notReady = "Not ready"; // XXX - var mediatagContextMenu; MT.importMediaTagMenu = function (common) { if (mediatagContextMenu) { return mediatagContextMenu; } diff --git a/www/common/sframe-common-file.js b/www/common/sframe-common-file.js index c10af2770..7b8601f13 100644 --- a/www/common/sframe-common-file.js +++ b/www/common/sframe-common-file.js @@ -47,7 +47,6 @@ 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.fileTableHeader)), h('div.cp-fileupload-header-close', h('span.fa.fa-times')), @@ -626,8 +625,6 @@ 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)) { diff --git a/www/common/toolbar.js b/www/common/toolbar.js index e1555279e..20cac8337 100644 --- a/www/common/toolbar.js +++ b/www/common/toolbar.js @@ -1394,7 +1394,6 @@ MessengerUI, Messages) { toolbar.isErrorState = bool; // Stop kickSpinner toolbar.title.toggleClass('cp-toolbar-unsync', bool); // "read only" next to the title if (bool && toolbar.spinner) { - Messages.offline = "OFFLINE"; // XXX toolbar.spinner.text(Messages.offline); } else { kickSpinner(toolbar, config); diff --git a/www/settings/inner.js b/www/settings/inner.js index 9051d3c34..8b5095cba 100644 --- a/www/settings/inner.js +++ b/www/settings/inner.js @@ -577,8 +577,6 @@ define([ cb(form); }, true); - Messages.settings_mediatagSizeTitle = "Autodownload size in MegaBytes (MB)"; // XXX - Messages.settings_mediatagSizeHint = 'Maximum size for automatically loading media elements (images, videos, pdf) embedded into the pads. Elements bigger than the specified size can be loaded manually. Use "-1" to always load the media elements automatically.'; // XXX makeBlock('mediatag-size', function(cb) { var $inputBlock = $('
', { 'class': 'cp-sidebarlayout-input-block', diff --git a/www/whiteboard/inner.js b/www/whiteboard/inner.js index bab1603dc..30c43aef1 100644 --- a/www/whiteboard/inner.js +++ b/www/whiteboard/inner.js @@ -335,7 +335,7 @@ define([ var maxSizeStr = Messages._getKey('formattedMB', [Util.bytesToMegabytes(MAX_IMAGE_SIZE)]); var addImageToCanvas = function (img) { if (img.src && img.src.length > MAX_IMAGE_SIZE) { - UI.warn(Messages._getKey('upload_tooLargeBrief', [maxSizeStr])); // XXX update key + UI.warn(Messages._getKey('upload_tooLargeBrief', [maxSizeStr])); return; } var w = img.width; From 69664dc0ef87963d531cd4483a205d548e5b9750 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 7 Dec 2020 11:26:08 +0100 Subject: [PATCH 45/51] Only allow blob URLs in restricted tags --- www/common/diffMarked.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/diffMarked.js b/www/common/diffMarked.js index 3d5704584..2bbd51dde 100644 --- a/www/common/diffMarked.js +++ b/www/common/diffMarked.js @@ -358,7 +358,7 @@ define([ // Only allow iframe, video and audio with local source var checkSrc = function (root) { if (restrictedTags.indexOf(root.nodeName.toUpperCase()) === -1) { return true; } - return root.getAttribute && /^(blob\:|\/)/.test(root.getAttribute('src')); + return root.getAttribute && /^blob\:/.test(root.getAttribute('src')); }; var removeForbiddenTags = function (root) { From 3e673bfd0560c06a30e9cedeb2ecd61c36f10ed2 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 7 Dec 2020 11:53:44 +0100 Subject: [PATCH 46/51] Don't store a copy of owned pads in your own drive --- 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 687326139..65078b287 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -1137,7 +1137,7 @@ define([ var ownedByMe = Array.isArray(owners) && owners.indexOf(edPublic) !== -1; // Add the pad if it does not exist in our drive - if (!contains || (ownedByMe && !inMyDrive)) { + if (!contains) { // || (ownedByMe && !inMyDrive)) { var autoStore = Util.find(store.proxy, ['settings', 'general', 'autostore']); if (autoStore !== 1 && !data.forceSave && !data.path && !ownedByMe) { // send event to inner to display the corner popup From 2647acbb78643e651b71d2d4f74c2f66e264a258 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 7 Dec 2020 15:42:25 +0100 Subject: [PATCH 47/51] Expose Content-Length header --- docs/example.nginx.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/example.nginx.conf b/docs/example.nginx.conf index 8319c657b..0a2edcf57 100644 --- a/docs/example.nginx.conf +++ b/docs/example.nginx.conf @@ -177,8 +177,8 @@ server { add_header Cache-Control max-age=31536000; add_header 'Access-Control-Allow-Origin' '*'; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; - add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range'; - add_header 'Access-Control-Expose-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range'; + add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range,Content-Length'; + add_header 'Access-Control-Expose-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range,Content-Length'; try_files $uri =404; } From 0aef54be6257bbceeefef73f53de19c521dbb102 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 7 Dec 2020 15:51:58 +0100 Subject: [PATCH 48/51] Clear cache with 'burn this anon drive' --- www/drive/main.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/www/drive/main.js b/www/drive/main.js index ea6345b02..0f2408f5b 100644 --- a/www/drive/main.js +++ b/www/drive/main.js @@ -54,7 +54,13 @@ define([ if (Utils.LocalStore.isLoggedIn()) { return; } Utils.LocalStore.setFSHash(''); Utils.LocalStore.clearThumbnail(); - window.location.reload(); + try { + Utils.Cache.clear(function () { + window.location.reload(); + }); + } catch (e) { + window.location.reload(); + } }); sframeChan.on('Q_DRIVE_USEROBJECT', function (data, cb) { Cryptpad.userObjectCommand(data, cb); From e3eef3abafb7cd2ef567abe6e73b6145001bc283 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 7 Dec 2020 16:21:33 +0100 Subject: [PATCH 49/51] Hide unnecesssary error messages from the console --- customize.dist/loading.js | 2 +- www/common/media-tag.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/customize.dist/loading.js b/customize.dist/loading.js index 22dc69c31..6e4781aaf 100644 --- a/customize.dist/loading.js +++ b/customize.dist/loading.js @@ -330,7 +330,7 @@ button:not(.btn).primary:hover{ var el3 = document.querySelector('.cp-loading-progress-container'); if (el3) { el3.innerHTML = makeBar(data); } } catch (e) { - if (!hasErrored) { console.error(e); } + //if (!hasErrored) { console.error(e); } } }; window.CryptPad_updateLoadingProgress = updateLoadingProgress; diff --git a/www/common/media-tag.js b/www/common/media-tag.js index a00377ff4..c328399c5 100644 --- a/www/common/media-tag.js +++ b/www/common/media-tag.js @@ -718,7 +718,7 @@ var factory = function () { if (cache[uid].mt !== mediaObject) { // Add progress for other instances of this tag cache[uid].mt.on('progress', function (obj) { - if (!mediaObject.bar) { makeProgressBar(cfg, mediaObject); } + if (!mediaObject.bar && !cfg.force) { makeProgressBar(cfg, mediaObject); } emit('progress', { progress: obj.progress }); From 92df689352508442280a552af579855086af0006 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 7 Dec 2020 16:21:45 +0100 Subject: [PATCH 50/51] Fix debug app with no hash --- www/common/sframe-chainpad-netflux-outer.js | 1 + www/debug/main.js | 2 ++ 2 files changed, 3 insertions(+) diff --git a/www/common/sframe-chainpad-netflux-outer.js b/www/common/sframe-chainpad-netflux-outer.js index c19299da2..f47dc812a 100644 --- a/www/common/sframe-chainpad-netflux-outer.js +++ b/www/common/sframe-chainpad-netflux-outer.js @@ -46,6 +46,7 @@ define([], function () { // shim between chainpad and netflux var msgIn = function (peer, msg) { try { + if (/^\[/.test(msg)) { return msg; } // Already decrypted var isHk = peer.length !== 32; var key = isNewHash ? validateKey : false; var decryptedMsg = Crypto.decrypt(msg, key, isHk); diff --git a/www/debug/main.js b/www/debug/main.js index b901beec0..af3d78a57 100644 --- a/www/debug/main.js +++ b/www/debug/main.js @@ -28,6 +28,8 @@ define([ // Loaded in load #2 nThen(function (waitFor) { $(waitFor()); + }).nThen(function (waitFor) { + SFCommonO.initIframe(waitFor); }).nThen(function (waitFor) { var req = { cfg: requireConfig, From f9c4387a6893707b19347b929d3a2c6677563a74 Mon Sep 17 00:00:00 2001 From: yflory Date: Mon, 7 Dec 2020 16:39:58 +0100 Subject: [PATCH 51/51] Fix rich text mediatag cache --- www/pad/inner.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/www/pad/inner.js b/www/pad/inner.js index a22c0d81d..4968cf4cd 100644 --- a/www/pad/inner.js +++ b/www/pad/inner.js @@ -498,9 +498,10 @@ define([ Array.prototype.slice.call(tags).forEach(function(tag) { var src = tag.getAttribute('src'); if (mediaTagMap[src]) { - mediaTagMap[src].forEach(function(n) { + tag.innerHTML = mediaTagMap[src]; + /*mediaTagMap[src].forEach(function(n) { tag.appendChild(n.cloneNode(true)); - }); + });*/ } }); };