Merge branch 'staging' into serviceworker

pull/1/head
yflory 7 years ago
commit 996245ec3d

@ -5,7 +5,7 @@ www/common/tippy/
www/common/jquery-ui/ www/common/jquery-ui/
server.js server.js
www/common/media-tag.js www/common/old-media-tag.js
www/scratch www/scratch
www/common/toolbar.js 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) # Badger release (v2.1.0)
## Goals ## Goals

@ -1152,7 +1152,7 @@ define(function () {
out.creation_newPadModalAdvanced = "Display the pad creation screen"; out.creation_newPadModalAdvanced = "Display the pad creation screen";
// Password prompt on the loadind 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_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_placeholder = "Type the password here...";
out.password_submit = "Submit"; out.password_submit = "Submit";

@ -896,13 +896,13 @@ var removeOwnedBlob = function (Env, blobId, unsafeKey, cb) {
Fs.unlink(blobPath, w(function (e) { Fs.unlink(blobPath, w(function (e) {
if (e) { if (e) {
w.abort(); w.abort();
return void cb(e); return void cb(e.code);
} }
})); }));
}).nThen(function () { }).nThen(function () {
// Delete the proof of ownership // Delete the proof of ownership
Fs.unlink(ownPath, function (e) { 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(); return void cb();
} else { } else {
// it failed in an unexpected way. log it // it failed in an unexpected way. log it
WARN(e, 'ownedUploadComplete'); WARN('ownedUploadComplete', e);
return void cb(e.code); return void cb(e.code);
} }
}); });
@ -1221,13 +1221,13 @@ var owned_upload_complete = function (Env, safeKey, id, cb) {
Mkdirp(filePath, w(function (e /*, path */) { Mkdirp(filePath, w(function (e /*, path */) {
if (e) { // does not throw error if the directory already existed if (e) { // does not throw error if the directory already existed
w.abort(); w.abort();
return void cb(e); return void cb(e.code);
} }
})); }));
Mkdirp(ownPath, w(function (e /*, path */) { Mkdirp(ownPath, w(function (e /*, path */) {
if (e) { // does not throw error if the directory already existed if (e) { // does not throw error if the directory already existed
w.abort(); w.abort();
return void cb(e); return void cb(e.code);
} }
})); }));
}).nThen(function (w) { }).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 // flow is dumb and I need to guard against this which will never happen
/*:: if (typeof(oldPath) === 'object') { throw new Error('should 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) { if (e) {
// Remove the ownership file // Remove the ownership file
// XXX not needed if we have a cleanup script?
Fs.unlink(finalOwnPath, function (e) { Fs.unlink(finalOwnPath, function (e) {
WARN(e, 'Removing ownership file ownedUploadComplete'); WARN('E_UNLINK_OWN_FILE', e);
}); });
w.abort(); w.abort();
return void cb(e.code); return void cb(e.code);

@ -9,7 +9,8 @@ define([
'/common/common-thumbnail.js', '/common/common-thumbnail.js',
'/common/wire.js', '/common/wire.js',
'/common/flat-dom.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.Hyperjson = Hyperjson;
window.Sortify = Sortify; window.Sortify = Sortify;
@ -295,6 +296,26 @@ define([
!secret.hashData.present); !secret.hashData.present);
}, "test support for ugly tracking query paramaters in url"); }, "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) { assert(function (cb) {
// TODO // TODO
return cb(true); return cb(true);

@ -49,6 +49,7 @@ define([
if (ua[0].indexOf(':') === -1 && ua[0].indexOf('/') && parent) { if (ua[0].indexOf(':') === -1 && ua[0].indexOf('/') && parent) {
ua[0] = parent.replace(/\/[^\/]*$/, '/') + ua[0]; ua[0] = parent.replace(/\/[^\/]*$/, '/') + ua[0];
} }
ua[0] = ua[0].replace(/^\/\.\.\//, '/');
var out = ua.join('#'); var out = ua.join('#');
//console.log(url + " --> " + out); //console.log(url + " --> " + out);
return out; return out;
@ -91,17 +92,32 @@ define([
}; };
var lessEngine; var lessEngine;
var tempCache = { key: Math.random() };
var getLessEngine = function (cb) { var getLessEngine = function (cb) {
if (lessEngine) { if (lessEngine) {
cb(lessEngine); cb(lessEngine);
} else { } else {
require(['/bower_components/less/dist/less.min.js'], function (Less) { require(['/bower_components/less/dist/less.min.js'], function (Less) {
if (lessEngine) { return void cb(lessEngine); }
lessEngine = Less; lessEngine = Less;
var doXHR = lessEngine.FileManager.prototype.doXHR; var doXHR = lessEngine.FileManager.prototype.doXHR;
lessEngine.FileManager.prototype.doXHR = function (url, type, callback, errback) { lessEngine.FileManager.prototype.doXHR = function (url, type, callback, errback) {
url = fixURL(url); url = fixURL(url);
//console.log("xhr: " + url); var cached = tempCache[url];
return doXHR(url, type, callback, errback); 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); cb(lessEngine);
}); });

@ -144,13 +144,15 @@ define([
}; };
dialog.frame = function (content) { dialog.frame = function (content) {
return h('div.alertify', { return $(h('div.alertify', {
tabindex: 1, tabindex: 1,
}, [ }, [
h('div.dialog', [ h('div.dialog', [
h('div', content), h('div', content),
]) ])
]); ])).click(function (e) {
e.stopPropagation();
})[0];
}; };
/** /**

@ -18,8 +18,10 @@ define([
var UIElements = {}; var UIElements = {};
// Configure MediaTags to use our local viewer // Configure MediaTags to use our local viewer
if (MediaTag && MediaTag.PdfPlugin) { if (MediaTag) {
MediaTag.PdfPlugin.viewer = '/common/pdfjs/web/viewer.html'; MediaTag.setDefaultConfig('pdf', {
viewer: '/common/pdfjs/web/viewer.html'
});
} }
UIElements.updateTags = function (common, href) { UIElements.updateTags = function (common, href) {
@ -626,7 +628,6 @@ define([
} }
} }
sframeChan.query('Q_SAVE_AS_TEMPLATE', { sframeChan.query('Q_SAVE_AS_TEMPLATE', {
title: title,
toSave: toSave toSave: toSave
}, function () { }, function () {
UI.alert(Messages.templateSaved); UI.alert(Messages.templateSaved);
@ -1035,52 +1036,6 @@ define([
// Avatars // 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) { UIElements.displayMediatagImage = function (Common, $tag, cb) {
if (!$tag.length || !$tag.is('media-tag')) { return void cb('NOT_MEDIATAG'); } if (!$tag.length || !$tag.is('media-tag')) { return void cb('NOT_MEDIATAG'); }
var observer = new MutationObserver(function(mutations) { var observer = new MutationObserver(function(mutations) {

@ -449,6 +449,26 @@ define([
}).nThen(function () { }).nThen(function () {
Crypt.get(parsed.hash, function (err, val) { Crypt.get(parsed.hash, function (err, val) {
if (err) { throw new Error(err); } 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); Crypt.put(parsed2.hash, val, cb, optsPut);
}, optsGet); }, optsGet);
}); });

File diff suppressed because one or more lines are too long

@ -111,7 +111,7 @@ define([
// RPC may not be responding // RPC may not be responding
// Send a report that can be handled manually // Send a report that can be handled manually
console.error(obj.error); 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) { exp.updateTitle = function (newTitle, cb) {
cb = cb || $.noop; cb = cb || $.noop;
if (newTitle === exp.title) { return void cb(); } if (newTitle === exp.title) { return void cb(); }
if (newTitle === exp.defaultTitle) {
newTitle = "";
}
metadataMgr.updateTitle(newTitle); metadataMgr.updateTitle(newTitle);
titleUpdated = cb; titleUpdated = cb;
}; };
@ -51,7 +54,9 @@ define([
if ($title) { if ($title) {
$title.find('span.cp-toolbar-title-value').text(md.title || md.defaultTitle); $title.find('span.cp-toolbar-title-value').text(md.title || md.defaultTitle);
$title.find('input').val(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; exp.title = md.title;
}); });
metadataMgr.onTitleChange(function (title) { metadataMgr.onTitleChange(function (title) {

@ -37,6 +37,9 @@ define([
var Nacl = window.nacl; var Nacl = window.nacl;
var APP = window.APP = {}; var APP = window.APP = {};
MediaTag.setDefaultConfig('download', {
text: Messages.download_mt_button
});
var andThen = function (common) { var andThen = function (common) {
var $appContainer = $('#cp-app-file-content'); var $appContainer = $('#cp-app-file-content');
@ -130,32 +133,17 @@ define([
$mt.attr('data-crypto-key', 'cryptpad:'+cryptKey); $mt.attr('data-crypto-key', 'cryptpad:'+cryptKey);
var rightsideDisplayed = false; 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(); $dlview.show();
$dlform.hide(); $dlform.hide();
var $dlButton = $dlview.find('media-tag button'); var $dlButton = $dlview.find('media-tag button');
if (ev) { $dlButton.click(); } 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) { if (!rightsideDisplayed) {
toolbar.$rightside toolbar.$rightside
.append(common.createButton('export', true, {}, function () { .append(common.createButton('export', true, {}, function () {
saveAs(decrypted.blob, decrypted.metadata.name); saveAs(decrypted.content, decrypted.metadata.name);
})); }));
rightsideDisplayed = true; rightsideDisplayed = true;
} }
@ -178,42 +166,12 @@ define([
} else { } else {
cb(); cb();
} }
}) }).on('progress', function (data) {
.on('decryptionError', function (e) { var p = data.progress +'%';
var error = e.originalEvent;
//UI.alert(error.message);
cb(error.message);
})
.on('decryptionProgress', function (e) {
var progress = e.originalEvent;
var p = progress.percent +'%';
$progress.width(p); $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) { var todoBigFile = function (sizeMb) {

@ -4,11 +4,15 @@
@import (once) '../../customize/src/less2/include/fileupload.less'; @import (once) '../../customize/src/less2/include/fileupload.less';
@import (once) '../../customize/src/less2/include/alertify.less'; @import (once) '../../customize/src/less2/include/alertify.less';
@import (once) '../../customize/src/less2/include/tippy.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(); .iconColors_main();
.fileupload_main(); .fileupload_main();
.alertify_main(); .alertify_main();
.tippy_main(); .tippy_main();
.checkmark_main(20px);
.password_main();
#cp-filepicker-dialog { #cp-filepicker-dialog {
display: none; display: none;

@ -4,7 +4,6 @@
else { this[name] = definition(); } else { this[name] = definition(); }
}('MediaTag', function() { }('MediaTag', function() {
var cache; var cache;
var PARANOIA = true;
var cypherChunkLength = 131088; var cypherChunkLength = 131088;
// Save a blob on the file system // 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 // Default config, can be overriden per media-tag call
var config = { var config = {
allowed: [ allowed: [
@ -49,6 +56,7 @@
image: function (metadata, url, content, cfg, cb) { image: function (metadata, url, content, cfg, cb) {
var img = document.createElement('img'); var img = document.createElement('img');
img.setAttribute('src', url); img.setAttribute('src', url);
img.blob = content;
cb(void 0, img); cb(void 0, img);
}, },
video: function (metadata, url, content, cfg, cb) { video: function (metadata, url, content, cfg, cb) {
@ -75,7 +83,8 @@
}, },
download: function (metadata, url, content, cfg, cb) { download: function (metadata, url, content, cfg, cb) {
var btn = document.createElement('button'); var btn = document.createElement('button');
btn.innerHTML = cfg.download.text; btn.innerHTML = cfg.download.text + '<br>' +
metadata.name ? '<b>' + fixHTML(metadata.name) + '</b>' : '';
btn.addEventListener('click', function () { btn.addEventListener('click', function () {
saveFile(content, url, metadata.name); saveFile(content, url, metadata.name);
}); });
@ -106,38 +115,24 @@
var Decrypt = { var Decrypt = {
// Create a nonce // Create a nonce
createNonce: function () { createNonce: function () {
if (!Array.prototype.fill) { var n = new Uint8Array(24);
// IE support for (var i = 0; i < 24; i++) { n[i] = 0; }
var arr = []; return n;
for (var i = 0; i < 24; i++) { arr[i] = 0; }
return new Uint8Array(arr);
}
return new Uint8Array(new Array(24).fill(0));
}, },
// Increment a nonce // Increment a nonce
// FIXME: remove throw?
increment: function (N) { increment: function (N) {
var l = N.length; var l = N.length;
while (l-- > 1) { 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 /* .jshint probably suspects this is unsafe because we lack types
but as long as this is only used on nonces, it should be safe */ 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 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 don't need to worry about this running out.
// you'd need a REAAAALLY big file // you'd need a REAAAALLY big file
if (l === 0) { if (l === 0) { throw new Error('E_NONCE_TOO_LARGE'); }
throw new Error('E_NONCE_TOO_LARGE');
} N[l] = 0;
} }
}, },
@ -153,13 +148,6 @@
return Array.prototype.slice.call(u8); 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. // Gets the key from the key string.
getKeyFromStr: function (str) { getKeyFromStr: function (str) {
return window.nacl.util.decodeBase64(str); return window.nacl.util.decodeBase64(str);
@ -287,7 +275,7 @@
// Get blob URL // Get blob URL
var url = decrypted.url; var url = decrypted.url;
if (!url) { if (!url && window.URL) {
url = decrypted.url = window.URL.createObjectURL(new Blob([blob], { url = decrypted.url = window.URL.createObjectURL(new Blob([blob], {
type: metadata.type type: metadata.type
})); }));

@ -601,8 +601,8 @@ define([
var $clone = $(inner).clone(); var $clone = $(inner).clone();
nThen(function (waitFor) { nThen(function (waitFor) {
$(inner).find('media-tag').each(function (i, el) { $(inner).find('media-tag').each(function (i, el) {
if (!$(el).data('blob')) { return; } if (!$(el).data('blob') || !el.blob) { return; }
Util.blobToImage($(el).data('blob'), waitFor(function (imgSrc) { Util.blobToImage(el.blob || $(el).data('blob'), waitFor(function (imgSrc) {
$clone.find('media-tag[src="' + $(el).attr('src') + '"] img') $clone.find('media-tag[src="' + $(el).attr('src') + '"] img')
.attr('src', imgSrc); .attr('src', imgSrc);
$clone.find('media-tag').parent() $clone.find('media-tag').parent()

@ -43,16 +43,6 @@ define([
_onRefresh: [] _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 () { $(window).click(function () {
$('.cp-dropdown-content').hide(); $('.cp-dropdown-content').hide();
}); });

Loading…
Cancel
Save