Merge branch 'staging' into modern-users

pull/1/head
ansuz 7 years ago
commit 0365092aaf

@ -204,7 +204,16 @@
}
span.cp-password-container {
display: flex;
align-items: center;
margin-bottom: 15px;
justify-content: space-between;
& > * {
margin-bottom: 0 !important;
}
button {
margin: 0 !important;
}
}
input[type="checkbox"], input[type="radio"] {
@ -220,10 +229,6 @@
}
}
nav {
text-align: right;
button:not(.btn):not(.pure-button):not(.md-button):not(.mdl-button) {
background-color: @colortheme_alertify-cancel;
@ -298,6 +303,9 @@
button.btn {
margin: 6px 4px;
}
nav {
text-align: right;
}
}
}

@ -5,7 +5,6 @@
input {
flex: 1;
min-width: 0;
margin-bottom: 0 !important; // Override margin from alertify
}
label, .fa {
margin-left: 10px;

@ -1117,6 +1117,16 @@ define(function () {
out.password_submit = "Valider";
out.password_show = "Afficher";
// Change password in pad properties
out.properties_addPassword = "Ajouter un mot de passe";
out.properties_changePassword = "Modifier le mot de passe";
out.properties_confirmNew = "Êtes-vous sûr ? Ajouter un mot de passe changera l'URL de ce pad. Les utilisateurs ne connaissant pas le nouveau mot de passe perdront l'accès au pad.";
out.properties_confirmChange = "Êtes-vous sûr ? Les utilisateurs ne connaissant pas le nouveau mot de passe perdront l'accès au pad.";
out.properties_passwordError = "Une erreur est survenue lors de la modification du mot de passe. Veuillez réessayer.";
out.properties_passwordWarning = "Le mot de passe a été modifié avec succès mais nous n'avons pas réussi à mettre à jour votre CryptDrive avec les nouvelles informations. Vous devrez peut-être supprimer manuellement l'ancienne version de ce pad.<br>Appuyez sur OK pour recharger le pad et mettre à jour vos droits d'accès.";
out.properties_passwordSuccess = "Le mot de passe a été modifié avec succès.<br>Appuyez sur OK pour mettre à jour vos droits d'accès.";
out.properties_changePasswordButton = "Valider";
// New share modal
out.share_linkCategory = "Partage";
out.share_linkAccess = "Droits d'accès";

@ -1155,13 +1155,23 @@ define(function () {
out.creation_newPadModalDescriptionAdvanced = "You can check the box (or press <b>Space</b> to change its value) if you want to display the pad creation screen (for owned pads, expiring pads, etc.).";
out.creation_newPadModalAdvanced = "Display the pad creation screen";
// Password prompt on the loadind screen
// Password prompt on the loading screen
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";
out.password_show = "Show";
// Change password in pad properties
out.properties_addPassword = "Add a password";
out.properties_changePassword = "Change the password";
out.properties_confirmNew = "Are you sure? Adding a password will change this pad's URL. Users without the password will lose access to this pad";
out.properties_confirmChange = "Are you sure? Users without the new password will lose access to this pad";
out.properties_passwordError = "An error occured while trying to change the password. Please try again.";
out.properties_passwordWarning = "The password was successfully changed but we were unable to update your CryptDrive with the new data. You may have to remove the old version of the pad manually.<br>Press OK to reload and update your acces rights.";
out.properties_passwordSuccess = "The password was successfully changed.<br>Press OK to reload and update your access rights.";
out.properties_changePasswordButton = "Submit";
// New share modal
out.share_linkCategory = "Share link";
out.share_linkAccess = "Access rights";

@ -136,12 +136,6 @@ define([
$d.append(UI.dialog.selectable(owners, {
id: 'cp-app-prop-owners',
}));
/* TODO
if (owned) {
var $deleteOwned = $('button').text(Messages.fc_delete_owned).click(function () {
});
$d.append($deleteOwned);
}*/
var expire = Messages.creation_expireFalse;
if (data.expire && typeof (data.expire) === "number") {
@ -153,11 +147,12 @@ define([
id: 'cp-app-prop-expire',
}));
if (typeof data.password !== "undefined") {
var hasPassword = typeof data.password !== "undefined";
if (hasPassword) {
$('<label>', {'for': 'cp-app-prop-password'}).text(Messages.creation_passwordValue)
.appendTo($d);
var password = UI.passwordInput({
id: 'cp-app-prop-expire',
id: 'cp-app-prop-password',
readonly: 'readonly'
});
var $pwInput = $(password).find('.cp-password-input');
@ -167,6 +162,51 @@ define([
$d.append(password);
}
var parsed = Hash.parsePadUrl(data.href);
if (owned && parsed.hashData.type === 'pad') {
var sframeChan = common.getSframeChannel();
var changePwTitle = Messages.properties_changePassword;
var changePwConfirm = Messages.properties_confirmChange;
if (!hasPassword) {
changePwTitle = Messages.properties_addPassword;
changePwConfirm = Messages.properties_confirmNew;
}
$('<label>', {'for': 'cp-app-prop-change-password'})
.text(changePwTitle).appendTo($d);
var newPassword = UI.passwordInput({
id: 'cp-app-prop-change-password',
style: 'flex: 1;'
});
var passwordOk = h('button', Messages.properties_changePasswordButton);
var changePass = h('span.cp-password-container', [
newPassword,
passwordOk
]);
$(passwordOk).click(function () {
UI.confirm(changePwConfirm, function (yes) {
if (!yes) { return; }
sframeChan.query("Q_PAD_PASSWORD_CHANGE", {
href: data.href,
password: $(newPassword).val()
}, function (err, data) {
if (err || data.error) {
return void UI.alert(Messages.properties_passwordError);
}
UI.findOKButton().click();
if (data.warning) {
return void UI.alert(Messages.properties_passwordWarning, function () {
common.gotoURL(hasPassword ? undefined : data.href);
}, {force: true});
}
return void UI.alert(Messages.properties_passwordSuccess, function () {
common.gotoURL(hasPassword ? undefined : data.href);
}, {force: true});
});
});
});
$d.append(changePass);
}
cb(void 0, $d);
};
var getPadProperties = function (common, data, cb) {

@ -5,6 +5,7 @@ define([
'/common/common-hash.js',
'/common/common-realtime.js',
'/common/outer/network-config.js',
'/bower_components/chainpad/chainpad.dist.js',
], function (Crypto, CPNetflux, Util, Hash, Realtime, NetConfig) {
var finish = function (S, err, doc) {
if (S.done) { return; }

@ -234,6 +234,12 @@ define([
});
};
common.writeLoginBlock = function (data, cb) {
postMessage('WRITE_LOGIN_BLOCK', data, function (obj) {
cb(obj);
});
};
// ANON RPC
// SFRAME: talk to anon_rpc from the iframe
@ -580,6 +586,9 @@ define([
pad.joinPad = function (data) {
postMessage("JOIN_PAD", data);
};
pad.leavePad = function (data, cb) {
postMessage("LEAVE_PAD", data, cb);
};
pad.sendPadMsg = function (data, cb) {
postMessage("SEND_PAD_MSG", data, cb);
};
@ -591,6 +600,91 @@ define([
pad.onConnectEvent = Util.mkEvent();
pad.onErrorEvent = Util.mkEvent();
common.changePadPassword = function (Crypt, href, newPassword, edPublic, cb) {
if (!href) { return void cb({ error: 'EINVAL_HREF' }); }
var parsed = Hash.parsePadUrl(href);
if (!parsed.hash) { return void cb({ error: 'EINVAL_HREF' }); }
var warning = false;
var newHash;
var oldChannel;
if (parsed.hashData.password) {
newHash = parsed.hash;
} else {
newHash = Hash.createRandomHash(parsed.type, newPassword);
}
var newHref = '/' + parsed.type + '/#' + newHash;
var optsGet = {};
var optsPut = {
password: newPassword
};
Nthen(function (waitFor) {
if (parsed.hashData && parsed.hashData.password) {
common.getPadAttribute('password', waitFor(function (err, password) {
optsGet.password = password;
}), href);
}
common.getPadAttribute('owners', waitFor(function (err, owners) {
if (!Array.isArray(owners) || owners.indexOf(edPublic) === -1) {
// We're not an owner, we shouldn't be able to change the password!
waitFor.abort();
return void cb({ error: 'EPERM' });
}
optsPut.owners = owners;
}), href);
common.getPadAttribute('expire', waitFor(function (err, expire) {
optsPut.expire = (expire - (+new Date())) / 1000; // Lifetime in seconds
}), href);
}).nThen(function (waitFor) {
Crypt.get(parsed.hash, waitFor(function (err, val) {
if (err) {
waitFor.abort();
return void cb({ error: err });
}
Crypt.put(newHash, val, waitFor(function (err) {
if (err) {
waitFor.abort();
return void cb({ error: err });
}
}), optsPut);
}), optsGet);
}).nThen(function (waitFor) {
var secret = Hash.getSecrets(parsed.type, parsed.hash, optsGet.password);
oldChannel = secret.channel;
pad.leavePad({
channel: oldChannel
}, waitFor());
pad.onDisconnectEvent.fire(true);
}).nThen(function (waitFor) {
common.removeOwnedChannel(oldChannel, waitFor(function (obj) {
if (obj && obj.error) {
waitFor.abort();
return void cb(obj);
}
}));
}).nThen(function (waitFor) {
common.setPadAttribute('password', newPassword, waitFor(function (err) {
if (err) { warning = true; }
}), href);
var secret = Hash.getSecrets(parsed.type, newHash, newPassword);
common.setPadAttribute('channel', secret.channel, waitFor(function (err) {
if (err) { warning = true; }
}), href);
if (parsed.hashData.password) { return; } // same hash
common.setPadAttribute('href', newHref, waitFor(function (err) {
if (err) { warning = true; }
}), href);
}).nThen(function () {
cb({
warning: warning,
hash: newHash,
href: newHref
});
});
};
// Loading events
common.loading = {};
common.loading.onDriveEvent = Util.mkEvent();
@ -862,7 +956,6 @@ define([
w.addEventListener('statechange', onStateChange);
return;
}
// XXX
// New version detected (from another tab): kill?
console.error('New version detected: ABORT?');
};

@ -276,6 +276,15 @@ define([
});
};
Store.writeLoginBlock = function (clientId, data, cb) {
store.rpc.writeLoginBlock(data, function (e, res) {
cb({
error: e,
data: res
});
});
};
Store.initRpc = function (clientId, data, cb) {
if (store.rpc) { return void cb(account); }
require(['/common/pinpad.js'], function (Pinpad) {
@ -1059,6 +1068,13 @@ define([
};
CpNfWorker.start(conf);
};
Store.leavePad = function (clientId, data, cb) {
var channel = channels[data.channel];
if (!channel || !channel.wc) { return void cb ({error: 'EINVAL'}); }
channel.wc.leave();
delete channels[data.channel];
cb();
};
Store.sendPadMsg = function (clientId, data, cb) {
var msg = data.msg;
var channel = channels[data.channel];

@ -23,6 +23,7 @@ define([
UPLOAD_COMPLETE: Store.uploadComplete,
UPLOAD_STATUS: Store.uploadStatus,
UPLOAD_CANCEL: Store.uploadCancel,
WRITE_LOGIN_BLOCK: Store.writeLoginBlock,
PIN_PADS: Store.pinPads,
UNPIN_PADS: Store.unpinPads,
GET_DELETED_PADS: Store.getDeletedPads,
@ -67,6 +68,7 @@ define([
// Pad
SEND_PAD_MSG: Store.sendPadMsg,
JOIN_PAD: Store.joinPad,
LEAVE_PAD: Store.leavePad,
GET_FULL_HISTORY: Store.getFullHistory,
IS_NEW_CHANNEL: Store.isNewChannel,
// Drive

@ -41,6 +41,7 @@ define([
if (!attr || !attr.trim()) { return void cb("E_INVAL_ATTR"); }
var data = exp.getFileData(id);
data[attr] = clone(value);
console.log(data);
cb(null);
};
exp.getPadAttribute = function (href, attr, cb) {

@ -221,6 +221,10 @@ define([
});
};
exp.writeLoginBlock = function (data, cb) {
cb();
};
cb(e, exp);
});
};

@ -140,6 +140,11 @@ define([
toolbar.initializing();
return;
}
if (text) {
// text is a boolean here. It means we won't try to reconnect
toolbar.failed();
return;
}
toolbar.reconnecting();
});
break;
@ -171,9 +176,12 @@ define([
}
};
var contentUpdate = function (newContent) {
var oldContent;
var contentUpdate = function (newContent, waitFor) {
if (JSONSortify(newContent) === JSONSortify(oldContent)) { return; }
try {
evContentUpdate.fire(newContent);
evContentUpdate.fire(newContent, waitFor);
setTimeout(function () { oldContent = newContent; });
} catch (e) {
console.log(e.stack);
UI.errorLoadingScreen(e.message);
@ -192,8 +200,9 @@ define([
cpNfInner.metadataMgr.updateMetadata(meta);
newContent = normalize(newContent);
contentUpdate(newContent);
nThen(function (waitFor) {
contentUpdate(newContent, waitFor);
}).nThen(function () {
if (!readOnly) {
var newContent2NoMeta = normalize(contentGetter());
var newContent2StrNoMeta = JSONSortify(newContent2NoMeta);
@ -232,6 +241,7 @@ define([
if (JSONSortify(newContent) !== JSONSortify(oldContent)) {
common.notify();
}
});
};
var setHistoryMode = function (bool, update) {
@ -284,11 +294,13 @@ define([
var newPad = false;
if (newContentStr === '') { newPad = true; }
// contentUpdate may be async so we need an nthen here
nThen(function (waitFor) {
if (!newPad) {
var newContent = JSON.parse(newContentStr);
cpNfInner.metadataMgr.updateMetadata(extractMetadata(newContent));
newContent = normalize(newContent);
contentUpdate(newContent);
contentUpdate(newContent, waitFor);
} else {
if (!cpNfInner.metadataMgr.getPrivateData().isNewFile) {
// We're getting 'new pad' but there is an existing file
@ -305,6 +317,7 @@ define([
title.updateTitle(title.defaultTitle);
evOnDefaultContentNeeded.fire();
}
}).nThen(function () {
stateChange(STATE.READY);
firstConnection = false;
if (!readOnly) { onLocal(); }
@ -336,10 +349,11 @@ define([
if (newPad && (!AppConfig.displayCreationScreen || (!skipTemp && skipCreation))) {
common.openTemplatePicker();
}
});
};
var onConnectionChange = function (info) {
if (state === STATE.DELETED) { return; }
stateChange(info.state ? STATE.INITIALIZING : STATE.DISCONNECTED);
stateChange(info.state ? STATE.INITIALIZING : STATE.DISCONNECTED, info.permanent);
/*if (info.state) {
UI.findOKButton().click();
} else {
@ -380,13 +394,19 @@ define([
common.createButton('import', true, options, function (c, f) {
if (async) {
fi(c, f, function (content) {
contentUpdate(content);
nThen(function (waitFor) {
contentUpdate(content, waitFor);
}).nThen(function () {
onLocal();
});
});
return;
}
contentUpdate(fi(c, f));
nThen(function (waitFor) {
contentUpdate(fi(c, f), waitFor);
}).nThen(function () {
onLocal();
});
})
);
};

@ -79,10 +79,12 @@ define([
evInfiniteSpinner.fire();
}, 2000);
sframeChan.on('EV_RT_DISCONNECT', function () {
sframeChan.on('EV_RT_DISCONNECT', function (isPermanent) {
isReady = false;
chainpad.abort();
onConnectionChange({ state: false });
// Permanent flag is here to choose if we wnat to display
// "reconnecting" or "disconnected" in the toolbar state
onConnectionChange({ state: false, permanent: isPermanent });
});
sframeChan.on('EV_RT_ERROR', function (err) {
isReady = false;

@ -112,8 +112,8 @@ define([], function () {
}
};
padRpc.onDisconnectEvent.reg(function () {
sframeChan.event('EV_RT_DISCONNECT');
padRpc.onDisconnectEvent.reg(function (permanent) {
sframeChan.event('EV_RT_DISCONNECT', permanent);
});
padRpc.onConnectEvent.reg(function (data) {

@ -143,12 +143,11 @@ define([
// or get it from the pad attributes
var needPassword = parsed.hashData && parsed.hashData.password;
if (needPassword) {
// Check if we have a password, and check if it is correct (file exists).
// It we don't have a correct password, display the password prompt.
// Maybe the file has been deleted from the server or the password has been changed.
Cryptpad.getPadAttribute('password', waitFor(function (err, val) {
if (val) {
// We already know the password, use it!
password = val;
todo();
} else {
var askPassword = function (wrongPasswordStored) {
// Ask for the password and check if the pad exists
// If the pad doesn't exist, it means the password isn't correct
// or the pad has been deleted
@ -162,7 +161,14 @@ define([
cb(false);
} else {
todo();
if (wrongPasswordStored) {
// Store the correct password
Cryptpad.setPadAttribute('password', password, function () {
correctPassword();
}, parsed.getUrl());
} else {
correctPassword();
}
cb(true);
}
};
@ -178,6 +184,19 @@ define([
Cryptpad.isNewChannel(window.location.href, password, next);
});
sframeChan.event("EV_PAD_PASSWORD");
};
if (val) {
password = val;
Cryptpad.getFileSize(window.location.href, password, function (e, size) {
if (size !== 0) {
return void todo();
}
// Wrong password or deleted file?
askPassword(true);
});
} else {
askPassword();
}
}), parsed.getUrl());
return;
@ -619,6 +638,15 @@ define([
});
});
sframeChan.on('Q_PAD_PASSWORD_CHANGE', function (data, cb) {
var href = data.href || window.location.href;
Cryptpad.changePadPassword(Cryptget, href, data.password, edPublic, cb);
});
sframeChan.on('Q_WRITE_LOGIN_BLOCK', function (data, cb) {
Cryptpad.writeLoginBlock(data, cb);
});
if (cfg.addRpc) {
cfg.addRpc(sframeChan, Cryptpad, Utils);
}

@ -74,6 +74,9 @@ define({
// Get the user's pin limit, usage and plan
'Q_PIN_GET_USAGE': true,
// Write/update the login block when the account password is changed
'Q_WRITE_LOGIN_BLOCK': true,
// Check the pin limit to determine if we can store the pad in the drive or if we should.
// display a warning
'Q_GET_PIN_LIMIT_STATUS': true,
@ -196,7 +199,6 @@ define({
// Anonymous users can empty their drive and remove FS_hash from localStorage
'EV_BURN_ANON_DRIVE': true,
// Inner drive needs to send command and receive updates from the async store
'Q_DRIVE_USEROBJECT': true,
'Q_DRIVE_GETOBJECT': true,
@ -214,6 +216,8 @@ define({
// Notifications about connection and disconnection from the network
'EV_NETWORK_DISCONNECT': true,
'EV_NETWORK_RECONNECT': true,
// Reload on new version
'EV_NEW_VERSION': true,
// Pad creation screen: create a pad with the selected attributes (owned, expire)
'Q_CREATE_PAD': true,
@ -224,19 +228,18 @@ define({
// The exact protocol is defined in common/test.js
'EV_TESTDATA': true,
// Critical error outside the iframe during loading screen
'EV_LOADING_ERROR': true,
// Ask for the pad password when a pad is protected
'EV_PAD_PASSWORD': true,
'Q_PAD_PASSWORD_VALUE': true,
// Change pad password
'Q_PAD_PASSWORD_CHANGE': true,
// Loading events to display in the loading screen
'EV_LOADING_INFO': true,
// Critical error outside the iframe during loading screen
'EV_LOADING_ERROR': true,
// Get all existing tags
'Q_GET_ALL_TAGS': true,
// Reload on new version
'EV_NEW_VERSION': true,
});

@ -48,6 +48,7 @@ define([
'cp-settings-resettips',
'cp-settings-thumbnails',
'cp-settings-userfeedback',
'cp-settings-change-password',
'cp-settings-delete'
],
'creation': [
@ -312,6 +313,7 @@ define([
};
create['delete'] = function () {
if (!common.isLoggedIn()) { return; }
var $div = $('<div>', { 'class': 'cp-settings-delete cp-sidebarlayout-element'});
$('<span>', {'class': 'label'}).text(Messages.settings_deleteTitle).appendTo($div);
@ -368,6 +370,30 @@ define([
return $div;
};
create['change-password'] = function () {
if (!common.isLoggedIn()) { return; }
var $div = $('<div>', { 'class': 'cp-settings-change-password cp-sidebarlayout-element'});
$('<span>', {'class': 'label'}).text("TODO Change your password").appendTo($div); // XXX
$('<span>', {'class': 'cp-sidebarlayout-description'})
.append("TODO").appendTo($div); // XXX
// var publicKey = privateData.edPublic;
var updateBlock = function (data, cb) {
sframeChan.query('Q_WRITE_LOGIN_BLOCK', data, function (err, obj) {
if (err || obj.error) { return void cb ({error: err || obj.error}); }
cb (obj);
});
};
updateBlock = updateBlock; // jshint..
return $div;
};
// Pad Creation settings
var setHTML = function (e, html) {

@ -1,81 +1,59 @@
define([
'jquery',
'/bower_components/chainpad-crypto/crypto.js',
'/common/toolbar3.js',
'json.sortify',
'/common/common-util.js',
'/bower_components/nthen/index.js',
'/common/sframe-common.js',
'/common/sframe-app-framework.js',
'/common/common-util.js',
'/common/common-hash.js',
'/common/common-interface.js',
'/api/config',
'/common/common-realtime.js',
'/common/common-thumbnail.js',
'/customize/pages.js',
'/customize/messages.js',
'/customize/application_config.js',
'/common/common-thumbnail.js',
'/whiteboard/colors.js',
'/customize/application_config.js',
'/bower_components/chainpad/chainpad.dist.js',
'/bower_components/secure-fabric.js/dist/fabric.min.js',
'/bower_components/file-saver/FileSaver.min.js',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/customize/src/less2/main.less',
], function (
$,
Crypto,
Toolbar,
JSONSortify,
Util,
Sortify,
nThen,
SFCommon,
Framework,
Util,
Hash,
UI,
ApiConfig,
CommonRealtime,
Thumb,
Pages,
Messages,
AppConfig,
Thumb,
Colors,
AppConfig,
ChainPad)
{
var saveAs = window.saveAs;
var APP = window.APP = {
$: $
};
var Fabric = APP.Fabric = window.fabric;
var stringify = function (obj) {
return JSONSortify(obj);
};
var toolbar;
var verbose = function (x) { console.log(x); };
verbose = function () {}; // comment out to enable verbose logging
var andThen = function (common) {
var config = {};
/* Initialize Fabric */
var canvas = APP.canvas = new Fabric.Canvas('cp-app-whiteboard-canvas', {
containerClass: 'cp-app-whiteboard-canvas-container'
});
var $canvas = $('canvas');
var $controls = $('#cp-app-whiteboard-controls');
var $canvasContainer = $('canvas').parents('.cp-app-whiteboard-canvas-container');
var mkControls = function (framework, canvas) {
var $pickers = $('#cp-app-whiteboard-pickers');
var $colors = $('#cp-app-whiteboard-colors');
var $cursors = $('#cp-app-whiteboard-cursors');
var $deleteButton = $('#cp-app-whiteboard-delete');
var $toggle = $('#cp-app-whiteboard-toggledraw');
var $controls = $('#cp-app-whiteboard-controls');
var $width = $('#cp-app-whiteboard-width');
var $widthLabel = $('label[for="cp-app-whiteboard-width"]');
var $opacity = $('#cp-app-whiteboard-opacity');
var $opacityLabel = $('label[for="cp-app-whiteboard-opacity"]');
var $toggle = $('#cp-app-whiteboard-toggledraw');
var $deleteButton = $('#cp-app-whiteboard-delete');
var metadataMgr = framework._.cpNfInner.metadataMgr;
// Brush
var readOnly = false;
var brush = {
color: '#000000',
opacity: 1
@ -136,39 +114,6 @@ define([
updateBrushOpacity();
$opacity.on('change', updateBrushOpacity);
var pickColor = function (current, cb) {
var $picker = $('<input>', {
type: 'color',
value: '#FFFFFF',
})
.on('change', function () {
var color = this.value;
cb(color);
}).appendTo($pickers);
setTimeout(function () {
$picker.val(current);
$picker.click();
});
};
var setColor = function (c) {
c = Colors.rgb2hex(c);
brush.color = c;
canvas.freeDrawingBrush.color = Colors.hex2rgba(brush.color, brush.opacity);
APP.$color.css({
'color': c,
});
createCursor();
};
var palette = AppConfig.whiteboardPalette || [
'red', 'blue', 'green', 'white', 'black', 'purple',
'gray', 'beige', 'brown', 'cyan', 'darkcyan', 'gold', 'yellow', 'pink'
];
$('.cp-app-whiteboard-palette-color').on('click', function () {
var color = $(this).css('background-color');
setColor(color);
});
APP.draw = true;
var toggleDrawMode = function () {
canvas.deactivateAll().renderAll();
@ -191,74 +136,49 @@ define([
canvas.discardActiveGroup();
}
canvas.renderAll();
config.onLocal();
framework.localChange();
};
$deleteButton.click(deleteSelection);
$(window).on('keyup', function (e) {
if (e.which === 46) { deleteSelection (); }
});
var setEditable = function (bool) {
APP.editable = bool;
if (readOnly && bool) { return; }
if (bool) { $controls.css('display', 'flex'); }
else { $controls.hide(); }
canvas.isDrawingMode = bool ? APP.draw : false;
if (!bool) {
canvas.deactivateAll();
canvas.renderAll();
}
canvas.forEachObject(function (object) {
object.selectable = bool;
var pickColor = function (current, cb) {
var $picker = $('<input>', {
type: 'color',
value: '#FFFFFF',
})
.on('change', function () {
var color = this.value;
cb(color);
}).appendTo($pickers);
setTimeout(function () {
$picker.val(current);
$picker.click();
});
$canvasContainer.find('canvas').css('border-color', bool? 'black': 'red');
};
var saveImage = APP.saveImage = function () {
var defaultName = "pretty-picture.png";
UI.prompt(Messages.exportPrompt, defaultName, function (filename) {
if (!(typeof(filename) === 'string' && filename)) { return; }
$canvas[0].toBlob(function (blob) {
saveAs(blob, filename);
});
var setColor = function (c) {
c = Colors.rgb2hex(c);
brush.color = c;
canvas.freeDrawingBrush.color = Colors.hex2rgba(brush.color, brush.opacity);
APP.$color.css({
'color': c,
});
createCursor();
};
APP.FM = common.createFileManager({});
APP.upload = function (title) {
var canvas = $canvas[0];
APP.canvas.deactivateAll().renderAll();
canvas.toBlob(function (blob) {
blob.name = title;
APP.FM.handleFile(blob);
var palette = AppConfig.whiteboardPalette || [
'red', 'blue', 'green', 'white', 'black', 'purple',
'gray', 'beige', 'brown', 'cyan', 'darkcyan', 'gold', 'yellow', 'pink'
];
$('.cp-app-whiteboard-palette-color').on('click', function () {
var color = $(this).css('background-color');
setColor(color);
});
};
var initializing = true;
var $bar = $('#cp-toolbar');
var Title;
var cpNfInner;
var metadataMgr;
config = {
readOnly: readOnly,
patchTransformer: ChainPad.NaiveJSONTransformer,
// cryptpad debug logging (default is 1)
// logLevel: 0,
validateContent: function (content) {
try {
JSON.parse(content);
return true;
} catch (e) {
console.log("Failed to parse, rejecting patch");
return false;
}
}
};
var addColorToPalette = function (color, i) {
if (readOnly) { return; }
if (framework.isReadOnly()) { return; }
var $color = $('<span>', {
'class': 'cp-app-whiteboard-palette-color',
})
@ -271,7 +191,7 @@ define([
})
.on('dblclick', function (e) {
e.preventDefault();
if (!APP.editable) { return; }
if (framework.isLocked()) { return; }
pickColor(Colors.rgb2hex($color.css('background-color')), function (c) {
$color.css({
'background-color': c,
@ -287,7 +207,7 @@ define([
var first = true;
var updatePalette = function (newPalette) {
if (first || stringify(palette) !== stringify(newPalette)) {
if (first || Sortify(palette) !== Sortify(newPalette)) {
palette = newPalette;
$colors.html('<div class="hidden">&nbsp;</div>');
palette.forEach(addColorToPalette);
@ -299,7 +219,7 @@ define([
var metadata = JSON.parse(JSON.stringify(metadataMgr.getMetadata()));
metadata.palette = newPalette;
metadataMgr.updateMetadata(metadata);
config.onLocal();
framework.localChange();
};
var makeColorButton = function ($container) {
@ -312,7 +232,7 @@ define([
return;
}
var $color = APP.$color = common.createButton(null, true, {
var $color = APP.$color = framework._.sfCommon.createButton(null, true, {
icon: 'fa-square',
title: Messages.canvas_chooseColor,
name: 'color',
@ -331,33 +251,73 @@ define([
return $color;
};
var stringifyInner = function (textValue) {
var obj = {
content: textValue,
metadata: metadataMgr.getMetadataLazy()
updateLocalPalette(palette);
metadataMgr.onChange(function () {
var md = metadataMgr.getMetadata();
if (md.palette) {
updateLocalPalette(md.palette);
}
});
return {
palette: palette,
makeColorButton: makeColorButton,
updateLocalPalette: updateLocalPalette,
};
// stringify the json and send it into chainpad
return stringify(obj);
};
var onLocal = config.onLocal = function () {
if (initializing) { return; }
if (readOnly) { return; }
var mkHelpMenu = function (framework) {
var $appContainer = $('#cp-app-whiteboard-container');
var helpMenu = framework._.sfCommon.createHelpMenu(['whiteboard']);
$appContainer.prepend(helpMenu.menu);
framework._.toolbar.$drawer.append(helpMenu.button);
};
var content = stringifyInner(canvas.toDatalessJSON());
// Start of the main loop
var andThen2 = function (framework) {
var canvas = APP.canvas = new Fabric.Canvas('cp-app-whiteboard-canvas', {
containerClass: 'cp-app-whiteboard-canvas-container'
});
var $canvas = $('canvas');
var $canvasContainer = $('canvas').parents('.cp-app-whiteboard-canvas-container');
var $controls = $('#cp-app-whiteboard-controls');
var metadataMgr = framework._.cpNfInner.metadataMgr;
try {
APP.realtime.contentUpdate(content);
} catch (e) {
APP.unrecoverable = true;
setEditable(false);
APP.toolbar.errorState(true, e.message);
var msg = Messages.chainpadError;
UI.errorLoadingScreen(msg, true, true);
console.error(e);
var setEditable = function (bool) {
if (bool) { $controls.css('display', 'flex'); }
else { $controls.hide(); }
canvas.isDrawingMode = bool ? APP.draw : false;
if (!bool) {
canvas.deactivateAll();
canvas.renderAll();
}
canvas.forEachObject(function (object) {
object.selectable = bool;
});
$canvasContainer.find('canvas').css('border-color', bool? 'black': 'red');
};
mkHelpMenu(framework);
var controls = mkControls(framework, canvas);
// ---------------------------------------------
// Whiteboard custom buttons
// ---------------------------------------------
var $rightside = framework._.toolbar.$rightside;
APP.FM = framework._.sfCommon.createFileManager({});
APP.upload = function (title) {
var canvas = $canvas[0];
APP.canvas.deactivateAll().renderAll();
canvas.toBlob(function (blob) {
blob.name = title;
APP.FM.handleFile(blob);
});
};
var addImageToCanvas = function (img) {
var w = img.width;
var h = img.height;
@ -370,107 +330,18 @@ define([
}
var cImg = new Fabric.Image(img, { left:0, top:0, angle:0, });
APP.canvas.add(cImg);
onLocal();
framework.localChange();
};
var initThumbnails = function () {
var oldThumbnailState;
var privateDat = metadataMgr.getPrivateData();
if (!privateDat.thumbnails) { return; }
var hash = privateDat.availableHashes.editHash ||
privateDat.availableHashes.viewHash;
var href = privateDat.pathname + '#' + hash;
var mkThumbnail = function () {
if (!hash) { return; }
if (initializing) { return; }
if (!APP.realtime) { return; }
var content = APP.realtime.getUserDoc();
if (content === oldThumbnailState) { return; }
var D = Thumb.getResizedDimensions($canvas[0], 'pad');
Thumb.fromCanvas($canvas[0], D, function (err, b64) {
oldThumbnailState = content;
Thumb.setPadThumbnail(common, href, privateDat.channel, b64);
});
};
window.setInterval(mkThumbnail, Thumb.UPDATE_INTERVAL);
window.setTimeout(mkThumbnail, Thumb.UPDATE_FIRST);
};
config.onInit = function (info) {
updateLocalPalette(palette);
readOnly = metadataMgr.getPrivateData().readOnly;
Title = common.createTitle({});
var configTb = {
displayed: [
'userlist',
'title',
'useradmin',
'spinner',
'newpad',
'share',
'limit',
'unpinnedWarning'
],
title: Title.getTitleConfig(),
metadataMgr: metadataMgr,
readOnly: readOnly,
realtime: info.realtime,
sfCommon: common,
$container: $bar,
$contentContainer: $('#cp-app-whiteboard-canvas-area')
};
toolbar = APP.toolbar = Toolbar.create(configTb);
Title.setToolbar(toolbar);
var $rightside = toolbar.$rightside;
var $drawer = toolbar.$drawer;
/* save as template */
if (!metadataMgr.getPrivateData().isTemplate) {
var templateObj = {
rt: info.realtime,
getTitle: function () { return metadataMgr.getMetadata().title; }
};
var $templateButton = common.createButton('template', true, templateObj);
$rightside.append($templateButton);
}
/* add an export button */
var $export = common.createButton('export', true, {}, saveImage);
$drawer.append($export);
if (common.isLoggedIn()) {
common.createButton('savetodrive', true, {}, function () {})
.click(function () {
UI.prompt(Messages.exportPrompt, document.title + '.png',
function (name) {
// Export to drive as PNG
framework._.sfCommon.createButton('savetodrive', true, {}).click(function () {
var defaultName = framework._.title.getTitle();
UI.prompt(Messages.exportPrompt, defaultName + '.png', function (name) {
if (name === null || !name.trim()) { return; }
APP.upload(name);
});
}).appendTo($rightside);
common.createButton('hashtag', true).appendTo($rightside);
}
var $forget = common.createButton('forget', true, {}, function (err) {
if (err) { return; }
setEditable(false);
});
$rightside.append($forget);
var $properties = common.createButton('properties', true);
toolbar.$drawer.append($properties);
var $appContainer = $('#cp-app-whiteboard-container');
var helpMenu = common.createHelpMenu(['whiteboard']);
$appContainer.prepend(helpMenu.menu);
toolbar.$drawer.append(helpMenu.button);
if (!readOnly) {
makeColorButton($rightside);
// Embed image
var onUpload = function (e) {
var file = e.target.files[0];
@ -484,7 +355,7 @@ define([
};
reader.readAsDataURL(file);
};
common.createButton('', true, {
framework._.sfCommon.createButton('', true, {
title: Messages.canvas_imageEmbed,
icon: 'fa-file-image-o',
name: 'embedImage'
@ -492,12 +363,12 @@ define([
$('<input>', {type:'file'}).on('change', onUpload).click();
}).appendTo($rightside);
if (common.isLoggedIn()) {
if (framework._.sfCommon.isLoggedIn()) {
var fileDialogCfg = {
onSelect: function (data) {
if (data.type === 'file') {
var mt = '<media-tag src="' + data.src + '" data-crypto-key="cryptpad:' + data.key + '"></media-tag>';
common.displayMediatagImage($(mt), function (err, $image) {
framework._.sfCommon.displayMediatagImage($(mt), function (err, $image) {
Util.blobURLToImage($image.attr('src'), function (imgSrc) {
var img = new Image();
img.onload = function () { addImageToCanvas(img); };
@ -508,8 +379,8 @@ define([
}
}
};
common.initFilePicker(fileDialogCfg);
APP.$mediaTagButton = common.createButton('mediatag', true).click(function () {
framework._.sfCommon.initFilePicker(fileDialogCfg);
framework._.sfCommon.createButton('mediatag', true).click(function () {
var pickerCfg = {
types: ['file'],
where: ['root'],
@ -517,166 +388,101 @@ define([
fileType: ['image/']
}
};
common.openFilePicker(pickerCfg);
framework._.sfCommon.openFilePicker(pickerCfg);
}).appendTo($rightside);
}
} else {
$colors.hide();
$controls.hide();
}
metadataMgr.onChange(function () {
var md = metadataMgr.getMetadata();
if (md.palette) {
updateLocalPalette(md.palette);
}
});
};
config.onReady = function (info) {
if (APP.realtime !== info.realtime) {
APP.realtime = info.realtime;
}
var userDoc = APP.realtime.getUserDoc();
var isNew = false;
var newDoc = '';
if (userDoc === "" || userDoc === "{}") { isNew = true; }
if (userDoc !== "") {
var hjson = JSON.parse(userDoc);
if (hjson && hjson.metadata) {
metadataMgr.updateMetadata(hjson.metadata);
}
if (typeof (hjson) !== 'object' || Array.isArray(hjson) ||
(hjson.metadata && typeof(hjson.metadata.type) !== 'undefined' &&
hjson.metadata.type !== 'whiteboard')) {
var errorText = Messages.typeError;
UI.errorLoadingScreen(errorText);
throw new Error(errorText);
}
newDoc = hjson.content;
if (framework.isReadOnly()) {
setEditable(false);
} else {
Title.updateTitle(Title.defaultTitle);
controls.makeColorButton($rightside);
}
nThen(function (waitFor) {
if (newDoc) {
canvas.loadFromJSON(newDoc, waitFor(function () {
console.log('loaded');
canvas.renderAll();
}));
}
}).nThen(function () {
setEditable(!readOnly);
initializing = false;
config.onLocal();
UI.removeLoadingScreen();
$('#cp-app-whiteboard-clear').on('click', function () {
canvas.clear();
framework.localChange();
});
initThumbnails();
// ---------------------------------------------
// End custom
// ---------------------------------------------
if (readOnly) { return; }
framework.onEditableChange(function () {
var locked = framework.isLocked() || framework.isReadOnly();
setEditable(!locked);
});
var privateDat = metadataMgr.getPrivateData();
var skipTemp = Util.find(privateDat,
['settings', 'general', 'creation', 'noTemplate']);
var skipCreation = Util.find(privateDat, ['settings', 'general', 'creation', 'skip']);
if (isNew && (!AppConfig.displayCreationScreen || (!skipTemp && skipCreation))) {
common.openTemplatePicker();
}
framework.setFileExporter('png', function (cb) {
$canvas[0].toBlob(function (blob) {
cb(blob);
});
});
framework.setNormalizer(function (c) {
return {
content: c.content
};
});
config.onRemote = function () {
if (initializing) { return; }
var userDoc = APP.realtime.getUserDoc();
var json = JSON.parse(userDoc);
var remoteDoc = json.content;
canvas.loadFromJSON(remoteDoc, function () {
framework.onContentUpdate(function (newContent, waitFor) {
var content = newContent.content;
canvas.loadFromJSON(content, waitFor(function () {
canvas.renderAll();
if (json.metadata) {
metadataMgr.updateMetadata(json.metadata);
}
}));
});
framework.setContentGetter(function () {
var content = canvas.toDatalessJSON();
if (content !== remoteDoc) { common.notify(); }
if (readOnly) { setEditable(false); }
};
config.onAbort = function () {
if (APP.unrecoverable) { return; }
// inform of network disconnect
setEditable(false);
toolbar.failed();
UI.alert(Messages.common_connectionLost, undefined, true);
};
config.onConnectionChange = function (info) {
if (APP.unrecoverable) { return; }
setEditable(info.state);
if (info.state) {
initializing = true;
//UI.findOKButton().click();
} else {
//UI.alert(Messages.common_connectionLost, undefined, true);
}
};
config.onError = function (err) {
common.onServerError(err, toolbar, function () {
APP.unrecoverable = true;
setEditable(false);
});
return {
content: content
};
cpNfInner = common.startRealtime(config);
metadataMgr = cpNfInner.metadataMgr;
cpNfInner.onInfiniteSpinner(function () {
if (APP.unrecoverable) { return; }
setEditable(false);
UI.confirm(Messages.realtime_unrecoverableError, function (yes) {
if (!yes) { return; }
common.gotoURL();
});
});
canvas.on('mouse:up', onLocal);
$('#cp-app-whiteboard-clear').on('click', function () {
canvas.clear();
onLocal();
framework.onReady(function () {
var oldThumbnailState;
var privateDat = metadataMgr.getPrivateData();
if (!privateDat.thumbnails) { return; }
var hash = privateDat.availableHashes.editHash ||
privateDat.availableHashes.viewHash;
var href = privateDat.pathname + '#' + hash;
var mkThumbnail = function () {
if (!hash) { return; }
if (framework.getState() !== 'READY') { return; }
if (!framework._.cpNfInner.chainpad) { return; }
var content = framework._.cpNfInner.chainpad.getUserDoc();
if (content === oldThumbnailState) { return; }
var D = Thumb.getResizedDimensions($canvas[0], 'pad');
Thumb.fromCanvas($canvas[0], D, function (err, b64) {
oldThumbnailState = content;
Thumb.setPadThumbnail(framework._.sfCommon, href, privateDat.channel, b64);
});
$('#save').on('click', function () {
saveImage();
};
window.setInterval(mkThumbnail, Thumb.UPDATE_INTERVAL);
window.setTimeout(mkThumbnail, Thumb.UPDATE_FIRST);
});
common.onLogout(function () { setEditable(false); });
canvas.on('mouse:up', framework.localChange);
framework.start();
};
var main = function () {
var common;
// var framework;
nThen(function (waitFor) {
$(waitFor(function () {
UI.addLoadingScreen();
var $div = $('<div>').append(Pages['/whiteboard/']());
$('body').append($div.html());
}));
SFCommon.create(waitFor(function (c) { APP.common = common = c; }));
}).nThen(function (waitFor) {
common.getSframeChannel().onReady(waitFor());
}).nThen(function (waitFor) {
common.handleNewFile(waitFor);
}).nThen(function (/*waitFor*/) {
andThen(common);
// Framework initialization
Framework.create({
patchTransformer: ChainPad.NaiveJSONTransformer,
toolbarContainer: '#cp-toolbar',
contentContainer: '#cp-app-whiteboard-canvas-area',
}, waitFor(function (framework) {
andThen2(framework);
}));
});
};
main();

Loading…
Cancel
Save