Merge branch 'cacheRT' into staging

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

@ -213,3 +213,54 @@ media-tag * {
width: 100%;
height: 100%;
}
media-tag button.btn {
background-color: #fff;
box-sizing: border-box;
outline: 0;
display: inline-flex;
align-items: center;
padding: 0 6px;
min-height: 36px;
line-height: 22px;
white-space: nowrap;
text-align: center;
text-transform: uppercase;
font-size: 14px;
text-decoration: none;
cursor: pointer;
border-radius: 0;
transition: none;
color: #3F4141;
border: 1px solid #3F4141;
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);
}
button.primary{
button:not(.btn).primary{
border: 1px solid #4591c4;
padding: 8px 12px;
text-transform: uppercase;
@ -250,7 +250,7 @@ button.primary{
font-weight: bold;
}
button.primary:hover{
button:not(.btn).primary:hover{
background-color: rgb(52, 118, 162);
}
@ -279,7 +279,7 @@ button.primary:hover{
var built = false;
var types = ['less', 'drive', 'migrate', 'sf', 'team', 'pad', 'end'];
var current;
var current, progress;
var makeList = function (data) {
var c = types.indexOf(data.type);
current = c;
@ -295,7 +295,7 @@ button.primary:hover{
};
var list = '<ul>';
types.forEach(function (el, i) {
if (i >= 6) { return; }
if (el === "end") { return; }
list += getLi(i);
});
list += '</ul>';
@ -303,7 +303,7 @@ button.primary:hover{
};
var makeBar = function (data) {
var c = types.indexOf(data.type);
var l = types.length;
var l = types.length - 1; // don't count "end" as a type
var progress = Math.min(data.progress, 100);
var p = (progress / l) + (100 * c / l);
var bar = '<div class="cp-loading-progress-bar">'+
@ -315,14 +315,22 @@ button.primary:hover{
var hasErrored = false;
var updateLoadingProgress = function (data) {
if (!built || !data) { return; }
// Make sure progress doesn't go backward
var c = types.indexOf(data.type);
if (c < current) { return console.error(data); }
if (c === current && progress > data.progress) { return console.error(data); }
progress = data.progress;
try {
document.querySelector('.cp-loading-spinner-container').style.display = 'none';
document.querySelector('.cp-loading-progress-list').innerHTML = makeList(data);
document.querySelector('.cp-loading-progress-container').innerHTML = makeBar(data);
var el1 = document.querySelector('.cp-loading-spinner-container');
if (el1) { el1.style.display = 'none'; }
var el2 = document.querySelector('.cp-loading-progress-list');
if (el2) { el2.innerHTML = makeList(data); }
var el3 = document.querySelector('.cp-loading-progress-container');
if (el3) { el3.innerHTML = makeBar(data); }
} catch (e) {
if (!hasErrored) { console.error(e); }
//if (!hasErrored) { console.error(e); }
}
};
window.CryptPad_updateLoadingProgress = updateLoadingProgress;

@ -14,7 +14,7 @@
right: 10vw;
bottom: 10vh;
box-sizing: border-box;
z-index: 100000; //Z file upload table container
z-index: 100001; //Z file upload table container: just above the file picker
display: none;
color: darken(@colortheme_drive-bg, 10%);
max-height: 180px;

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

@ -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() {
word-wrap: break-word;
@ -84,23 +132,8 @@
margin-top: 4px;
}
}
media-tag {
cursor: pointer;
* {
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;
}
.mediatag_cryptpad();
pre.markmap {
border: 1px solid #ddd;

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

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

@ -177,8 +177,8 @@ server {
add_header Cache-Control max-age=31536000;
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range';
add_header 'Access-Control-Expose-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range,Content-Length';
add_header 'Access-Control-Expose-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range,Content-Length';
try_files $uri =404;
}

@ -419,9 +419,11 @@ const getHistoryOffset = (Env, channelName, lastKnownHash, _cb) => {
// fall through to the next block if the offset of the hash in question is not in memory
if (lastKnownHash && typeof(lkh) !== "number") { return; }
// If we have a lastKnownHash or we didn't ask for one, we don't need the next blocks
waitFor.abort();
// Since last 2 checkpoints
if (!lastKnownHash) {
waitFor.abort();
// Less than 2 checkpoints in the history: return everything
if (index.cpIndex.length < 2) { return void cb(null, 0); }
// Otherwise return the second last checkpoint's index
@ -436,7 +438,16 @@ const getHistoryOffset = (Env, channelName, lastKnownHash, _cb) => {
to reconcile their differences. */
}
offset = lkh;
// If our lastKnownHash is older than the 2nd to last checkpoint,
// only send the last 2 checkpoints and ignore "lkh"
// 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) => {
// 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) {
if (req.method === 'OPTIONS' && /\/blob\//.test(req.url)) {
res.setHeader('Access-Control-Allow-Origin', '*');

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

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

@ -274,10 +274,30 @@
// given a path, asynchronously return an arraybuffer
Util.fetch = function (src, cb, progress) {
var CB = Util.once(cb);
var getCacheKey = function (src) {
var _src = src.replace(/(\/)*$/, ''); // Remove trailing slashes
var idx = _src.lastIndexOf('/');
var cacheKey = _src.slice(idx+1);
if (!/^[a-f0-9]{48}$/.test(cacheKey)) { cacheKey = undefined; }
return cacheKey;
};
Util.fetch = function (src, cb, progress, cache) {
var CB = Util.once(Util.mkAsync(cb));
var 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);
if (progress) {
xhr.addEventListener("progress", function (evt) {
@ -293,11 +313,36 @@
if (/^4/.test(''+this.status)) {
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);
};
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) {
var byteString = atob(dataURI.split(',')[1]);
var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];

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

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

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

@ -42,6 +42,7 @@ define([
var APP = window.APP = {
editable: false,
online: true,
mobile: function () {
if (window.matchMedia) { return !window.matchMedia('(any-pointer:fine)').matches; }
else { return $('body').width() <= 600; }
@ -267,13 +268,25 @@ define([
};
// Handle disconnect/reconnect
var setEditable = function (state, isHistory) {
// If isHistory and isSf are both false, update the "APP.online" flag
// If isHistory is true, update the "APP.history" flag
// isSf is used to detect offline shared folders: setEditable is called on displayDirectory
var setEditable = function (state, isHistory, isSf) {
if (APP.closed || !APP.$content || !$.contains(document.documentElement, APP.$content[0])) { return; }
if (isHistory) {
APP.history = !state;
} else if (!isSf) {
APP.online = state;
}
state = APP.online && !APP.history && state;
APP.editable = !APP.readOnly && state;
if (!state) {
APP.$content.addClass('cp-app-drive-readonly');
if (!isHistory) {
if (!APP.history || !APP.online) {
$('#cp-app-drive-connection-state').show();
} else {
$('#cp-app-drive-connection-state').hide();
}
$('[draggable="true"]').attr('draggable', false);
}
@ -3670,6 +3683,15 @@ define([
}
var readOnlyFolder = false;
// If the shared folder is offline, add the "DISCONNECTED" banner, otherwise
// use the normal "editable" behavior (based on drive offline or history mode)
if (sfId && manager.folders[sfId].offline) {
setEditable(false, false, true);
} else {
setEditable(true, false, true);
}
if (APP.readOnly) {
// Read-only drive (team?)
$content.prepend($readOnly.clone());
@ -4149,6 +4171,17 @@ define([
data.name = Util.fixFileName(folderName);
data.folderName = Util.fixFileName(folderName) + '.zip';
var uo = manager.user.userObject;
if (sfId && manager.folders[sfId]) {
uo = manager.folders[sfId].userObject;
}
if (uo.getFilesRecursively) {
data.list = uo.getFilesRecursively(folderElement).map(function (el) {
var d = uo.getFileData(el);
return d.channel;
});
}
APP.FM.downloadFolder(data, function (err, obj) {
console.log(err, obj);
console.log('DONE');

@ -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;
// Configure MediaTags to use our local viewer
// This file is loaded by sframe-common so the following config is used in all the inner apps
if (MediaTag) {
MediaTag.setDefaultConfig('pdf', {
viewer: '/common/pdfjs/web/viewer.html'
});
MediaTag.setDefaultConfig('download', {
text: Messages.mediatag_saveButton,
textDl: Messages.mediatag_loadButton,
});
}
MT.MediaTag = MediaTag;
// Cache of the avatars outer html (including <media-tag>)
var avatars = {};
@ -68,7 +74,7 @@ define([
childList: true,
characterData: false
});
MediaTag($tag[0]).on('error', function (data) {
MediaTag($tag[0], {force: true}).on('error', function (data) {
console.error(data);
});
};
@ -241,7 +247,6 @@ define([
var locked = false;
var show = function (_i) {
if (locked) { return; }
locked = true;
if (_i < 0) { i = 0; }
else if (_i > tags.length -1) { i = tags.length - 1; }
else { i = _i; }
@ -285,7 +290,6 @@ define([
if (_key) { key = 'cryptpad:' + Nacl.util.encodeBase64(_key); }
}
if (!src || !key) {
locked = false;
$spinner.hide();
return void UI.log(Messages.error);
}
@ -299,13 +303,18 @@ define([
locked = false;
$spinner.hide();
UI.log(Messages.error);
}).on('progress', function () {
$spinner.hide();
locked = true;
}).on('complete', function () {
locked = false;
$spinner.hide();
});
});
}
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function() {
locked = false;
$spinner.hide();
});
});
@ -377,6 +386,14 @@ define([
'tabindex': '-1',
'data-icon': "fa-eye",
}, Messages.pad_mediatagPreview)),
h('li.cp-svg', h('a.cp-app-code-context-openin.dropdown-item', {
'tabindex': '-1',
'data-icon': "fa-external-link",
}, Messages.pad_mediatagOpen)),
h('li.cp-svg', h('a.cp-app-code-context-share.dropdown-item', {
'tabindex': '-1',
'data-icon': "fa-shhare-alt",
}, Messages.pad_mediatagShare)),
h('li', h('a.cp-app-code-context-saveindrive.dropdown-item', {
'tabindex': '-1',
'data-icon': "fa-cloud-upload",
@ -413,12 +430,29 @@ define([
}
else if ($this.hasClass("cp-app-code-context-download")) {
var media = Util.find($mt, [0, '_mediaObject']);
if (!media) { return void console.error('no media'); }
if (!media.complete) { return void UI.warn(Messages.mediatag_notReady); }
if (!(media && media._blob)) { return void console.error($mt); }
window.saveAs(media._blob.content, media.name);
}
else if ($this.hasClass("cp-app-code-context-open")) {
$mt.trigger('preview');
}
else if ($this.hasClass("cp-app-code-context-openin")) {
var hash = common.getHashFromMediaTag($mt);
common.openURL(Hash.hashToHref(hash, 'file'));
}
else if ($this.hasClass("cp-app-code-context-share")) {
var data = {
file: true,
pathname: '/file/',
hashes: {
fileHash: common.getHashFromMediaTag($mt)
},
title: Util.find($mt[0], ['_mediaObject', 'name']) || ''
};
common.getSframeChannel().event('EV_SHARE_OPEN', data);
}
});
return m;

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

@ -1,8 +1,6 @@
(function(name, definition) {
if (typeof module !== 'undefined') { module.exports = definition(); }
else if (typeof define === 'function' && typeof define.amd === 'object') { define(definition); }
else { this[name] = definition(); }
}('MediaTag', function() {
(function (window) {
var factory = function () {
var Promise = window.Promise;
var cache;
var cypherChunkLength = 131088;
@ -63,7 +61,8 @@
],
pdf: {},
download: {
text: "Download"
text: "Save",
textDl: "Load attachment"
},
Plugins: {
/**
@ -114,8 +113,8 @@
},
download: function (metadata, url, content, cfg, cb) {
var btn = document.createElement('button');
btn.setAttribute('class', 'btn btn-success');
btn.innerHTML = cfg.download.text + '<br>' +
btn.setAttribute('class', 'btn btn-default');
btn.innerHTML = '<i class="fa fa-save"></i>' + cfg.download.text + '<br>' +
(metadata.name ? '<b>' + fixHTML(metadata.name) + '</b>' : '');
btn.addEventListener('click', function () {
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
var download = function (src, _cb) {
var download = function (src, _cb, progressCb) {
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.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.onload = function () {
// Error?
if (/^4/.test('' + this.status)) { return void cb("XHR_ERROR " + this.status); }
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);
};
if (!cacheKey) { return void fetch(); }
getBlobCache(cacheKey, function (err, u8) {
if (err || !u8) { return void fetch(); }
cb(null, u8);
});
};
// Decryption tools
var Decrypt = {
// 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.
var decrypt = function (u8, strKey, done, progressCb) {
var Nacl = window.nacl;
@ -372,6 +617,7 @@
var handlers = cfg.handlers || {
'progress': [],
'complete': [],
'metadata': [],
'error': []
};
@ -422,6 +668,7 @@
// End media-tag rendering: display the tag and emit the event
var end = function (decrypted) {
mediaObject.complete = true;
process(mediaObject, decrypted, cfg, function (err) {
if (err) { return void emit('error', err); }
mediaObject._blob = decrypted;
@ -429,33 +676,79 @@
});
};
// If we have the blob in our cache, don't download & decrypt it again, just display
if (cache[uid]) {
end(cache[uid]);
return mediaObject;
}
var error = function (err) {
mediaObject.tag.innerHTML = '<img style="width: 100px; height: 100px;" src="/images/broken.png">';
emit('error', err);
};
var getCache = function () {
var c = cache[uid];
if (!c || !c.promise || !c.mt) { return; }
return c;
};
var dl = function () {
// Download the encrypted blob
cache[uid] = getCache() || {
promise: new Promise(function (resolve, reject) {
download(src, function (err, u8Encrypted) {
if (err) {
if (err === "XHR_ERROR 404") {
mediaObject.tag.innerHTML = '<img style="width: 100px; height: 100px;" src="/images/broken.png">';
}
return void emit('error', err);
return void reject(err);
}
// Decrypt the blob
decrypt(u8Encrypted, strKey, function (errDecryption, u8Decrypted) {
if (errDecryption) {
return void emit('error', errDecryption);
return void reject(errDecryption);
}
// Cache and display the decrypted blob
cache[uid] = u8Decrypted;
end(u8Decrypted);
emit('metadata', u8Decrypted.metadata);
resolve(u8Decrypted);
}, function (progress) {
emit('progress', {
progress: 50+0.5*progress
});
});
}, function (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;
@ -468,5 +761,20 @@
config[key] = value;
};
init.fetchDecryptedMetadata = fetchDecryptedMetadata;
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);
callback("");
}
});
}, void 0, common.getCache());
};
APP.docEditor = new window.DocsAPI.DocEditor("cp-app-oo-placeholder-a", APP.ooconfig);

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

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

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

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

@ -1,9 +1,11 @@
define([
'/file/file-crypto.js',
'/common/common-hash.js',
'/common/common-util.js',
'/common/outer/cache-store.js',
'/bower_components/nthen/index.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
], function (FileCrypto, Hash, nThen) {
], function (FileCrypto, Hash, Util, Cache, nThen) {
var Nacl = window.nacl;
var module = {};
@ -31,9 +33,11 @@ define([
};
var actual = 0;
var encryptedArr = [];
var again = function (err, box) {
if (err) { onError(err); }
if (box) {
encryptedArr.push(box);
actual += box.length;
var progressValue = (actual / estimate * 100);
progressValue = Math.min(progressValue, 100);
@ -55,10 +59,12 @@ define([
var uri = ['', 'blob', id.slice(0,2), id].join('/');
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();
});
});
};
common.uploadStatus(teamId, estimate, function (e, pending) {

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

@ -40,6 +40,14 @@ define([
userObject: userObject,
leave: leave
};
if (proxy.on) {
proxy.on('disconnect', function () {
Env.folders[id].offline = true;
});
proxy.on('reconnect', function () {
Env.folders[id].online = true;
});
}
return userObject;
};

@ -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 () {
toolbar.offline(false);
var newContentStr = cpNfInner.chainpad.getUserDoc();
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("Or else something is terribly wrong, reloading.");
Feedback.send("NON_EMPTY_NEWDOC");
setTimeout(function () { common.gotoURL(); }, 1000);
// The cache may be wrong, empty it and reload after.
waitFor.abort();
onCorruptedCache();
return;
}
console.log('updating title');
title.updateTitle(title.defaultTitle);
evOnDefaultContentNeeded.fire();
}
}).nThen(function () {
// We have a valid chainpad, reenable cache fix in case with reconnect with
// a corrupted cache
noCache = false;
stateChange(STATE.READY);
firstConnection = false;
@ -734,6 +783,7 @@ define([
onRemote: onRemote,
onLocal: onLocal,
onInit: onInit,
onCacheReady: onCacheReady,
onReady: function () { evStart.reg(onReady); },
onConnectionChange: onConnectionChange,
onError: onError,

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

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

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

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

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

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

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

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

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

@ -48,69 +48,6 @@ define([
return new Blob(chunks);
};
var concatBuffer = function (a, b) { // TODO make this not so ugly
return new Uint8Array(slice(a).concat(slice(b)));
};
var fetchMetadata = function (src, cb) {
var done = false;
var CB = function (err, res) {
if (done) { return; }
done = true;
cb(err, res);
};
var xhr = new XMLHttpRequest();
xhr.open("GET", src, true);
xhr.setRequestHeader('Range', 'bytes=0-1');
xhr.responseType = 'arraybuffer';
xhr.onerror= function () { return CB('XHR_ERROR'); };
xhr.onload = function () {
if (/^4/.test('' + this.status)) { return CB('XHR_ERROR'); }
var res = new Uint8Array(xhr.response);
var size = decodePrefix(res);
var xhr2 = new XMLHttpRequest();
xhr2.open("GET", src, true);
xhr2.setRequestHeader('Range', 'bytes=2-' + (size + 2));
xhr2.responseType = 'arraybuffer';
xhr2.onload = function () {
if (/^4/.test('' + this.status)) { return CB('XHR_ERROR'); }
var res2 = new Uint8Array(xhr2.response);
var all = concatBuffer(res, res2);
CB(void 0, all);
};
xhr2.send(null);
};
xhr.send(null);
};
var decryptMetadata = function (u8, key) {
var prefix = u8.subarray(0, 2);
var metadataLength = decodePrefix(prefix);
var metaBox = new Uint8Array(u8.subarray(2, 2 + metadataLength));
var metaChunk = Nacl.secretbox.open(metaBox, createNonce(), key);
try {
return JSON.parse(Nacl.util.encodeUTF8(metaChunk));
}
catch (e) { return null; }
};
var fetchDecryptedMetadata = function (src, key, cb) {
if (typeof(src) !== 'string') {
return window.setTimeout(function () {
cb('NO_SOURCE');
});
}
fetchMetadata(src, function (e, buffer) {
if (e) { return cb(e); }
cb(void 0, decryptMetadata(buffer, key));
});
};
var decrypt = function (u8, key, done, progress) {
var MAX = u8.length;
var _progress = function (offset) {
@ -128,6 +65,11 @@ define([
metadata: undefined,
};
var cancelled = false;
var cancel = function () {
cancelled = true;
};
var metaBox = new Uint8Array(u8.subarray(2, 2 + metadataLength));
var metaChunk = Nacl.secretbox.open(metaBox, nonce, key);
@ -168,6 +110,7 @@ define([
var chunks = [];
var again = function () {
if (cancelled) { return; }
takeChunk(function (e, plaintext) {
if (e) {
return setTimeout(function () {
@ -188,6 +131,10 @@ define([
};
again();
return {
cancel: cancel
};
};
// metadata
@ -258,8 +205,5 @@ define([
encrypt: encrypt,
joinChunks: joinChunks,
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"
data-localization="upload_choose"></label>
</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;">
<media-tag id="cp-app-file-view"></media-tag>
</div>

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

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

@ -53,15 +53,57 @@
editor.plugins.mediatag.import($mt);
}
});
editor.addCommand('downloadMT', {
exec: function (editor) {
var w = targetWidget;
targetWidget = undefined;
var $mt = $(w.$).find('media-tag');
editor.plugins.mediatag.download($mt);
}
});
editor.addCommand('openMT', {
exec: function (editor) {
var w = targetWidget;
targetWidget = undefined;
var $mt = $(w.$).find('media-tag');
editor.plugins.mediatag.open($mt);
}
});
editor.addCommand('shareMT', {
exec: function (editor) {
var w = targetWidget;
targetWidget = undefined;
var $mt = $(w.$).find('media-tag');
editor.plugins.mediatag.share($mt);
}
});
if (editor.addMenuItems) {
editor.addMenuGroup('mediatag');
editor.addMenuItem('open', {
label: Messages.open,
icon: 'iframe',
command: 'openMT',
group: 'mediatag'
});
editor.addMenuItem('share', {
label: Messages.share,
icon: 'link',
command: 'shareMT',
group: 'mediatag'
});
editor.addMenuItem('importMediatag', {
label: Messages.import,
icon: 'save',
command: 'importMediatag',
group: 'mediatag'
});
editor.addMenuItem('download', {
label: Messages.download,
icon: 'save',
command: 'downloadMT',
group: 'mediatag'
});
editor.addMenuItem('mediatag', {
label: Messages.options,
icon: 'image',
@ -76,6 +118,9 @@
targetWidget = element;
return {
mediatag: CKEDITOR.TRISTATE_OFF,
open: CKEDITOR.TRISTATE_OFF,
share: CKEDITOR.TRISTATE_OFF,
download: CKEDITOR.TRISTATE_OFF,
importMediatag: CKEDITOR.TRISTATE_OFF,
};
}

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

@ -33,7 +33,7 @@ define([
// loading screen setup.
var done = waitFor();
var onMsg = function (msg) {
var data = JSON.parse(msg.data);
var data = typeof(msg.data) === "object" ? msg.data : JSON.parse(msg.data);
if (data.q !== 'READY') { return; }
window.removeEventListener('message', onMsg);
var _done = done;

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

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

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

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

@ -55,10 +55,10 @@ define([
sframeChan.event('EV_'+obj.data.ev, obj.data.data);
}
if (obj.data.ev === 'NETWORK_RECONNECT') {
sframeChan.event('EV_NETWORK_RECONNECT');
sframeChan.event('EV_NETWORK_RECONNECT', obj.data.data);
}
if (obj.data.ev === 'NETWORK_DISCONNECT') {
sframeChan.event('EV_NETWORK_DISCONNECT');
sframeChan.event('EV_NETWORK_DISCONNECT', obj.data.data);
}
});

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

Loading…
Cancel
Save