Merge branch 'staging' into cryptDriveStuff

pull/1/head
yflory 6 years ago
commit b74e28fed3

@ -1,3 +1,79 @@
# Yak release (v2.24.0)
## Goals
We've recently had an intern join our team, so this release and those until the end of summer are likely to feature a lot of small usability fixes.
Otherwise, we've continued to develop team-centric features, particularly the way that registered users share pads with friends.
Finally, we prioritized the ability to archive files for a period instead of deleting them, which we've been planning for a while.
## Update notes
* There are some important steps in this release:
* **make sure you read the full update notes before proceeding!**
* [@zimbatm](https://github.com/zimbatm) added the ability to configure the location of your configuration file via environment variables when launching the server:
* `CRYPTPAD_CONFIG=/home/cryptpad/cryptpad/cryptpad-config/config.js /home/cryptpad/cryptpad/server.js`
* We discovered a bug in our Xenops release which resulted in the server's list of pads stored for each user to be incorrect.
* if you're running CryptPad 2.23.0, we recommend that you disable any scripts configured to delete inactive pads
* updating to 2.24.0 will fix the issue in the client, but each user's list of "pinned pads" won't be corrected until they visit your instance and run the latest code
* This release introduces the ability to archive some data instead of deleting it, since it can be scary to remove user data when you can't easily inspect it to see what it is
* to take advantage of this new functionality you'll need to update your configuration file with three new configuration points:
* set `retainData` to `true` if you want to archive channels instead of deleting them
* either by user command or due to inactivity
* the server will fall back to its default deletion behaviour if this value is `false` or not set at all
* set `archiveRetentionTime` to the number of days that an archived pad should be stored in the archive directory before being deleted permanently
* set `archivePath` to the path where you'd like archives to be stored
* it should not be publicly accessible in order to respect the users' wishes
* We've introduced some new scripts to work with the database, some of which were needed to diagnose problems stemming from the pinning bug
* `evict-inactive.js` identifies channels which are unpinned and inactive and archives them
* unlike `delete-inactive.js` it only handles channels, not files or any other kind of data
* ...but it's much safer, since nothing is removed permanently
* in the coming releases we'll implement archival for other types of data so that we can fully remove unsafe scripts
* `diagnose-archive-conflicts.js` checks all the files in your archive and identifies whether they can be restored safely or if they conflict with newer files in the production database
* `restore-archived.js` restores any channels archived by the server or evict-inactive.js, excluding those which would conflict with the database
* This release depends on updates to some serverside dependencies. Run `npm update`:
* `ws` addresses a potential vulnerability, so if possible anyone running earlier versions of CryptPad should update
* `chainpad-server` handles users' websocket connections and we needed to make a few changes to deal with changes in the `ws` API
* `heapdump` is no longer a default dependency, though you can install it if you want its functionality
* This release also features a **Clientside migration** which modifies users' CryptDrives. Any clients which are running both the latest code after the update as well as an older version in another browser or device risk creating conflicts in their account data. To prevent this, update in the following manner:
1. ensure that you've added the configuration values listed above
2. shut down the server and ensure that it doesn't restart until you've completed the following steps
3. pull the latest clientside and serverside code via git
4. `npm update` to get the latest serverside dependencies
5. update the cache-busting string if you are handling the cache manually, otherwise allow the server to handle this as per its default
5. restart the server: clients with open tabs should be prompted to reload instead of reconnecting because the server's version has changed
* We recommend that you test a local version of CryptPad before deploying this latest code, as aspects of the above-mentioned migrations are not backwards-compatible.
* you can roll back, but users' CryptDrives might have errors coping with data introduced by newer features.
## Features
* As mentioned above, CryptPad instances can be configured to temporarily archive files instead of deleting them permanently.
* as a user this means if you accidentally delete a file you have the option of contacting your administrator and asking them to help
* if they're really nice and have the spare time to help you, they might actually recover your data!
* A contributor is working on translating CryptPad into the Catalan language.
* if your preferred language isn't supported, you can do the same on https://weblate.cryptpad.fr
* We added the ability to add colors to folders in users CryptDrives, along with support for arbitrary folder metadata which we aren't using yet.
* Users with existing friends on the platform will run a migration to allow them to share pads with friends directly instead of sending them a link.
* they'll receive a notification indicating the title of the pad and who shared it
* if you've already added friends on the platform, you can send them pads from the usual "sharing menu"
* Our code editor already offered the ability to set their color theme and highlighting mode, but now those values will be previewed when mousing over the the option in the dropdown.
* Our slide editor now offers the same theme selection as the code editor
* It's now possible to view the history of a shared folder by clicking the history button while viewing the shared folder's contents.
## Bug fixes
* The CryptDrive received a number of usability fixes this time around:
* better styles when hovering over interactive elements in the drive (cursors, shading, etc)
* clicking the history button in the drive a second time will exit history mode
* after being resized, the tree pane now correctly responds to mobile layout styles
* the path indicator also adapts to very narrow layouts
* the user's current location is preserved when renaming the current folder or its ancestors
* you can right-click on elements in the tree and expand or collapse all of their children
* A user noticed that one-on-one chats did not seem to be deleted, as their messages were still available after a reload.
* they were deleted but our usage of the sharedWorker API incorrectly preserved a local cache of those message until you closed all of your browser tabs
* We've also fixed some elements of the chat UI, notably the position of the chat's scrollbar when first loading older messages and how the interface scrolls to keep up with new messages.
* We've noticed some cases of tooltips getting stuck in the UI and implemented some measures to prevent this from happening.
* After "unfriending" another user it was possible that they would be automatically re-added as friends.
# Xenops release (v2.23.0) # Xenops release (v2.23.0)
## Goals ## Goals

@ -33,6 +33,7 @@ VOLUME /cryptpad/tasks
VOLUME /cryptpad/block VOLUME /cryptpad/block
VOLUME /cryptpad/blob VOLUME /cryptpad/blob
VOLUME /cryptpad/blobstage VOLUME /cryptpad/blobstage
VOLUME /cryptpad/data
# Copy cryptpad and tini from the build container # Copy cryptpad and tini from the build container
COPY --from=build /sbin/tini /sbin/tini COPY --from=build /sbin/tini /sbin/tini

@ -3,8 +3,8 @@
var map = { var map = {
'ca': 'Català', 'ca': 'Català',
'de': 'Deutsch', 'de': 'Deutsch',
'es': 'Español',
'el': 'Ελληνικά', 'el': 'Ελληνικά',
'es': 'Español',
'fr': 'Français', 'fr': 'Français',
'it': 'Italiano', 'it': 'Italiano',
'nb': 'Norwegian Bokmål', 'nb': 'Norwegian Bokmål',
@ -12,8 +12,8 @@ var map = {
'pt-br': 'Português do Brasil', 'pt-br': 'Português do Brasil',
'ro': 'Română', 'ro': 'Română',
'ru': 'Русский', 'ru': 'Русский',
//'te': 'తెలుగు',
'zh': '繁體中文', 'zh': '繁體中文',
'te': 'తెలుగు',
}; };
var messages = {}; var messages = {};

@ -103,7 +103,7 @@ define([
])*/ ])*/
]) ])
]), ]),
h('div.cp-version-footer', "CryptPad v2.23.0 (Xenops)") h('div.cp-version-footer', "CryptPad v2.24.0 (Yak)")
]); ]);
}; };

@ -21,22 +21,6 @@ define([
h('h2.text-center', Msg.about_core) h('h2.text-center', Msg.about_core)
]), ]),
]), ]),
h('div.row.align-items-center', [
h('div.col-12.col-sm-12.col-md-12.col-lg-6.cp-bio-avatar', [
h('img.img-fluid', {'src': '/customize/images/CalebJames.jpg'})
]),
h('div.col-12.col-sm-12.col-md-12.col-lg-6.cp-profile-det', [
h('h3', "Caleb James Delisle"),
h('hr'),
Pages.setHTML(h('div#bioCaleb'), '<p>Caleb is a cryptography developer, Machine Technology graduate of the Franklin County Technical School and lifelong tinkerer.<br/>In 2011, he started the cjdns Open Source project to show that secure networking could be invisible and easily deployed.<br/>After joining XWiki SAS in 2014, he started the CryptPad project with the intent of bringing the same transparent security to collaborative editing.<br/>He\'s always trying to learn from more experienced colleagues and when someone passes through the Research Team office, his favorite words are "Pull up a chair!".</p>'),
h('a.cp-soc-media', { href : 'https://twitter.com/cjdelisle'}, [
h('i.fa.fa-twitter')
]),
h('a.cp-soc-media', { href : 'https://github.com/cjdelisle'}, [
h('i.fa.fa-github')
])
]),
]),
h('div.row.align-items-center',[ h('div.row.align-items-center',[
h('div.col-12.col-sm-12.col-md-12.col-lg-6.order-lg-2.cp-bio-avatar.cp-bio-avatar-right', [ h('div.col-12.col-sm-12.col-md-12.col-lg-6.order-lg-2.cp-bio-avatar.cp-bio-avatar-right', [
h('img.img-fluid', {'src': '/customize/images/AaronMacSween.jpg'}) h('img.img-fluid', {'src': '/customize/images/AaronMacSween.jpg'})
@ -74,16 +58,16 @@ define([
]), ]),
h('div.row.align-items-center', [ h('div.row.align-items-center', [
h('div.col-12.col-sm-12.col-md-12.col-lg-6.cp-bio-avatar', [ h('div.col-12.col-sm-12.col-md-12.col-lg-6.cp-bio-avatar', [
h('img.img-fluid', {'src': '/customize/images/Pierre-new.jpg'}) h('img.img-fluid', {'src': '/customize/images/CalebJames.jpg'})
]), ]),
h('div.col-12.col-sm-12.col-md-12.col-lg-6.cp-profile-det', [ h('div.col-12.col-sm-12.col-md-12.col-lg-6.cp-profile-det', [
h('h3', "Pierre Bondoerffer"), h('h3', "Caleb James Delisle"),
h('hr'), h('hr'),
Pages.setHTML(h('div#bioPierre'), '<p>Resident CSS wizard and emoji extraordinaire, Pierre is passionate about anything related to technology. He loves to hack around computers and put parts together.<br/>He is currently studying at 42, where he learns about algorithms, networking, kernel programming and graphics.<br/>As a part of an internship, he joined XWiki SAS and worked on CryptPad to improve user experience. He also maintains the Spanish translation.</p>'), Pages.setHTML(h('div#bioCaleb'), '<p>Caleb is a cryptography developer, Machine Technology graduate of the Franklin County Technical School and lifelong tinkerer.<br/>In 2011, he started the cjdns Open Source project to show that secure networking could be invisible and easily deployed.<br/>After joining XWiki SAS in 2014, he started the CryptPad project with the intent of bringing the same transparent security to collaborative editing.<br/>He\'s always trying to learn from more experienced colleagues and when someone passes through the Research Team office, his favorite words are "Pull up a chair!".</p>'),
h('a.cp-soc-media', { href : 'https://twitter.com/pbondoer'}, [ h('a.cp-soc-media', { href : 'https://twitter.com/cjdelisle'}, [
h('i.fa.fa-twitter') h('i.fa.fa-twitter')
]), ]),
h('a.cp-soc-media', { href : 'https://github.com/pbondoer'}, [ h('a.cp-soc-media', { href : 'https://github.com/cjdelisle'}, [
h('i.fa.fa-github') h('i.fa.fa-github')
]) ])
]), ]),

@ -31,3 +31,4 @@ services:
- ./data/tasks:/cryptpad/tasks:rw - ./data/tasks:/cryptpad/tasks:rw
- ./data/block:/cryptpad/block:rw - ./data/block:/cryptpad/block:rw
- ./data/config:/cryptpad/cfg:rw - ./data/config:/cryptpad/cfg:rw
- ./data/data:/cryptpad/data:rw

@ -1,7 +1,7 @@
{ {
"name": "cryptpad", "name": "cryptpad",
"description": "realtime collaborative visual editor with zero knowlege server", "description": "realtime collaborative visual editor with zero knowlege server",
"version": "2.23.0", "version": "2.24.0",
"license": "AGPL-3.0+", "license": "AGPL-3.0+",
"repository": { "repository": {
"type": "git", "type": "git",

@ -371,6 +371,20 @@ define([
return cb(true); return cb(true);
}, "version 2 hash failed to parse correctly"); }, "version 2 hash failed to parse correctly");
assert(function (cb) {
var x;
var set_x = function (v) {
x = v;
};
Util.mkAsync(set_x)(7);
set_x(5);
Util.mkAsync(function (expected) {
cb(x === expected);
})(7);
}, "test mkAsync");
assert(function (cb) { assert(function (cb) {
Wire.create({ Wire.create({
constructor: function (cb) { constructor: function (cb) {

@ -799,6 +799,11 @@ define([
// forever, this is a solution which just searches for tooltips which have no corrisponding element and removes // forever, this is a solution which just searches for tooltips which have no corrisponding element and removes
// them. // them.
$('.tippy-popper').each(function (i, el) { $('.tippy-popper').each(function (i, el) {
if (el._tippy && el._tippy.reference && document.body.contains(el._tippy.reference)) {
el._tippy.destroy();
el.remove();
return;
}
if ($('[aria-describedby=' + el.getAttribute('id') + ']').length === 0) { if ($('[aria-describedby=' + el.getAttribute('id') + ']').length === 0) {
el.remove(); el.remove();
} }
@ -849,6 +854,9 @@ define([
mutations.forEach(function(mutation) { mutations.forEach(function(mutation) {
if (mutation.type === "childList") { if (mutation.type === "childList") {
for (var i = 0; i < mutation.addedNodes.length; i++) { for (var i = 0; i < mutation.addedNodes.length; i++) {
if ($(mutation.addedNodes[i]).attr('title')) {
addTippy(0, mutation.addedNodes[i]);
}
$(mutation.addedNodes[i]).find('[title]').each(addTippy); $(mutation.addedNodes[i]).find('[title]').each(addTippy);
} }

@ -196,8 +196,8 @@ define([
}; };
Thumb.initPadThumbnails = function (common, opts) { Thumb.initPadThumbnails = function (common, opts) {
if (!opts.href || !opts.getContent) { if (!opts.type || !opts.getContent) {
throw new Error("href and getContent are needed for thumbnails"); throw new Error("type and getContent are needed for thumbnails");
} }
var oldThumbnailState; var oldThumbnailState;
var mkThumbnail = function () { var mkThumbnail = function () {

@ -162,7 +162,7 @@ define([
} }
var parsed = Hash.parsePadUrl(data.href || data.roHref); var parsed = Hash.parsePadUrl(data.href || data.roHref);
if (!data.noEditPassword && owned && parsed.hashData.type === 'pad') { if (!data.noEditPassword && owned && parsed.hashData.type === 'pad' && parsed.type !== "sheet") { // FIXME SHEET fix password change for sheets
var sframeChan = common.getSframeChannel(); var sframeChan = common.getSframeChannel();
var changePwTitle = Messages.properties_changePassword; var changePwTitle = Messages.properties_changePassword;
var changePwConfirm = Messages.properties_confirmChange; var changePwConfirm = Messages.properties_confirmChange;
@ -289,7 +289,7 @@ define([
id: 'cp-app-prop-size', id: 'cp-app-prop-size',
})); }));
if (data.sharedFolder) { // XXX debug if (data.sharedFolder) {
$('<label>', {'for': 'cp-app-prop-channel'}).text('Channel ID').appendTo($d); $('<label>', {'for': 'cp-app-prop-channel'}).text('Channel ID').appendTo($d);
if (AppConfig.pinBugRecovery) { $d.append(h('p', AppConfig.pinBugRecovery)); } if (AppConfig.pinBugRecovery) { $d.append(h('p', AppConfig.pinBugRecovery)); }
$d.append(UI.dialog.selectable(data.channel, { $d.append(UI.dialog.selectable(data.channel, {
@ -412,6 +412,7 @@ define([
if (!friend.notifications || !friend.curvePublic) { return; } if (!friend.notifications || !friend.curvePublic) { return; }
common.mailbox.sendTo("SHARE_PAD", { common.mailbox.sendTo("SHARE_PAD", {
href: href, href: href,
password: config.password,
name: myName, name: myName,
title: title title: title
}, { }, {
@ -1737,16 +1738,20 @@ define([
var pressed = ''; var pressed = '';
var to; var to;
$container.keydown(function (e) { $container.keydown(function (e) {
var $value = $innerblock.find('[data-value].cp-dropdown-element-active'); var $value = $innerblock.find('[data-value].cp-dropdown-element-active:visible');
if (e.which === 38) { // Up if (e.which === 38) { // Up
if ($value.length) { if ($value.length) {
$value.mouseleave();
var $prev = $value.prev(); var $prev = $value.prev();
$prev.mouseenter();
setActive($prev); setActive($prev);
} }
} }
if (e.which === 40) { // Down if (e.which === 40) { // Down
if ($value.length) { if ($value.length) {
$value.mouseleave();
var $next = $value.next(); var $next = $value.next();
$next.mouseenter();
setActive($next); setActive($next);
} }
} }
@ -1757,6 +1762,7 @@ define([
} }
} }
if (e.which === 27) { // Esc if (e.which === 27) { // Esc
$value.mouseleave();
hide(); hide();
} }
}); });

@ -2,6 +2,15 @@
define([], function () { define([], function () {
var Util = window.CryptPad_Util = {}; var Util = window.CryptPad_Util = {};
Util.mkAsync = function (f) {
return function () {
var args = Array.prototype.slice.call(arguments);
setTimeout(function () {
f.apply(null, args);
});
};
};
// If once is true, after the event has been fired, any further handlers which are // If once is true, after the event has been fired, any further handlers which are
// registered will fire immediately, and this type of event cannot be fired twice. // registered will fire immediately, and this type of event cannot be fired twice.
Util.mkEvent = function (once) { Util.mkEvent = function (once) {

@ -84,6 +84,12 @@ define([
} }
}; };
var stripTags = function (text) {
var div = document.createElement("div");
div.innerHTML = text;
return div.innerText;
};
renderer.heading = function (text, level) { renderer.heading = function (text, level) {
var i = 0; var i = 0;
var safeText = text.toLowerCase().replace(/[^\w]+/g, '-'); var safeText = text.toLowerCase().replace(/[^\w]+/g, '-');
@ -99,7 +105,7 @@ define([
toc.push({ toc.push({
level: level, level: level,
id: id, id: id,
title: text title: stripTags(text)
}); });
return "<h" + level + " id=\"" + id + "\"><a href=\"#" + id + "\" class=\"anchor\"></a>" + text + "</h" + level + ">"; return "<h" + level + " id=\"" + id + "\"><a href=\"#" + id + "\" class=\"anchor\"></a>" + text + "</h" + level + ">";
}; };

@ -191,9 +191,6 @@ define([
userObject.version = version = 8; userObject.version = version = 8;
} }
}).nThen(function () { }).nThen(function () {
if (!AppConfig.migrateFriends) { return; } // XXX
// Migration 9: send our mailbox channel to existing friends // Migration 9: send our mailbox channel to existing friends
var migrateFriends = function () { var migrateFriends = function () {
var network = store.network; var network = store.network;

@ -57,7 +57,12 @@ define([
.html(Messages._getKey(key, [msg.content.name || Messages.anonymous, msg.content.title])); .html(Messages._getKey(key, [msg.content.name || Messages.anonymous, msg.content.title]));
$(el).find('.cp-notification-content').addClass("cp-clickable") $(el).find('.cp-notification-content').addClass("cp-clickable")
.click(function () { .click(function () {
common.openURL(msg.content.href); var todo = function () { common.openURL(msg.content.href); };
if (!msg.content.password) { return void todo(); }
common.getSframeChannel().query('Q_SESSIONSTORAGE_PUT', {
key: 'newPadPassword',
value: msg.content.password
}, todo);
}); });
$(el).find('.cp-notification-dismiss').css('display', 'flex'); $(el).find('.cp-notification-dismiss').css('display', 'flex');
}; };

@ -102,9 +102,12 @@ define([
Cryptpad.onlyoffice.onEvent.reg(function (obj) { Cryptpad.onlyoffice.onEvent.reg(function (obj) {
if (obj.ev === 'MESSAGE' && !/^cp\|/.test(obj.data)) { if (obj.ev === 'MESSAGE' && !/^cp\|/.test(obj.data)) {
try { try {
var validateKey = obj.data.validateKey || true;
var skipCheck = validateKey === true;
var msg = obj.data.msg;
obj.data = { obj.data = {
msg: JSON.parse(Utils.crypto.decrypt(obj.data, Utils.secret.keys.validateKey)), msg: JSON.parse(Utils.crypto.decrypt(msg, validateKey, skipCheck)),
hash: obj.data.slice(0,64) hash: msg.slice(0,64)
}; };
} catch (e) { } catch (e) {
console.error(e); console.error(e);

@ -8077,9 +8077,6 @@ jQuery.fn.extend({
prop = jQuery.extend( {}, prop ); prop = jQuery.extend( {}, prop );
function doAnimation() { function doAnimation() {
// XXX 'this' does not always have a nodeName when running the
// test suite
if ( optall.queue === false ) { if ( optall.queue === false ) {
jQuery._mark( this ); jQuery._mark( this );
} }

@ -1683,7 +1683,7 @@ define([
state: (2 + (version / 10)), state: (2 + (version / 10)),
progress: progress progress: progress
}); });
}); }, store);
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
postMessage(clientId, 'LOADING_DRIVE', { postMessage(clientId, 'LOADING_DRIVE', {
state: 3 state: 3

@ -1,6 +1,7 @@
define([ define([
'/common/common-messaging.js', '/common/common-messaging.js',
], function (Messaging) { '/common/common-hash.js',
], function (Messaging, Hash) {
var getRandomTimeout = function (ctx) { var getRandomTimeout = function (ctx) {
var lag = ctx.store.realtime.getLag().lag || 0; var lag = ctx.store.realtime.getLag().lag || 0;
@ -156,6 +157,50 @@ define([
cb(true); cb(true);
}; };
// Hide duplicates when receiving a SHARE_PAD notification:
// Keep only one notification per channel: the stronger and more recent one
var channels = {};
handlers['SHARE_PAD'] = function (ctx, box, data, cb) {
var msg = data.msg;
var hash = data.hash;
var content = msg.content;
// content.name, content.title, content.href, content.password
var channel = Hash.hrefToHexChannelId(content.href, content.password);
var parsed = Hash.parsePadUrl(content.href);
var mode = parsed.hashData && parsed.hashData.mode || 'n/a';
var old = channels[channel];
var toRemove;
if (old) {
// New hash is weaker, ignore
if (old.mode === 'edit' && mode === 'view') {
return void cb(true);
}
// New hash is not weaker, clear the old one
toRemove = old.data;
}
// Update the data
channels[channel] = {
mode: mode,
data: {
type: box.type,
hash: hash
}
};
cb(false, toRemove);
};
removeHandlers['SHARE_PAD'] = function (ctx, box, data, hash) {
var content = data.content;
var channel = Hash.hrefToHexChannelId(content.href, content.password);
var old = channels[channel];
if (old && old.data && old.data.hash === hash) {
delete channels[channel];
}
};
return { return {
add: function (ctx, box, data, cb) { add: function (ctx, box, data, cb) {
/** /**

@ -157,6 +157,7 @@ proxy.mailboxes = {
var openChannel = function (ctx, type, m, onReady) { var openChannel = function (ctx, type, m, onReady) {
var box = ctx.boxes[type] = { var box = ctx.boxes[type] = {
type: type,
queue: [], // Store the messages to send when the channel is ready queue: [], // Store the messages to send when the channel is ready
history: [], // All the hashes loaded from the server in corretc order history: [], // All the hashes loaded from the server in corretc order
content: {}, // Content of the messages that should be displayed content: {}, // Content of the messages that should be displayed
@ -228,8 +229,8 @@ proxy.mailboxes = {
msg: msg, msg: msg,
hash: hash hash: hash
}; };
Handlers.add(ctx, box, message, function (toDismiss) { Handlers.add(ctx, box, message, function (dismissed, toDismiss) {
if (toDismiss) { if (dismissed) { // This message should be removed
dismiss(ctx, { dismiss(ctx, {
type: type, type: type,
hash: hash hash: hash
@ -238,6 +239,11 @@ proxy.mailboxes = {
}); });
return; return;
} }
if (toDismiss) { // List of other messages to remove
dismiss(ctx, toDismiss, '', function () {
console.log('Notification handled automatically');
});
}
box.content[hash] = msg; box.content[hash] = msg;
showMessage(ctx, type, message); showMessage(ctx, type, message);
}); });

@ -26,7 +26,10 @@ define([
if (!c.id) { c.id = chan.wc.myID + '-' + client; } if (!c.id) { c.id = chan.wc.myID + '-' + client; }
chan.history.forEach(function (msg) { chan.history.forEach(function (msg) {
ctx.emit('MESSAGE', msg, [client]); ctx.emit('MESSAGE', {
msg: msg,
validateKey: chan.validateKey
}, [client]);
}); });
// ==> And push the new tab to the list // ==> And push the new tab to the list
@ -37,7 +40,8 @@ define([
var onOpen = function (wc) { var onOpen = function (wc) {
ctx.channels[channel] = ctx.channels[channel] || { ctx.channels[channel] = ctx.channels[channel] || {
history: [] history: [],
validateKey: obj.validateKey
}; };
chan = ctx.channels[channel]; chan = ctx.channels[channel];
@ -61,7 +65,10 @@ define([
}); });
wc.on('message', function (msg) { wc.on('message', function (msg) {
chan.history.push(msg); chan.history.push(msg);
ctx.emit('MESSAGE', msg, chan.clients); ctx.emit('MESSAGE', {
msg: msg,
validateKey: chan.validateKey
}, chan.clients);
}); });
chan.wc = wc; chan.wc = wc;
@ -101,6 +108,7 @@ define([
}; };
network.on('message', function (msg, sender) { network.on('message', function (msg, sender) {
if (!ctx.channels[channel]) { return; }
var hk = network.historyKeeper; var hk = network.historyKeeper;
if (sender !== hk) { return; } if (sender !== hk) { return; }
@ -115,7 +123,12 @@ define([
// Keep only metadata messages for the current channel // Keep only metadata messages for the current channel
if (parsed.channel && parsed.channel !== channel) { return; } if (parsed.channel && parsed.channel !== channel) { return; }
// Ignore the metadata message // Ignore the metadata message
if (parsed.validateKey && parsed.channel) { return; } if (parsed.validateKey && parsed.channel) {
if (!chan.validateKey) {
chan.validateKey = parsed.validateKey;
}
return;
}
// End of history: emit READY // End of history: emit READY
if (parsed.state && parsed.state === 1 && parsed.channel) { if (parsed.state && parsed.state === 1 && parsed.channel) {
ctx.emit('READY', '', chan.clients); ctx.emit('READY', '', chan.clients);
@ -132,7 +145,9 @@ define([
if (hash === chan.lastKnownHash || hash === chan.lastCpHash) { return; } if (hash === chan.lastKnownHash || hash === chan.lastCpHash) { return; }
chan.lastKnownHash = hash; chan.lastKnownHash = hash;
ctx.emit('MESSAGE', msg, chan.clients); ctx.emit('MESSAGE', {
msg: msg,
}, chan.clients);
chan.history.push(msg); chan.history.push(msg);
}); });
@ -176,7 +191,9 @@ define([
return void chan.sendMsg(data.isCp, cb); return void chan.sendMsg(data.isCp, cb);
} }
chan.sendMsg(data.msg, cb); chan.sendMsg(data.msg, cb);
ctx.emit('MESSAGE', data.msg, chan.clients.filter(function (cl) { ctx.emit('MESSAGE', {
msg: data.msg
}, chan.clients.filter(function (cl) {
return cl !== clientId; return cl !== clientId;
})); }));
}; };

@ -625,6 +625,11 @@ define([
var root = exp.find([ROOT]); var root = exp.find([ROOT]);
var toClean = []; var toClean = [];
for (var id in fd) { for (var id in fd) {
if (String(id) !== String(Number(id))) {
debug("Invalid file ID in filesData.", id);
toClean.push(id);
continue;
}
id = Number(id); id = Number(id);
var el = fd[id]; var el = fd[id];

@ -342,7 +342,7 @@ define([
}); });
// Remove the elements from the old location (without unpinning) // Remove the elements from the old location (without unpinning)
Env.user.userObject.delete(resolved.main, waitFor()); Env.user.userObject.delete(resolved.main, waitFor()); // FIXME waitFor() is called synchronously
} }
} }
} }
@ -369,7 +369,7 @@ define([
if (copy) { return; } if (copy) { return; }
// Remove the elements from the old location (without unpinning) // Remove the elements from the old location (without unpinning)
uoFrom.delete(paths, waitFor()); uoFrom.delete(paths, waitFor()); // FIXME waitFor() is called synchronously
} }
}); });
} }
@ -707,6 +707,7 @@ define([
if (type === 'expirable') { if (type === 'expirable') {
return function (fileId) { return function (fileId) {
var data = userObject.getFileData(fileId); var data = userObject.getFileData(fileId);
if (!data) { return; }
// Don't push duplicates // Don't push duplicates
if (result.indexOf(data.channel) !== -1) { return; } if (result.indexOf(data.channel) !== -1) { return; }
// Return pads owned by someone else or expired by time // Return pads owned by someone else or expired by time
@ -718,6 +719,7 @@ define([
if (type === 'owned') { if (type === 'owned') {
return function (fileId) { return function (fileId) {
var data = userObject.getFileData(fileId); var data = userObject.getFileData(fileId);
if (!data) { return; }
// Don't push duplicates // Don't push duplicates
if (result.indexOf(data.channel) !== -1) { return; } if (result.indexOf(data.channel) !== -1) { return; }
// Return owned pads // Return owned pads
@ -729,6 +731,7 @@ define([
if (type === "pin") { if (type === "pin") {
return function (fileId) { return function (fileId) {
var data = userObject.getFileData(fileId); var data = userObject.getFileData(fileId);
if (!data) { return; }
// Don't pin pads owned by someone else // Don't pin pads owned by someone else
if (_ownedByOther(Env, data.owners)) { return; } if (_ownedByOther(Env, data.owners)) { return; }
// Don't push duplicates // Don't push duplicates

@ -223,6 +223,11 @@ define([
sframeChan.event("EV_PAD_PASSWORD"); sframeChan.event("EV_PAD_PASSWORD");
}; };
if (!val && sessionStorage.newPadPassword) {
val = sessionStorage.newPadPassword;
delete sessionStorage.newPadPassword;
}
if (val) { if (val) {
password = val; password = val;
Cryptpad.getFileSize(window.location.href, password, waitFor(function (e, size) { Cryptpad.getFileSize(window.location.href, password, waitFor(function (e, size) {

@ -832,11 +832,17 @@ MessengerUI, Messages) {
return $spin; return $spin;
}; };
var createLimit = function (toolbar) { var createLimit = function (toolbar, config) {
var $limitIcon = $('<span>', {'class': 'fa fa-exclamation-triangle'}); var $limitIcon = $('<span>', {'class': 'fa fa-exclamation-triangle'});
var $limit = toolbar.$userAdmin.find('.'+LIMIT_CLS).attr({ var $limit = toolbar.$userAdmin.find('.'+LIMIT_CLS).attr({
'title': Messages.pinLimitReached 'title': Messages.pinLimitReached
}).append($limitIcon).hide(); }).append($limitIcon).hide();
var priv = config.metadataMgr.getPrivateData();
var origin = priv.origin;
var l = document.createElement("a");
l.href = origin;
var todo = function (e, overLimit) { var todo = function (e, overLimit) {
if (e) { return void console.error("Unable to get the pinned usage", e); } if (e) { return void console.error("Unable to get the pinned usage", e); }
if (overLimit) { if (overLimit) {
@ -845,7 +851,7 @@ MessengerUI, Messages) {
key = 'pinLimitReachedAlertNoAccounts'; key = 'pinLimitReachedAlertNoAccounts';
} }
$limit.show().click(function () { $limit.show().click(function () {
UI.alert(Messages._getKey(key, [encodeURIComponent(window.location.hostname)]), null, true); UI.alert(Messages._getKey(key, [encodeURIComponent(l.hostname)]), null, true);
}); });
} }
}; };

@ -565,7 +565,7 @@
"download_step1": "Laden...", "download_step1": "Laden...",
"download_step2": "Entschlüsselung...", "download_step2": "Entschlüsselung...",
"todo_title": "CryptTodo", "todo_title": "CryptTodo",
"todo_newTodoNamePlaceholder": "Beschreibe deine Aufgabe...", "todo_newTodoNamePlaceholder": "Beschreibe deine Aufgabe",
"todo_newTodoNameTitle": "Diese Aufgabe zu deiner ToDo-Liste hinzufügen", "todo_newTodoNameTitle": "Diese Aufgabe zu deiner ToDo-Liste hinzufügen",
"todo_markAsCompleteTitle": "Diese Aufgabe als erledigt markieren", "todo_markAsCompleteTitle": "Diese Aufgabe als erledigt markieren",
"todo_markAsIncompleteTitle": "Diese Aufgabe als nicht erledigt markieren", "todo_markAsIncompleteTitle": "Diese Aufgabe als nicht erledigt markieren",
@ -830,7 +830,7 @@
"generic": { "generic": {
"more": "Erfahre mehr über die Nutzung von CryptPad, indem du unsere <a href=\"/faq.html\" target=\"_blank\">FAQ</a> liest", "more": "Erfahre mehr über die Nutzung von CryptPad, indem du unsere <a href=\"/faq.html\" target=\"_blank\">FAQ</a> liest",
"share": "Benutze das Teilen-Menü (<span class=\"fa fa-share-alt\"></span>), um Links zu generieren, die Mitarbeiter zum Lesen oder Bearbeiten einladen", "share": "Benutze das Teilen-Menü (<span class=\"fa fa-share-alt\"></span>), um Links zu generieren, die Mitarbeiter zum Lesen oder Bearbeiten einladen",
"save": "Alle Änderungen werden automatisch synchronisiert. Du misst sie also nicht speichern" "save": "Alle Änderungen werden automatisch synchronisiert. Du musst sie also nicht selbst speichern"
}, },
"text": { "text": {
"formatting": "Du kannst die Werkzeugleiste anzeigen oder verbergen, indem du auf <span class=\"fa fa-caret-down\"></span> oder <span class=\"fa fa-caret-up\"></span> klickst", "formatting": "Du kannst die Werkzeugleiste anzeigen oder verbergen, indem du auf <span class=\"fa fa-caret-down\"></span> oder <span class=\"fa fa-caret-up\"></span> klickst",
@ -1074,5 +1074,6 @@
"fm_info_sharedFolderHistory": "Dies ist nur der Verlauf deines geteilten Ordners: <b>{0}</b><br/>Dein CryptDrive bleibt beim Navigieren im Nur-Lesen-Modus.", "fm_info_sharedFolderHistory": "Dies ist nur der Verlauf deines geteilten Ordners: <b>{0}</b><br/>Dein CryptDrive bleibt beim Navigieren im Nur-Lesen-Modus.",
"share_description": "Wähle aus, was du teilen möchtest. Dir wird dann ein entsprechender Link anzeigt. Du kannst es auch direkt an deine Freunde in CryptPad senden.", "share_description": "Wähle aus, was du teilen möchtest. Dir wird dann ein entsprechender Link anzeigt. Du kannst es auch direkt an deine Freunde in CryptPad senden.",
"fc_expandAll": "Alle ausklappen", "fc_expandAll": "Alle ausklappen",
"fc_collapseAll": "Alle einklappen" "fc_collapseAll": "Alle einklappen",
"fc_color": "Farbe ändern"
} }

@ -1074,5 +1074,6 @@
"share_withFriends": "Partager", "share_withFriends": "Partager",
"notifications_dismiss": "Cacher", "notifications_dismiss": "Cacher",
"fm_info_sharedFolderHistory": "Vous regardez l'historique de votre dossier partagé <b>{0}</b><br/>Votre CryptDrive restera en lecture seule pendant la navigation.", "fm_info_sharedFolderHistory": "Vous regardez l'historique de votre dossier partagé <b>{0}</b><br/>Votre CryptDrive restera en lecture seule pendant la navigation.",
"share_description": "Choisissez ce que vous souhaitez partager puis obtenez le lien ou envoyez-le directement à vos amis CryptPad." "share_description": "Choisissez ce que vous souhaitez partager puis obtenez le lien ou envoyez-le directement à vos amis CryptPad.",
"fc_color": "Changer la couleur"
} }

@ -1075,5 +1075,33 @@
"share_withFriends": "Share", "share_withFriends": "Share",
"notifications_dismiss": "Dismiss", "notifications_dismiss": "Dismiss",
"fm_info_sharedFolderHistory": "This is only the history of your shared folder: <b>{0}</b><br/>Your CryptDrive will stay in read-only mode while you navigate.", "fm_info_sharedFolderHistory": "This is only the history of your shared folder: <b>{0}</b><br/>Your CryptDrive will stay in read-only mode while you navigate.",
"share_description": "Choose what you'd like to share and either get the link or send it directly to your CryptPad friends." "share_description": "Choose what you'd like to share and either get the link or send it directly to your CryptPad friends.",
"supportPage": "Support",
"admin_cat_support": "Support",
"admin_supportInitHelp": "Your server is not yet configured to have a support mailbox. If you want a support mailbox to receive messages from your users, you should ask your server administrator to run the script located in \"./scripts/generate-admin-keys.js\", then store the public key in the \"config.js\" file and send you the private key.",
"admin_supportInitPrivate": "Your CryptPad instance is configured to use a support mailbox but your account doesn't have the correct private key to access it. Please use the following form to add or update the private key to your account.",
"admin_supportAddKey": "Add private key",
"admin_supportAddError": "Invalid private key",
"admin_supportInitTitle": "Support mailbox initialization",
"admin_supportInitHint": "You can configure a support mailbox in order to give users of your CryptPad instance a way to contact you securely if they have an issue with their account.",
"admin_supportListTitle": "Support mailbox",
"admin_supportListHint": "Here is the list of tickets sent by the users to the support mailbox. All the administrators can see the messages and the answers. A closed ticket cannot be reopened. You can only remove (hide) closed tickets, and the removed tickets are still visible by the other administrators.",
"support_disabledTitle": "Support is not enabled",
"support_disabledHint": "This CryptPad instance is not yet configured to use a support form.",
"support_cat_new": "New ticket",
"support_formTitle": "Ticket title",
"support_formHint": "This form can be used to create a new support ticket. Using this form, you can contact the administrators to solve issues or ask any question in a secure way. Please don't create a new ticket if you already have an open ticket about the same issue, but use reply button to provide more information.",
"support_formButton": "Send",
"support_formTitleError": "Error: title is empty",
"support_formContentError": "Error: content is empty",
"support_formMessage": "Type your message...",
"support_cat_tickets": "Existing tickets",
"support_listTitle": "Support tickets",
"support_listHint": "Here is the list of tickets sent to the administrators and their answers. A closed ticket cannot be re-opened, you have to make a new one. You can hide tickets that have been closed but they will still be visible by the administrators.",
"support_answer": "Reply",
"support_close": "Close the ticket",
"support_remove": "Remove the ticket",
"support_showData": "Show/hide user data",
"support_from": "<b>From:</b> {0}",
"support_closed": "This ticket has been closed"
} }

@ -311,12 +311,12 @@ define([
_getFiles[FILES_DATA] = function () { _getFiles[FILES_DATA] = function () {
var ret = []; var ret = [];
if (!files[FILES_DATA]) { return ret; } if (!files[FILES_DATA]) { return ret; }
return Object.keys(files[FILES_DATA]).map(Number); return Object.keys(files[FILES_DATA]).map(Number).filter(Boolean);
}; };
_getFiles[SHARED_FOLDERS] = function () { _getFiles[SHARED_FOLDERS] = function () {
var ret = []; var ret = [];
if (!files[SHARED_FOLDERS]) { return ret; } if (!files[SHARED_FOLDERS]) { return ret; }
return Object.keys(files[SHARED_FOLDERS]).map(Number); return Object.keys(files[SHARED_FOLDERS]).map(Number).filter(Boolean);
}; };
var getFiles = exp.getFiles = function (categories) { var getFiles = exp.getFiles = function (categories) {
var ret = []; var ret = [];

@ -2194,15 +2194,22 @@ define([
var data = manager.getSharedFolderData(id); var data = manager.getSharedFolderData(id);
var parsed = Hash.parsePadUrl(data.href); var parsed = Hash.parsePadUrl(data.href);
if (!parsed || !parsed.hash) { return void console.error("Invalid href: "+data.href); } if (!parsed || !parsed.hash) { return void console.error("Invalid href: "+data.href); }
var friends = common.getFriends();
var modal = UIElements.createSFShareModal({ var modal = UIElements.createSFShareModal({
origin: APP.origin, origin: APP.origin,
pathname: "/drive/", pathname: "/drive/",
friends: friends,
title: data.title,
password: data.password,
common: common,
hashes: { hashes: {
editHash: parsed.hash editHash: parsed.hash
} }
}); });
$shareBlock.click(function () { $shareBlock.click(function () {
UI.openCustomModal(modal); UI.openCustomModal(modal, {
wide: Object.keys(friends).length !== 0
});
}); });
$container.append($shareBlock); $container.append($shareBlock);
}; };
@ -3399,7 +3406,9 @@ define([
//data.noPassword = true; //data.noPassword = true;
data.noEditPassword = true; data.noEditPassword = true;
data.noExpiration = true; data.noExpiration = true;
data.sharedFolder = true; // XXX debug // this is here to allow users to check the channel id of a shared folder
// we should remove it at some point
data.sharedFolder = true;
} }
UIElements.getProperties(common, data, cb); UIElements.getProperties(common, data, cb);
@ -3537,6 +3546,7 @@ define([
friends: friends, friends: friends,
title: data.title, title: data.title,
common: common, common: common,
password: data.password,
hashes: { hashes: {
editHash: parsed.hash editHash: parsed.hash
} }
@ -3550,6 +3560,7 @@ define([
origin: APP.origin, origin: APP.origin,
pathname: "/" + padType + "/", pathname: "/" + padType + "/",
friends: friends, friends: friends,
password: data.password,
hashes: { hashes: {
editHash: parsed.hash, editHash: parsed.hash,
viewHash: roParsed.hash, viewHash: roParsed.hash,

@ -17,7 +17,11 @@ define([
{ {
var APP = window.APP = {}; var APP = window.APP = {};
var init = false;
var andThen = function (common) { var andThen = function (common) {
if (init) { return; }
init = true;
var metadataMgr = common.getMetadataMgr(); var metadataMgr = common.getMetadataMgr();
var sframeChan = common.getSframeChannel(); var sframeChan = common.getSframeChannel();
@ -38,6 +42,7 @@ define([
var modal = f({ var modal = f({
origin: origin, origin: origin,
pathname: pathname, pathname: pathname,
password: priv.password,
hashes: hashes, hashes: hashes,
common: common, common: common,
title: data.title, title: data.title,
@ -50,7 +55,7 @@ define([
password: priv.password password: priv.password
} }
}); });
UI.findCancelButton().click(); $('button.cancel').click(); // Close any existing alertify
UI.openCustomModal(UI.dialog.tabs(modal), { UI.openCustomModal(UI.dialog.tabs(modal), {
wide: Object.keys(friends).length !== 0 wide: Object.keys(friends).length !== 0
}); });

Loading…
Cancel
Save