Merge branch 'inviteUI' of github.com:xwiki-labs/cryptpad into inviteUI

This commit is contained in:
ansuz
2019-12-18 12:02:52 -05:00
23 changed files with 604 additions and 200 deletions

View File

@@ -592,6 +592,7 @@ define([
}];
var modal = dialog.customModal(content, {buttons: buttons});
UI.openCustomModal(modal);
return modal;
};
UI.log = function (msg) {

View File

@@ -84,6 +84,7 @@ define([
var myData = createData(store.proxy, false);
if (store.proxy.friends) {
store.proxy.friends.me = myData;
delete store.proxy.friends.me.channel;
}
if (store.modules['team']) {
store.modules['team'].updateMyData(myData);

View File

@@ -1684,6 +1684,11 @@ define([
dismissButton
]) // XXX
]);
$(linkMessage).keydown(function (e) {
if (e.which === 13) {
e.stopPropagation();
}
});
var localStore = window.cryptpadStore;
localStore.get('hide-alert-teamInvite', function (val) {
if (val === '1') { return; }
@@ -1762,7 +1767,7 @@ define([
onClick: function () {
return process();
},
keys: [13]
keys: []
}, {
className: 'primary cp-teams-invite-copy',
name: Messages.team_inviteLinkCopy, // XXX
@@ -4156,7 +4161,7 @@ define([
};
var content = h('div.cp-share-modal', [
setHTML(h('p'), text)
setHTML(h('p'), text),
]);
UI.proposal(content, todo);
};

View File

@@ -435,8 +435,6 @@
return mediaObject;
}
mediaObject.tag.innerHTML = '<img style="width: 100px; height: 100px;">';
// Download the encrypted blob
download(src, function (err, u8Encrypted) {
if (err) {

View File

@@ -3,9 +3,10 @@ define([
'/customize/messages.js',
'/common/common-util.js',
'/common/common-interface.js',
'/common/common-ui-elements.js',
'/common/hyperscript.js',
'/common/diffMarked.js',
], function ($, Messages, Util, UI, h, DiffMd) {
], function ($, Messages, Util, UI, UIElements, h, DiffMd) {
'use strict';
var debug = console.log;
@@ -13,6 +14,8 @@ define([
var MessengerUI = {};
var mutedUsers = {};
var dataQuery = function (id) {
return '[data-key="' + id + '"]';
};
@@ -67,8 +70,11 @@ define([
h('div.cp-app-contacts-category-content')
]),
h('div.cp-app-contacts-friends.cp-app-contacts-category', [
h('div.cp-app-contacts-category-content'),
h('h2.cp-app-contacts-category-title', Messages.contacts_friends),
h('button.cp-app-contacts-muted-button',[
h('i.fa.fa-bell-slash'),
Messages.contacts_manageMuted
]),
h('div.cp-app-contacts-category-content.cp-contacts-friends')
]),
h('div.cp-app-contacts-rooms.cp-app-contacts-category', [
h('div.cp-app-contacts-category-content'),
@@ -184,7 +190,7 @@ define([
markup.message = function (msg) {
if (msg.type !== 'MSG') { return; }
var curvePublic = msg.author;
var name = typeof msg.name !== "undefined" ?
var name = (typeof msg.name !== "undefined" || !contactsData[msg.author]) ?
(msg.name || Messages.anonymous) :
contactsData[msg.author].displayName;
var d = msg.time ? new Date(msg.time) : undefined;
@@ -486,6 +492,20 @@ define([
}
};
var unmuteUser = function (curve) {
execCommand('UNMUTE_USER', curve, function (e) {
if (e) { return void console.error(e); }
});
};
var muteUser = function (data) {
execCommand('MUTE_USER', {
curvePublic: data.curvePublic,
name: data.displayName || data.name,
avatar: data.avatar
}, function (e /*, removed */) {
if (e) { return void console.error(e); }
});
};
var removeFriend = function (curvePublic) {
execCommand('REMOVE_FRIEND', curvePublic, function (e /*, removed */) {
if (e) { return void console.error(e); }
@@ -499,6 +519,21 @@ define([
title: room.name
});
var curve;
if (room.isFriendChat) {
var __channel = state.channels[id];
curve = __channel.curvePublic;
}
var unmute = h('span.cp-app-contacts-remove.fa.fa-bell.cp-unmute-icon', {
title: Messages.contacts_unmute || 'unmute',
style: (curve && mutedUsers[curve]) ? undefined : 'display: none;'
});
var mute = h('span.cp-app-contacts-remove.fa.fa-bell-slash.cp-mute-icon', {
title: Messages.contacts_mute || 'mute',
style: (curve && mutedUsers[curve]) ? 'display: none;' : undefined
});
var remove = h('span.cp-app-contacts-remove.fa.fa-user-times', {
title: Messages.contacts_remove
});
@@ -511,8 +546,12 @@ define([
});
var rightCol = h('span.cp-app-contacts-right-col', [
h('span.cp-app-contacts-name', [room.name]),
room.isFriendChat ? remove :
(room.isPadChat || room.isTeamChat) ? undefined : leaveRoom,
h('span.cp-app-contacts-icons', [
room.isFriendChat ? mute : undefined,
room.isFriendChat ? unmute : undefined,
room.isFriendChat ? remove :
(room.isPadChat || room.isTeamChat) ? undefined : leaveRoom,
])
]);
var friendData = room.isFriendChat ? userlist[0] : {};
@@ -523,23 +562,43 @@ define([
if (friendData.profile) { window.open(origin + '/profile/#' + friendData.profile); }
});
$(unmute).on('click dblclick', function (e) {
e.stopPropagation();
var channel = state.channels[id];
if (!channel.isFriendChat) { return; }
var curvePublic = channel.curvePublic;
$(mute).show();
$(unmute).hide();
unmuteUser(curvePublic);
});
$(mute).on('click dblclick', function (e) {
e.stopPropagation();
var channel = state.channels[id];
if (!channel.isFriendChat) { return; }
var curvePublic = channel.curvePublic;
var friend = contactsData[curvePublic] || friendData;
$(mute).hide();
$(unmute).show();
muteUser(friend);
});
$(remove).click(function (e) {
e.stopPropagation();
var channel = state.channels[id];
if (!channel.isFriendChat) { return; }
var curvePublic = channel.curvePublic;
var friend = contactsData[curvePublic] || friendData;
UI.confirm(Messages._getKey('contacts_confirmRemove', [
Util.fixHTML(friend.name)
]), function (yes) {
var content = h('div', [
UI.setHTML(h('p'), Messages._getKey('contacts_confirmRemove', [Util.fixHTML(friend.name)])),
]);
UI.confirm(content, function (yes) {
if (!yes) { return; }
removeFriend(curvePublic, function (e) {
if (e) { return void console.error(e); }
});
removeFriend(curvePublic);
// TODO remove friend from userlist ui
// FIXME seems to trigger EJOINED from netflux-websocket (from server);
// (tried to join a channel in which you were already present)
}, undefined, true);
});
});
if (friendData.avatar && avatars[friendData.avatar]) {
@@ -792,6 +851,62 @@ define([
// var onJoinRoom
// var onLeaveRoom
var updateMutedList = function () {
execCommand('GET_MUTED_USERS', null, function (err, muted) {
if (err) { return void console.error(err); }
mutedUsers = muted;
var $button = $userlist.find('.cp-app-contacts-muted-button');
$('.cp-app-contacts-friend[data-user]')
.find('.cp-mute-icon').show();
$('.cp-app-contacts-friend[data-user]')
.find('.cp-unmute-icon').hide();
if (!muted || Object.keys(muted).length === 0) {
$button.hide();
return;
}
var rows = Object.keys(muted).map(function (curve) {
$('.cp-app-contacts-friend[data-user="'+curve+'"]')
.find('.cp-mute-icon').hide();
$('.cp-app-contacts-friend[data-user="'+curve+'"]')
.find('.cp-unmute-icon').show();
var data = muted[curve];
var avatar = h('span.cp-avatar');
var button = h('button', {
'data-user': curve
}, [
h('i.fa.fa-bell'),
Messages.contacts_unmute || 'unmute'
]);
UIElements.displayAvatar(common, $(avatar), data.avatar, data.name);
$(button).click(function () {
unmuteUser(curve, button);
execCommand('UNMUTE_USER', curve, function (e, data) {
if (e) { return void console.error(e); }
$(button).closest('div').remove();
if (!data) { $button.hide(); }
$('.cp-app-contacts-friend[data-user="'+curve+'"]')
.find('.cp-mute-icon').show();
});
});
return h('div.cp-contacts-muted-user', [
h('span', avatar),
h('span', data.name),
button
]);
});
var content = h('div', [
h('h4', Messages.contacts_mutedUsers || 'Muted users...'),
h('div.cp-contacts-muted-table', rows)
]);
$button.off('click');
$button.click(function () {
UI.alert(content);
}).show();
});
};
var ready = false;
var onMessengerReady = function () {
@@ -806,6 +921,8 @@ define([
rooms.forEach(initializeRoom);
});
updateMutedList();
$container.removeClass('cp-app-contacts-initializing');
};
@@ -882,6 +999,10 @@ define([
onUpdateData(data);
return;
}
if (cmd === 'UPDATE_MUTED') {
updateMutedList();
return;
}
if (cmd === 'MESSAGE') {
onMessage(data);
return;

View File

@@ -12,6 +12,13 @@ define([
var handlers = {};
var removeHandlers = {};
var isMuted = function (ctx, data) {
var muted = ctx.store.proxy.mutedUsers || {};
var curvePublic = Util.find(data, ['msg', 'author']);
if (!curvePublic) { return false; }
return Boolean(muted[curvePublic]);
};
// Store the friend request displayed to avoid duplicates
var friendRequest = {};
handlers['FRIEND_REQUEST'] = function (ctx, box, data, cb) {
@@ -21,6 +28,8 @@ define([
return void cb(true);
}
if (isMuted(ctx, data)) { return void cb(true); }
// Don't show duplicate friend request: if we already have a friend request
// in memory from the same user, dismiss the new one
if (friendRequest[data.msg.author]) { return void cb(true); }
@@ -30,10 +39,22 @@ define([
// If the user is already in our friend list, automatically accept the request
if (Messaging.getFriend(ctx.store.proxy, data.msg.author) ||
ctx.store.proxy.friends_pending[data.msg.author]) {
delete ctx.store.proxy.friends_pending[data.msg.author];
Messaging.acceptFriendRequest(ctx.store, data.msg.content, function (obj) {
if (obj && obj.error) {
return void cb();
}
Messaging.addToFriendList({
proxy: ctx.store.proxy,
realtime: ctx.store.realtime,
pinPads: ctx.pinPads
}, data.msg.content, function (err) {
if (err) { console.error(err); }
if (ctx.store.messenger) {
ctx.store.messenger.onFriendAdded(data.msg.content);
}
});
ctx.updateMetadata();
cb(true);
});
return;
@@ -170,6 +191,8 @@ define([
var content = msg.content;
// content.name, content.title, content.href, content.password
if (isMuted(ctx, data)) { return void cb(true); }
var channel = Hash.hrefToHexChannelId(content.href, content.password);
var parsed = Hash.parsePadUrl(content.href);
var mode = parsed.hashData && parsed.hashData.mode || 'n/a';
@@ -212,6 +235,9 @@ define([
supportMessage = true;
cb();
};
removeHandlers['SUPPORT_MESSAGE'] = function () {
supportMessage = false;
};
// Incoming edit rights request: add data before sending it to inner
handlers['REQUEST_PAD_ACCESS'] = function (ctx, box, data, cb) {
@@ -220,6 +246,8 @@ define([
if (msg.author !== content.user.curvePublic) { return void cb(true); }
if (isMuted(ctx, data)) { return void cb(true); }
var channel = content.channel;
var res = ctx.store.manager.findChannel(channel);
@@ -270,6 +298,9 @@ define([
var content = msg.content;
if (msg.author !== content.user.curvePublic) { return void cb(true); }
if (isMuted(ctx, data)) { return void cb(true); }
if (!content.teamChannel && !(content.href && content.title && content.channel)) {
console.log('Remove invalid notification');
return void cb(true);
@@ -327,6 +358,9 @@ define([
var content = msg.content;
if (msg.author !== content.user.curvePublic) { return void cb(true); }
if (isMuted(ctx, data)) { return void cb(true); }
if (!content.team) {
console.log('Remove invalid notification');
return void cb(true);

View File

@@ -428,20 +428,21 @@ define([
}
var channel = ctx.channels[data.channel];
if (!channel) {
return void cb({error: "NO_SUCH_CHANNEL"});
}
// Unfriend with mailbox
if (ctx.store.mailbox && data.curvePublic && data.notifications) {
Messaging.removeFriend(ctx.store, curvePublic, function (obj) {
if (obj && obj.error) { return void cb({error:obj.error}); }
ctx.updateMetadata();
cb(obj);
});
return;
}
// Unfriend with channel
if (!channel) {
return void cb({error: "NO_SUCH_CHANNEL"});
}
try {
var msg = [Types.unfriend, proxy.curvePublic, +new Date()];
var msgStr = JSON.stringify(msg);
@@ -458,6 +459,40 @@ define([
}
};
var getAllClients = function (ctx) {
var all = [];
Array.prototype.push.apply(all, ctx.friendsClients);
Object.keys(ctx.channels).forEach(function (id) {
Array.prototype.push.apply(all, ctx.channels[id].clients);
});
return Util.deduplicateString(all);
};
var muteUser = function (ctx, data, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
var proxy = ctx.store.proxy;
var muted = proxy.mutedUsers = proxy.mutedUsers || {};
if (muted[data.curvePublic]) { return void cb(); }
muted[data.curvePublic] = data;
ctx.emit('UPDATE_MUTED', null, getAllClients(ctx));
cb();
};
var unmuteUser = function (ctx, curvePublic, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
var proxy = ctx.store.proxy;
var muted = proxy.mutedUsers = proxy.mutedUsers || {};
delete muted[curvePublic];
ctx.emit('UPDATE_MUTED', null, getAllClients(ctx));
cb(Object.keys(muted).length);
};
var getMutedUsers = function (ctx, cb) {
var proxy = ctx.store.proxy;
if (cb) {
return void cb(proxy.mutedUsers || {});
}
return proxy.mutedUsers || {};
};
var openChannel = function (ctx, data) {
var proxy = ctx.store.proxy;
var network = ctx.store.network;
@@ -664,7 +699,14 @@ define([
nThen(function (waitFor) {
// Load or get all friends channels
Object.keys(friends).forEach(function (key) {
if (key === 'me') { return; }
if (key === 'me') {
// At some point a bug inserted a friend's channel into our "me" data.
// This led to displaying our name instead of our friend's name in the
// contacts app. The following line is here to prevent this issue to happen
// again.
delete friends.me.channel;
return;
}
var friend = clone(friends[key]);
if (typeof(friend) !== 'object') { return; }
if (!friend.channel) { return; }
@@ -887,15 +929,6 @@ define([
});
};
var getAllClients = function (ctx) {
var all = [];
Array.prototype.push.apply(all, ctx.friendsClients);
Object.keys(ctx.channels).forEach(function (id) {
Array.prototype.push.apply(all, ctx.channels[id].clients);
});
return Util.deduplicateString(all);
};
Msg.init = function (cfg, waitFor, emit) {
var messenger = {};
var store = cfg.store;
@@ -911,6 +944,9 @@ define([
range_requests: {}
};
store.proxy.on('change', ['mutedUsers'], function () {
ctx.emit('UPDATE_MUTED', null, getAllClients(ctx));
});
ctx.store.network.on('message', function(msg, sender) {
onDirectMessage(ctx, msg, sender);
@@ -942,6 +978,12 @@ define([
var channel = friend.channel;
if (!channel) { return; }
// Already friend? don't load the channel a second time
var chanId = friend.channel;
var chan = ctx.channels[chanId];
if (chan) { return; }
// Load the channel and add the friend to the contacts app
loadFriend(ctx, null, friend, function () {
emit('FRIEND', {
curvePublic: friend.curvePublic,
@@ -990,6 +1032,9 @@ define([
if (cmd === 'GET_ROOMS') {
return void getRooms(ctx, data, cb);
}
if (cmd === 'GET_MUTED_USERS') {
return void getMutedUsers(ctx, cb);
}
if (cmd === 'GET_USERLIST') {
return void getUserList(ctx, data, cb);
}
@@ -1002,6 +1047,12 @@ define([
if (cmd === 'REMOVE_FRIEND') {
return void removeFriend(ctx, data, cb);
}
if (cmd === 'MUTE_USER') {
return void muteUser(ctx, data, cb);
}
if (cmd === 'UNMUTE_USER') {
return void unmuteUser(ctx, data, cb);
}
if (cmd === 'GET_STATUS') {
return void getStatus(ctx, data, cb);
}

View File

@@ -451,7 +451,6 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto) {
// copy the new profile from the old one
members[curve] = Util.clone(members[author]);
members[curve].curvePublic = curve;
// and erase the old one
delete members[author];
return true;
@@ -776,7 +775,6 @@ var factory = function (Util, Hash, CPNetflux, Sortify, nThen, Crypto) {
Object.keys(data).forEach(function (curve) {
if (!isValidId(curve) || isMap(ref.state.members[curve])) { return delete data[curve]; }
});
console.error('SENDING INVITE');
send(['INVITE', data], cb);
};

View File

@@ -446,7 +446,6 @@ define([
// If we've been kicked, don't try to update our data, we'll close everything
// in the next nThen part
var state = roster.getState();
console.error(state);
var me = Util.find(ctx, ['store', 'proxy', 'curvePublic']);
if (!state.members[me]) { return; }
@@ -1280,6 +1279,8 @@ define([
var seeds = data.seeds; // {scrypt, preview}
var bytes64 = data.bytes64;
if (!teamId || !team) { return void cb({error: 'EINVAL'}); }
var roster = team.roster;
var teamName;
@@ -1312,12 +1313,16 @@ define([
var putOpts = {
initialState: '{}',
network: ctx.store.network,
metadata: {
owners: [ctx.store.proxy.edPublic, ephemeralKeys.edPublic]
}
};
(function () {
// a random signing keypair to prevent further writes to the channel
// we don't need to remember it cause we're only writing once
var sign = Invite.generateSignPair(); // { validateKey, signKey}
putOpts.metadata.validateKey = sign.validateKey;
// visible with only the invite link
var previewContent = {
@@ -1325,7 +1330,6 @@ define([
message: message,
author: Messaging.createData(ctx.store.proxy, false),
displayName: name,
curvePublic: ephemeralKeys.curvePublic,
};
var cryptput_config = {
@@ -1352,6 +1356,7 @@ define([
// a different random signing key so that the server can't correlate these documents
// as components of an invite
var sign = Invite.generateSignPair(); // { validateKey, signKey}
putOpts.metadata.validateKey = sign.validateKey;
// available only with the link and the content
var inviteContent = {
@@ -1486,13 +1491,16 @@ define([
// Accept the roster invitation: relplace our ephemeral keys with our user keys
var rosterData = Util.find(inviteContent, ['teamData', 'keys', 'roster']);
var myKeys = inviteContent.ephemeral;
if (!rosterData || !myKeys) {
waitFor.abort();
return void cb({error: 'INVALID_INVITE_CONTENT'});
}
var rosterKeys = Crypto.Team.deriveMemberKeys(rosterData.edit, myKeys);
Roster.create({
network: ctx.store.network,
channel: rosterData.channel,
keys: rosterKeys,
anon_rpc: ctx.store.anon_rpc,
lastKnownHash: rosterData.lastKnownHash, // XXX Can we trust this user?
}, waitFor(function (err, roster) {
if (err) {
waitFor.abort();