Merge branch 'cacheRT' into staging

pull/1/head
yflory 4 years ago
commit 7c187f92cb

@ -213,3 +213,54 @@ media-tag * {
width: 100%; width: 100%;
height: 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;
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.btn b {
margin-left: 5px;
}
media-tag button.btn .fa {
display: inline;
margin-right: 5px;
}

@ -241,7 +241,7 @@ p.cp-password-info{
animation-timing-function: cubic-bezier(.6,0.15,0.4,0.85); animation-timing-function: cubic-bezier(.6,0.15,0.4,0.85);
} }
button.primary{ button:not(.btn).primary{
border: 1px solid #4591c4; border: 1px solid #4591c4;
padding: 8px 12px; padding: 8px 12px;
text-transform: uppercase; text-transform: uppercase;
@ -250,7 +250,7 @@ button.primary{
font-weight: bold; font-weight: bold;
} }
button.primary:hover{ button:not(.btn).primary:hover{
background-color: rgb(52, 118, 162); background-color: rgb(52, 118, 162);
} }
@ -279,7 +279,7 @@ button.primary:hover{
var built = false; var built = false;
var types = ['less', 'drive', 'migrate', 'sf', 'team', 'pad', 'end']; var types = ['less', 'drive', 'migrate', 'sf', 'team', 'pad', 'end'];
var current; var current, progress;
var makeList = function (data) { var makeList = function (data) {
var c = types.indexOf(data.type); var c = types.indexOf(data.type);
current = c; current = c;
@ -295,7 +295,7 @@ button.primary:hover{
}; };
var list = '<ul>'; var list = '<ul>';
types.forEach(function (el, i) { types.forEach(function (el, i) {
if (i >= 6) { return; } if (el === "end") { return; }
list += getLi(i); list += getLi(i);
}); });
list += '</ul>'; list += '</ul>';
@ -303,7 +303,7 @@ button.primary:hover{
}; };
var makeBar = function (data) { var makeBar = function (data) {
var c = types.indexOf(data.type); 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 progress = Math.min(data.progress, 100);
var p = (progress / l) + (100 * c / l); var p = (progress / l) + (100 * c / l);
var bar = '<div class="cp-loading-progress-bar">'+ var bar = '<div class="cp-loading-progress-bar">'+
@ -315,14 +315,22 @@ button.primary:hover{
var hasErrored = false; var hasErrored = false;
var updateLoadingProgress = function (data) { var updateLoadingProgress = function (data) {
if (!built || !data) { return; } if (!built || !data) { return; }
// Make sure progress doesn't go backward
var c = types.indexOf(data.type); var c = types.indexOf(data.type);
if (c < current) { return console.error(data); } if (c < current) { return console.error(data); }
if (c === current && progress > data.progress) { return console.error(data); }
progress = data.progress;
try { try {
document.querySelector('.cp-loading-spinner-container').style.display = 'none'; var el1 = document.querySelector('.cp-loading-spinner-container');
document.querySelector('.cp-loading-progress-list').innerHTML = makeList(data); if (el1) { el1.style.display = 'none'; }
document.querySelector('.cp-loading-progress-container').innerHTML = makeBar(data); 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) { } catch (e) {
if (!hasErrored) { console.error(e); } //if (!hasErrored) { console.error(e); }
} }
}; };
window.CryptPad_updateLoadingProgress = updateLoadingProgress; window.CryptPad_updateLoadingProgress = updateLoadingProgress;

@ -14,7 +14,7 @@
right: 10vw; right: 10vw;
bottom: 10vh; bottom: 10vh;
box-sizing: border-box; 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; display: none;
color: darken(@colortheme_drive-bg, 10%); color: darken(@colortheme_drive-bg, 10%);
max-height: 180px; max-height: 180px;

@ -2,6 +2,10 @@
@import (reference) "./variables.less"; @import (reference) "./variables.less";
.forms_main() { .forms_main() {
--LessLoader_require: LessLoader_currentFile();
}
& {
@alertify-fore: @colortheme_modal-fg; @alertify-fore: @colortheme_modal-fg;
@alertify-btn-fg: @alertify-fore; @alertify-btn-fg: @alertify-fore;
@alertify-light-bg: fade(@alertify-fore, 25%); @alertify-light-bg: fade(@alertify-fore, 25%);
@ -124,6 +128,14 @@
font-weight: bold; font-weight: bold;
} }
&.btn-default {
border-color: @cryptpad_text_col;
color: @cryptpad_text_col;
&:hover, &:active, &:focus {
background-color: #ccc;
}
}
&.danger, &.btn-danger { &.danger, &.btn-danger {
background-color: @colortheme_alertify-red; background-color: @colortheme_alertify-red;
border-color: @colortheme_alertify-red-border; border-color: @colortheme_alertify-red-border;

@ -64,6 +64,54 @@
} }
} }
.mediatag_cryptpad() {
media-tag {
&:empty {
display: none !important;
}
cursor: pointer;
* {
max-width: 100%;
}
iframe[src$=".pdf"] {
width: 100%;
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;
}
b {
margin-left: 5px;
}
}
}
media-tag:empty {
width: 100px;
height: 100px;
display: inline-block;
border: 1px solid #BBB;
}
}
.markdown_cryptpad() { .markdown_cryptpad() {
word-wrap: break-word; word-wrap: break-word;
@ -84,23 +132,8 @@
margin-top: 4px; margin-top: 4px;
} }
} }
media-tag {
cursor: pointer; .mediatag_cryptpad();
* {
max-width: 100%;
}
iframe[src$=".pdf"] {
width: 100%;
height: 80vh;
max-height: 90vh;
}
}
media-tag:empty {
width: 100px;
height: 100px;
display: inline-block;
border: 1px solid #BBB;
}
pre.markmap { pre.markmap {
border: 1px solid #ddd; border: 1px solid #ddd;

@ -1,6 +1,7 @@
@import (reference) "./colortheme-all.less"; @import (reference) "./colortheme-all.less";
@import (reference) "./variables.less"; @import (reference) "./variables.less";
@import (reference) "./browser.less"; @import (reference) "./browser.less";
@import (reference) "./markdown.less";
.modals-ui-elements_main() { .modals-ui-elements_main() {
--LessLoader_require: LessLoader_currentFile(); --LessLoader_require: LessLoader_currentFile();
@ -214,6 +215,7 @@
flex: 1; flex: 1;
min-width: 0; min-width: 0;
overflow: auto; overflow: auto;
.mediatag_cryptpad();
media-tag { media-tag {
& > * { & > * {
max-width: 100%; max-width: 100%;

@ -118,7 +118,7 @@
//border-radius: 0 0.25em 0.25em 0; //border-radius: 0 0.25em 0.25em 0;
//border: 1px solid #adadad; //border: 1px solid #adadad;
border-left: 0px; border-left: 0px;
height: @variables_input-height; height: 40px;
margin: 0 !important; margin: 0 !important;
} }
} }

@ -177,8 +177,8 @@ server {
add_header Cache-Control max-age=31536000; add_header Cache-Control max-age=31536000;
add_header 'Access-Control-Allow-Origin' '*'; add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; 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-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'; 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; try_files $uri =404;
} }

@ -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 // 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 (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 // Since last 2 checkpoints
if (!lastKnownHash) { if (!lastKnownHash) {
waitFor.abort();
// Less than 2 checkpoints in the history: return everything // Less than 2 checkpoints in the history: return everything
if (index.cpIndex.length < 2) { return void cb(null, 0); } if (index.cpIndex.length < 2) { return void cb(null, 0); }
// Otherwise return the second last checkpoint's index // Otherwise return the second last checkpoint's index
@ -436,7 +438,16 @@ const getHistoryOffset = (Env, channelName, lastKnownHash, _cb) => {
to reconcile their differences. */ 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"
// 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);
})); }));
}).nThen((w) => { }).nThen((w) => {
// skip past this block if the offset is anything other than -1 // skip past this block if the offset is anything other than -1

@ -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) { app.use(function (req, res, next) {
if (req.method === 'OPTIONS' && /\/blob\//.test(req.url)) { if (req.method === 'OPTIONS' && /\/blob\//.test(req.url)) {
res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Origin', '*');

@ -65,12 +65,13 @@ define([
switch (e.which) { switch (e.which) {
case 27: // cancel case 27: // cancel
if (typeof(no) === 'function') { no(e); } if (typeof(no) === 'function') { no(e); }
$(el || window).off('keydown', handler);
break; break;
case 13: // enter case 13: // enter
if (typeof(yes) === 'function') { yes(e); } if (typeof(yes) === 'function') { yes(e); }
$(el || window).off('keydown', handler);
break; break;
} }
$(el || window).off('keydown', handler);
}; };
$(el || window).keydown(handler); $(el || window).keydown(handler);

@ -3,9 +3,9 @@ define([
'/common/common-util.js', '/common/common-util.js',
'/common/visible.js', '/common/visible.js',
'/common/common-hash.js', '/common/common-hash.js',
'/file/file-crypto.js', '/common/media-tag.js',
'/bower_components/tweetnacl/nacl-fast.min.js', '/bower_components/tweetnacl/nacl-fast.min.js',
], function ($, Util, Visible, Hash, FileCrypto) { ], function ($, Util, Visible, Hash, MediaTag) {
var Nacl = window.nacl; var Nacl = window.nacl;
var Thumb = { var Thumb = {
dimension: 100, dimension: 100,
@ -314,7 +314,7 @@ define([
var hexFileName = secret.channel; var hexFileName = secret.channel;
var src = fileHost + Hash.getBlobPathFromHex(hexFileName); var src = fileHost + Hash.getBlobPathFromHex(hexFileName);
var key = secret.keys && secret.keys.cryptKey; 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) {
if (e === 'XHR_ERROR') { return; } if (e === 'XHR_ERROR') { return; }
return console.error(e); return console.error(e);

@ -274,10 +274,30 @@
// given a path, asynchronously return an arraybuffer // given a path, asynchronously return an arraybuffer
Util.fetch = function (src, cb, progress) { var getCacheKey = function (src) {
var CB = Util.once(cb); 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 xhr = new XMLHttpRequest(); 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;
var fetch = function () {
xhr = new XMLHttpRequest();
xhr.open("GET", src, true); xhr.open("GET", src, true);
if (progress) { if (progress) {
xhr.addEventListener("progress", function (evt) { xhr.addEventListener("progress", function (evt) {
@ -293,11 +313,36 @@
if (/^4/.test(''+this.status)) { if (/^4/.test(''+this.status)) {
return CB('XHR_ERROR'); return CB('XHR_ERROR');
} }
return void CB(void 0, new Uint8Array(xhr.response));
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 && xhr.abort) { xhr.abort(); }
}
};
};
Util.dataURIToBlob = function (dataURI) { Util.dataURIToBlob = function (dataURI) {
var byteString = atob(dataURI.split(',')[1]); var byteString = atob(dataURI.split(',')[1]);
var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]; var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];

@ -6,10 +6,11 @@ define([
'/common/common-hash.js', '/common/common-hash.js',
'/common/common-realtime.js', '/common/common-realtime.js',
'/common/outer/network-config.js', '/common/outer/network-config.js',
'/common/outer/cache-store.js',
'/common/pinpad.js', '/common/pinpad.js',
'/bower_components/nthen/index.js', '/bower_components/nthen/index.js',
'/bower_components/chainpad/chainpad.dist.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) { var finish = function (S, err, doc) {
if (S.done) { return; } if (S.done) { return; }
S.cb(err, doc); S.cb(err, doc);
@ -92,7 +93,8 @@ define([
validateKey: secret.keys.validateKey || undefined, validateKey: secret.keys.validateKey || undefined,
crypto: Crypto.createEncryptor(secret.keys), crypto: Crypto.createEncryptor(secret.keys),
logLevel: 0, logLevel: 0,
initialState: opt.initialState initialState: opt.initialState,
Cache: Cache
}; };
return config; return config;
}; };
@ -132,9 +134,11 @@ define([
}; };
config.onError = function (info) { config.onError = function (info) {
console.warn(info);
finish(Session, info.error); finish(Session, info.error);
}; };
config.onChannelError = function (info) { config.onChannelError = function (info) {
console.error(info);
finish(Session, info.error); finish(Session, info.error);
}; };

@ -3,6 +3,7 @@ define([
'/customize/messages.js', '/customize/messages.js',
'/common/common-util.js', '/common/common-util.js',
'/common/common-hash.js', '/common/common-hash.js',
'/common/outer/cache-store.js',
'/common/common-messaging.js', '/common/common-messaging.js',
'/common/common-constants.js', '/common/common-constants.js',
'/common/common-feedback.js', '/common/common-feedback.js',
@ -14,7 +15,7 @@ define([
'/customize/application_config.js', '/customize/application_config.js',
'/bower_components/nthen/index.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, Messaging, Constants, Feedback, Visible, UserObject, LocalStore, Channel, Block,
AppConfig, Nthen) { AppConfig, Nthen) {
@ -701,7 +702,7 @@ define([
}); });
}; };
common.useFile = function (Crypt, cb, optsPut) { common.useFile = function (Crypt, cb, optsPut, onProgress) {
var fileHost = Config.fileHost || window.location.origin; var fileHost = Config.fileHost || window.location.origin;
var data = common.fromFileData; var data = common.fromFileData;
var parsed = Hash.parsePadUrl(data.href); var parsed = Hash.parsePadUrl(data.href);
@ -758,7 +759,9 @@ define([
return void cb(err); return void cb(err);
} }
u8 = _u8; u8 = _u8;
})); }), function (progress) {
onProgress(progress * 50);
}, Cache);
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
require(["/file/file-crypto.js"], waitFor(function (FileCrypto) { require(["/file/file-crypto.js"], waitFor(function (FileCrypto) {
FileCrypto.decrypt(u8, key, waitFor(function (err, _res) { FileCrypto.decrypt(u8, key, waitFor(function (err, _res) {
@ -767,7 +770,9 @@ define([
return void cb(err); return void cb(err);
} }
res = _res; res = _res;
})); }), function (progress) {
onProgress(50 + progress * 50);
});
})); }));
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
var ext = Util.parseFilename(data.title).ext; var ext = Util.parseFilename(data.title).ext;
@ -991,6 +996,8 @@ define([
pad.onJoinEvent = Util.mkEvent(); pad.onJoinEvent = Util.mkEvent();
pad.onLeaveEvent = Util.mkEvent(); pad.onLeaveEvent = Util.mkEvent();
pad.onDisconnectEvent = Util.mkEvent(); pad.onDisconnectEvent = Util.mkEvent();
pad.onCacheEvent = Util.mkEvent();
pad.onCacheReadyEvent = Util.mkEvent();
pad.onConnectEvent = Util.mkEvent(); pad.onConnectEvent = Util.mkEvent();
pad.onErrorEvent = Util.mkEvent(); pad.onErrorEvent = Util.mkEvent();
pad.onMetadataEvent = Util.mkEvent(); pad.onMetadataEvent = Util.mkEvent();
@ -1003,6 +1010,10 @@ define([
postMessage("GIVE_PAD_ACCESS", data, cb); postMessage("GIVE_PAD_ACCESS", data, cb);
}; };
common.onCorruptedCache = function (channel) {
postMessage("CORRUPTED_CACHE", channel);
};
common.setPadMetadata = function (data, cb) { common.setPadMetadata = function (data, cb) {
postMessage('SET_PAD_METADATA', data, cb); postMessage('SET_PAD_METADATA', data, cb);
}; };
@ -1956,6 +1967,8 @@ define([
PAD_JOIN: common.padRpc.onJoinEvent.fire, PAD_JOIN: common.padRpc.onJoinEvent.fire,
PAD_LEAVE: common.padRpc.onLeaveEvent.fire, PAD_LEAVE: common.padRpc.onLeaveEvent.fire,
PAD_DISCONNECT: common.padRpc.onDisconnectEvent.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_CONNECT: common.padRpc.onConnectEvent.fire,
PAD_ERROR: common.padRpc.onErrorEvent.fire, PAD_ERROR: common.padRpc.onErrorEvent.fire,
PAD_METADATA: common.padRpc.onMetadataEvent.fire, PAD_METADATA: common.padRpc.onMetadataEvent.fire,

@ -303,14 +303,22 @@ define([
return renderParagraph(p); return renderParagraph(p);
}; };
// Note: iframe, video and audio are used in mediatags and are allowed in rich text pads.
var forbiddenTags = [ var forbiddenTags = [
'SCRIPT', 'SCRIPT',
'IFRAME', //'IFRAME',
'OBJECT', 'OBJECT',
'APPLET', 'APPLET',
'VIDEO', // privacy implications of videos are the same as images //'VIDEO', // privacy implications of videos are the same as images
'AUDIO', // same with audio //'AUDIO', // same with audio
'SOURCE'
];
var restrictedTags = [
'IFRAME',
'VIDEO',
'AUDIO'
]; ];
var unsafeTag = function (info) { var unsafeTag = function (info) {
/*if (info.node && $(info.node).parents('media-tag').length) { /*if (info.node && $(info.node).parents('media-tag').length) {
// Do not remove elements inside a media-tag // Do not remove elements inside a media-tag
@ -347,9 +355,16 @@ define([
parent.removeChild(node); 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) { var removeForbiddenTags = function (root) {
if (!root) { return; } if (!root) { return; }
if (forbiddenTags.indexOf(root.nodeName.toUpperCase()) !== -1) { removeNode(root); } if (forbiddenTags.indexOf(root.nodeName.toUpperCase()) !== -1) { removeNode(root); }
if (!checkSrc(root)) { removeNode(root); }
slice(root.children).forEach(removeForbiddenTags); slice(root.children).forEach(removeForbiddenTags);
}; };
@ -658,7 +673,7 @@ define([
$(contextMenu.menu).find('li').show(); $(contextMenu.menu).find('li').show();
contextMenu.show(e); contextMenu.show(e);
}); });
if ($mt.children().length) { if ($mt.children().length && $mt[0]._mediaObject) {
$mt.off('click dblclick preview'); $mt.off('click dblclick preview');
$mt.on('preview', onPreview($mt)); $mt.on('preview', onPreview($mt));
if ($mt.find('img').length) { if ($mt.find('img').length) {
@ -668,15 +683,15 @@ define([
} }
return; return;
} }
MediaTag(el); var mediaObject = MediaTag(el);
var observer = new MutationObserver(function(mutations) { var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) { mutations.forEach(function(mutation) {
if (mutation.type === 'childList') { if (mutation.type === 'childList') {
var list_values = slice(mutation.target.children) var list_values = slice(el.children)
.map(function (el) { return el.outerHTML; }) .map(function (el) { return el.outerHTML; })
.join(''); .join('');
mediaMap[mutation.target.getAttribute('src')] = list_values; mediaMap[el.getAttribute('src')] = list_values;
observer.disconnect(); if (mediaObject.complete) { observer.disconnect(); }
} }
}); });
$mt.off('click dblclick preview'); $mt.off('click dblclick preview');
@ -689,6 +704,7 @@ define([
}); });
observer.observe(el, { observer.observe(el, {
attributes: false, attributes: false,
subtree: true,
childList: true, childList: true,
characterData: false characterData: false
}); });

@ -42,6 +42,7 @@ define([
var APP = window.APP = { var APP = window.APP = {
editable: false, editable: false,
online: true,
mobile: function () { mobile: function () {
if (window.matchMedia) { return !window.matchMedia('(any-pointer:fine)').matches; } if (window.matchMedia) { return !window.matchMedia('(any-pointer:fine)').matches; }
else { return $('body').width() <= 600; } else { return $('body').width() <= 600; }
@ -267,13 +268,25 @@ define([
}; };
// Handle disconnect/reconnect // 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 (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; APP.editable = !APP.readOnly && state;
if (!state) { if (!state) {
APP.$content.addClass('cp-app-drive-readonly'); APP.$content.addClass('cp-app-drive-readonly');
if (!isHistory) { if (!APP.history || !APP.online) {
$('#cp-app-drive-connection-state').show(); $('#cp-app-drive-connection-state').show();
} else {
$('#cp-app-drive-connection-state').hide();
} }
$('[draggable="true"]').attr('draggable', false); $('[draggable="true"]').attr('draggable', false);
} }
@ -3670,6 +3683,15 @@ define([
} }
var readOnlyFolder = false; 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) { if (APP.readOnly) {
// Read-only drive (team?) // Read-only drive (team?)
$content.prepend($readOnly.clone()); $content.prepend($readOnly.clone());
@ -4149,6 +4171,17 @@ define([
data.name = Util.fixFileName(folderName); data.name = Util.fixFileName(folderName);
data.folderName = Util.fixFileName(folderName) + '.zip'; 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) { APP.FM.downloadFolder(data, function (err, obj) {
console.log(err, obj); console.log(err, obj);
console.log('DONE'); console.log('DONE');

@ -0,0 +1,33 @@
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'); }
cb(null, data);
}, { raw: true });
};
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);
}, { raw: true });
};
return {
getBlobCache: getBlobCache,
setBlobCache: setBlobCache
};
};
return S;
});

@ -17,11 +17,17 @@ define([
var Nacl = window.nacl; var Nacl = window.nacl;
// Configure MediaTags to use our local viewer // 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) { if (MediaTag) {
MediaTag.setDefaultConfig('pdf', { MediaTag.setDefaultConfig('pdf', {
viewer: '/common/pdfjs/web/viewer.html' viewer: '/common/pdfjs/web/viewer.html'
}); });
MediaTag.setDefaultConfig('download', {
text: Messages.mediatag_saveButton,
textDl: Messages.mediatag_loadButton,
});
} }
MT.MediaTag = MediaTag;
// Cache of the avatars outer html (including <media-tag>) // Cache of the avatars outer html (including <media-tag>)
var avatars = {}; var avatars = {};
@ -68,7 +74,7 @@ define([
childList: true, childList: true,
characterData: false characterData: false
}); });
MediaTag($tag[0]).on('error', function (data) { MediaTag($tag[0], {force: true}).on('error', function (data) {
console.error(data); console.error(data);
}); });
}; };
@ -241,7 +247,6 @@ define([
var locked = false; var locked = false;
var show = function (_i) { var show = function (_i) {
if (locked) { return; } if (locked) { return; }
locked = true;
if (_i < 0) { i = 0; } if (_i < 0) { i = 0; }
else if (_i > tags.length -1) { i = tags.length - 1; } else if (_i > tags.length -1) { i = tags.length - 1; }
else { i = _i; } else { i = _i; }
@ -285,7 +290,6 @@ define([
if (_key) { key = 'cryptpad:' + Nacl.util.encodeBase64(_key); } if (_key) { key = 'cryptpad:' + Nacl.util.encodeBase64(_key); }
} }
if (!src || !key) { if (!src || !key) {
locked = false;
$spinner.hide(); $spinner.hide();
return void UI.log(Messages.error); return void UI.log(Messages.error);
} }
@ -299,13 +303,18 @@ define([
locked = false; locked = false;
$spinner.hide(); $spinner.hide();
UI.log(Messages.error); UI.log(Messages.error);
}).on('progress', function () {
$spinner.hide();
locked = true;
}).on('complete', function () {
locked = false;
$spinner.hide();
}); });
}); });
} }
var observer = new MutationObserver(function(mutations) { var observer = new MutationObserver(function(mutations) {
mutations.forEach(function() { mutations.forEach(function() {
locked = false;
$spinner.hide(); $spinner.hide();
}); });
}); });
@ -377,6 +386,14 @@ define([
'tabindex': '-1', 'tabindex': '-1',
'data-icon': "fa-eye", 'data-icon': "fa-eye",
}, Messages.pad_mediatagPreview)), }, 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', { h('li', h('a.cp-app-code-context-saveindrive.dropdown-item', {
'tabindex': '-1', 'tabindex': '-1',
'data-icon': "fa-cloud-upload", 'data-icon': "fa-cloud-upload",
@ -413,12 +430,29 @@ define([
} }
else if ($this.hasClass("cp-app-code-context-download")) { else if ($this.hasClass("cp-app-code-context-download")) {
var media = Util.find($mt, [0, '_mediaObject']); 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); } if (!(media && media._blob)) { return void console.error($mt); }
window.saveAs(media._blob.content, media.name); window.saveAs(media._blob.content, media.name);
} }
else if ($this.hasClass("cp-app-code-context-open")) { else if ($this.hasClass("cp-app-code-context-open")) {
$mt.trigger('preview'); $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; return m;

@ -1,17 +1,18 @@
define([ define([
'jquery', 'jquery',
'/common/cryptget.js',
'/file/file-crypto.js', '/file/file-crypto.js',
'/common/common-hash.js', '/common/common-hash.js',
'/common/common-util.js', '/common/common-util.js',
'/common/common-interface.js', '/common/common-interface.js',
'/common/hyperscript.js', '/common/hyperscript.js',
'/common/common-feedback.js', '/common/common-feedback.js',
'/common/inner/cache.js',
'/customize/messages.js', '/customize/messages.js',
'/bower_components/nthen/index.js', '/bower_components/nthen/index.js',
'/bower_components/saferphore/index.js', '/bower_components/saferphore/index.js',
'/bower_components/jszip/dist/jszip.min.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 saveAs = window.saveAs;
var sanitize = function (str) { var sanitize = function (str) {
@ -53,9 +54,6 @@ define([
var _downloadFile = function (ctx, fData, cb, updateProgress) { var _downloadFile = function (ctx, fData, cb, updateProgress) {
var cancelled = false; var cancelled = false;
var cancel = function () {
cancelled = true;
};
var href = (fData.href && fData.href.indexOf('#') !== -1) ? fData.href : fData.roHref; var href = (fData.href && fData.href.indexOf('#') !== -1) ? fData.href : fData.roHref;
var parsed = Hash.parsePadUrl(href); var parsed = Hash.parsePadUrl(href);
var hash = parsed.hash; var hash = parsed.hash;
@ -63,10 +61,13 @@ define([
var secret = Hash.getSecrets('file', hash, fData.password); var secret = Hash.getSecrets('file', hash, fData.password);
var src = (ctx.fileHost || '') + Hash.getBlobPathFromHex(secret.channel); var src = (ctx.fileHost || '') + Hash.getBlobPathFromHex(secret.channel);
var key = secret.keys && secret.keys.cryptKey; 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 (cancelled) { return; }
if (err) { return void cb('E404'); } 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 (cancelled) { return; }
if (err) { return void cb(err); } if (err) { return void cb(err); }
if (!res.content) { return void cb('EEMPTY'); } if (!res.content) { return void cb('EEMPTY'); }
@ -78,8 +79,25 @@ define([
content: res.content, content: res.content,
download: dl download: dl
}); });
}, updateProgress && updateProgress.progress2); }, function (data) {
}, updateProgress && updateProgress.progress); if (cancelled) { return; }
if (updateProgress && updateProgress.progress2) {
updateProgress.progress2(data);
}
});
}, function (data) {
if (cancelled) { return; }
if (updateProgress && updateProgress.progress) {
updateProgress.progress(data);
}
}, ctx.cache);
var cancel = function () {
cancelled = true;
if (fetchObj && fetchObj.cancel) { fetchObj.cancel(); }
if (decryptObj && decryptObj.cancel) { decryptObj.cancel(); }
};
return { return {
cancel: cancel cancel: cancel
}; };
@ -162,10 +180,10 @@ define([
if (ctx.stop) { return; } if (ctx.stop) { return; }
if (to) { clearTimeout(to); } if (to) { clearTimeout(to); }
//setTimeout(g, 2000); //setTimeout(g, 2000);
g();
w();
ctx.done++; ctx.done++;
ctx.updateProgress('download', {max: ctx.max, current: ctx.done}); ctx.updateProgress('download', {max: ctx.max, current: ctx.done});
g();
w();
}; };
var error = function (err) { var error = function (err) {
@ -274,7 +292,7 @@ define([
}; };
// Main function. Create the empty zip and fill it starting from drive.root // 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'); } if (!data || !data.uo || !data.uo.drive) { return void cb('EEMPTY'); }
var sem = Saferphore.create(5); var sem = Saferphore.create(5);
var ctx = { var ctx = {
@ -288,7 +306,8 @@ define([
sem: sem, sem: sem,
updateProgress: progress, updateProgress: progress,
max: 0, max: 0,
done: 0 done: 0,
cache: cache
}; };
var filesData = data.sharedFolderId && ctx.sf[data.sharedFolderId] ? ctx.sf[data.sharedFolderId].filesData : ctx.data.filesData; var filesData = data.sharedFolderId && ctx.sf[data.sharedFolderId] ? ctx.sf[data.sharedFolderId].filesData : ctx.data.filesData;
progress('reading', -1); progress('reading', -1);
@ -312,13 +331,14 @@ define([
delete ctx.zip; delete ctx.zip;
}; };
return { return {
stop: stop stop: stop,
cancel: stop
}; };
}; };
var _downloadFolder = function (ctx, data, cb, updateProgress) { 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 if (errors && errors.length) { console.error(errors); } // TODO show user errors
var dl = function () { var dl = function () {
saveAs(blob, data.folderName); saveAs(blob, data.folderName);
@ -332,10 +352,13 @@ define([
if (typeof progress.current !== "number") { return; } if (typeof progress.current !== "number") { return; }
updateProgress.folderProgress(progress.current / progress.max); updateProgress.folderProgress(progress.current / progress.max);
} }
else if (state === "compressing") {
updateProgress.folderProgress(2);
}
else if (state === "done") { else if (state === "done") {
updateProgress.folderProgress(1); updateProgress.folderProgress(3);
} }
}); }, ctx.cache);
}; };
var createExportUI = function (origin) { var createExportUI = function (origin) {

@ -1,8 +1,6 @@
(function(name, definition) { (function (window) {
if (typeof module !== 'undefined') { module.exports = definition(); } var factory = function () {
else if (typeof define === 'function' && typeof define.amd === 'object') { define(definition); } var Promise = window.Promise;
else { this[name] = definition(); }
}('MediaTag', function() {
var cache; var cache;
var cypherChunkLength = 131088; var cypherChunkLength = 131088;
@ -63,7 +61,8 @@
], ],
pdf: {}, pdf: {},
download: { download: {
text: "Download" text: "Save",
textDl: "Load attachment"
}, },
Plugins: { Plugins: {
/** /**
@ -114,8 +113,8 @@
}, },
download: function (metadata, url, content, cfg, cb) { download: function (metadata, url, content, cfg, cb) {
var btn = document.createElement('button'); var btn = document.createElement('button');
btn.setAttribute('class', 'btn btn-success'); btn.setAttribute('class', 'btn btn-default');
btn.innerHTML = cfg.download.text + '<br>' + btn.innerHTML = '<i class="fa fa-save"></i>' + cfg.download.text + '<br>' +
(metadata.name ? '<b>' + fixHTML(metadata.name) + '</b>' : ''); (metadata.name ? '<b>' + fixHTML(metadata.name) + '</b>' : '');
btn.addEventListener('click', function () { btn.addEventListener('click', function () {
saveFile(content, url, metadata.name); saveFile(content, url, metadata.name);
@ -125,30 +124,187 @@
} }
}; };
var makeProgressBar = function (cfg, mediaObject) {
if (mediaObject.bar) { return; }
mediaObject.bar = true;
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;
width: 50px;
margin-left: 5px;
line-height: 25px;
vertical-align: top;
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 = '<style>'+style+'</style>';
mediaObject.tag.appendChild(container);
mediaObject.tag.appendChild(text);
};
var makeDownloadButton = function (cfg, mediaObject, size, cb) {
var metadata = cfg.metadata || {};
var i = '<i class="fa fa-paperclip"></i>';
var name = metadata.name ? '<span class="mediatag-download-name">'+ i +'<b>'+
fixHTML(metadata.name)+'</b></span>' : '';
var btn = document.createElement('button');
btn.setAttribute('class', 'btn btn-default mediatag-download-btn');
btn.innerHTML = name + '<span>' + (name ? '' : i) +
cfg.download.textDl + ' <b>(' + size + 'MB)</b></span>';
btn.addEventListener('click', function () {
makeProgressBar(cfg, mediaObject);
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(); }
}
cb();
});
mediaObject.tag.innerHTML = '';
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 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);
cb = function () {};
};
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();
};
if (!cacheKey) { return void check(); }
getBlobCache(cacheKey, function (err, u8) {
if (err || !u8) { return void check(); }
cb(null, 0);
});
};
// Download a blob from href // Download a blob from href
var download = function (src, _cb) { var download = function (src, _cb, progressCb) {
var cb = function (e, res) { var cb = function (e, res) {
_cb(e, res); _cb(e, res);
cb = function () {}; cb = function () {};
}; };
var cacheKey = getCacheKey(src);
var fetch = function () {
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
xhr.open('GET', src, true); xhr.open('GET', src, true);
xhr.responseType = 'arraybuffer'; xhr.responseType = 'arraybuffer';
var progress = function (offset) {
progressCb(offset * 100);
};
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.onerror = function () { return void cb("XHR_ERROR"); };
xhr.onload = function () { xhr.onload = function () {
// Error? // Error?
if (/^4/.test('' + this.status)) { return void cb("XHR_ERROR " + this.status); } if (/^4/.test('' + this.status)) { return void cb("XHR_ERROR " + this.status); }
var arrayBuffer = xhr.response; var arrayBuffer = xhr.response;
if (arrayBuffer) { cb(null, new Uint8Array(arrayBuffer)); } if (arrayBuffer) {
var u8 = new Uint8Array(arrayBuffer);
if (cacheKey) {
return void setBlobCache(cacheKey, u8, function () {
cb(null, u8);
});
}
cb(null, u8);
}
}; };
xhr.send(null); xhr.send(null);
}; };
if (!cacheKey) { return void fetch(); }
getBlobCache(cacheKey, function (err, u8) {
if (err || !u8) { return void fetch(); }
cb(null, u8);
});
};
// Decryption tools // Decryption tools
var Decrypt = { var Decrypt = {
// Create a nonce // Create a nonce
@ -192,6 +348,95 @@
} }
}; };
// 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. // Decrypts a Uint8Array with the given key.
var decrypt = function (u8, strKey, done, progressCb) { var decrypt = function (u8, strKey, done, progressCb) {
var Nacl = window.nacl; var Nacl = window.nacl;
@ -372,6 +617,7 @@
var handlers = cfg.handlers || { var handlers = cfg.handlers || {
'progress': [], 'progress': [],
'complete': [], 'complete': [],
'metadata': [],
'error': [] 'error': []
}; };
@ -422,6 +668,7 @@
// End media-tag rendering: display the tag and emit the event // End media-tag rendering: display the tag and emit the event
var end = function (decrypted) { var end = function (decrypted) {
mediaObject.complete = true;
process(mediaObject, decrypted, cfg, function (err) { process(mediaObject, decrypted, cfg, function (err) {
if (err) { return void emit('error', err); } if (err) { return void emit('error', err); }
mediaObject._blob = decrypted; mediaObject._blob = decrypted;
@ -429,33 +676,79 @@
}); });
}; };
// If we have the blob in our cache, don't download & decrypt it again, just display var error = function (err) {
if (cache[uid]) { mediaObject.tag.innerHTML = '<img style="width: 100px; height: 100px;" src="/images/broken.png">';
end(cache[uid]); emit('error', err);
return mediaObject; };
}
var getCache = function () {
var c = cache[uid];
if (!c || !c.promise || !c.mt) { return; }
return c;
};
var dl = function () {
// Download the encrypted blob // Download the encrypted blob
cache[uid] = getCache() || {
promise: new Promise(function (resolve, reject) {
download(src, function (err, u8Encrypted) { download(src, function (err, u8Encrypted) {
if (err) { if (err) {
if (err === "XHR_ERROR 404") { return void reject(err);
mediaObject.tag.innerHTML = '<img style="width: 100px; height: 100px;" src="/images/broken.png">';
}
return void emit('error', err);
} }
// Decrypt the blob // Decrypt the blob
decrypt(u8Encrypted, strKey, function (errDecryption, u8Decrypted) { decrypt(u8Encrypted, strKey, function (errDecryption, u8Decrypted) {
if (errDecryption) { if (errDecryption) {
return void emit('error', errDecryption); return void reject(errDecryption);
} }
// Cache and display the decrypted blob emit('metadata', u8Decrypted.metadata);
cache[uid] = u8Decrypted; resolve(u8Decrypted);
end(u8Decrypted); }, function (progress) {
emit('progress', {
progress: 50+0.5*progress
});
});
}, function (progress) { }, function (progress) {
emit('progress', { emit('progress', {
progress: progress progress: 0.5*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 && !cfg.force) { makeProgressBar(cfg, mediaObject); }
emit('progress', {
progress: obj.progress
});
});
}
cache[uid].promise.then(function (u8) {
end(u8);
}, function (err) {
error(err);
});
};
if (cfg.force) { dl(); return mediaObject; }
var maxSize = typeof(config.maxDownloadSize) === "number" ? config.maxDownloadSize
: (5 * 1024 * 1024);
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);
});
}); });
return mediaObject; return mediaObject;
@ -468,5 +761,20 @@
config[key] = value; config[key] = value;
}; };
init.fetchDecryptedMetadata = fetchDecryptedMetadata;
return init; return init;
})); };
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory();
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/bower_components/es6-promise/es6-promise.min.js'
], function () {
return factory();
});
} else {
// unsupported initialization
}
}(typeof(window) !== 'undefined'? window : {}));

@ -1423,7 +1423,7 @@ define([
console.error(e); console.error(e);
callback(""); callback("");
} }
}); }, void 0, common.getCache());
}; };
APP.docEditor = new window.DocsAPI.DocEditor("cp-app-oo-placeholder-a", APP.ooconfig); APP.docEditor = new window.DocsAPI.DocEditor("cp-app-oo-placeholder-a", APP.ooconfig);

@ -10,6 +10,7 @@ define([
'/common/common-realtime.js', '/common/common-realtime.js',
'/common/common-messaging.js', '/common/common-messaging.js',
'/common/pinpad.js', '/common/pinpad.js',
'/common/outer/cache-store.js',
'/common/outer/sharedfolder.js', '/common/outer/sharedfolder.js',
'/common/outer/cursor.js', '/common/outer/cursor.js',
'/common/outer/onlyoffice.js', '/common/outer/onlyoffice.js',
@ -28,7 +29,7 @@ define([
'/bower_components/nthen/index.js', '/bower_components/nthen/index.js',
'/bower_components/saferphore/index.js', '/bower_components/saferphore/index.js',
], function (Sortify, UserObject, ProxyManager, Migrate, Hash, Util, Constants, Feedback, ], function (Sortify, UserObject, ProxyManager, Migrate, Hash, Util, Constants, Feedback,
Realtime, Messaging, Pinpad, Realtime, Messaging, Pinpad, Cache,
SF, Cursor, OnlyOffice, Mailbox, Profile, Team, Messenger, History, SF, Cursor, OnlyOffice, Mailbox, Profile, Team, Messenger, History,
NetConfig, AppConfig, NetConfig, AppConfig,
Crypto, ChainPad, CpNetflux, Listmap, nThen, Saferphore) { Crypto, ChainPad, CpNetflux, Listmap, nThen, Saferphore) {
@ -120,10 +121,13 @@ define([
Store.getSharedFolder = function (clientId, data, cb) { Store.getSharedFolder = function (clientId, data, cb) {
var s = getStore(data.teamId); var s = getStore(data.teamId);
var id = data.id; var id = data.id;
var proxy;
if (!s || !s.manager) { return void cb({ error: 'ENOTFOUND' }); } if (!s || !s.manager) { return void cb({ error: 'ENOTFOUND' }); }
if (s.manager.folders[id]) { 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 // If it is loaded, return the shared folder proxy
return void cb(s.manager.folders[id].proxy); return void cb(proxy);
} else { } else {
// Otherwise, check if we know this shared folder // Otherwise, check if we know this shared folder
var shared = Util.find(s.proxy, ['drive', UserObject.SHARED_FOLDERS]) || {}; var shared = Util.find(s.proxy, ['drive', UserObject.SHARED_FOLDERS]) || {};
@ -1133,7 +1137,7 @@ define([
var ownedByMe = Array.isArray(owners) && owners.indexOf(edPublic) !== -1; var ownedByMe = Array.isArray(owners) && owners.indexOf(edPublic) !== -1;
// Add the pad if it does not exist in our drive // 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']); var autoStore = Util.find(store.proxy, ['settings', 'general', 'autostore']);
if (autoStore !== 1 && !data.forceSave && !data.path && !ownedByMe) { if (autoStore !== 1 && !data.forceSave && !data.path && !ownedByMe) {
// send event to inner to display the corner popup // send event to inner to display the corner popup
@ -1590,13 +1594,20 @@ define([
Store.leavePad(null, data, function () {}); Store.leavePad(null, data, function () {});
}; };
var conf = { var conf = {
Cache: Cache,
onCacheStart: function () {
postMessage(clientId, "PAD_CACHE");
},
onCacheReady: function () {
postMessage(clientId, "PAD_CACHE_READY");
},
onReady: function (pad) { onReady: function (pad) {
var padData = pad.metadata || {}; var padData = pad.metadata || {};
channel.data = padData; channel.data = padData;
if (padData && padData.validateKey && store.messenger) { if (padData && padData.validateKey && store.messenger) {
store.messenger.storeValidateKey(data.channel, padData.validateKey); store.messenger.storeValidateKey(data.channel, padData.validateKey);
} }
postMessage(clientId, "PAD_READY"); postMessage(clientId, "PAD_READY", pad.noCache);
}, },
onMessage: function (m, user, validateKey, isCp, hash) { onMessage: function (m, user, validateKey, isCp, hash) {
channel.lastHash = hash; channel.lastHash = hash;
@ -1727,6 +1738,14 @@ define([
channel.sendMessage(msg, clientId, cb); 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 // Unpin and pin the new channel in all team when changing a pad password
Store.changePadPasswordPin = function (clientId, data, cb) { Store.changePadPasswordPin = function (clientId, data, cb) {
var oldChannel = data.oldChannel; var oldChannel = data.oldChannel;
@ -2640,6 +2659,7 @@ define([
readOnly: false, readOnly: false,
validateKey: secret.keys.validateKey || undefined, validateKey: secret.keys.validateKey || undefined,
crypto: Crypto.createEncryptor(secret.keys), crypto: Crypto.createEncryptor(secret.keys),
Cache: Cache,
userName: 'fs', userName: 'fs',
logLevel: 1, logLevel: 1,
ChainPad: ChainPad, ChainPad: ChainPad,

@ -0,0 +1,93 @@
define([
'/common/common-util.js',
'/bower_components/localforage/dist/localforage.min.js',
], function (Util, localForage) {
var S = {};
var cache = localForage.createInstance({
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) {
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; }
// 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 = Util.once(Util.mkAsync(cb || function () {}));
if (!Array.isArray(val) || !validateKey) { return void cb('EINVAL'); }
checkCheckpoints(val);
cache.setItem(id, {
k: validateKey,
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 = Util.once(Util.mkAsync(cb || function () {}));
cache.removeItem(id, function () {
cb();
});
};
S.clear = function (cb) {
cb = Util.once(Util.mkAsync(cb || function () {}));
cache.clear(cb);
};
self.CryptPad_clearIndexedDB = S.clear;
return S;
});

@ -1,9 +1,10 @@
define([ define([
'/common/common-constants.js', '/common/common-constants.js',
'/common/common-hash.js', '/common/common-hash.js',
'/common/outer/cache-store.js',
'/bower_components/localforage/dist/localforage.min.js', '/bower_components/localforage/dist/localforage.min.js',
'/customize/application_config.js', '/customize/application_config.js',
], function (Constants, Hash, localForage, AppConfig) { ], function (Constants, Hash, Cache, localForage, AppConfig) {
var LocalStore = {}; var LocalStore = {};
LocalStore.setThumbnail = function (key, value, cb) { LocalStore.setThumbnail = function (key, value, cb) {
@ -119,7 +120,14 @@ define([
return void AppConfig.customizeLogout(cb); return void AppConfig.customizeLogout(cb);
} }
if (cb) { cb(); } cb = cb || function () {};
try {
Cache.clear(cb);
} catch (e) {
console.error(e);
cb();
}
}; };
var loginHandlers = []; var loginHandlers = [];
LocalStore.loginReload = function () { LocalStore.loginReload = function () {

@ -2,12 +2,13 @@ define([
'/common/common-hash.js', '/common/common-hash.js',
'/common/common-util.js', '/common/common-util.js',
'/common/userObject.js', '/common/userObject.js',
'/common/outer/cache-store.js',
'/bower_components/nthen/index.js', '/bower_components/nthen/index.js',
'/bower_components/chainpad-crypto/crypto.js', '/bower_components/chainpad-crypto/crypto.js',
'/bower_components/chainpad-listmap/chainpad-listmap.js', '/bower_components/chainpad-listmap/chainpad-listmap.js',
'/bower_components/chainpad/chainpad.dist.js', '/bower_components/chainpad/chainpad.dist.js',
], function (Hash, Util, UserObject, ], function (Hash, Util, UserObject, Cache,
nThen, Crypto, Listmap, ChainPad) { nThen, Crypto, Listmap, ChainPad) {
var SF = {}; var SF = {};
@ -174,6 +175,7 @@ define([
ChainPad: ChainPad, ChainPad: ChainPad,
classic: true, classic: true,
network: network, network: network,
Cache: Cache,
metadata: { metadata: {
validateKey: secret.keys.validateKey || undefined, validateKey: secret.keys.validateKey || undefined,
owners: owners owners: owners

@ -88,6 +88,7 @@ define([
CHANGE_PAD_PASSWORD_PIN: Store.changePadPasswordPin, CHANGE_PAD_PASSWORD_PIN: Store.changePadPasswordPin,
GET_LAST_HASH: Store.getLastHash, GET_LAST_HASH: Store.getLastHash,
GET_SNAPSHOT: Store.getSnapshot, GET_SNAPSHOT: Store.getSnapshot,
CORRUPTED_CACHE: Store.corruptedCache,
// Drive // Drive
DRIVE_USEROBJECT: Store.userObjectCommand, DRIVE_USEROBJECT: Store.userObjectCommand,
// Settings, // Settings,

@ -12,6 +12,7 @@ define([
'/common/common-feedback.js', '/common/common-feedback.js',
'/common/outer/invitation.js', '/common/outer/invitation.js',
'/common/cryptget.js', '/common/cryptget.js',
'/common/outer/cache-store.js',
'/bower_components/chainpad-listmap/chainpad-listmap.js', '/bower_components/chainpad-listmap/chainpad-listmap.js',
'/bower_components/chainpad-crypto/crypto.js', '/bower_components/chainpad-crypto/crypto.js',
@ -21,7 +22,7 @@ define([
'/bower_components/saferphore/index.js', '/bower_components/saferphore/index.js',
'/bower_components/tweetnacl/nacl-fast.min.js', '/bower_components/tweetnacl/nacl-fast.min.js',
], function (Util, Hash, Constants, Realtime, ], 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) { Listmap, Crypto, CpNetflux, ChainPad, nThen, Saferphore) {
var Team = {}; var Team = {};
@ -57,11 +58,11 @@ define([
}); });
proxy.on('disconnect', function () { proxy.on('disconnect', function () {
team.offline = true; team.offline = true;
team.sendEvent('NETWORK_DISCONNECT'); team.sendEvent('NETWORK_DISCONNECT', team.id);
}); });
proxy.on('reconnect', function () { proxy.on('reconnect', function () {
team.offline = false; team.offline = false;
team.sendEvent('NETWORK_RECONNECT'); team.sendEvent('NETWORK_RECONNECT', team.id);
}); });
} }
proxy.on('change', [], function (o, n, p) { proxy.on('change', [], function (o, n, p) {
@ -426,6 +427,7 @@ define([
channel: secret.channel, channel: secret.channel,
crypto: crypto, crypto: crypto,
ChainPad: ChainPad, ChainPad: ChainPad,
Cache: Cache,
metadata: { metadata: {
validateKey: secret.keys.validateKey || undefined, validateKey: secret.keys.validateKey || undefined,
}, },
@ -573,6 +575,7 @@ define([
logLevel: 1, logLevel: 1,
classic: true, classic: true,
ChainPad: ChainPad, ChainPad: ChainPad,
Cache: Cache,
owners: [ctx.store.proxy.edPublic] owners: [ctx.store.proxy.edPublic]
}; };
nThen(function (waitFor) { nThen(function (waitFor) {
@ -931,7 +934,9 @@ define([
if (!team) { return void cb ({error: 'ENOENT'}); } if (!team) { return void cb ({error: 'ENOENT'}); }
if (!team.roster) { return void cb({error: 'NO_ROSTER'}); } if (!team.roster) { return void cb({error: 'NO_ROSTER'}); }
var state = team.roster.getState() || {}; 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) { var setTeamMetadata = function (ctx, data, cId, cb) {
@ -1879,15 +1884,15 @@ define([
var t = Util.clone(teams); var t = Util.clone(teams);
Object.keys(t).forEach(function (id) { Object.keys(t).forEach(function (id) {
// If failure to load the team, don't send it // 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; t[id].error = true;
}); });
cb(t); cb(t);
}; };
team.execCommand = function (clientId, obj, cb) { team.execCommand = function (clientId, obj, cb) {
if (ctx.store.offline) {
return void cb({ error: 'OFFLINE' });
}
var cmd = obj.cmd; var cmd = obj.cmd;
var data = obj.data; var data = obj.data;
@ -1911,30 +1916,36 @@ define([
return void setTeamMetadata(ctx, data, clientId, cb); return void setTeamMetadata(ctx, data, clientId, cb);
} }
if (cmd === 'OFFER_OWNERSHIP') { if (cmd === 'OFFER_OWNERSHIP') {
if (ctx.store.offline) { return void cb({ error: 'OFFLINE' }); }
return void offerOwnership(ctx, data, clientId, cb); return void offerOwnership(ctx, data, clientId, cb);
} }
if (cmd === 'ANSWER_OWNERSHIP') { if (cmd === 'ANSWER_OWNERSHIP') {
if (ctx.store.offline) { return void cb({ error: 'OFFLINE' }); }
return void answerOwnership(ctx, data, clientId, cb); return void answerOwnership(ctx, data, clientId, cb);
} }
if (cmd === 'DESCRIBE_USER') { if (cmd === 'DESCRIBE_USER') {
return void describeUser(ctx, data, clientId, cb); return void describeUser(ctx, data, clientId, cb);
} }
if (cmd === 'INVITE_TO_TEAM') { if (cmd === 'INVITE_TO_TEAM') {
if (ctx.store.offline) { return void cb({ error: 'OFFLINE' }); }
return void inviteToTeam(ctx, data, clientId, cb); return void inviteToTeam(ctx, data, clientId, cb);
} }
if (cmd === 'LEAVE_TEAM') { if (cmd === 'LEAVE_TEAM') {
return void leaveTeam(ctx, data, clientId, cb); return void leaveTeam(ctx, data, clientId, cb);
} }
if (cmd === 'JOIN_TEAM') { if (cmd === 'JOIN_TEAM') {
if (ctx.store.offline) { return void cb({ error: 'OFFLINE' }); }
return void joinTeam(ctx, data, clientId, cb); return void joinTeam(ctx, data, clientId, cb);
} }
if (cmd === 'REMOVE_USER') { if (cmd === 'REMOVE_USER') {
return void removeUser(ctx, data, clientId, cb); return void removeUser(ctx, data, clientId, cb);
} }
if (cmd === 'DELETE_TEAM') { if (cmd === 'DELETE_TEAM') {
if (ctx.store.offline) { return void cb({ error: 'OFFLINE' }); }
return void deleteTeam(ctx, data, clientId, cb); return void deleteTeam(ctx, data, clientId, cb);
} }
if (cmd === 'CREATE_TEAM') { if (cmd === 'CREATE_TEAM') {
if (ctx.store.offline) { return void cb({ error: 'OFFLINE' }); }
return void createTeam(ctx, data, clientId, cb); return void createTeam(ctx, data, clientId, cb);
} }
if (cmd === 'GET_EDITABLE_FOLDERS') { if (cmd === 'GET_EDITABLE_FOLDERS') {
@ -1947,6 +1958,7 @@ define([
return void getPreviewContent(ctx, data, clientId, cb); return void getPreviewContent(ctx, data, clientId, cb);
} }
if (cmd === 'ACCEPT_LINK_INVITATION') { if (cmd === 'ACCEPT_LINK_INVITATION') {
if (ctx.store.offline) { return void cb({ error: 'OFFLINE' }); }
return void acceptLinkInvitation(ctx, data, clientId, cb); return void acceptLinkInvitation(ctx, data, clientId, cb);
} }
}; };

@ -1,9 +1,11 @@
define([ define([
'/file/file-crypto.js', '/file/file-crypto.js',
'/common/common-hash.js', '/common/common-hash.js',
'/common/common-util.js',
'/common/outer/cache-store.js',
'/bower_components/nthen/index.js', '/bower_components/nthen/index.js',
'/bower_components/tweetnacl/nacl-fast.min.js', '/bower_components/tweetnacl/nacl-fast.min.js',
], function (FileCrypto, Hash, nThen) { ], function (FileCrypto, Hash, Util, Cache, nThen) {
var Nacl = window.nacl; var Nacl = window.nacl;
var module = {}; var module = {};
@ -31,9 +33,11 @@ define([
}; };
var actual = 0; var actual = 0;
var encryptedArr = [];
var again = function (err, box) { var again = function (err, box) {
if (err) { onError(err); } if (err) { onError(err); }
if (box) { if (box) {
encryptedArr.push(box);
actual += box.length; actual += box.length;
var progressValue = (actual / estimate * 100); var progressValue = (actual / estimate * 100);
progressValue = Math.min(progressValue, 100); progressValue = Math.min(progressValue, 100);
@ -55,10 +59,12 @@ define([
var uri = ['', 'blob', id.slice(0,2), id].join('/'); var uri = ['', 'blob', id.slice(0,2), id].join('/');
console.log("encrypted blob is now available as %s", uri); console.log("encrypted blob is now available as %s", uri);
var box_u8 = Util.uint8ArrayJoin(encryptedArr);
Cache.setBlobCache(id, box_u8, function (err) {
if (err) { console.warn(err); }
cb(); cb();
}); });
});
}; };
common.uploadStatus(teamId, estimate, function (e, pending) { common.uploadStatus(teamId, estimate, function (e, pending) {

@ -65,11 +65,13 @@ define([
cb(undefined, data.content, msg); cb(undefined, data.content, msg);
}; };
evReady.reg(function () { evReady.reg(function () {
postMsg(JSON.stringify({ var toSend = {
txid: txid, txid: txid,
content: content, 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 // If the type is a query, your handler will be invoked with a reply function that takes
// one argument (the content to reply with). // one argument (the content to reply with).
chan.on = function (queryType, handler, quiet) { chan.on = function (queryType, handler, quiet) {
var h = function (data, msg) { var h = function (data, msg, raw) {
handler(data.content, function (replyContent) { handler(data.content, function (replyContent) {
postMsg(JSON.stringify({ var toSend = {
txid: data.txid, txid: data.txid,
content: replyContent content: replyContent
})); };
postMsg(raw ? toSend : JSON.stringify(toSend));
}, msg); }, msg);
}; };
(handlers[queryType] = handlers[queryType] || []).push(h); (handlers[queryType] = handlers[queryType] || []).push(h);
@ -150,7 +153,7 @@ define([
onMsg.reg(function (msg) { onMsg.reg(function (msg) {
if (!chanLoaded) { return; } if (!chanLoaded) { return; }
if (!msg.data || msg.data === '_READY') { 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 (typeof(data.ack) !== "undefined") {
if (acks[data.txid]) { acks[data.txid](!data.ack); } if (acks[data.txid]) { acks[data.txid](!data.ack); }
} else if (typeof(data.q) === 'string') { } else if (typeof(data.q) === 'string') {
@ -163,7 +166,7 @@ define([
})); }));
} }
handlers[data.q].forEach(function (f) { 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; data = undefined;
}); });
} else { } else {

@ -40,6 +40,14 @@ define([
userObject: userObject, userObject: userObject,
leave: leave 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; return userObject;
}; };

@ -467,7 +467,51 @@ define([
}); });
}; };
var noCache = false; // Prevent reload loops
var onCorruptedCache = function () {
if (noCache) {
UI.errorLoadingScreen(Messages.unableToDisplay, false, function () {
common.gotoURL('');
});
}
noCache = true;
var sframeChan = common.getSframeChannel();
sframeChan.event("EV_CORRUPTED_CACHE");
};
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);
}
// 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);
// Make sure we're using the correct app for this cache
if (metadata && typeof(metadata.type) !== 'undefined' && metadata.type !== type) {
return void onCorruptedCache();
}
cpNfInner.metadataMgr.updateMetadata(metadata);
newContent = normalize(newContent);
if (!unsyncMode) {
contentUpdate(newContent, function () { return function () {}; });
}
UI.removeLoadingScreen(emitResize);
};
var onReady = function () { var onReady = function () {
toolbar.offline(false);
var newContentStr = cpNfInner.chainpad.getUserDoc(); var newContentStr = cpNfInner.chainpad.getUserDoc();
if (state === STATE.DELETED) { return; } if (state === STATE.DELETED) { return; }
@ -508,14 +552,19 @@ define([
console.log("Either this is an empty document which has not been touched"); console.log("Either this is an empty document which has not been touched");
console.log("Or else something is terribly wrong, reloading."); console.log("Or else something is terribly wrong, reloading.");
Feedback.send("NON_EMPTY_NEWDOC"); Feedback.send("NON_EMPTY_NEWDOC");
setTimeout(function () { common.gotoURL(); }, 1000); // The cache may be wrong, empty it and reload after.
waitFor.abort();
onCorruptedCache();
return; return;
} }
console.log('updating title');
title.updateTitle(title.defaultTitle); title.updateTitle(title.defaultTitle);
evOnDefaultContentNeeded.fire(); evOnDefaultContentNeeded.fire();
} }
}).nThen(function () { }).nThen(function () {
// We have a valid chainpad, reenable cache fix in case with reconnect with
// a corrupted cache
noCache = false;
stateChange(STATE.READY); stateChange(STATE.READY);
firstConnection = false; firstConnection = false;
@ -734,6 +783,7 @@ define([
onRemote: onRemote, onRemote: onRemote,
onLocal: onLocal, onLocal: onLocal,
onInit: onInit, onInit: onInit,
onCacheReady: onCacheReady,
onReady: function () { evStart.reg(onReady); }, onReady: function () { evStart.reg(onReady); },
onConnectionChange: onConnectionChange, onConnectionChange: onConnectionChange,
onError: onError, onError: onError,

@ -34,6 +34,7 @@ define([
var onLocal = config.onLocal || function () { }; var onLocal = config.onLocal || function () { };
var setMyID = config.setMyID || function () { }; var setMyID = config.setMyID || function () { };
var onReady = config.onReady || function () { }; var onReady = config.onReady || function () { };
var onCacheReady = config.onCacheReady || function () { };
var onError = config.onError || function () { }; var onError = config.onError || function () { };
var userName = config.userName; var userName = config.userName;
var initialState = config.initialState; var initialState = config.initialState;
@ -93,6 +94,9 @@ define([
evInfiniteSpinner.fire(); evInfiniteSpinner.fire();
}, 2000); }, 2000);
sframeChan.on('EV_RT_CACHE_READY', function () {
onCacheReady({realtime: chainpad});
});
sframeChan.on('EV_RT_DISCONNECT', function (isPermanent) { sframeChan.on('EV_RT_DISCONNECT', function (isPermanent) {
isReady = false; isReady = false;
chainpad.abort(); chainpad.abort();

@ -46,6 +46,7 @@ define([], function () {
// shim between chainpad and netflux // shim between chainpad and netflux
var msgIn = function (peer, msg) { var msgIn = function (peer, msg) {
try { try {
if (/^\[/.test(msg)) { return msg; } // Already decrypted
var isHk = peer.length !== 32; var isHk = peer.length !== 32;
var key = isNewHash ? validateKey : false; var key = isNewHash ? validateKey : false;
var decryptedMsg = Crypto.decrypt(msg, key, isHk); var decryptedMsg = Crypto.decrypt(msg, key, isHk);
@ -114,16 +115,25 @@ define([], function () {
if (firstConnection) { if (firstConnection) {
firstConnection = false; firstConnection = false;
// Add the handlers to the WebChannel // 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.onJoinEvent.reg(function (m) { sframeChan.event('EV_RT_JOIN', m); });
padRpc.onLeaveEvent.reg(function (m) { sframeChan.event('EV_RT_LEAVE', m); }); padRpc.onLeaveEvent.reg(function (m) { sframeChan.event('EV_RT_LEAVE', m); });
} }
}; };
padRpc.onMessageEvent.reg(function (msg) { onMessage(msg); });
padRpc.onDisconnectEvent.reg(function (permanent) { padRpc.onDisconnectEvent.reg(function (permanent) {
sframeChan.event('EV_RT_DISCONNECT', permanent); sframeChan.event('EV_RT_DISCONNECT', permanent);
}); });
padRpc.onCacheReadyEvent.reg(function () {
sframeChan.event('EV_RT_CACHE_READY');
});
padRpc.onCacheEvent.reg(function () {
sframeChan.event('EV_RT_CACHE');
});
padRpc.onConnectEvent.reg(function (data) { padRpc.onConnectEvent.reg(function (data) {
onOpen(data); onOpen(data);
}); });

@ -48,7 +48,7 @@ define([
}; };
var tableHeader = h('div.cp-fileupload-header', [ 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')), h('div.cp-fileupload-header-close', h('span.fa.fa-times')),
]); ]);
@ -260,7 +260,8 @@ define([
// name // name
$('<td>').append($link).appendTo($tr); $('<td>').append($link).appendTo($tr);
// size // size
$('<td>').text(UIElements.prettySize(estimate)).appendTo($tr); var size = estimate ? UIElements.prettySize(estimate) : '';
$(h('td.cp-fileupload-size')).text(size).appendTo($tr);
// progress // progress
$('<td>', {'class': 'cp-fileupload-table-progress'}).append($progressContainer).appendTo($tr); $('<td>', {'class': 'cp-fileupload-table-progress'}).append($progressContainer).appendTo($tr);
// cancel // cancel
@ -588,12 +589,11 @@ define([
queue.next(); queue.next();
}; };
/*
var cancelled = function () { var cancelled = function () {
$row.find('.cp-fileupload-table-cancel').addClass('cancelled').html('').append(h('span.fa.fa-minus')); $row.find('.cp-fileupload-table-cancel').addClass('cancelled').html('').append(h('span.fa.fa-minus'));
queue.inProgress = false; queue.inProgress = false;
queue.next(); queue.next();
};*/ };
/** /**
* Update progress in the download panel, for downloading a file * Update progress in the download panel, for downloading a file
@ -627,6 +627,17 @@ define([
*/ */
var updateProgress = function (progressValue) { var updateProgress = function (progressValue) {
var text = Math.round(progressValue*100) + '%'; 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); $pv.text(text);
$pb.css({ $pb.css({
width: (progressValue * 100) + '%' width: (progressValue * 100) + '%'
@ -638,8 +649,10 @@ define([
fileHost: privateData.fileHost, fileHost: privateData.fileHost,
get: common.getPad, get: common.getPad,
sframeChan: sframeChan, sframeChan: sframeChan,
cache: common.getCache()
}; };
downloadFunction(ctx, data, function (err, obj) {
var dl = downloadFunction(ctx, data, function (err, obj) {
$link.prepend($('<span>', {'class': 'fa fa-external-link'})) $link.prepend($('<span>', {'class': 'fa fa-external-link'}))
.attr('href', '#') .attr('href', '#')
.click(function (e) { .click(function (e) {
@ -655,19 +668,17 @@ define([
folderProgress: updateProgress, folderProgress: updateProgress,
}); });
/* var $cancel = $row.find('.cp-fileupload-table-cancel').html('');
var $cancel = $('<span>', {'class': 'cp-fileupload-table-cancel-button fa fa-times'}).click(function () { if (dl && dl.cancel) {
$('<span>', {
'class': 'cp-fileupload-table-cancel-button fa fa-times'
}).click(function () {
dl.cancel(); dl.cancel();
$cancel.remove(); $cancel.remove();
$row.find('.cp-fileupload-table-progress-value').text(Messages.upload_cancelled); $row.find('.cp-fileupload-table-progress-value').text(Messages.upload_cancelled);
cancelled(); cancelled();
}); }).appendTo($cancel);
*/ }
$row.find('.cp-fileupload-table-cancel')
.html('')
.append(h('span.fa.fa-minus'));
//.append($cancel);
}; };
File.downloadFile = function (fData, cb) { File.downloadFile = function (fData, cb) {

@ -100,12 +100,13 @@ define([
'/common/common-constants.js', '/common/common-constants.js',
'/common/common-feedback.js', '/common/common-feedback.js',
'/common/outer/local-store.js', '/common/outer/local-store.js',
'/common/outer/cache-store.js',
'/customize/application_config.js', '/customize/application_config.js',
'/common/test.js', '/common/test.js',
'/common/userObject.js', '/common/userObject.js',
], waitFor(function (_CpNfOuter, _Cryptpad, _Crypto, _Cryptget, _SFrameChannel, ], waitFor(function (_CpNfOuter, _Cryptpad, _Crypto, _Cryptget, _SFrameChannel,
_SecureIframe, _Messaging, _Notifier, _Hash, _Util, _Realtime, _SecureIframe, _Messaging, _Notifier, _Hash, _Util, _Realtime,
_Constants, _Feedback, _LocalStore, _AppConfig, _Test, _UserObject) { _Constants, _Feedback, _LocalStore, _Cache, _AppConfig, _Test, _UserObject) {
CpNfOuter = _CpNfOuter; CpNfOuter = _CpNfOuter;
Cryptpad = _Cryptpad; Cryptpad = _Cryptpad;
Crypto = Utils.Crypto = _Crypto; Crypto = Utils.Crypto = _Crypto;
@ -120,6 +121,7 @@ define([
Utils.Constants = _Constants; Utils.Constants = _Constants;
Utils.Feedback = _Feedback; Utils.Feedback = _Feedback;
Utils.LocalStore = _LocalStore; Utils.LocalStore = _LocalStore;
Utils.Cache = _Cache;
Utils.UserObject = _UserObject; Utils.UserObject = _UserObject;
Utils.currentPad = currentPad; Utils.currentPad = currentPad;
AppConfig = _AppConfig; AppConfig = _AppConfig;
@ -678,6 +680,20 @@ 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'}); }
Utils.Cache.setBlobCache(data.id, data.u8, function (err) {
if (err) { return void cb({error: err}); }
cb();
});
});
sframeChan.on('Q_GET_ATTRIBUTE', function (data, cb) { sframeChan.on('Q_GET_ATTRIBUTE', function (data, cb) {
Cryptpad.getAttribute(data.key, function (e, data) { Cryptpad.getAttribute(data.key, function (e, data) {
cb({ cb({
@ -1697,6 +1713,10 @@ define([
}); });
}; };
sframeChan.on('EV_CORRUPTED_CACHE', function () {
Cryptpad.onCorruptedCache(secret.channel);
});
sframeChan.on('Q_CREATE_PAD', function (data, cb) { sframeChan.on('Q_CREATE_PAD', function (data, cb) {
if (!isNewFile || rtStarted) { return; } if (!isNewFile || rtStarted) { return; }
// Create a new hash // Create a new hash
@ -1811,7 +1831,12 @@ define([
} }
startRealtime(); startRealtime();
cb(); cb();
}, cryptputCfg); }, cryptputCfg, function (progress) {
sframeChan.event('EV_LOADING_INFO', {
type: 'pad',
progress: progress
});
});
return; return;
} }
// Start realtime outside the iframe and callback // Start realtime outside the iframe and callback

@ -11,6 +11,7 @@ define([
'/common/sframe-common-codemirror.js', '/common/sframe-common-codemirror.js',
'/common/sframe-common-cursor.js', '/common/sframe-common-cursor.js',
'/common/sframe-common-mailbox.js', '/common/sframe-common-mailbox.js',
'/common/inner/cache.js',
'/common/inner/common-mediatag.js', '/common/inner/common-mediatag.js',
'/common/metadata-manager.js', '/common/metadata-manager.js',
@ -36,6 +37,7 @@ define([
CodeMirror, CodeMirror,
Cursor, Cursor,
Mailbox, Mailbox,
Cache,
MT, MT,
MetadataMgr, MetadataMgr,
AppConfig, AppConfig,
@ -142,7 +144,7 @@ define([
} }
return; return;
}; };
funcs.importMediaTag = function ($mt) { var getMtData = function ($mt) {
if (!$mt || !$mt.is('media-tag')) { return; } if (!$mt || !$mt.is('media-tag')) { return; }
var chanStr = $mt.attr('src'); var chanStr = $mt.attr('src');
var keyStr = $mt.attr('data-crypto-key'); var keyStr = $mt.attr('data-crypto-key');
@ -154,10 +156,27 @@ define([
var channel = src.replace(/\/blob\/[0-9a-f]{2}\//i, ''); var channel = src.replace(/\/blob\/[0-9a-f]{2}\//i, '');
// Get key // Get key
var key = keyStr.replace(/cryptpad:/i, ''); 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; var metadata = $mt[0]._mediaObject._blob.metadata;
ctx.sframeChan.query('Q_IMPORT_MEDIATAG', { ctx.sframeChan.query('Q_IMPORT_MEDIATAG', {
channel: channel, channel: data.channel,
key: key, key: data.key,
name: metadata.name, name: metadata.name,
type: metadata.type, type: metadata.type,
owners: metadata.owners owners: metadata.owners
@ -588,6 +607,10 @@ define([
}); });
}; };
funcs.getCache = function () {
return ctx.cache;
};
/* funcs.storeLinkToClipboard = function (readOnly, cb) { /* funcs.storeLinkToClipboard = function (readOnly, cb) {
ctx.sframeChan.query('Q_STORE_LINK_TO_CLIPBOARD', readOnly, function (err) { ctx.sframeChan.query('Q_STORE_LINK_TO_CLIPBOARD', readOnly, function (err) {
if (cb) { cb(err); } if (cb) { cb(err); }
@ -794,12 +817,24 @@ define([
modules[type].onEvent(obj.data); modules[type].onEvent(obj.data);
}); });
ctx.cache = Cache.create(ctx.sframeChan);
ctx.metadataMgr.onReady(waitFor()); ctx.metadataMgr.onReady(waitFor());
}).nThen(function () { }).nThen(function () {
var privateData = ctx.metadataMgr.getPrivateData(); var privateData = ctx.metadataMgr.getPrivateData();
funcs.addShortcuts(window, Boolean(privateData.app)); 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);
}
if (MT.MediaTag && ctx.cache) {
MT.MediaTag.setDefaultConfig('Cache', ctx.cache);
}
try { try {
var feedback = privateData.feedbackAllowed; var feedback = privateData.feedbackAllowed;
Feedback.init(feedback); Feedback.init(feedback);

@ -1388,6 +1388,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(Messages.offline);
} else {
kickSpinner(toolbar, config);
}
};
// On log out, remove permanently the realtime elements of the toolbar // On log out, remove permanently the realtime elements of the toolbar
Common.onLogout(function () { Common.onLogout(function () {
failed(); failed();

@ -28,6 +28,8 @@ define([
// Loaded in load #2 // Loaded in load #2
nThen(function (waitFor) { nThen(function (waitFor) {
$(waitFor()); $(waitFor());
}).nThen(function (waitFor) {
SFCommonO.initIframe(waitFor);
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
var req = { var req = {
cfg: requireConfig, cfg: requireConfig,

@ -82,6 +82,8 @@ define([
var readOnly = !secret.keys.editKeyStr; var readOnly = !secret.keys.editKeyStr;
if (!manager || !manager.folders[fId]) { return; } if (!manager || !manager.folders[fId]) { return; }
manager.folders[fId].userObject.setReadOnly(readOnly, secret.keys.secondaryKey); 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 // Remove from memory folders that have been deleted from the drive remotely

@ -54,8 +54,14 @@ define([
if (Utils.LocalStore.isLoggedIn()) { return; } if (Utils.LocalStore.isLoggedIn()) { return; }
Utils.LocalStore.setFSHash(''); Utils.LocalStore.setFSHash('');
Utils.LocalStore.clearThumbnail(); Utils.LocalStore.clearThumbnail();
try {
Utils.Cache.clear(function () {
window.location.reload(); window.location.reload();
}); });
} catch (e) {
window.location.reload();
}
});
sframeChan.on('Q_DRIVE_USEROBJECT', function (data, cb) { sframeChan.on('Q_DRIVE_USEROBJECT', function (data, cb) {
Cryptpad.userObjectCommand(data, cb); Cryptpad.userObjectCommand(data, cb);
}); });

@ -1,5 +1,6 @@
@import (reference) '../../customize/src/less2/include/tokenfield.less'; @import (reference) '../../customize/src/less2/include/tokenfield.less';
@import (reference) '../../customize/src/less2/include/framework.less'; @import (reference) '../../customize/src/less2/include/framework.less';
@import (reference) '../../customize/src/less2/include/markdown.less';
&.cp-app-file { &.cp-app-file {
@ -47,6 +48,7 @@
z-index: -1; z-index: -1;
} }
.mediatag_cryptpad();
media-tag { media-tag {
img { img {
max-width: 100%; max-width: 100%;
@ -64,7 +66,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; padding: 0px;
margin: 0px; margin: 0px;
@ -156,6 +200,9 @@
max-height: 100%; max-height: 100%;
max-width: 100%; max-width: 100%;
} }
&:empty {
display: none !important;
}
} }
} }

@ -48,69 +48,6 @@ define([
return new Blob(chunks); 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 decrypt = function (u8, key, done, progress) {
var MAX = u8.length; var MAX = u8.length;
var _progress = function (offset) { var _progress = function (offset) {
@ -128,6 +65,11 @@ define([
metadata: undefined, metadata: undefined,
}; };
var cancelled = false;
var cancel = function () {
cancelled = true;
};
var metaBox = new Uint8Array(u8.subarray(2, 2 + metadataLength)); var metaBox = new Uint8Array(u8.subarray(2, 2 + metadataLength));
var metaChunk = Nacl.secretbox.open(metaBox, nonce, key); var metaChunk = Nacl.secretbox.open(metaBox, nonce, key);
@ -168,6 +110,7 @@ define([
var chunks = []; var chunks = [];
var again = function () { var again = function () {
if (cancelled) { return; }
takeChunk(function (e, plaintext) { takeChunk(function (e, plaintext) {
if (e) { if (e) {
return setTimeout(function () { return setTimeout(function () {
@ -188,6 +131,10 @@ define([
}; };
again(); again();
return {
cancel: cancel
};
}; };
// metadata // metadata
@ -258,8 +205,5 @@ define([
encrypt: encrypt, encrypt: encrypt,
joinChunks: joinChunks, joinChunks: joinChunks,
computeEncryptedSize: computeEncryptedSize, computeEncryptedSize: computeEncryptedSize,
decryptMetadata: decryptMetadata,
fetchMetadata: fetchMetadata,
fetchDecryptedMetadata: fetchDecryptedMetadata,
}; };
}); });

@ -16,11 +16,6 @@
<label for="cp-app-file-upfile" class="btn btn-primary cp-app-file-block unselectable" data-localization-title="upload_choose" <label for="cp-app-file-upfile" class="btn btn-primary cp-app-file-block unselectable" data-localization-title="upload_choose"
data-localization="upload_choose"></label> data-localization="upload_choose"></label>
</div> </div>
<div id="cp-app-file-download-form" style="display: none;">
<input type="button" name="dl" id="cp-app-file-dlfile" class="cp-app-file-input" />
<label for="cp-app-file-dlfile" class="btn btn-success cp-app-file-block unselectable" data-localization-title="download_button"><span data-localization="download_button"></span></label>
<span class="cp-app-file-block" id="cp-app-file-dlprogress"></span>
</div>
<div id="cp-app-file-download-view" style="display: none;"> <div id="cp-app-file-download-view" style="display: none;">
<media-tag id="cp-app-file-view"></media-tag> <media-tag id="cp-app-file-view"></media-tag>
</div> </div>

@ -8,6 +8,7 @@ define([
'/common/common-util.js', '/common/common-util.js',
'/common/common-hash.js', '/common/common-hash.js',
'/common/common-interface.js', '/common/common-interface.js',
'/common/hyperscript.js',
'/customize/messages.js', '/customize/messages.js',
'/file/file-crypto.js', '/file/file-crypto.js',
@ -29,6 +30,7 @@ define([
Util, Util,
Hash, Hash,
UI, UI,
h,
Messages, Messages,
FileCrypto, FileCrypto,
MediaTag) MediaTag)
@ -37,18 +39,12 @@ define([
var Nacl = window.nacl; var Nacl = window.nacl;
var APP = window.APP = {}; var APP = window.APP = {};
MediaTag.setDefaultConfig('download', {
text: Messages.download_mt_button
});
var andThen = function (common) { var andThen = function (common) {
var $appContainer = $('#cp-app-file-content'); var $appContainer = $('#cp-app-file-content');
var $form = $('#cp-app-file-upload-form'); var $form = $('#cp-app-file-upload-form');
var $dlform = $('#cp-app-file-download-form');
var $dlview = $('#cp-app-file-download-view'); var $dlview = $('#cp-app-file-download-view');
var $label = $form.find('label'); var $label = $form.find('label');
var $dllabel = $dlform.find('label span');
var $progress = $('#cp-app-file-dlprogress');
var $bar = $('.cp-toolbar-container'); var $bar = $('.cp-toolbar-container');
var $body = $('body'); var $body = $('body');
@ -88,21 +84,45 @@ define([
var toolbar = APP.toolbar = Toolbar.create(configTb); var toolbar = APP.toolbar = Toolbar.create(configTb);
if (!uploadMode) { if (!uploadMode) {
(function () {
var hexFileName = secret.channel; var hexFileName = secret.channel;
var src = fileHost + Hash.getBlobPathFromHex(hexFileName); var src = fileHost + Hash.getBlobPathFromHex(hexFileName);
var key = secret.keys && secret.keys.cryptKey; var key = secret.keys && secret.keys.cryptKey;
var cryptKey = Nacl.util.encodeBase64(key); var cryptKey = Nacl.util.encodeBase64(key);
FileCrypto.fetchDecryptedMetadata(src, key, function (e, metadata) { var $mt = $dlview.find('media-tag');
if (e) { $mt.attr('src', src);
if (e === 'XHR_ERROR') { $mt.attr('data-crypto-key', 'cryptpad:'+cryptKey);
return void UI.errorLoadingScreen(Messages.download_resourceNotAvailable, false, function () { $mt.css('transform', 'scale(2)');
common.gotoURL('/file/');
}); var rightsideDisplayed = false;
} var metadataReceived = false;
return void console.error(e); 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 // Add pad attributes when the file is saved in the drive
Title.onTitleChange(function () { Title.onTitleChange(function () {
var owners = metadata.owners; var owners = metadata.owners;
@ -137,93 +157,13 @@ define([
toolbar.$drawer.append(common.createButton('hashtag', true)); toolbar.$drawer.append(common.createButton('hashtag', true));
} }
toolbar.$file.show(); toolbar.$file.show();
var displayFile = function (ev, sizeMb, CB) {
var called_back;
var cb = function (e) {
if (called_back) { return; }
called_back = true;
if (CB) { CB(e); }
};
var $mt = $dlview.find('media-tag');
$mt.attr('src', src);
$mt.attr('data-crypto-key', 'cryptpad:'+cryptKey);
var rightsideDisplayed = false;
MediaTag($mt[0]).on('complete', function (decrypted) {
$dlview.show();
$dlform.hide();
var $dlButton = $dlview.find('media-tag button');
if (ev) { $dlButton.click(); }
if (!rightsideDisplayed) {
toolbar.$drawer
.append(common.createButton('export', true, {}, function () {
saveAs(decrypted.content, decrypted.metadata.name);
}));
rightsideDisplayed = true;
}
// make pdfs big
var toolbarHeight = $('#cp-toolbar').height();
var $another_iframe = $('media-tag iframe').css({
'height': 'calc(100vh - ' + toolbarHeight + 'px)',
'width': '100vw',
'position': 'absolute',
'bottom': 0,
'left': 0,
'border': 0
});
if ($another_iframe.length) {
$another_iframe.load(function () {
cb();
});
} else {
cb();
}
}).on('progress', function (data) {
var p = data.progress +'%';
$progress.width(p);
}).on('error', function (err) { }).on('error', function (err) {
console.error(err);
});
};
var todoBigFile = function (sizeMb) {
$dlform.show();
UI.removeLoadingScreen();
$dllabel.append($('<br>'));
$dllabel.append(Util.fixHTML(metadata.name));
// don't display the size if you don't know it.
if (typeof(sizeM) === 'number') {
$dllabel.append($('<br>'));
$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', $appContainer.css('background-color',
common.getAppConfig().appBackgroundColor); common.getAppConfig().appBackgroundColor);
if (err) { UI.alert(err); } UI.warn(Messages.error);
}); console.error(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; return;
} }

@ -46,6 +46,7 @@ define([
'/common/test.js', '/common/test.js',
'/bower_components/diff-dom/diffDOM.js', '/bower_components/diff-dom/diffDOM.js',
'/bower_components/file-saver/FileSaver.min.js',
'css!/customize/src/print.css', 'css!/customize/src/print.css',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css', 'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
@ -462,7 +463,9 @@ define([
setTimeout(function() { // Just in case setTimeout(function() { // Just in case
var tags = dom.querySelectorAll('media-tag:empty'); var tags = dom.querySelectorAll('media-tag:empty');
Array.prototype.slice.call(tags).forEach(function(el) { Array.prototype.slice.call(tags).forEach(function(el) {
MediaTag(el); var mediaObject = MediaTag(el, {
body: dom
});
$(el).on('keydown', function(e) { $(el).on('keydown', function(e) {
if ([8, 46].indexOf(e.which) !== -1) { if ([8, 46].indexOf(e.which) !== -1) {
$(el).remove(); $(el).remove();
@ -472,13 +475,17 @@ define([
var observer = new MutationObserver(function(mutations) { var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) { mutations.forEach(function(mutation) {
if (mutation.type === 'childList') { if (mutation.type === 'childList') {
var list_values = [].slice.call(el.children); var list_values = slice(el.children)
.map(function (el) { return el.outerHTML; })
.join('');
mediaTagMap[el.getAttribute('src')] = list_values; mediaTagMap[el.getAttribute('src')] = list_values;
if (mediaObject.complete) { observer.disconnect(); }
} }
}); });
}); });
observer.observe(el, { observer.observe(el, {
attributes: false, attributes: false,
subtree: true,
childList: true, childList: true,
characterData: false characterData: false
}); });
@ -491,9 +498,10 @@ define([
Array.prototype.slice.call(tags).forEach(function(tag) { Array.prototype.slice.call(tags).forEach(function(tag) {
var src = tag.getAttribute('src'); var src = tag.getAttribute('src');
if (mediaTagMap[src]) { if (mediaTagMap[src]) {
mediaTagMap[src].forEach(function(n) { tag.innerHTML = mediaTagMap[src];
tag.appendChild(n.cloneNode()); /*mediaTagMap[src].forEach(function(n) {
}); tag.appendChild(n.cloneNode(true));
});*/
} }
}); });
}; };
@ -1084,6 +1092,9 @@ define([
border: Messages.pad_mediatagBorder, border: Messages.pad_mediatagBorder,
preview: Messages.pad_mediatagPreview, preview: Messages.pad_mediatagPreview,
'import': Messages.pad_mediatagImport, 'import': Messages.pad_mediatagImport,
download: Messages.download_mt_button,
share: Messages.pad_mediatagShare,
open: Messages.pad_mediatagOpen,
options: Messages.pad_mediatagOptions options: Messages.pad_mediatagOptions
}; };
Ckeditor._commentsTranslations = { Ckeditor._commentsTranslations = {
@ -1164,6 +1175,28 @@ define([
editor.plugins.mediatag.import = function($mt) { editor.plugins.mediatag.import = function($mt) {
framework._.sfCommon.importMediaTag($mt); framework._.sfCommon.importMediaTag($mt);
}; };
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);
};
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); Links.init(Ckeditor, editor);
}).nThen(function() { }).nThen(function() {
// Move ckeditor parts to have a structure like the other apps // Move ckeditor parts to have a structure like the other apps

@ -53,15 +53,57 @@
editor.plugins.mediatag.import($mt); 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) { if (editor.addMenuItems) {
editor.addMenuGroup('mediatag'); 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', { editor.addMenuItem('importMediatag', {
label: Messages.import, label: Messages.import,
icon: 'save', icon: 'save',
command: 'importMediatag', command: 'importMediatag',
group: 'mediatag' group: 'mediatag'
}); });
editor.addMenuItem('download', {
label: Messages.download,
icon: 'save',
command: 'downloadMT',
group: 'mediatag'
});
editor.addMenuItem('mediatag', { editor.addMenuItem('mediatag', {
label: Messages.options, label: Messages.options,
icon: 'image', icon: 'image',
@ -76,6 +118,9 @@
targetWidget = element; targetWidget = element;
return { return {
mediatag: CKEDITOR.TRISTATE_OFF, mediatag: CKEDITOR.TRISTATE_OFF,
open: CKEDITOR.TRISTATE_OFF,
share: CKEDITOR.TRISTATE_OFF,
download: CKEDITOR.TRISTATE_OFF,
importMediatag: CKEDITOR.TRISTATE_OFF, importMediatag: CKEDITOR.TRISTATE_OFF,
}; };
} }

@ -52,10 +52,10 @@ define([
: Share.getShareModal; : Share.getShareModal;
f(common, { f(common, {
origin: priv.origin, origin: priv.origin,
pathname: priv.pathname, pathname: data.pathname || priv.pathname,
password: priv.password, password: data.hashes ? '' : priv.password,
isTemplate: priv.isTemplate, isTemplate: data.hashes ? false : priv.isTemplate,
hashes: priv.hashes, hashes: data.hashes || priv.hashes,
common: common, common: common,
title: data.title, title: data.title,
versionHash: data.versionHash, versionHash: data.versionHash,
@ -64,8 +64,8 @@ define([
hideIframe(); hideIframe();
}, },
fileData: { fileData: {
hash: priv.hashes.fileHash, hash: (data.hashes && data.hashes.fileHash) || priv.hashes.fileHash,
password: priv.password password: data.hashes ? '' : priv.password
} }
}, function (e, modal) { }, function (e, modal) {
if (e) { console.error(e); } if (e) { console.error(e); }

@ -33,7 +33,7 @@ define([
// loading screen setup. // loading screen setup.
var done = waitFor(); var done = waitFor();
var onMsg = function (msg) { 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; } if (data.q !== 'READY') { return; }
window.removeEventListener('message', onMsg); window.removeEventListener('message', onMsg);
var _done = done; var _done = done;

@ -74,6 +74,10 @@
margin-right: 100%; margin-right: 100%;
} }
} }
& > .fa {
align-self: center;
margin-right: -16px;
}
} }
.cp-settings-info-block { .cp-settings-info-block {
[type="text"] { [type="text"] {

@ -51,7 +51,7 @@ define([
'cp-settings-info-block', 'cp-settings-info-block',
'cp-settings-displayname', 'cp-settings-displayname',
'cp-settings-language-selector', 'cp-settings-language-selector',
'cp-settings-resettips', 'cp-settings-mediatag-size',
'cp-settings-change-password', 'cp-settings-change-password',
'cp-settings-delete' 'cp-settings-delete'
], ],
@ -62,6 +62,7 @@ define([
'cp-settings-userfeedback', 'cp-settings-userfeedback',
], ],
'drive': [ 'drive': [
'cp-settings-resettips',
'cp-settings-drive-duplicate', 'cp-settings-drive-duplicate',
'cp-settings-thumbnails', 'cp-settings-thumbnails',
'cp-settings-drive-backup', 'cp-settings-drive-backup',
@ -576,6 +577,57 @@ define([
cb(form); cb(form);
}, true); }, true);
makeBlock('mediatag-size', function(cb) {
var $inputBlock = $('<div>', {
'class': 'cp-sidebarlayout-input-block',
});
var spinner;
var $input = $('<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 // Security
makeBlock('safe-links', function(cb) { makeBlock('safe-links', function(cb) {
@ -777,7 +829,7 @@ define([
Feedback.send('FULL_DRIVE_EXPORT_COMPLETE'); Feedback.send('FULL_DRIVE_EXPORT_COMPLETE');
saveAs(blob, filename); saveAs(blob, filename);
}, errors); }, errors);
}, ui.update); }, ui.update, common.getCache());
ui.onCancel(function() { ui.onCancel(function() {
ui.close(); ui.close();
bu.stop(); bu.stop();

@ -397,6 +397,7 @@ define([
var fmConfig = { var fmConfig = {
body: $('body'), body: $('body'),
noStore: true, // Don't store attachments into our drive
onUploaded: function (ev, data) { onUploaded: function (ev, data) {
if (ev.callback) { if (ev.callback) {
ev.callback(data); ev.callback(data);

@ -46,7 +46,9 @@ define([
Backup, Backup,
Messages) Messages)
{ {
var APP = {}; var APP = {
teams: {}
};
var driveAPP = {}; var driveAPP = {};
var saveAs = window.saveAs; var saveAs = window.saveAs;
//var SHARED_FOLDER_NAME = Messages.fm_sharedFolderName; //var SHARED_FOLDER_NAME = Messages.fm_sharedFolderName;
@ -91,6 +93,8 @@ define([
var readOnly = !secret.keys.editKeyStr; var readOnly = !secret.keys.editKeyStr;
if (!manager || !manager.folders[fId]) { return; } if (!manager || !manager.folders[fId]) { return; }
manager.folders[fId].userObject.setReadOnly(readOnly, secret.keys.secondaryKey); 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 // Remove from memory folders that have been deleted from the drive remotely
@ -211,6 +215,11 @@ define([
if (obj && obj.error) { if (obj && obj.error) {
return void UI.warn(Messages.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); common.displayAvatar($avatar, obj.avatar, obj.name);
$category.append($avatar); $category.append($avatar);
$avatar.append(h('span.cp-sidebarlayout-category-name', obj.name)); $avatar.append(h('span.cp-sidebarlayout-category-name', obj.name));
@ -333,6 +342,11 @@ define([
}); });
APP.drive = drive; APP.drive = drive;
driveAPP.refresh = drive.refresh; driveAPP.refresh = drive.refresh;
if (APP.teams[id] && APP.teams[id].offline) {
setEditable(false);
drive.refresh();
}
}); });
}; };
@ -406,7 +420,15 @@ define([
content.push(h('h3', Messages.team_listTitle + ' ' + slots)); content.push(h('h3', Messages.team_listTitle + ' ' + slots));
APP.teams = {};
keys.forEach(function (id) { keys.forEach(function (id) {
if (!obj[id].empty) {
APP.teams[id] = {
offline: obj[id] && obj[id].offline
};
}
var team = obj[id]; var team = obj[id];
if (team.empty) { if (team.empty) {
list.push(h('div.cp-team-list-team.empty', [ list.push(h('div.cp-team-list-team.empty', [
@ -1049,7 +1071,7 @@ define([
Feedback.send('FULL_TEAMDRIVE_EXPORT_COMPLETE'); Feedback.send('FULL_TEAMDRIVE_EXPORT_COMPLETE');
saveAs(blob, filename); saveAs(blob, filename);
}, errors); }, errors);
}, ui.update); }, ui.update, common.getCache);
ui.onCancel(function() { ui.onCancel(function() {
ui.close(); ui.close();
bu.stop(); bu.stop();
@ -1433,13 +1455,15 @@ define([
} }
}); });
var onDisconnect = function (noAlert) { var onDisconnect = function (teamId) {
if (APP.team && teamId && APP.team !== teamId) { return; }
setEditable(false); setEditable(false);
if (APP.team && driveAPP.refresh) { driveAPP.refresh(); } if (APP.team && driveAPP.refresh) { driveAPP.refresh(); }
toolbar.failed(); 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); setEditable(true);
if (APP.team && driveAPP.refresh) { driveAPP.refresh(); } if (APP.team && driveAPP.refresh) { driveAPP.refresh(); }
toolbar.reconnecting(); toolbar.reconnecting();
@ -1449,11 +1473,17 @@ define([
sframeChan.on('EV_DRIVE_LOG', function (msg) { sframeChan.on('EV_DRIVE_LOG', function (msg) {
UI.log(msg); UI.log(msg);
}); });
sframeChan.on('EV_NETWORK_DISCONNECT', function () { sframeChan.on('EV_NETWORK_DISCONNECT', function (teamId) {
onDisconnect(); onDisconnect(teamId);
if (teamId && APP.teams[teamId]) {
APP.teams[teamId].offline = true;
}
}); });
sframeChan.on('EV_NETWORK_RECONNECT', function () { sframeChan.on('EV_NETWORK_RECONNECT', function (teamId) {
onReconnect(); onReconnect(teamId);
if (teamId && APP.teams[teamId]) {
APP.teams[teamId].offline = false;
}
}); });
common.onLogout(function () { setEditable(false); }); common.onLogout(function () { setEditable(false); });
}); });

@ -55,10 +55,10 @@ define([
sframeChan.event('EV_'+obj.data.ev, obj.data.data); sframeChan.event('EV_'+obj.data.ev, obj.data.data);
} }
if (obj.data.ev === 'NETWORK_RECONNECT') { 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') { if (obj.data.ev === 'NETWORK_DISCONNECT') {
sframeChan.event('EV_NETWORK_DISCONNECT'); sframeChan.event('EV_NETWORK_DISCONNECT', obj.data.data);
} }
}); });

@ -331,10 +331,11 @@ define([
APP.FM.handleFile(blob); 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) { var addImageToCanvas = function (img) {
// 1 MB maximum if (img.src && img.src.length > MAX_IMAGE_SIZE) {
if (img.src && img.src.length > 1 * 1024 * 1024) { UI.warn(Messages._getKey('upload_tooLargeBrief', [maxSizeStr]));
UI.warn(Messages.upload_tooLargeBrief);
return; return;
} }
var w = img.width; var w = img.width;
@ -356,8 +357,8 @@ define([
var file = e.target.files[0]; var file = e.target.files[0];
var reader = new FileReader(); var reader = new FileReader();
// 1 MB maximum // 1 MB maximum
if (file.size > 1 * 1024 * 1024) { if (file.size > MAX_IMAGE_SIZE) {
UI.warn(Messages.upload_tooLargeBrief); UI.warn(Messages._getKey('upload_tooLargeBrief', [maxSizeStr]));
return; return;
} }
reader.onload = function () { reader.onload = function () {

Loading…
Cancel
Save