Merge branch 'msg' into staging

pull/1/head
yflory 7 years ago
commit 159d72f33b

@ -489,6 +489,10 @@ define([
return loadingScreen(); return loadingScreen();
}; };
Pages['/friends/'] = Pages['/friends/index.html'] = function () {
return loadingScreen();
};
Pages['/pad/'] = Pages['/pad/index.html'] = function () { Pages['/pad/'] = Pages['/pad/index.html'] = function () {
return loadingScreen(); return loadingScreen();
}; };
@ -501,5 +505,9 @@ define([
return loadingScreen(); return loadingScreen();
}; };
Pages['/invite/'] = Pages['/invite/index.html'] = function () {
return loadingScreen();
};
return Pages; return Pages;
}); });

@ -21,6 +21,7 @@
.slideColor { color: @toolbar-slide-bg; } .slideColor { color: @toolbar-slide-bg; }
.pollColor { color: @toolbar-poll-bg; } .pollColor { color: @toolbar-poll-bg; }
.fileColor { color: @toolbar-file-bg; } .fileColor { color: @toolbar-file-bg; }
.friendsColor { color: @toolbar-friends-bg; }
.whiteboardColor { color: @toolbar-whiteboard-bg; } .whiteboardColor { color: @toolbar-whiteboard-bg; }
.driveColor { color: @toolbar-drive-bg; } .driveColor { color: @toolbar-drive-bg; }
.defaultColor { color: @toolbar-default-bg; } .defaultColor { color: @toolbar-default-bg; }
@ -255,6 +256,11 @@ body {
@color: @toolbar-file-color; @color: @toolbar-file-color;
.addToolbarColors(@color, @bgcolor); .addToolbarColors(@color, @bgcolor);
} }
&.app-friends {
@bgcolor: @toolbar-friends-bg;
@color: @toolbar-friends-color;
.addToolbarColors(@color, @bgcolor);
}
} }

@ -97,6 +97,8 @@
@toolbar-drive-color: #fff; @toolbar-drive-color: #fff;
@toolbar-file-bg: #cd2532; @toolbar-file-bg: #cd2532;
@toolbar-file-color: #fff; @toolbar-file-color: #fff;
@toolbar-friends-bg: #607B8D;
@toolbar-friends-color: #fff;
@toolbar-default-bg: #ddd; @toolbar-default-bg: #ddd;
@toolbar-default-color: #000; @toolbar-default-color: #000;

@ -12,7 +12,7 @@ $(function () {
var Messages = Cryptpad.Messages; var Messages = Cryptpad.Messages;
var $body = $('body'); var $body = $('body');
var isMainApp = function () { var isMainApp = function () {
return /^\/(pad|code|slide|poll|whiteboard|file|media|drive)\/$/.test(location.pathname); return /^\/(pad|code|slide|poll|whiteboard|file|media|friends|drive)\/$/.test(location.pathname);
}; };
var rightLink = function (ref, loc, txt) { var rightLink = function (ref, loc, txt) {
@ -148,6 +148,9 @@ $(function () {
} else if (/file/.test(pathname)) { } else if (/file/.test(pathname)) {
$('body').append(h('body', Pages[pathname]()).innerHTML); $('body').append(h('body', Pages[pathname]()).innerHTML);
require([ '/file/main.js' ], ready); require([ '/file/main.js' ], ready);
} else if (/friends/.test(pathname)) {
$('body').append(h('body', Pages[pathname]()).innerHTML);
require([ '/friends/main.js' ], ready);
} else if (/pad/.test(pathname)) { } else if (/pad/.test(pathname)) {
$('body').append(h('body', Pages[pathname]()).innerHTML); $('body').append(h('body', Pages[pathname]()).innerHTML);
require([ '/pad/main.js' ], ready); require([ '/pad/main.js' ], ready);
@ -183,6 +186,8 @@ $(function () {
} else if (/^\/($|^\/index\.html$)/.test(pathname)) { } else if (/^\/($|^\/index\.html$)/.test(pathname)) {
// TODO use different top bar // TODO use different top bar
require([ '/customize/main.js', ], function () {}); require([ '/customize/main.js', ], function () {});
} else if (/invite/.test(pathname)) {
require([ '/invite/main.js'], function () {});
} else { } else {
require([ '/customize/main.js', ], function () {}); require([ '/customize/main.js', ], function () {});
} }

@ -256,6 +256,11 @@ define(function () {
out.profile_description = "Description"; out.profile_description = "Description";
out.profile_fieldSaved = 'New value saved: {0}'; out.profile_fieldSaved = 'New value saved: {0}';
out.profile_inviteButton = "Connect";
out.profile_inviteButtonTitle ='Create a link that will invite this user to connect with you.';
out.profile_inviteExplanation = "Clicking <strong>OK</strong> will create a link to a secure messaging session that <em>only {0} will be able to redeem.</em><br><br>The link will be copied to your clipboard and can be shared publicly.";
out.profile_viewMyProfile = "View my profile";
// File manager // File manager
out.fm_rootName = "Documents"; out.fm_rootName = "Documents";

@ -54,7 +54,7 @@ Version 1
if (!hash) { return; } if (!hash) { return; }
var parsed = {}; var parsed = {};
var hashArr = fixDuplicateSlashes(hash).split('/'); var hashArr = fixDuplicateSlashes(hash).split('/');
if (['media', 'file', 'user'].indexOf(type) === -1) { if (['media', 'file', 'user', 'invite'].indexOf(type) === -1) {
parsed.type = 'pad'; parsed.type = 'pad';
if (hash.slice(0,1) !== '/' && hash.length >= 56) { if (hash.slice(0,1) !== '/' && hash.length >= 56) {
// Old hash // Old hash
@ -93,6 +93,16 @@ Version 1
} }
return parsed; return parsed;
} }
if (['invite'].indexOf(type) !== -1) {
parsed.type = 'invite';
if (hashArr[1] && hashArr[1] === '1') {
parsed.version = 1;
parsed.channel = hashArr[2];
parsed.pubkey = hashArr[3].replace(/-/g, '/');
return parsed;
}
return parsed;
}
return; return;
}; };
var parsePadUrl = Hash.parsePadUrl = function (href) { var parsePadUrl = Hash.parsePadUrl = function (href) {
@ -320,5 +330,11 @@ Version 1
return hash; return hash;
}; };
Hash.createInviteUrl = function (curvePublic, channel) {
channel = channel || Hash.createChannelId();
return window.location.origin + '/invite/#/1/' + channel +
'/' + curvePublic.replace(/\//g, '-') + '/';
};
return Hash; return Hash;
}); });

@ -0,0 +1,444 @@
define([
'jquery',
'/bower_components/chainpad-crypto/crypto.js',
'/common/curve.js'
], function ($, Crypto, Curve) {
var Msg = {};
var Types = {
message: 'MSG'
};
// TODO: pin the chat channel!
// TODO: new Types
// - send a rename message to the chat
// - petnames
// - close a chat / remove a friend
// TODO
// - mute a channel (hide notifications or don't open it?)
//
var pending = {};
var createData = Msg.createData = function (common, hash) {
var proxy = common.getProxy();
return {
channel: hash || common.createChannelId(),
displayName: proxy[common.displayNameKey],
profile: proxy.profile.view,
edPublic: proxy.edPublic,
curvePublic: proxy.curvePublic,
avatar: proxy.profile.avatar
};
};
var getFriend = function (common, pubkey) {
var proxy = common.getProxy();
if (pubkey === proxy.edPublic) {
var data = createData(common);
delete data.channel;
return data;
}
return proxy.friends ? proxy.friends[pubkey] : undefined;
};
var getFriendList = Msg.getFriendList = function (common) {
var proxy = common.getProxy();
return proxy.friends || {};
};
Msg.getFriendChannelsList = function (common) {
var friends = getFriendList(common);
var list = [];
Object.keys(friends).forEach(function (key) {
list.push(friends[key].channel);
});
return list;
};
// Messaging tools
var avatars = {};
Msg.getFriendListUI = function (common, open) {
var proxy = common.getProxy();
var $block = $('<div>');
var friends = proxy.friends || {};
Object.keys(friends).forEach(function (f) {
var data = friends[f];
var $friend = $('<div>', {'class': 'friend'}).appendTo($block);
$friend.data('key', f);
var $rightCol = $('<span>', {'class': 'right-col'});
$('<span>', {'class': 'name'}).text(data.displayName).appendTo($rightCol);
$friend.dblclick(function () {
window.open('/profile/#' + data.profile);
});
$friend.click(function () {
open(data.edPublic);
});
if (data.avatar && avatars[data.avatar]) {
$friend.append(avatars[data.avatar]);
$friend.append($rightCol);
} else {
common.displayAvatar($friend, data.avatar, data.displayName, function ($img) {
if (data.avatar && $img) {
avatars[data.avatar] = $img[0].outerHTML;
}
$friend.append($rightCol);
});
}
});
return $block;
};
Msg.createOwnedChannel = function (common, channelId, validateKey, owners, cb) {
var network = common.getNetwork();
network.join(channelId).then(function (wc) {
var cfg = {
validateKey: validateKey,
owners: owners
};
var msg = ['GET_HISTORY', wc.id, cfg];
network.sendto(network.historyKeeper, JSON.stringify(msg)).then(cb, function (err) {
throw new Error(err);
});
}, function (err) {
throw new Error(err);
});
};
var channels = Msg.channels = window.channels = {};
var pushMsg = function (channel, cryptMsg) {
var msg = channel.encryptor.decrypt(cryptMsg);
var parsedMsg = JSON.parse(msg);
if (parsedMsg[0] !== Types.message) { return; }
parsedMsg.shift();
channel.messages.push([cryptMsg.slice(0,64), parsedMsg]);
};
var onDirectMessage = function (msg, sender) {
if (sender !== Msg.hk) { return; }
var parsed = JSON.parse(msg);
if ((parsed.validateKey || parsed.owners) && parsed.channel) {
return;
}
if (parsed.state && parsed.state === 1 && parsed.channel) {
if (channels[parsed.channel]) {
// parsed.channel is Ready
// TODO: call a function that shows that the channel is ready? (remove a spinner, ...)
// channel[parsed.channel].ready();
channels[parsed.channel].ready = true;
}
return;
}
var chan = parsed[3];
if (!chan || !channels[chan]) { return; }
pushMsg(channels[chan], parsed[4]);
};
var onMessage = function (msg, sender, chan) {
if (!channels[chan.id]) { return; }
pushMsg(channels[chan.id], msg);
channels[chan.id].notify();
channels[chan.id].refresh();
};
var createChatBox = function (common, $container, edPublic) {
var data = getFriend(common, edPublic);
var proxy = common.getProxy();
var $header = $('<div>', {'class': 'header'}).appendTo($container);
$('<div>', {'class': 'messages'}).appendTo($container);
var $inputBlock = $('<div>', {'class': 'input'}).appendTo($container);
// Input
var channel = channels[data.channel];
var $input = $('<input>', {type: 'text'}).appendTo($inputBlock);
var send = function () {
if (!$input.val()) { return; }
var msg = [Types.message, proxy.edPublic, +new Date(), $input.val()];
var msgStr = JSON.stringify(msg);
var cryptMsg = channel.encryptor.encrypt(msgStr);
channel.wc.bcast(cryptMsg).then(function () {
$input.val('');
pushMsg(channel, cryptMsg);
channel.refresh();
}, function (err) {
console.error(err);
});
};
$('<button>').text('TODO: Send').appendTo($inputBlock).click(send); // XXX
$input.on('keypress', function (e) {
if (e.which === 13) { send(); }
});
// Header
var $rightCol = $('<span>', {'class': 'right-col'});
$('<span>', {'class': 'name'}).text(data.displayName).appendTo($rightCol);
if (data.avatar && avatars[data.avatar]) {
$header.append(avatars[data.avatar]);
$header.append($rightCol);
} else {
common.displayAvatar($header, data.avatar, data.displayName, function ($img) {
if (data.avatar && $img) {
avatars[data.avatar] = $img[0].outerHTML;
}
$header.append($rightCol);
});
}
};
Msg.init = function (common, $listContainer, $msgContainer) {
var network = common.getNetwork();
var proxy = common.getProxy();
Msg.hk = network.historyKeeper;
var friends = getFriendList(common);
network.on('message', onDirectMessage);
// Refresh the active channel
var refresh = function (edPublic) {
if (Msg.active !== edPublic) { return; }
var data = friends[edPublic];
var channel = channels[data.channel];
if (!channel) { return; }
var $chat = $msgContainer.find('.chat').filter(function (idx, el) {
return $(el).data('key') === edPublic;
});
if (!$chat.length) { return; }
// Add new messages
var messages = channel.messages;
var $messages = $chat.find('.messages');
var $msg, msg, date, name;
var last = channel.lastDisplayed || -1;
for (var i = last + 1; i<messages.length; i++) {
msg = messages[i][1]; // 0 is the hash, 1 the array
$msg = $('<div>', {'class': 'message'}).appendTo($messages);
// date
date = msg[1] ? new Date(msg[1]).toLocaleString() : '?';
//$('<div>', {'class':'date'}).text(date).appendTo($msg);
$msg.attr('title', date);
// name
if (msg[0] !== channel.lastSender) {
name = getFriend(common, msg[0]).displayName;
$('<div>', {'class':'sender'}).text(name).appendTo($msg);
}
channel.lastSender = msg[0];
// content
$('<div>', {'class':'content'}).text(msg[2]).appendTo($msg);
}
$messages.scrollTop($messages[0].scrollHeight);
channel.lastDisplayed = i-1;
channel.unnotify();
if (messages.length > 10) {
var lastKnownMsg = messages[messages.length - 11];
data.lastKnownHash = lastKnownMsg[0];
}
};
// Display a new channel
var display = function (edPublic) {
var isNew = false;
var $chat = $msgContainer.find('.chat').filter(function (idx, el) {
return $(el).data('key') === edPublic;
});
if (!$chat.length) {
$chat = $('<div>', {'class':'chat'}).data('key', edPublic).appendTo($msgContainer);
createChatBox(common, $chat, edPublic);
isNew = true;
}
// Show the correct div
$msgContainer.find('.chat').hide();
$chat.show();
Msg.active = edPublic;
refresh(edPublic);
};
// Display friend list
common.getFriendListUI(common, display).appendTo($listContainer);
// Notify on new messages
var notify = function (edPublic) {
if (Msg.active === edPublic) { return; }
var $friend = $listContainer.find('.friend').filter(function (idx, el) {
return $(el).data('key') === edPublic;
});
$friend.addClass('notify');
};
var unnotify = function (edPublic) {
var $friend = $listContainer.find('.friend').filter(function (idx, el) {
return $(el).data('key') === edPublic;
});
$friend.removeClass('notify');
};
// Open the channels
Object.keys(friends).forEach(function (f) {
var data = friends[f];
var keys = Curve.deriveKeys(data.curvePublic, proxy.curvePrivate);
var encryptor = Curve.createEncryptor(keys);
channels[data.channel] = {
keys: keys,
encryptor: encryptor,
messages: [],
refresh: function () { refresh(data.edPublic); },
notify: function () { notify(data.edPublic); },
unnotify: function () { unnotify(data.edPublic); }
};
network.join(data.channel).then(function (chan) {
channels[data.channel].wc = chan;
chan.on('message', function (msg, sender) {
onMessage(msg, sender, chan);
});
var cfg = {
validateKey: keys.validateKey,
owners: [proxy.edPublic, data.edPublic],
lastKnownHash: data.lastKnownHash
};
var msg = ['GET_HISTORY', chan.id, cfg];
network.sendto(network.historyKeeper, JSON.stringify(msg))
.then($.noop, function (err) {
throw new Error(err);
});
}, function (err) {
console.error(err);
});
});
};
// Invitation
// Remove should be called from the friend app at the moment
// The other user will know it from the private channel ("REMOVE_FRIEND" message?)
Msg.removeFromFriendList = function (common, edPublic, cb) {
var proxy = common.getProxy();
if (!proxy.friends) {
return;
}
var friends = proxy.friends;
delete friends[edPublic];
common.whenRealtimeSyncs(common.getRealtime(), cb);
};
var addToFriendList = Msg.addToFriendList = function (common, data, cb) {
var proxy = common.getProxy();
if (!proxy.friends) {
proxy.friends = {};
}
var friends = proxy.friends;
var pubKey = data.edPublic;
if (pubKey === proxy.edPublic) { return void cb("E_MYKEY"); }
if (friends[pubKey]) { return void cb("E_EXISTS"); }
friends[pubKey] = data;
common.whenRealtimeSyncs(common.getRealtime(), function () {
common.pinPads([data.channel], cb);
});
common.changeDisplayName(proxy[common.displayNameKey]);
};
Msg.addDirectMessageHandler = function (common) {
var network = common.getNetwork();
if (!network) { return void console.error('Network not ready'); }
network.on('message', function (message, sender) {
var msg;
if (sender === network.historyKeeper) { return; }
try {
var parsed = common.parsePadUrl(window.location.href);
if (!parsed.hashData) { return; }
var chan = parsed.hashData.channel;
// Decrypt
var keyStr = parsed.hashData.key;
var cryptor = Crypto.createEditCryptor(keyStr);
var key = cryptor.cryptKey;
var decryptMsg = Crypto.decrypt(message, key);
// Parse
msg = JSON.parse(decryptMsg);
if (msg[1] !== parsed.hashData.channel) { return; }
var msgData = msg[2];
var msgStr;
if (msg[0] === "FRIEND_REQ") {
msg = ["FRIEND_REQ_NOK", chan];
var existing = getFriend(common, msgData.edPublic);
if (existing) {
msg = ["FRIEND_REQ_OK", chan, createData(common, existing.channel)];
msgStr = Crypto.encrypt(JSON.stringify(msg), key);
network.sendto(sender, msgStr);
return;
}
common.confirm("Accept friend?", function (yes) { // XXX
if (yes) {
pending[sender] = msgData;
msg = ["FRIEND_REQ_OK", chan, createData(common, msgData.channel)];
}
msgStr = Crypto.encrypt(JSON.stringify(msg), key);
network.sendto(sender, msgStr);
});
return;
}
if (msg[0] === "FRIEND_REQ_OK") {
// XXX
addToFriendList(common, msgData, function (err) {
if (err) {
return void common.log('Error while adding that friend to the list');
}
common.log('Friend invite accepted.');
var msg = ["FRIEND_REQ_ACK", chan];
var msgStr = Crypto.encrypt(JSON.stringify(msg), key);
network.sendto(sender, msgStr);
});
return;
}
if (msg[0] === "FRIEND_REQ_NOK") {
// XXX
common.log('Friend invite rejected');
return;
}
if (msg[0] === "FRIEND_REQ_ACK") {
// XXX
var data = pending[sender];
if (!data) { return; }
addToFriendList(common, data, function (err) {
if (err) {
return void common.log('Error while adding that friend to the list');
}
common.log('Friend added to the list.');
});
return;
}
// TODO: timeout ACK: warn the user
} catch (e) {
console.error("Cannot parse direct message", msg || message, "from", sender, e);
}
});
};
Msg.inviteFromUserlist = function (common, netfluxId) {
var network = common.getNetwork();
var parsed = common.parsePadUrl(window.location.href);
if (!parsed.hashData) { return; }
// Message
var chan = parsed.hashData.channel;
var myData = createData(common);
var msg = ["FRIEND_REQ", chan, myData];
// Encryption
var keyStr = parsed.hashData.key;
var cryptor = Crypto.createEditCryptor(keyStr);
var key = cryptor.cryptKey;
var msgStr = Crypto.encrypt(JSON.stringify(msg), key);
// Send encrypted message
network.sendto(netfluxId, msgStr);
};
return Msg;
});

@ -52,7 +52,8 @@ define(function () {
name: exp.myUserName, name: exp.myUserName,
uid: Cryptpad.getUid(), uid: Cryptpad.getUid(),
avatar: Cryptpad.getAvatarUrl(), avatar: Cryptpad.getAvatarUrl(),
profile: Cryptpad.getProfileUrl() profile: Cryptpad.getProfileUrl(),
edPublic: Cryptpad.getProxy().edPublic
}; };
addToUserData(myData); addToUserData(myData);
Cryptpad.setAttribute('username', exp.myUserName, function (err) { Cryptpad.setAttribute('username', exp.myUserName, function (err) {
@ -81,7 +82,8 @@ define(function () {
name: "", name: "",
uid: Cryptpad.getUid(), uid: Cryptpad.getUid(),
avatar: Cryptpad.getAvatarUrl(), avatar: Cryptpad.getAvatarUrl(),
profile: Cryptpad.getProfileUrl() profile: Cryptpad.getProfileUrl(),
edPublic: Cryptpad.getProxy().edPublic
}; };
addToUserData(myData); addToUserData(myData);
onLocal(); onLocal();

@ -10,6 +10,7 @@ define([
'/common/common-userlist.js', '/common/common-userlist.js',
'/common/common-title.js', '/common/common-title.js',
'/common/common-metadata.js', '/common/common-metadata.js',
'/common/common-messaging.js',
'/common/common-codemirror.js', '/common/common-codemirror.js',
'/common/common-file.js', '/common/common-file.js',
'/file/file-crypto.js', '/file/file-crypto.js',
@ -19,7 +20,7 @@ define([
'/customize/application_config.js', '/customize/application_config.js',
'/common/media-tag.js', '/common/media-tag.js',
], function ($, Config, Messages, Store, Util, Hash, UI, History, UserList, Title, Metadata, ], function ($, Config, Messages, Store, Util, Hash, UI, History, UserList, Title, Metadata,
CodeMirror, Files, FileCrypto, Clipboard, Pinpad, AppConfig, MediaTag) { Messaging, CodeMirror, Files, FileCrypto, Clipboard, Pinpad, AppConfig, MediaTag) {
/* This file exposes functionality which is specific to Cryptpad, but not to /* This file exposes functionality which is specific to Cryptpad, but not to
any particular pad type. This includes functions for committing metadata any particular pad type. This includes functions for committing metadata
@ -107,6 +108,17 @@ define([
common.findWeaker = Hash.findWeaker; common.findWeaker = Hash.findWeaker;
common.findStronger = Hash.findStronger; common.findStronger = Hash.findStronger;
common.serializeHash = Hash.serializeHash; common.serializeHash = Hash.serializeHash;
common.createInviteUrl = Hash.createInviteUrl;
// Messaging
common.initMessaging = Messaging.init;
common.addDirectMessageHandler = Messaging.addDirectMessageHandler;
common.inviteFromUserlist = Messaging.inviteFromUserlist;
common.createOwnedChannel = Messaging.createOwnedChannel;
common.getFriendList = Messaging.getFriendList;
common.getFriendChannelsList = Messaging.getFriendChannelsList;
common.getFriendListUI = Messaging.getFriendListUI;
common.createData = Messaging.createData;
// Userlist // Userlist
common.createUserList = UserList.create; common.createUserList = UserList.create;
@ -148,6 +160,14 @@ define([
} }
return; return;
}; };
common.getUserlist = function () {
if (store) {
if (store.getProxy() && store.getProxy().info) {
return store.getProxy().info.userList;
}
}
return;
};
common.getProfileUrl = function () { common.getProfileUrl = function () {
if (store && store.getProfile()) { if (store && store.getProfile()) {
return store.getProfile().view; return store.getProfile().view;
@ -158,6 +178,12 @@ define([
return store.getProfile().avatar; return store.getProfile().avatar;
} }
}; };
common.getDisplayName = function () {
if (getProxy()) {
return getProxy()[common.displayNameKey] || '';
}
return '';
};
var randomToken = function () { var randomToken = function () {
return Math.random().toString(16).replace(/0./, ''); return Math.random().toString(16).replace(/0./, '');
@ -330,6 +356,21 @@ define([
typeof(proxy.edPublic) === 'string'; typeof(proxy.edPublic) === 'string';
}; };
common.hasCurveKeys = function (proxy) {
return typeof(proxy) === 'object' &&
typeof(proxy.curvePrivate) === 'string' &&
typeof(proxy.curvePublic) === 'string';
};
common.getPublicKeys = function (proxy) {
proxy = proxy || common.getProxy();
if (!proxy || !proxy.edPublic || !proxy.curvePublic) { return; }
return {
curve: proxy.curvePublic,
ed: proxy.edPublic,
};
};
common.isArray = $.isArray; common.isArray = $.isArray;
/* /*
@ -737,6 +778,11 @@ define([
if (avatarChan) { list.push(avatarChan); } if (avatarChan) { list.push(avatarChan); }
} }
if (getProxy().friends) {
var fList = common.getFriendChannelsList(common);
list = list.concat(fList);
}
list.push(common.base64ToHex(userChannel)); list.push(common.base64ToHex(userChannel));
list.sort(); list.sort();
@ -1211,7 +1257,6 @@ define([
return button; return button;
}; };
var emoji_patt = /([\uD800-\uDBFF][\uDC00-\uDFFF])/; var emoji_patt = /([\uD800-\uDBFF][\uDC00-\uDFFF])/;
var isEmoji = function (str) { var isEmoji = function (str) {
return emoji_patt.test(str); return emoji_patt.test(str);
@ -1731,6 +1776,8 @@ define([
Store.ready(function (err, storeObj) { Store.ready(function (err, storeObj) {
store = common.store = env.store = storeObj; store = common.store = env.store = storeObj;
common.addDirectMessageHandler(common);
var proxy = getProxy(); var proxy = getProxy();
var network = getNetwork(); var network = getNetwork();

@ -0,0 +1,51 @@
define([
'/common/curve.js',
'/bower_components/chainpad-listmap/chainpad-listmap.js',
], function (Curve, Listmap) {
var Edit = {};
Edit.create = function (config, cb) { //network, channel, theirs, mine, cb) {
var network = config.network;
var channel = config.channel;
var keys = config.keys;
try {
var encryptor = Curve.createEncryptor(keys);
var lm = Listmap.create({
network: network,
data: {},
channel: channel,
readOnly: false,
validateKey: keys.validateKey || undefined,
crypto: encryptor,
userName: 'lol',
logLevel: 1,
});
var done = function () {
// TODO make this abort and disconnect the session after the
// user has finished making changes to the object, and they
// have propagated.
};
lm.proxy
.on('create', function () {
console.log('created');
})
.on('ready', function () {
console.log('ready');
cb(lm, done);
})
.on('disconnect', function () {
console.log('disconnected');
})
.on('change', [], function (o, n, p) {
console.log(o, n, p);
});
} catch (e) {
console.error(e);
}
};
return Edit;
});

@ -0,0 +1,86 @@
define([
'/bower_components/tweetnacl/nacl-fast.min.js',
], function () {
var Nacl = window.nacl;
var Curve = {};
var concatenateUint8s = function (A) {
var len = 0;
var offset = 0;
A.forEach(function (uints) {
len += uints.length || 0;
});
var c = new Uint8Array(len);
A.forEach(function (x) {
c.set(x, offset);
offset += x.length;
});
return c;
};
var encodeBase64 = Nacl.util.encodeBase64;
var decodeBase64 = Nacl.util.decodeBase64;
var decodeUTF8 = Nacl.util.decodeUTF8;
var encodeUTF8 = Nacl.util.encodeUTF8;
Curve.encrypt = function (message, secret) {
var buffer = decodeUTF8(message);
var nonce = Nacl.randomBytes(24);
var box = Nacl.box.after(buffer, nonce, secret);
return encodeBase64(nonce) + '|' + encodeBase64(box);
};
Curve.decrypt = function (packed, secret) {
var unpacked = packed.split('|');
var nonce = decodeBase64(unpacked[0]);
var box = decodeBase64(unpacked[1]);
var message = Nacl.box.open.after(box, nonce, secret);
return encodeUTF8(message);
};
Curve.signAndEncrypt = function (msg, cryptKey, signKey) {
var packed = Curve.encrypt(msg, cryptKey);
return encodeBase64(Nacl.sign(decodeUTF8(packed), signKey));
};
Curve.openSigned = function (msg, cryptKey /*, validateKey STUBBED*/) {
var content = decodeBase64(msg).subarray(64);
return Curve.decrypt(encodeUTF8(content), cryptKey);
};
Curve.deriveKeys = function (theirs, mine) {
var pub = decodeBase64(theirs);
var secret = decodeBase64(mine);
var sharedSecret = Nacl.box.before(pub, secret);
var salt = decodeUTF8('CryptPad.signingKeyGenerationSalt');
// 64 uint8s
var hash = Nacl.hash(concatenateUint8s([salt, sharedSecret]));
var signKp = Nacl.sign.keyPair.fromSeed(hash.subarray(0, 32));
var cryptKey = hash.subarray(32, 64);
return {
cryptKey: encodeBase64(cryptKey),
signKey: encodeBase64(signKp.secretKey),
validateKey: encodeBase64(signKp.publicKey)
};
};
Curve.createEncryptor = function (keys) {
var cryptKey = decodeBase64(keys.cryptKey);
var signKey = decodeBase64(keys.signKey);
var validateKey = decodeBase64(keys.validateKey);
return {
encrypt: function (msg) {
return Curve.signAndEncrypt(msg, cryptKey, signKey);
},
decrypt: function (packed) {
return Curve.openSigned(packed, cryptKey, validateKey);
}
};
};
return Curve;
});

@ -206,7 +206,8 @@ define([
} }
// if the user is logged in, but does not have signing keys... // if the user is logged in, but does not have signing keys...
if (Cryptpad.isLoggedIn() && !Cryptpad.hasSigningKeys(proxy)) { if (Cryptpad.isLoggedIn() && (!Cryptpad.hasSigningKeys(proxy) ||
!Cryptpad.hasCurveKeys(proxy))) {
return void requestLogin(); return void requestLogin();
} }
@ -218,8 +219,11 @@ define([
// Trigger userlist update when the avatar has changed // Trigger userlist update when the avatar has changed
Cryptpad.changeDisplayName(proxy[Cryptpad.displayNameKey]); Cryptpad.changeDisplayName(proxy[Cryptpad.displayNameKey]);
}); });
proxy.on('change', ['friends'], function () {
// Trigger userlist update when the avatar has changed
Cryptpad.changeDisplayName(proxy[Cryptpad.displayNameKey]);
});
proxy.on('change', [tokenKey], function () { proxy.on('change', [tokenKey], function () {
console.log('wut');
var localToken = tryParsing(localStorage.getItem(tokenKey)); var localToken = tryParsing(localStorage.getItem(tokenKey));
if (localToken !== proxy[tokenKey]) { if (localToken !== proxy[tokenKey]) {
return void requestLogin(); return void requestLogin();

@ -22,7 +22,12 @@ define([
// 16 bytes for a deterministic channel key // 16 bytes for a deterministic channel key
var channelSeed = dispense(16); var channelSeed = dispense(16);
// 32 bytes for a curve key // 32 bytes for a curve key
opt.curveSeed = dispense(32); var curveSeed = dispense(32);
var curvePair = Nacl.box.keyPair.fromSecretKey(new Uint8Array(curveSeed));
opt.curvePrivate = Nacl.util.encodeBase64(curvePair.secretKey);
opt.curvePublic = Nacl.util.encodeBase64(curvePair.publicKey);
// 32 more for a signing key // 32 more for a signing key
var edSeed = opt.edSeed = dispense(32); var edSeed = opt.edSeed = dispense(32);
@ -109,6 +114,9 @@ define([
res.edPrivate = opt.edPrivate; res.edPrivate = opt.edPrivate;
res.edPublic = opt.edPublic; res.edPublic = opt.edPublic;
res.curvePrivate = opt.curvePrivate;
res.curvePublic = opt.curvePublic;
// they tried to just log in but there's no such user // they tried to just log in but there's no such user
if (!isRegister && isProxyEmpty(rt.proxy)) { if (!isRegister && isProxyEmpty(rt.proxy)) {
rt.network.disconnect(); // clean up after yourself rt.network.disconnect(); // clean up after yourself

@ -200,7 +200,8 @@ types of messages:
return sendMsg(ctx, data, cb); return sendMsg(ctx, data, cb);
}; };
network.on('message', function (msg) { network.on('message', function (msg, sender) {
if (sender !== network.historyKeeper) { return; }
onMsg(ctx, msg); onMsg(ctx, msg);
}); });
@ -304,7 +305,8 @@ types of messages:
} }
}; };
network.on('message', function (msg) { network.on('message', function (msg, sender) {
if (sender !== network.historyKeeper) { return; }
onAnonMsg(ctx, msg); onAnonMsg(ctx, msg);
}); });

@ -148,7 +148,8 @@ define([
//if (user !== userNetfluxId) { //if (user !== userNetfluxId) {
var data = userData[user] || {}; var data = userData[user] || {};
var userId = data.uid; var userId = data.uid;
if (!data.uid) { return; } if (!userId) { return; }
data.netfluxId = user;
if (uids.indexOf(userId) === -1) {// && (!myUid || userId !== myUid)) { if (uids.indexOf(userId) === -1) {// && (!myUid || userId !== myUid)) {
uids.push(userId); uids.push(userId);
list.push(data); list.push(data);
@ -206,8 +207,19 @@ define([
// Editors // Editors
editUsersNames.forEach(function (data) { editUsersNames.forEach(function (data) {
var name = data.name || Messages.anonymous; var name = data.name || Messages.anonymous;
var $name = $('<span>', {'class': 'name'}).text(name);
var $span = $('<span>', {'title': name, 'class': 'avatar'}); var $span = $('<span>', {'title': name, 'class': 'avatar'});
var $rightCol = $('<span>', {'class': 'right-col'});
$('<span>', {'class': 'name'}).text(name).appendTo($rightCol);
var proxy = Cryptpad.getProxy();
if (Cryptpad.isLoggedIn() && data.edPublic && data.edPublic !== proxy.edPublic) {
if (!proxy.friends || !proxy.friends[data.edPublic]) {
var $button = $('<button>', {'class': 'friend'}).appendTo($rightCol);
$button.text('Add friend').click(function (e) {
e.stopPropagation();
Cryptpad.inviteFromUserlist(Cryptpad, data.netfluxId);
});
}
}
if (data.profile) { if (data.profile) {
$span.addClass('clickable'); $span.addClass('clickable');
$span.click(function () { $span.click(function () {
@ -216,13 +228,13 @@ define([
} }
if (data.avatar && avatars[data.avatar]) { if (data.avatar && avatars[data.avatar]) {
$span.append(avatars[data.avatar]); $span.append(avatars[data.avatar]);
$span.append($name); $span.append($rightCol);
} else { } else {
Cryptpad.displayAvatar($span, data.avatar, name, function ($img) { Cryptpad.displayAvatar($span, data.avatar, name, function ($img) {
if (data.avatar && $img) { if (data.avatar && $img) {
avatars[data.avatar] = $img[0].outerHTML; avatars[data.avatar] = $img[0].outerHTML;
} }
$span.append($name); $span.append($rightCol);
}); });
} }
$span.data('uid', data.uid); $span.data('uid', data.uid);
@ -664,7 +676,7 @@ define([
$('<span>', {'class': 'bar3'}).appendTo($container); $('<span>', {'class': 'bar3'}).appendTo($container);
$('<span>', {'class': 'bar4'}).appendTo($container); $('<span>', {'class': 'bar4'}).appendTo($container);
$('<span>', {'class': 'disconnected fa fa-exclamation-circle'}).appendTo($a); $('<span>', {'class': 'disconnected fa fa-exclamation-circle'}).appendTo($a);
if (config.realtime) { if (config.network) {
checkLag(toolbar, config, $a); checkLag(toolbar, config, $a);
setInterval(function () { setInterval(function () {
if (!toolbar.connected) { return; } if (!toolbar.connected) { return; }

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html class="cp pad">
<head>
<title>CryptPad</title>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script async data-bootload="/customize/template.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.1.15"></script>
<style>
html, body {
margin: 0px;
padding: 0px;
}
#pad-iframe {
position:fixed;
top:0px;
left:0px;
bottom:0px;
right:0px;
width:100%;
height:100%;
border:none;
margin:0;
padding:0;
overflow:hidden;
}
</style>
</head>
<body>
<iframe id="pad-iframe"></iframe><script src="/common/noscriptfix.js"></script>

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<script src="/bower_components/jquery/dist/jquery.min.js"></script>
<script async data-bootload="/friends/inner.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.1.15"></script>
<style>.loading-hidden, .loading-hidden * {display: none !important;}</style>
</head>
<body class="loading-hidden">
<div id="toolbar" class="toolbar-container"></div>
<div id="app">
<div id="friendList"></div>
<div id="messaging"></div>
</div>
</body>
</html>

@ -0,0 +1,13 @@
define([
'jquery',
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
'less!/friends/main.less',
'less!/customize/src/less/toolbar.less',
], function ($) {
$('.loading-hidden').removeClass('loading-hidden');
// dirty hack to get rid the flash of the lock background
setTimeout(function () {
$('#app').addClass('ready');
}, 100);
});

@ -0,0 +1,51 @@
define([
'jquery',
'/bower_components/chainpad-crypto/crypto.js',
'/common/toolbar2.js',
'/common/cryptpad-common.js',
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/customize/src/less/cryptpad.less',
], function ($, Crypto, Toolbar, Cryptpad) {
//var Messages = Cryptpad.Messages;
var APP = window.APP = {
Cryptpad: Cryptpad
};
$(function () {
var andThen = function () {
Cryptpad.addLoadingScreen();
var ifrw = $('#pad-iframe')[0].contentWindow;
var $iframe = $('#pad-iframe').contents();
//var $appContainer = $iframe.find('#app');
var $list = $iframe.find('#friendList');
var $messages = $iframe.find('#messaging');
var $bar = $iframe.find('.toolbar-container');
var displayed = ['useradmin', 'newpad', 'limit', 'lag', 'spinner'];
var configTb = {
displayed: displayed,
ifrw: ifrw,
common: Cryptpad,
$container: $bar,
network: Cryptpad.getNetwork()
};
var toolbar = APP.toolbar = Toolbar.create(configTb);
toolbar.$rightside.html(''); // Remove the drawer if we don't use it to hide the toolbar
Cryptpad.initMessaging(Cryptpad, $list, $messages);
Cryptpad.removeLoadingScreen();
};
Cryptpad.ready(function () {
andThen();
Cryptpad.reportAppUsage();
});
});
});

@ -0,0 +1,178 @@
@import "/customize/src/less/variables.less";
@import "/customize/src/less/mixins.less";
@button-border: 2px;
@bg-color: @toolbar-friends-bg;
@color: @toolbar-friends-color;
html, body {
margin: 0px;
height: 100%;
}
#toolbar {
display: flex; // We need this to remove a 3px border at the bottom of the toolbar
}
body {
display: flex;
flex-flow: column;
}
#app {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
min-height: 0;
}
#app.ready {
//background: url('/customize/bg3.jpg') no-repeat center center;
background-size: cover;
background-position: center;
}
.cryptpad-toolbar {
padding: 0px;
display: inline-block;
}
@keyframes example {
0% {
background: rgba(0,0,0,0.1);
}
50% {
background: rgba(0,0,0,0.3);
}
100% {
background: rgba(0,0,0,0.1);
}
}
#friendList {
width: 350px;
height: 100%;
background-color: lighten(@bg-color, 10%);
.friend {
background: rgba(0,0,0,0.1);
padding: 5px;
margin: 10px;
cursor: pointer;
&:hover {
background-color: rgba(0,0,0,0.3);
}
&.notify {
animation: example 2s ease-in-out infinite;
}
}
}
#friendList .friend, #messaging .header {
overflow: hidden;
text-overflow: ellipsis;
font-size: 16px;
display: flex;
align-items: center;
cursor: pointer;
color: @color;
.default, media-tag {
display: inline-flex;
width: 50px;
height: 50px;
justify-content: center;
align-items: center;
margin-right: 5px;
border-radius: 10px / 6px;
overflow: hidden;
border: 1px solid black;
box-sizing: content-box;
}
.default {
.unselectable();
background: white;
color: black;
font-size: 40px;
}
.right-col {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: flex;
flex-flow: column;
.name {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.friend {
padding: 0;
}
}
media-tag {
min-height: 50px;
min-width: 50px;
max-height: 50px;
max-width: 50px;
img {
color: #000;
min-width: 100%;
min-height: 100%;
max-width: none;
max-height: none; // To override 'media-tag img' in slide.less
}
}
}
#messaging {
flex: 1;
height: 100%;
background-color: lighten(@bg-color, 20%);
min-width: 0;
.header {
background-color: lighten(@bg-color, 15%);
padding: 10px;
}
.chat {
height: 100%;
display: flex;
flex-flow: column;
.messages {
padding: 0 20px;
margin: 10px 0;
flex: 1;
overflow-x: auto;
.message {
& > div {
padding: 0 10px;
}
.content {
overflow: hidden;
word-wrap: break-word;
}
.date {
display: none;
font-style: italic;
}
.sender {
margin-top: 10px;
font-weight: bold;
}
}
}
}
.input {
background-color: lighten(@bg-color, 15%);
height: 50px;
display: flex;
align-items: center;
justify-content: center;
padding: 0 75px;
input {
flex: 1;
}
}
}

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html class="cp">
<!-- If this file is not called customize.dist/src/template.html, it is generated -->
<head>
<title data-localization="main_title">Cryptpad: Zero Knowledge, Collaborative Real Time Editing</title>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="icon" type="image/png" href="/customize/main-favicon.png" id="favicon"/>
<script async data-bootload="/customize/template.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.1.15"></script>
<link rel="stylesheet" href="/bower_components/codemirror/lib/codemirror.css">
<link rel="stylesheet" href="/bower_components/codemirror/addon/dialog/dialog.css">
<link rel="stylesheet" href="/bower_components/codemirror/addon/fold/foldgutter.css" />
</head>
<body class="html">
<noscript>
<p><strong>OOPS</strong> In order to do encryption in your browser, Javascript is really <strong>really</strong> required.</p>
<p><strong>OUPS</strong> Afin de pouvoir réaliser le chiffrement dans votre navigateur, Javascript est <strong>vraiment</strong> nécessaire.</p>
</noscript>
</html>

@ -0,0 +1,86 @@
define([
'jquery',
'/common/cryptpad-common.js',
'/bower_components/chainpad-listmap/chainpad-listmap.js',
'/common/curve.js',
'less!/invite/main.less',
], function ($, Cryptpad, Listmap, Curve) {
var APP = window.APP = {};
//var Messages = Cryptpad.Messages;
var onInit = function () {};
var onDisconnect = function () {};
var onChange = function () {};
var andThen = function () {
var hash = window.location.hash.slice(1);
var info = Cryptpad.parseTypeHash('invite', hash);
console.log(info);
if (!info.pubkey) {
Cryptpad.removeLoadingScreen();
Cryptpad.alert('invalid invite');
return;
}
var proxy = Cryptpad.getProxy();
var mySecret = proxy.curvePrivate;
var keys = Curve.deriveKeys(info.pubkey, mySecret);
var encryptor = Curve.createEncryptor(keys);
Cryptpad.removeLoadingScreen();
var listmapConfig = {
data: {},
network: Cryptpad.getNetwork(),
channel: info.channel,
readOnly: false,
validateKey: keys.validateKey,
crypto: encryptor,
userName: 'profile',
logLevel: 1,
};
var lm = APP.lm = Listmap.create(listmapConfig);
lm.proxy.on('create', onInit)
.on('ready', function () {
APP.initialized = true;
console.log(JSON.stringify(lm.proxy));
})
.on('disconnect', onDisconnect)
.on('change', [], onChange);
};
$(function () {
var $main = $('#mainBlock');
// Language selector
var $sel = $('#language-selector');
Cryptpad.createLanguageSelector(undefined, $sel);
$sel.find('button').addClass('btn').addClass('btn-secondary');
$sel.show();
// User admin menu
var $userMenu = $('#user-menu');
var userMenuCfg = {
$initBlock: $userMenu
};
var $userAdmin = Cryptpad.createUserAdminMenu(userMenuCfg);
$userAdmin.find('button').addClass('btn').addClass('btn-secondary');
$(window).click(function () {
$('.cryptpad-dropdown').hide();
});
// main block is hidden in case javascript is disabled
$main.removeClass('hidden');
APP.$container = $('#container');
Cryptpad.ready(function () {
Cryptpad.reportAppUsage();
andThen();
});
});
});

@ -0,0 +1,137 @@
.cp {
#mainBlock {
z-index: 1;
width: 1000px;
max-width: 90%;
margin: auto;
#container {
font-size: 25px;
width: 100%;
}
}
#header {
display: flex;
#rightside {
flex: 1;
display: flex;
flex-flow: column;
}
}
#avatar {
width: 300px;
//height: 350px;
margin: 10px;
margin-right: 20px;
text-align: center;
&> span {
display: inline-block;
text-align: center;
height: 300px;
width: 300px;
border: 1px solid black;
border-radius: 10px;
overflow: hidden;
position: relative;
.delete {
right: 0;
position: absolute;
opacity: 0.7;
&:hover {
opacity: 1;
}
}
}
img {
max-width: 100%;
max-height: 100%;
vertical-align: top;
}
media-tag {
height: 100%;
width: 100%;
display: inline-flex;
justify-content: center;
align-items: center;
img {
min-width: 100%;
min-height: 100%;
max-width: none;
max-height: none;
flex-shrink: 0;
}
}
button {
height: 40px;
margin: 5px;
}
}
#displayName, #link {
width: 100%;
height: 40px;
margin: 10px 0;
input {
width: 100%;
font-size: 20px;
box-sizing: border-box;
padding-right: 30px;
}
input:focus ~ .edit {
display: none;
}
.edit {
position: absolute;
margin-left: -25px;
margin-top: 8px;
}
.temp {
font-weight: 400;
font-family: sans-serif;
}
.displayName {
font-weight: bold;
font-size: 30px;
}
.displayName, .link {
line-height: 40px;
}
}
#description {
position: relative;
font-size: 16px;
border: 1px solid #DDD;
margin-bottom: 20px;
.rendered {
padding: 0 15px;
}
.ok, .spin {
position: absolute;
top: 2px;
right: 2px;
display: none;
z-index: 1000;
}
textarea {
width: 100%;
height: 300px;
}
.CodeMirror {
border: 1px solid #DDD;
font-family: monospace;
font-size: 16px;
line-height: initial;
pre {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
}
}
#createProfile {
height: 100%;
display: flex;
flex-flow: column;
align-items: center;
justify-content: center;
}
}

@ -91,6 +91,9 @@ define([
proxy.edPrivate = result.edPrivate; proxy.edPrivate = result.edPrivate;
proxy.edPublic = result.edPublic; proxy.edPublic = result.edPublic;
proxy.curvePrivate = result.curvePrivate;
proxy.curvePublic = result.curvePublic;
Cryptpad.feedback('LOGIN', true); Cryptpad.feedback('LOGIN', true);
Cryptpad.whenRealtimeSyncs(result.realtime, function() { Cryptpad.whenRealtimeSyncs(result.realtime, function() {
Cryptpad.login(result.userHash, result.userName, function () { Cryptpad.login(result.userHash, result.userName, function () {

@ -78,8 +78,11 @@ define([
var CREATE_ID = "createProfile"; var CREATE_ID = "createProfile";
var HEADER_ID = "header"; var HEADER_ID = "header";
var HEADER_RIGHT_ID = "rightside"; var HEADER_RIGHT_ID = "rightside";
var CREATE_INVITE_BUTTON = 'inviteButton';
var VIEW_PROFILE_BUTTON = 'viewProfileButton';
var createEditableInput = function ($block, name, ph, getValue, setValue, realtime) { var createEditableInput = function ($block, name, ph, getValue, setValue, realtime, fallbackValue) {
fallbackValue = fallbackValue || ''; // don't ever display 'null' or 'undefined'
var lastVal; var lastVal;
getValue(function (value) { getValue(function (value) {
lastVal = value; lastVal = value;
@ -104,7 +107,7 @@ define([
if (err) { return void console.error(err); } if (err) { return void console.error(err); }
Cryptpad.whenRealtimeSyncs(realtime, function () { Cryptpad.whenRealtimeSyncs(realtime, function () {
lastVal = newVal; lastVal = newVal;
Cryptpad.log(Messages._getKey('profile_fieldSaved', [newVal])); Cryptpad.log(Messages._getKey('profile_fieldSaved', [newVal || fallbackValue]));
editing = false; editing = false;
}); });
}); });
@ -150,8 +153,69 @@ define([
createEditableInput($block, DISPLAYNAME_ID, placeholder, 32, getValue, setValue, rt); createEditableInput($block, DISPLAYNAME_ID, placeholder, 32, getValue, setValue, rt);
}; };
*/ */
var addCreateInviteLinkButton = function ($container) {
var obj = APP.lm.proxy;
var proxy = Cryptpad.getProxy();
var userViewHash = Cryptpad.find(proxy, ['profile', 'view']);
if (!APP.readOnly || !obj.curveKey || userViewHash === window.location.hash.slice(1)) {
console.log("edit mode or missing curve key, or you're viewing your own profile");
return;
}
// sanitize user inputs
var unsafeName = obj.name || '';
console.log(unsafeName);
var name = Cryptpad.fixHTML(unsafeName) || Messages.anonymous;
console.log(name);
console.log("Creating invite button");
var $button = $("<button>", {
id: CREATE_INVITE_BUTTON,
title: Messages.profile_inviteButtonTitle,
})
.addClass('btn btn-success')
.text(Messages.profile_inviteButton)
.click(function (e) {
Cryptpad.confirm(Messages._getKey('profile_inviteExplanation', [name]), function (yes) {
if (!yes) { return; }
console.log(obj.curveKey);
Cryptpad.alert("TODO");
// TODO create a listmap object using your curve keys
// TODO fill the listmap object with your invite data
// TODO generate link to invite object
// TODO copy invite link to clipboard
}, null, true);
})
.appendTo($container);
};
var addViewButton = function ($container) {
if (!Cryptpad.isLoggedIn() || window.location.hash) {
return;
}
var hash = Cryptpad.find(Cryptpad.getProxy(), ['profile', 'view']);
var url = '/profile/#' + hash;
var $button = $('<button>', {
'class': 'btn btn-success',
id: VIEW_PROFILE_BUTTON,
})
.text(Messages.profile_viewMyProfile)
.click(function () {
window.open(url, '_blank');
});
$container.append($button);
};
var addDisplayName = function ($container) { var addDisplayName = function ($container) {
var $block = $('<div>', {id: DISPLAYNAME_ID}).appendTo($container); var $block = $('<div>', {id: DISPLAYNAME_ID}).appendTo($container);
var getValue = function (cb) { var getValue = function (cb) {
cb(APP.lm.proxy.name); cb(APP.lm.proxy.name);
}; };
@ -161,6 +225,8 @@ define([
getValue(function (value) { getValue(function (value) {
$span.text(value || Messages.anonymous); $span.text(value || Messages.anonymous);
}); });
addCreateInviteLinkButton($block);
return; return;
} }
var setValue = function (value, cb) { var setValue = function (value, cb) {
@ -168,7 +234,7 @@ define([
cb(); cb();
}; };
var rt = Cryptpad.getStore().getProxy().info.realtime; var rt = Cryptpad.getStore().getProxy().info.realtime;
createEditableInput($block, DISPLAYNAME_ID, placeholder, getValue, setValue, rt); createEditableInput($block, DISPLAYNAME_ID, placeholder, getValue, setValue, rt, Messages.anonymous);
}; };
var addLink = function ($container) { var addLink = function ($container) {
@ -330,9 +396,18 @@ define([
$container.append($block); $container.append($block);
}; };
var onReady = function () { var onReady = function () {
APP.$container.find('#'+CREATE_ID).remove(); APP.$container.find('#'+CREATE_ID).remove();
var obj = APP.lm && APP.lm.proxy;
if (!APP.readOnly) {
var pubKeys = Cryptpad.getPublicKeys();
if (pubKeys && pubKeys.curve) {
obj.curveKey = pubKeys.curve;
}
}
if (!APP.initialized) { if (!APP.initialized) {
var $header = $('<div>', {id: HEADER_ID}).appendTo(APP.$container); var $header = $('<div>', {id: HEADER_ID}).appendTo(APP.$container);
addAvatar($header); addAvatar($header);
@ -340,6 +415,7 @@ define([
addDisplayName($rightside); addDisplayName($rightside);
addLink($rightside); addLink($rightside);
addDescription(APP.$container); addDescription(APP.$container);
addViewButton(APP.$container); //$rightside);
addPublicKey(APP.$container); addPublicKey(APP.$container);
APP.initialized = true; APP.initialized = true;
} }
@ -377,7 +453,7 @@ define([
if (obj.profile && obj.profile.view && obj.profile.edit) { if (obj.profile && obj.profile.view && obj.profile.edit) {
return void andThen(obj.profile.edit); return void andThen(obj.profile.edit);
} }
// If the user doesn't have a public profile, ask him if he wants to create one // If the user doesn't have a public profile, ask them if they want to create one
var todo = function () { var todo = function () {
var secret = Cryptpad.getSecrets(); var secret = Cryptpad.getSecrets();
obj.profile = {}; obj.profile = {};

@ -58,7 +58,7 @@
min-height: 100%; min-height: 100%;
max-width: none; max-width: none;
max-height: none; max-height: none;
flex-shrink: 0; flex: 1;
} }
} }
button { button {
@ -96,6 +96,15 @@
line-height: 40px; line-height: 40px;
} }
} }
// I tried using flexbox but messed with how the pencil icon was displayed
#inviteButton {
float: right;
}
#viewProfileButton {
margin-bottom: 20px;
float: right;
}
#description { #description {
position: relative; position: relative;
font-size: 16px; font-size: 16px;

@ -75,6 +75,8 @@ define([
var proxy = result.proxy; var proxy = result.proxy;
proxy.edPublic = result.edPublic; proxy.edPublic = result.edPublic;
proxy.edPrivate = result.edPrivate; proxy.edPrivate = result.edPrivate;
proxy.curvePublic = result.curvePublic;
proxy.curvePrivate = result.curvePrivate;
Cryptpad.feedback('REGISTRATION', true); Cryptpad.feedback('REGISTRATION', true);

Loading…
Cancel
Save