Merge branch 'staging' into serviceworker

This commit is contained in:
yflory committed 2018-06-11 10:17:50 +02:00
commit 996245ec3d
17 files changed
+571 -159

No files matched your search

+1 -1
View File
@@ -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
+27
View File
@@ -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
+1 -1
View File
@@ -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";
+7 -8
View File
@@ -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
View File
@@ -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);
+18 -2
View File
@@ -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);
});
+4 -2
View File
@@ -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];
};
/**
+4 -49
View File
@@ -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) {
+20
View File
@@ -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
View File
@@ -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 ({ "<": "&lt;", ">": "&gt", "&": "&amp;", '"': "&#34;", "'": "&#39;" })[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;
}));
+1 -1
View File
@@ -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);
}
});
}
+5
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 ({ "<": "&lt;", ">": "&gt", "&": "&amp;", '"': "&#34;", "'": "&#39;" })[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
View File
@@ -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()
-10
View File
@@ -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();
});