Merge branch 'staging' into serviceworker
This commit is contained in:
commit
996245ec3d
17 files changed
+571
-159
No files matched your search
+1
-1
@@ -5,7 +5,7 @@ www/common/tippy/
|
||||
www/common/jquery-ui/
|
||||
|
||||
server.js
|
||||
www/common/media-tag.js
|
||||
www/common/old-media-tag.js
|
||||
www/scratch
|
||||
|
||||
www/common/toolbar.js
|
||||
|
||||
@@ -1,3 +1,30 @@
|
||||
# Coati release (v2.2.0)
|
||||
|
||||
## Goals
|
||||
|
||||
For this release we wanted to continue our efforts towards improving CryptPad usability. We've also added a new Kanban application which was in its final stage for quite some time.
|
||||
|
||||
## What's new
|
||||
|
||||
### Features
|
||||
|
||||
* We've added a new kanban application!
|
||||
* You can create boards, add items to those boards and move items from one board to another.
|
||||
* It includes almost all the features seen in the other apps: templates, password protection, history, read-only, etc.
|
||||
* Kanban can be shared and used collaboratively.
|
||||
* This new app was prototyped by @ldubost, and based on [jkanban](https://github.com/riktar/jkanban) by @riktar
|
||||
* We've improved our tagging feature.
|
||||
* When you want to add tags to a pad, you will see suggestions based on the tags you've already used
|
||||
* There is a new *Tags* category in CryptDrive for logged in users. It shows all the tags you've used in your pads and their number of use.
|
||||
* In the Poll application, the line where your cursor is located will be highlighted so that you can see easily which option you're looking at.
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* We've fixed two interface bugs in the Share menu which made it difficult to change the access rights for the link (edit or read-only) in some cases.
|
||||
* A bug introduced in the previous version prevented loading of the drive if it contained some content from an alpha version of CryptPad.
|
||||
* Some parts of our UI were using CSS values not supported by all browsers.
|
||||
* Some pads created more than one year ago were not loading properly.
|
||||
|
||||
# Badger release (v2.1.0)
|
||||
|
||||
## Goals
|
||||
|
||||
@@ -1152,7 +1152,7 @@ define(function () {
|
||||
out.creation_newPadModalAdvanced = "Display the pad creation screen";
|
||||
|
||||
// Password prompt on the loadind screen
|
||||
out.password_info = "The pad you're tyring to open is protected with a password. Enter the correct password to access its content.";
|
||||
out.password_info = "The pad you're trying to open is protected with a password. Enter the correct password to access its content.";
|
||||
out.password_error = "Pad not found!<br>This error can be caused by two factors: either the password in invalid, or the pad has been deleted from the server.";
|
||||
out.password_placeholder = "Type the password here...";
|
||||
out.password_submit = "Submit";
|
||||
|
||||
@@ -896,13 +896,13 @@ var removeOwnedBlob = function (Env, blobId, unsafeKey, cb) {
|
||||
Fs.unlink(blobPath, w(function (e) {
|
||||
if (e) {
|
||||
w.abort();
|
||||
return void cb(e);
|
||||
return void cb(e.code);
|
||||
}
|
||||
}));
|
||||
}).nThen(function () {
|
||||
// Delete the proof of ownership
|
||||
Fs.unlink(ownPath, function (e) {
|
||||
cb(e);
|
||||
cb(e && e.code);
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -1204,7 +1204,7 @@ var owned_upload_complete = function (Env, safeKey, id, cb) {
|
||||
return void cb();
|
||||
} else {
|
||||
// it failed in an unexpected way. log it
|
||||
WARN(e, 'ownedUploadComplete');
|
||||
WARN('ownedUploadComplete', e);
|
||||
return void cb(e.code);
|
||||
}
|
||||
});
|
||||
@@ -1221,13 +1221,13 @@ var owned_upload_complete = function (Env, safeKey, id, cb) {
|
||||
Mkdirp(filePath, w(function (e /*, path */) {
|
||||
if (e) { // does not throw error if the directory already existed
|
||||
w.abort();
|
||||
return void cb(e);
|
||||
return void cb(e.code);
|
||||
}
|
||||
}));
|
||||
Mkdirp(ownPath, w(function (e /*, path */) {
|
||||
if (e) { // does not throw error if the directory already existed
|
||||
w.abort();
|
||||
return void cb(e);
|
||||
return void cb(e.code);
|
||||
}
|
||||
}));
|
||||
}).nThen(function (w) {
|
||||
@@ -1254,12 +1254,11 @@ var owned_upload_complete = function (Env, safeKey, id, cb) {
|
||||
|
||||
// flow is dumb and I need to guard against this which will never happen
|
||||
/*:: if (typeof(oldPath) === 'object') { throw new Error('should never happen'); } */
|
||||
Fs.rename(oldPath /* XXX */, finalPath, w(function (e) {
|
||||
Fs.rename(oldPath, finalPath, w(function (e) {
|
||||
if (e) {
|
||||
// Remove the ownership file
|
||||
// XXX not needed if we have a cleanup script?
|
||||
Fs.unlink(finalOwnPath, function (e) {
|
||||
WARN(e, 'Removing ownership file ownedUploadComplete');
|
||||
WARN('E_UNLINK_OWN_FILE', e);
|
||||
});
|
||||
w.abort();
|
||||
return void cb(e.code);
|
||||
|
||||
+22
-1
@@ -9,7 +9,8 @@ define([
|
||||
'/common/common-thumbnail.js',
|
||||
'/common/wire.js',
|
||||
'/common/flat-dom.js',
|
||||
], function ($, Hyperjson, Sortify, Drive, Test, Hash, Util, Thumb, Wire, Flat) {
|
||||
'/common/media-tag.js',
|
||||
], function ($, Hyperjson, Sortify, Drive, Test, Hash, Util, Thumb, Wire, Flat, MediaTag) {
|
||||
window.Hyperjson = Hyperjson;
|
||||
window.Sortify = Sortify;
|
||||
|
||||
@@ -295,6 +296,26 @@ define([
|
||||
!secret.hashData.present);
|
||||
}, "test support for ugly tracking query paramaters in url");
|
||||
|
||||
assert(function (cb) {
|
||||
try {
|
||||
MediaTag(void 0).on('progress').on('decryption');
|
||||
return void cb(true);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return void cb(false);
|
||||
}
|
||||
}, 'check that MediaTag does the right thing when passed no value');
|
||||
|
||||
assert(function (cb) {
|
||||
try {
|
||||
MediaTag(document.createElement('div')).on('progress').on('decryption');
|
||||
return void cb(true);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return void cb(false);
|
||||
}
|
||||
}, 'check that MediaTag does the right thing when passed no value');
|
||||
|
||||
assert(function (cb) {
|
||||
// TODO
|
||||
return cb(true);
|
||||
|
||||
@@ -49,6 +49,7 @@ define([
|
||||
if (ua[0].indexOf(':') === -1 && ua[0].indexOf('/') && parent) {
|
||||
ua[0] = parent.replace(/\/[^\/]*$/, '/') + ua[0];
|
||||
}
|
||||
ua[0] = ua[0].replace(/^\/\.\.\//, '/');
|
||||
var out = ua.join('#');
|
||||
//console.log(url + " --> " + out);
|
||||
return out;
|
||||
@@ -91,17 +92,32 @@ define([
|
||||
};
|
||||
|
||||
var lessEngine;
|
||||
var tempCache = { key: Math.random() };
|
||||
var getLessEngine = function (cb) {
|
||||
if (lessEngine) {
|
||||
cb(lessEngine);
|
||||
} else {
|
||||
require(['/bower_components/less/dist/less.min.js'], function (Less) {
|
||||
if (lessEngine) { return void cb(lessEngine); }
|
||||
lessEngine = Less;
|
||||
var doXHR = lessEngine.FileManager.prototype.doXHR;
|
||||
lessEngine.FileManager.prototype.doXHR = function (url, type, callback, errback) {
|
||||
url = fixURL(url);
|
||||
//console.log("xhr: " + url);
|
||||
return doXHR(url, type, callback, errback);
|
||||
var cached = tempCache[url];
|
||||
if (cached && cached.res) {
|
||||
var res = cached.res;
|
||||
return void setTimeout(function () { callback(res[0], res[1]); });
|
||||
}
|
||||
if (cached) { return void cached.queue.push(callback); }
|
||||
cached = tempCache[url] = { queue: [ callback ], res: undefined };
|
||||
return doXHR(url, type, function (text, lastModified) {
|
||||
cached.res = [ text, lastModified ];
|
||||
var queue = cached.queue;
|
||||
cached.queue = [];
|
||||
queue.forEach(function (f) {
|
||||
setTimeout(function () { f(text, lastModified); });
|
||||
});
|
||||
}, errback);
|
||||
};
|
||||
cb(lessEngine);
|
||||
});
|
||||
|
||||
@@ -144,13 +144,15 @@ define([
|
||||
};
|
||||
|
||||
dialog.frame = function (content) {
|
||||
return h('div.alertify', {
|
||||
return $(h('div.alertify', {
|
||||
tabindex: 1,
|
||||
}, [
|
||||
h('div.dialog', [
|
||||
h('div', content),
|
||||
])
|
||||
]);
|
||||
])).click(function (e) {
|
||||
e.stopPropagation();
|
||||
})[0];
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,8 +18,10 @@ define([
|
||||
var UIElements = {};
|
||||
|
||||
// Configure MediaTags to use our local viewer
|
||||
if (MediaTag && MediaTag.PdfPlugin) {
|
||||
MediaTag.PdfPlugin.viewer = '/common/pdfjs/web/viewer.html';
|
||||
if (MediaTag) {
|
||||
MediaTag.setDefaultConfig('pdf', {
|
||||
viewer: '/common/pdfjs/web/viewer.html'
|
||||
});
|
||||
}
|
||||
|
||||
UIElements.updateTags = function (common, href) {
|
||||
@@ -626,7 +628,6 @@ define([
|
||||
}
|
||||
}
|
||||
sframeChan.query('Q_SAVE_AS_TEMPLATE', {
|
||||
title: title,
|
||||
toSave: toSave
|
||||
}, function () {
|
||||
UI.alert(Messages.templateSaved);
|
||||
@@ -1035,52 +1036,6 @@ define([
|
||||
|
||||
// Avatars
|
||||
|
||||
// Enable mediatags
|
||||
$(window.document).on('decryption', function (e) {
|
||||
var decrypted = e.originalEvent;
|
||||
if (decrypted.callback) {
|
||||
var cb = decrypted.callback;
|
||||
cb(function (mediaObject) {
|
||||
var root = mediaObject.element;
|
||||
if (!root) { return; }
|
||||
|
||||
if (mediaObject.type === 'image') {
|
||||
$(root).data('blob', decrypted.blob);
|
||||
}
|
||||
|
||||
if (mediaObject.type !== 'download') { return; }
|
||||
|
||||
var metadata = decrypted.metadata;
|
||||
|
||||
var title = '';
|
||||
var size = 0;
|
||||
if (metadata && metadata.name) {
|
||||
title = metadata.name;
|
||||
}
|
||||
|
||||
if (decrypted.blob) {
|
||||
size = decrypted.blob.size;
|
||||
}
|
||||
|
||||
var sizeMb = Util.bytesToMegabytes(size);
|
||||
|
||||
var $btn = $(root).find('button');
|
||||
$btn.addClass('btn btn-success')
|
||||
.attr('type', 'download')
|
||||
.html(function () {
|
||||
var text = Messages.download_mt_button + '<br>';
|
||||
if (title) {
|
||||
text += '<b>' + Util.fixHTML(title) + '</b><br>';
|
||||
}
|
||||
if (size) {
|
||||
text += '<em>' + Messages._getKey('formattedMB', [sizeMb]) + '</em>';
|
||||
}
|
||||
return text;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
UIElements.displayMediatagImage = function (Common, $tag, cb) {
|
||||
if (!$tag.length || !$tag.is('media-tag')) { return void cb('NOT_MEDIATAG'); }
|
||||
var observer = new MutationObserver(function(mutations) {
|
||||
|
||||
@@ -449,6 +449,26 @@ define([
|
||||
}).nThen(function () {
|
||||
Crypt.get(parsed.hash, function (err, val) {
|
||||
if (err) { throw new Error(err); }
|
||||
try {
|
||||
// Try to fix the title before importing the template
|
||||
var parsed = JSON.parse(val);
|
||||
var meta;
|
||||
if (Array.isArray(parsed) && typeof(parsed[3]) === "object") {
|
||||
meta = parsed[3].metadata; // pad
|
||||
} else if (parsed.info) {
|
||||
meta = parsed.info; // poll
|
||||
} else {
|
||||
meta = parsed.metadata;
|
||||
}
|
||||
if (typeof(meta) === "object") {
|
||||
meta.defaultTitle = meta.title || meta.defaultTitle;
|
||||
delete meta.users;
|
||||
delete meta.title;
|
||||
}
|
||||
val = JSON.stringify(parsed);
|
||||
} catch (e) {
|
||||
console.log("Can't fix template title", e);
|
||||
}
|
||||
Crypt.put(parsed2.hash, val, cb, optsPut);
|
||||
}, optsGet);
|
||||
});
|
||||
|
||||
+428
-1
@@ -1 +1,428 @@
|
||||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.MediaTag=t():e.MediaTag=t()}(this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}var n={};return t.m=e,t.c=n,t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=63)}([function(e,t,n){"use strict";var r={IMAGE:"image",AUDIO:"audio",VIDEO:"video",PDF:"pdf",DASH:"dash",DOWNLOAD:"download",CRYPTO:"crypto",CLEAR_KEY:"clear-key",MEDIA_OBJECT:"media-object"};e.exports=r},function(e,t,n){"use strict";var r={MATCHER:"matcher",RENDERER:"renderer",FILTER:"filter",SANITIZER:"sanitizer"};e.exports=r},function(e,t,n){"use strict";function r(e){if(e instanceof Array){var t=[];return e.forEach(function(e){if(e.mediaObject)t.push(e.mediaObject);else{new c(e,r.processingEngine).mediaObjects.forEach(function(e){t.push(r.processingEngine.start(e))})}}),t}new c(e,r.processingEngine).mediaObjects.forEach(function(e){r.processingEngine.start(e)})}var o=n(31),i=n(33),u=n(34),a=n(35),c=n(36);r.pluginStore=r.pluginStore||new u,r.uriStore=r.uriStore||new a("../plugins"),r.processingEngine=r.processingEngine||new o(r.pluginStore),r.matchingEngine=r.matchingEngine||new i(r.pluginStore,r.uriStore),r.loadingEngine=null,e.exports=r},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}var u=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),a=n(1),c=n(5),s=n(6),f=function(e){function t(e,n){r(this,t);var i=o(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e,a.MATCHER,c.ANY));return i.targetType=n,i}return i(t,e),u(t,[{key:"getTargetType",value:function(){return this.targetType}}]),t}(s);e.exports=f},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}var u=n(1),a=n(5),c=n(6),s=function(e){function t(e){return r(this,t),o(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e,u.RENDERER,a.ONCE))}return i(t,e),t}(c);e.exports=s},function(e,t,n){"use strict";var r={EVERY:"every",ANY:"any",ONCE:"once"};e.exports=r},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var o=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),i=function(){function e(t,n,o){r(this,e),this.identifier=t,this.type=n,this.occurrence=o}return o(e,[{key:"getIdentifier",value:function(){if(this.identifier)return this.identifier;throw new Error("Plugin has not identifier")}},{key:"getType",value:function(){if(this.type)return this.type;throw new Error("Plugin has no type")}},{key:"start",value:function(e){return this.process(e)}}]),e}();e.exports=i},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__pLine truncated
|
||||
(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() {
|
||||
var cache;
|
||||
var cypherChunkLength = 131088;
|
||||
|
||||
// Save a blob on the file system
|
||||
var saveFile = function (blob, url, fileName) {
|
||||
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
|
||||
window.navigator.msSaveOrOpenBlob(blob, fileName);
|
||||
} else {
|
||||
// We want to be able to download the file with a name, so we need an "a" tag with
|
||||
// a download attribute
|
||||
var a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = fileName;
|
||||
// It's not in the DOM, so we can't use a.click();
|
||||
var event = new MouseEvent("click");
|
||||
a.dispatchEvent(event);
|
||||
}
|
||||
};
|
||||
|
||||
var fixHTML = function (str) {
|
||||
if (!str) { return ''; }
|
||||
return str.replace(/[<>&"']/g, function (x) {
|
||||
return ({ "<": "<", ">": ">", "&": "&", '"': """, "'": "'" })[x];
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// Default config, can be overriden per media-tag call
|
||||
var config = {
|
||||
allowed: [
|
||||
'image/png',
|
||||
'image/jpeg',
|
||||
'image/jpg',
|
||||
'image/gif',
|
||||
'audio/mp3',
|
||||
'audio/ogg',
|
||||
'audio/wav',
|
||||
'audio/webm',
|
||||
'video/mp4',
|
||||
'video/ogg',
|
||||
'video/webm',
|
||||
'application/pdf',
|
||||
//'application/dash+xml', // FIXME?
|
||||
'download'
|
||||
],
|
||||
pdf: {},
|
||||
download: {
|
||||
text: "Download"
|
||||
},
|
||||
Plugins: {
|
||||
image: function (metadata, url, content, cfg, cb) {
|
||||
var img = document.createElement('img');
|
||||
img.setAttribute('src', url);
|
||||
img.blob = content;
|
||||
cb(void 0, img);
|
||||
},
|
||||
video: function (metadata, url, content, cfg, cb) {
|
||||
var video = document.createElement('video');
|
||||
video.setAttribute('src', url);
|
||||
video.setAttribute('controls', true);
|
||||
cb(void 0, video);
|
||||
},
|
||||
audio: function (metadata, url, content, cfg, cb) {
|
||||
var audio = document.createElement('audio');
|
||||
audio.setAttribute('src', url);
|
||||
audio.setAttribute('controls', true);
|
||||
cb(void 0, audio);
|
||||
},
|
||||
pdf: function (metadata, url, content, cfg, cb) {
|
||||
var iframe = document.createElement('iframe');
|
||||
if (cfg.pdf.viewer) { // PDFJS
|
||||
var viewerUrl = cfg.pdf.viewer + '?file=' + url;
|
||||
iframe.src = viewerUrl + '#' + window.encodeURIComponent(metadata.name);
|
||||
return void cb (void 0, iframe);
|
||||
}
|
||||
iframe.src = url + '#' + window.encodeURIComponent(metadata.name);
|
||||
return void cb (void 0, iframe);
|
||||
},
|
||||
download: function (metadata, url, content, cfg, cb) {
|
||||
var btn = document.createElement('button');
|
||||
btn.setAttribute('class', 'btn btn-success');
|
||||
btn.innerHTML = cfg.download.text + '<br>' +
|
||||
(metadata.name ? '<b>' + fixHTML(metadata.name) + '</b>' : '');
|
||||
btn.addEventListener('click', function () {
|
||||
saveFile(content, url, metadata.name);
|
||||
});
|
||||
cb(void 0, btn);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Download a blob from href
|
||||
var download = function (src, cb) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', src, true);
|
||||
xhr.responseType = 'arraybuffer';
|
||||
|
||||
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)); }
|
||||
};
|
||||
|
||||
xhr.send(null);
|
||||
};
|
||||
|
||||
// Decryption tools
|
||||
var Decrypt = {
|
||||
// Create a nonce
|
||||
createNonce: function () {
|
||||
var n = new Uint8Array(24);
|
||||
for (var i = 0; i < 24; i++) { n[i] = 0; }
|
||||
return n;
|
||||
},
|
||||
|
||||
// Increment a nonce
|
||||
increment: function (N) {
|
||||
var l = N.length;
|
||||
while (l-- > 1) {
|
||||
/* .jshint probably suspects this is unsafe because we lack types
|
||||
but as long as this is only used on nonces, it should be safe */
|
||||
if (N[l] !== 255) { return void N[l]++; } // jshint ignore:line
|
||||
|
||||
// you don't need to worry about this running out.
|
||||
// you'd need a REAAAALLY big file
|
||||
if (l === 0) { throw new Error('E_NONCE_TOO_LARGE'); }
|
||||
|
||||
N[l] = 0;
|
||||
}
|
||||
},
|
||||
|
||||
decodePrefix: function (A) {
|
||||
return (A[0] << 8) | A[1];
|
||||
},
|
||||
joinChunks: function (chunks) {
|
||||
return new Blob(chunks);
|
||||
},
|
||||
|
||||
// Convert a Uint8Array into Array.
|
||||
slice: function (u8) {
|
||||
return Array.prototype.slice.call(u8);
|
||||
},
|
||||
|
||||
// Gets the key from the key string.
|
||||
getKeyFromStr: function (str) {
|
||||
return window.nacl.util.decodeBase64(str);
|
||||
}
|
||||
};
|
||||
|
||||
// Decrypts a Uint8Array with the given key.
|
||||
var decrypt = function (u8, strKey, done, progressCb) {
|
||||
var Nacl = window.nacl;
|
||||
|
||||
var progress = function (offset) {
|
||||
progressCb((offset / u8.length) * 100);
|
||||
};
|
||||
|
||||
var key = Decrypt.getKeyFromStr(strKey);
|
||||
var nonce = Decrypt.createNonce();
|
||||
var i = 0;
|
||||
var prefix = u8.subarray(0, 2);
|
||||
var metadataLength = Decrypt.decodePrefix(prefix);
|
||||
|
||||
var res = { metadata: undefined };
|
||||
|
||||
// Get metadata
|
||||
var metaBox = new Uint8Array(u8.subarray(2, 2 + metadataLength));
|
||||
var metaChunk = Nacl.secretbox.open(metaBox, nonce, key);
|
||||
|
||||
Decrypt.increment(nonce);
|
||||
|
||||
try { res.metadata = JSON.parse(Nacl.util.encodeUTF8(metaChunk)); }
|
||||
catch (e) { return void done('E_METADATA_DECRYPTION'); }
|
||||
|
||||
if (!res.metadata) { return void done('NO_METADATA'); }
|
||||
|
||||
var takeChunk = function (cb) {
|
||||
setTimeout(function () {
|
||||
var start = i * cypherChunkLength + 2 + metadataLength;
|
||||
var end = start + cypherChunkLength;
|
||||
i++;
|
||||
|
||||
// Get the chunk
|
||||
var box = new Uint8Array(u8.subarray(start, end));
|
||||
|
||||
// Decrypt the chunk
|
||||
var plaintext = Nacl.secretbox.open(box, nonce, key);
|
||||
Decrypt.increment(nonce);
|
||||
|
||||
if (!plaintext) { return void cb('DECRYPTION_FAILURE'); }
|
||||
|
||||
progress(Math.min(end, u8.length));
|
||||
|
||||
cb(void 0, plaintext);
|
||||
});
|
||||
};
|
||||
|
||||
var chunks = [];
|
||||
|
||||
// decrypt file contents
|
||||
var again = function () {
|
||||
takeChunk(function (e, plaintext) {
|
||||
if (e) { return setTimeout(function () { done(e); }); }
|
||||
|
||||
if (plaintext) {
|
||||
if (i * cypherChunkLength < u8.length) { // not done
|
||||
chunks.push(plaintext);
|
||||
return again();
|
||||
}
|
||||
|
||||
chunks.push(plaintext);
|
||||
res.content = Decrypt.joinChunks(chunks);
|
||||
return void done(void 0, res);
|
||||
}
|
||||
done('UNEXPECTED_ENDING');
|
||||
});
|
||||
};
|
||||
again();
|
||||
};
|
||||
|
||||
// Get type
|
||||
var getType = function (mediaObject, metadata, cfg) {
|
||||
var mime = metadata.type;
|
||||
var s = metadata.type.split('/');
|
||||
var type = s[0];
|
||||
var extension = s[1];
|
||||
|
||||
mediaObject.name = metadata.name;
|
||||
if (mime && cfg.allowed.indexOf(mime) !== -1) {
|
||||
mediaObject.type = type;
|
||||
mediaObject.extension = extension;
|
||||
mediaObject.mime = mime;
|
||||
return type;
|
||||
} else if (cfg.allowed.indexOf('download') !== -1) {
|
||||
mediaObject.type = type;
|
||||
mediaObject.extension = extension;
|
||||
mediaObject.mime = mime;
|
||||
return 'download';
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Copy attributes
|
||||
var copyAttributes = function (origin, dest) {
|
||||
Object.keys(origin.attributes).forEach(function (i) {
|
||||
if (!/^data-attr/.test(origin.attributes[i].name)) { return; }
|
||||
var name = origin.attributes[i].name.slice(10);
|
||||
var value = origin.attributes[i].value;
|
||||
dest.setAttribute(name, value);
|
||||
});
|
||||
};
|
||||
|
||||
// Process
|
||||
var process = function (mediaObject, decrypted, cfg, cb) {
|
||||
var metadata = decrypted.metadata;
|
||||
var blob = decrypted.content;
|
||||
|
||||
var mediaType = getType(mediaObject, metadata, cfg);
|
||||
|
||||
if (mediaType === 'application') {
|
||||
mediaType = mediaObject.extension;
|
||||
}
|
||||
|
||||
if (!mediaType || !cfg.Plugins[mediaType]) {
|
||||
return void cb('NO_PLUGIN_FOUND');
|
||||
}
|
||||
|
||||
// Get blob URL
|
||||
var url = decrypted.url;
|
||||
if (!url && window.URL) {
|
||||
url = decrypted.url = window.URL.createObjectURL(new Blob([blob], {
|
||||
type: metadata.type
|
||||
}));
|
||||
}
|
||||
|
||||
cfg.Plugins[mediaType](metadata, url, blob, cfg, function (err, el) {
|
||||
if (err || !el) { return void cb(err || 'ERR_MEDIATAG_DISPLAY'); }
|
||||
copyAttributes(mediaObject.tag, el);
|
||||
mediaObject.tag.innerHTML = '';
|
||||
mediaObject.tag.appendChild(el);
|
||||
cb();
|
||||
});
|
||||
};
|
||||
|
||||
var addMissingConfig = function (base, target) {
|
||||
Object.keys(target).forEach(function (k) {
|
||||
if (!target[k]) { return; }
|
||||
// Target is an object, fix it recursively
|
||||
if (typeof target[k] === "object" && !Array.isArray(target[k])) {
|
||||
// Sub-object
|
||||
if (base[k] && (typeof base[k] !== "object" || Array.isArray(base[k]))) { return; }
|
||||
else if (base[k]) { addMissingConfig(base[k], target[k]); }
|
||||
else {
|
||||
base[k] = {};
|
||||
addMissingConfig(base[k], target[k]);
|
||||
}
|
||||
}
|
||||
// Target is array or immutable, copy the value if it's missing
|
||||
if (!base[k]) {
|
||||
base[k] = Array.isArray(target[k]) ? JSON.parse(JSON.stringify(target[k]))
|
||||
: target[k];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Initialize a media-tag
|
||||
var init = function (el, cfg) {
|
||||
cfg = cfg || {};
|
||||
|
||||
addMissingConfig(cfg, config);
|
||||
|
||||
// Handle jQuery elements
|
||||
if (typeof(el) === "object" && el.jQuery) { el = el[0]; }
|
||||
|
||||
// Abort smoothly if the element is not a media-tag
|
||||
if (!el || el.nodeName !== "MEDIA-TAG") {
|
||||
console.error("Not a media-tag!");
|
||||
return {
|
||||
on: function () { return this; }
|
||||
};
|
||||
}
|
||||
|
||||
var handlers = cfg.handlers || {
|
||||
'progress': [],
|
||||
'complete': [],
|
||||
'error': []
|
||||
};
|
||||
|
||||
var mediaObject = el._mediaObject = {
|
||||
handlers: handlers,
|
||||
tag: el
|
||||
};
|
||||
|
||||
var emit = function (ev, data) {
|
||||
// Check if the event name is valid
|
||||
if (Object.keys(handlers).indexOf(ev) === -1) {
|
||||
return void console.error("Invalid mediatag event");
|
||||
}
|
||||
|
||||
// Call the handlers
|
||||
handlers[ev].forEach(function (h) {
|
||||
// Make sure a bad handler won't break the media-tag script
|
||||
try {
|
||||
h(data);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
mediaObject.on = function (ev, handler) {
|
||||
// Check if the event name is valid
|
||||
if (Object.keys(handlers).indexOf(ev) === -1) {
|
||||
console.error("Invalid mediatag event");
|
||||
return mediaObject;
|
||||
}
|
||||
// Check if the handler is valid
|
||||
if (typeof (handler) !== "function") {
|
||||
console.error("Handler is not a function!");
|
||||
return mediaObject;
|
||||
}
|
||||
// Add the handler
|
||||
handlers[ev].push(handler);
|
||||
return mediaObject;
|
||||
};
|
||||
|
||||
var src = el.getAttribute('src');
|
||||
var strKey = el.getAttribute('data-crypto-key');
|
||||
if (/^cryptpad:/.test(strKey)) {
|
||||
strKey = strKey.slice(9);
|
||||
}
|
||||
var uid = [src, strKey].join('');
|
||||
|
||||
// End media-tag rendering: display the tag and emit the event
|
||||
var end = function (decrypted) {
|
||||
process(mediaObject, decrypted, cfg, function (err) {
|
||||
if (err) { return void emit('error', err); }
|
||||
emit('complete', decrypted);
|
||||
});
|
||||
};
|
||||
|
||||
// If we have the blob in our cache, don't download & decrypt it again, just display
|
||||
if (cache[uid]) {
|
||||
end(cache[uid]);
|
||||
return mediaObject;
|
||||
}
|
||||
|
||||
// Download the encrypted blob
|
||||
download(src, function (err, u8Encrypted) {
|
||||
if (err) {
|
||||
return void emit('error', err);
|
||||
}
|
||||
// Decrypt the blob
|
||||
decrypt(u8Encrypted, strKey, function (errDecryption, u8Decrypted) {
|
||||
if (errDecryption) {
|
||||
return void emit('error', errDecryption);
|
||||
}
|
||||
// Cache and display the decrypted blob
|
||||
cache[uid] = u8Decrypted;
|
||||
end(u8Decrypted);
|
||||
}, function (progress) {
|
||||
emit('progress', {
|
||||
progress: progress
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return mediaObject;
|
||||
};
|
||||
|
||||
// Add the cache as a property of MediaTag
|
||||
cache = init.__Cryptpad_Cache = {};
|
||||
|
||||
init.setDefaultConfig = function (key, value) {
|
||||
config[key] = value;
|
||||
};
|
||||
|
||||
return init;
|
||||
}));
|
||||
@@ -111,7 +111,7 @@ define([
|
||||
// RPC may not be responding
|
||||
// Send a report that can be handled manually
|
||||
console.error(obj.error);
|
||||
Feedback.send('ERROR_DELETING_OWNED_PAD=' + channelId, true);
|
||||
Feedback.send('ERROR_DELETING_OWNED_PAD=' + channelId + '|' + obj.error, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -42,6 +42,9 @@ define([
|
||||
exp.updateTitle = function (newTitle, cb) {
|
||||
cb = cb || $.noop;
|
||||
if (newTitle === exp.title) { return void cb(); }
|
||||
if (newTitle === exp.defaultTitle) {
|
||||
newTitle = "";
|
||||
}
|
||||
metadataMgr.updateTitle(newTitle);
|
||||
titleUpdated = cb;
|
||||
};
|
||||
@@ -51,7 +54,9 @@ define([
|
||||
if ($title) {
|
||||
$title.find('span.cp-toolbar-title-value').text(md.title || md.defaultTitle);
|
||||
$title.find('input').val(md.title || md.defaultTitle);
|
||||
$title.find('input').prop('placeholder', md.defaultTitle);
|
||||
}
|
||||
exp.defaultTitle = md.defaultTitle;
|
||||
exp.title = md.title;
|
||||
});
|
||||
metadataMgr.onTitleChange(function (title) {
|
||||
|
||||
+9
-51
@@ -37,6 +37,9 @@ 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');
|
||||
@@ -130,32 +133,17 @@ define([
|
||||
$mt.attr('data-crypto-key', 'cryptpad:'+cryptKey);
|
||||
|
||||
var rightsideDisplayed = false;
|
||||
$(window.document).on('decryption', function (e) {
|
||||
/* FIXME
|
||||
we're listening for decryption events and assuming that only
|
||||
the main media-tag exists. In practice there is also your avatar
|
||||
and there could be other things in the future, so we should
|
||||
figure out a generic way target media-tag decryption events.
|
||||
*/
|
||||
var decrypted = e.originalEvent;
|
||||
if (decrypted.callback) {
|
||||
decrypted.callback();
|
||||
}
|
||||
|
||||
MediaTag($mt[0]).on('complete', function (decrypted) {
|
||||
$dlview.show();
|
||||
$dlform.hide();
|
||||
var $dlButton = $dlview.find('media-tag button');
|
||||
if (ev) { $dlButton.click(); }
|
||||
$dlButton.addClass('btn btn-success');
|
||||
var text = Messages.download_mt_button + '<br>';
|
||||
text += '<b>' + Util.fixHTML(title) + '</b><br>';
|
||||
text += '<em>' + Messages._getKey('formattedMB', [sizeMb]) + '</em>';
|
||||
$dlButton.html(text);
|
||||
|
||||
if (!rightsideDisplayed) {
|
||||
toolbar.$rightside
|
||||
.append(common.createButton('export', true, {}, function () {
|
||||
saveAs(decrypted.blob, decrypted.metadata.name);
|
||||
saveAs(decrypted.content, decrypted.metadata.name);
|
||||
}));
|
||||
rightsideDisplayed = true;
|
||||
}
|
||||
@@ -178,42 +166,12 @@ define([
|
||||
} else {
|
||||
cb();
|
||||
}
|
||||
})
|
||||
.on('decryptionError', function (e) {
|
||||
var error = e.originalEvent;
|
||||
//UI.alert(error.message);
|
||||
cb(error.message);
|
||||
})
|
||||
.on('decryptionProgress', function (e) {
|
||||
var progress = e.originalEvent;
|
||||
var p = progress.percent +'%';
|
||||
}).on('progress', function (data) {
|
||||
var p = data.progress +'%';
|
||||
$progress.width(p);
|
||||
}).on('error', function (err) {
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
/**
|
||||
* Allowed mime types that have to be set for a rendering after a decryption.
|
||||
*
|
||||
* @type {Array}
|
||||
*/
|
||||
var allowedMediaTypes = [
|
||||
'image/png',
|
||||
'image/jpeg',
|
||||
'image/jpg',
|
||||
'image/gif',
|
||||
'audio/mp3',
|
||||
'audio/ogg',
|
||||
'audio/wav',
|
||||
'audio/webm',
|
||||
'video/mp4',
|
||||
'video/ogg',
|
||||
'video/webm',
|
||||
'application/pdf',
|
||||
'application/dash+xml',
|
||||
'download'
|
||||
];
|
||||
MediaTag.CryptoFilter.setAllowedMediaTypes(allowedMediaTypes);
|
||||
|
||||
MediaTag($mt[0]);
|
||||
};
|
||||
|
||||
var todoBigFile = function (sizeMb) {
|
||||
|
||||
@@ -4,11 +4,15 @@
|
||||
@import (once) '../../customize/src/less2/include/fileupload.less';
|
||||
@import (once) '../../customize/src/less2/include/alertify.less';
|
||||
@import (once) '../../customize/src/less2/include/tippy.less';
|
||||
@import (once) '../../customize/src/less2/include/checkmark.less';
|
||||
@import (once) '../../customize/src/less2/include/password-input.less';
|
||||
|
||||
.iconColors_main();
|
||||
.fileupload_main();
|
||||
.alertify_main();
|
||||
.tippy_main();
|
||||
.checkmark_main(20px);
|
||||
.password_main();
|
||||
|
||||
#cp-filepicker-dialog {
|
||||
display: none;
|
||||
|
||||
+18
-30
@@ -4,7 +4,6 @@
|
||||
else { this[name] = definition(); }
|
||||
}('MediaTag', function() {
|
||||
var cache;
|
||||
var PARANOIA = true;
|
||||
var cypherChunkLength = 131088;
|
||||
|
||||
// Save a blob on the file system
|
||||
@@ -23,6 +22,14 @@
|
||||
}
|
||||
};
|
||||
|
||||
var fixHTML = function (str) {
|
||||
if (!str) { return ''; }
|
||||
return str.replace(/[<>&"']/g, function (x) {
|
||||
return ({ "<": "<", ">": ">", "&": "&", '"': """, "'": "'" })[x];
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// Default config, can be overriden per media-tag call
|
||||
var config = {
|
||||
allowed: [
|
||||
@@ -49,6 +56,7 @@
|
||||
image: function (metadata, url, content, cfg, cb) {
|
||||
var img = document.createElement('img');
|
||||
img.setAttribute('src', url);
|
||||
img.blob = content;
|
||||
cb(void 0, img);
|
||||
},
|
||||
video: function (metadata, url, content, cfg, cb) {
|
||||
@@ -75,7 +83,8 @@
|
||||
},
|
||||
download: function (metadata, url, content, cfg, cb) {
|
||||
var btn = document.createElement('button');
|
||||
btn.innerHTML = cfg.download.text;
|
||||
btn.innerHTML = cfg.download.text + '<br>' +
|
||||
metadata.name ? '<b>' + fixHTML(metadata.name) + '</b>' : '';
|
||||
btn.addEventListener('click', function () {
|
||||
saveFile(content, url, metadata.name);
|
||||
});
|
||||
@@ -106,38 +115,24 @@
|
||||
var Decrypt = {
|
||||
// Create a nonce
|
||||
createNonce: function () {
|
||||
if (!Array.prototype.fill) {
|
||||
// IE support
|
||||
var arr = [];
|
||||
for (var i = 0; i < 24; i++) { arr[i] = 0; }
|
||||
return new Uint8Array(arr);
|
||||
}
|
||||
return new Uint8Array(new Array(24).fill(0));
|
||||
var n = new Uint8Array(24);
|
||||
for (var i = 0; i < 24; i++) { n[i] = 0; }
|
||||
return n;
|
||||
},
|
||||
|
||||
// Increment a nonce
|
||||
// FIXME: remove throw?
|
||||
increment: function (N) {
|
||||
var l = N.length;
|
||||
while (l-- > 1) {
|
||||
if (PARANOIA) {
|
||||
if (typeof(N[l]) !== 'number') {
|
||||
throw new Error('E_UNSAFE_TYPE');
|
||||
}
|
||||
if (N[l] > 255) {
|
||||
throw new Error('E_OUT_OF_BOUNDS');
|
||||
}
|
||||
}
|
||||
/* .jshint probably suspects this is unsafe because we lack types
|
||||
but as long as this is only used on nonces, it should be safe */
|
||||
if (N[l] !== 255) { return void N[l]++; } // jshint ignore:line
|
||||
N[l] = 0;
|
||||
|
||||
// you don't need to worry about this running out.
|
||||
// you'd need a REAAAALLY big file
|
||||
if (l === 0) {
|
||||
throw new Error('E_NONCE_TOO_LARGE');
|
||||
}
|
||||
if (l === 0) { throw new Error('E_NONCE_TOO_LARGE'); }
|
||||
|
||||
N[l] = 0;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -153,13 +148,6 @@
|
||||
return Array.prototype.slice.call(u8);
|
||||
},
|
||||
|
||||
// Gets the random key string.
|
||||
getRandomKeyStr: function () {
|
||||
var Nacl = window.nacl;
|
||||
var rdm = Nacl.randomBytes(18);
|
||||
return Nacl.util.encodeBase64(rdm);
|
||||
},
|
||||
|
||||
// Gets the key from the key string.
|
||||
getKeyFromStr: function (str) {
|
||||
return window.nacl.util.decodeBase64(str);
|
||||
@@ -287,7 +275,7 @@
|
||||
|
||||
// Get blob URL
|
||||
var url = decrypted.url;
|
||||
if (!url) {
|
||||
if (!url && window.URL) {
|
||||
url = decrypted.url = window.URL.createObjectURL(new Blob([blob], {
|
||||
type: metadata.type
|
||||
}));
|
||||
|
||||
+2
-2
@@ -601,8 +601,8 @@ define([
|
||||
var $clone = $(inner).clone();
|
||||
nThen(function (waitFor) {
|
||||
$(inner).find('media-tag').each(function (i, el) {
|
||||
if (!$(el).data('blob')) { return; }
|
||||
Util.blobToImage($(el).data('blob'), waitFor(function (imgSrc) {
|
||||
if (!$(el).data('blob') || !el.blob) { return; }
|
||||
Util.blobToImage(el.blob || $(el).data('blob'), waitFor(function (imgSrc) {
|
||||
$clone.find('media-tag[src="' + $(el).attr('src') + '"] img')
|
||||
.attr('src', imgSrc);
|
||||
$clone.find('media-tag').parent()
|
||||
|
||||
@@ -43,16 +43,6 @@ define([
|
||||
_onRefresh: []
|
||||
};
|
||||
|
||||
// Decryption event for avatar mediatag (TODO not needed anymore?)
|
||||
$(window.document).on('decryption', function (e) {
|
||||
var decrypted = e.originalEvent;
|
||||
if (decrypted.callback) { decrypted.callback(); }
|
||||
})
|
||||
.on('decryptionError', function (e) {
|
||||
var error = e.originalEvent;
|
||||
UI.alert(error.message);
|
||||
});
|
||||
|
||||
$(window).click(function () {
|
||||
$('.cp-dropdown-content').hide();
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user