diff --git a/customize.dist/src/less2/include/alertify.less b/customize.dist/src/less2/include/alertify.less
index cd066e2c0..6662a9df7 100644
--- a/customize.dist/src/less2/include/alertify.less
+++ b/customize.dist/src/less2/include/alertify.less
@@ -30,7 +30,7 @@
// These show only once
.alertify-logs {
- z-index: 10000; // alertify logs
+ z-index: 100001; // alertify logs should be in front of alertify modals
@media print {
visibility: hidden;
}
@@ -339,7 +339,7 @@
.alertify-logs {
position: fixed;
- z-index: 99999; // alertify logs
+ z-index: 100001; // alertify logs (just in front of alertify modals
&.bottom, &:not(.top) {
bottom: 16px;
@@ -459,8 +459,15 @@
}
}
}
+ .cp-ownership {
+ & > label {
+ font-weight: bold;
+ }
+ }
+ }
+ .cp-share-column {
.cp-share-grid, .cp-share-list {
- .avatar_main(50px);
+ .avatar_main(40px);
display: flex;
justify-content: space-between;
flex-wrap: wrap;
@@ -513,6 +520,9 @@
color: @colortheme_alertify-primary-text;
order: -1 !important;
}
+ .cp-share-friend-avatar {
+ min-height: 40px;
+ }
.cp-share-friend-name {
overflow: hidden;
white-space: nowrap;
@@ -525,11 +535,6 @@
visibility: hidden;
}
}
- .cp-ownership {
- & > label {
- font-weight: bold;
- }
- }
}
}
diff --git a/www/common/common-messaging.js b/www/common/common-messaging.js
index a9d618225..8eec5c4a3 100644
--- a/www/common/common-messaging.js
+++ b/www/common/common-messaging.js
@@ -10,7 +10,7 @@ define([
var Msg = {};
var createData = Msg.createData = function (proxy, hash) {
- return {
+ var data = {
channel: hash || Hash.createChannelId(),
displayName: proxy['cryptpad.username'],
profile: proxy.profile && proxy.profile.view,
@@ -19,6 +19,8 @@ define([
notifications: Util.find(proxy, ['mailboxes', 'notifications', 'channel']),
avatar: proxy.profile && proxy.profile.avatar
};
+ if (hash === false) { delete data.channel; }
+ return data;
};
var getFriend = Msg.getFriend = function (proxy, pubkey) {
diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js
index c21b189f1..3b8dac905 100644
--- a/www/common/common-ui-elements.js
+++ b/www/common/common-ui-elements.js
@@ -146,18 +146,6 @@ define([
}, function () {
});
var $div = $(removeCol.div);
- var others1 = removeCol.others;
- $div.append(h('div.cp-share-grid', others1));
- $div.find('.cp-share-friend').click(function () {
- var sel = $(this).hasClass('cp-selected');
- if (!sel) {
- $(this).addClass('cp-selected');
- } else {
- var order = $(this).attr('data-order');
- order = order ? 'order:'+order : '';
- $(this).removeClass('cp-selected').attr('style', order);
- }
- });
// When clicking on the remove button, we check the selected users.
// If you try to remove yourself, we'll display an additional warning message
var btnMsg = pending ? Messages.owner_removePendingButton : Messages.owner_removeButton;
@@ -244,18 +232,6 @@ define([
//console.log(arguments);
});
$div2 = $(addCol.div);
- var others2 = addCol.others;
- $div2.append(h('div.cp-share-grid', others2));
- $div2.find('.cp-share-friend').click(function () {
- var sel = $(this).hasClass('cp-selected');
- if (!sel) {
- $(this).addClass('cp-selected');
- } else {
- var order = $(this).attr('data-order');
- order = order ? 'order:'+order : '';
- $(this).removeClass('cp-selected').attr('style', order);
- }
- });
// When clicking on the add button, we get the selected users.
var addButton = h('button.no-margin', Messages.owner_addButton);
$(addButton).click(function () {
@@ -724,6 +700,19 @@ define([
onSelect();
});
+ $(div).append(h('div.cp-share-grid', others));
+ $div.on('click', '.cp-share-friend', function () {
+ var sel = $(this).hasClass('cp-selected');
+ if (!sel) {
+ $(this).addClass('cp-selected');
+ } else {
+ var order = $(this).attr('data-order');
+ order = order ? 'order:'+order : '';
+ $(this).removeClass('cp-selected').attr('style', order);
+ }
+ onSelect();
+ });
+
return {
others: others,
div: div
@@ -749,7 +738,7 @@ define([
// Replace "copy link" by "share with friends" if at least one friend is selected
// Also create the "share with friends" button if it doesn't exist
var refreshButtons = function () {
- var $nav = $div.parents('.alertify').find('nav');
+ var $nav = $div.closest('.alertify').find('nav');
var friendMode = $div.find('.cp-share-friend.cp-selected').length;
if (friendMode) {
@@ -759,6 +748,7 @@ define([
}
};
+ config.noInclude = true;
var friendsList = UIElements.getFriendsList(Messages.share_linkFriends, config, refreshButtons);
var friendDiv = friendsList.div;
$div.append(friendDiv);
@@ -784,7 +774,6 @@ define([
friends: teams
}, refreshButtons);
$div.append(teamsList.div);
- $(teamsList.div).append(h('div.cp-share-grid', teamsList.others));
var shareButtons = [{
className: 'primary cp-share-with-friends',
@@ -870,19 +859,9 @@ define([
$(el).attr('data-order', i).css('order', i);
});
// Display them
+ $(friendDiv).find('.cp-share-grid').detach();
$(friendDiv).append(h('div.cp-share-grid', others));
$div.append(UI.dialog.getButtons(shareButtons, config.onClose));
- $div.find('.cp-share-friend').click(function () {
- var sel = $(this).hasClass('cp-selected');
- if (!sel) {
- $(this).addClass('cp-selected');
- } else {
- var order = $(this).attr('data-order');
- order = order ? 'order:'+order : '';
- $(this).removeClass('cp-selected').attr('style', order);
- }
- refreshButtons();
- });
refreshButtons();
});
return div;
@@ -903,7 +882,6 @@ define([
var friendsUIClass = hasFriends ? '.cp-share-columns' : '';
var mainShareColumn = h('div.cp-share-column.contains-nav', [
- hasFriends ? h('p', Messages.share_description) : undefined,
h('label', Messages.share_linkAccess),
h('br'),
UI.createRadio('cp-share-editable', 'cp-share-editable-true',
@@ -1195,6 +1173,78 @@ define([
return UI.dialog.customModal(link, {buttons: linkButtons});
};
+ UIElements.createInviteTeamModal = function (config) {
+ var common = config.common;
+ var hasFriends = Object.keys(config.friends || {}).length !== 0;
+ var friendsList = hasFriends ? createShareWithFriends(config) : undefined;
+
+ if (!hasFriends) {
+ return void UI.alert('No friend to invite'); // XXX
+ }
+ var privateData = common.getMetadataMgr().getPrivateData();
+ var team = privateData.teams[config.teamId];
+ if (!team) { return void UI.warn(Messages.error); }
+
+ var module = config.module || common.makeUniversal('team', { onEvent: function () {} });
+
+ var $div;
+ var refreshButton = function () {
+ if (!$div) { return; }
+ var $modal = $div.closest('.alertify');
+ var $nav = $modal.find('nav');
+ var $btn = $nav.find('button.primary');
+ var selected = $div.find('.cp-share-friend.cp-selected').length;
+ if (selected) {
+ $btn.prop('disabled', '');
+ } else {
+ $btn.prop('disabled', 'disabled');
+ }
+ };
+ var list = UIElements.getFriendsList('Pick the friends you want to invite to the team', { // XXX
+ common: common,
+ friends: config.friends,
+ }, refreshButton);
+ $div = $(list.div);
+ refreshButton();
+
+ var buttons = [{
+ className: 'cancel',
+ name: Messages.cancel,
+ onClick: function () {},
+ keys: [27]
+ }, {
+ className: 'primary',
+ name: 'INVITE', // XXX
+ onClick: function () {
+ var $sel = $div.find('.cp-share-friend.cp-selected');
+ var sel = $sel.toArray();
+ if (!sel.length) { return; }
+
+ var friends = sel.forEach(function (el) {
+ var curve = $(el).attr('data-curve');
+ module.execCommand('INVITE_TO_TEAM', {
+ teamId: config.teamId,
+ user: config.friends[curve]
+ }, function (obj) {
+ if (obj && obj.error) {
+ console.error(obj.error);
+ return UI.warn(Messages.error);
+ }
+ });
+ });
+ },
+ keys: [13]
+ }];
+
+ var content = h('div', [
+ h('h4', 'Invite friends to your team: '+ team.name),
+ list.div
+ ]);
+
+ var modal = UI.dialog.customModal(content, {buttons: buttons});
+ UI.openCustomModal(modal);
+ };
+
UIElements.createButton = function (common, type, rightside, data, callback) {
var AppConfig = common.getAppConfig();
var button;
@@ -3503,27 +3553,7 @@ define([
var content = h('div.cp-share-modal', [
setHTML(h('p'), text)
]);
- var buttons = [{
- name: Messages.friendRequest_later,
- onClick: function () {},
- keys: [27]
- }, {
- className: 'primary',
- name: Messages.friendRequest_accept,
- onClick: function () {
- todo(true);
- },
- keys: [13]
- }, {
- className: 'primary',
- name: Messages.friendRequest_decline,
- onClick: function () {
- todo(false);
- },
- keys: [[13, 'ctrl']]
- }];
- var modal = UI.dialog.customModal(content, {buttons: buttons});
- UI.openCustomModal(modal);
+ UI.proposal(content, todo);
};
UIElements.displayAddOwnerModal = function (common, data) {
@@ -3648,27 +3678,85 @@ define([
});
};
- var buttons = [{
- name: Messages.friendRequest_later,
- onClick: function () {},
- keys: [27]
- }, {
- className: 'primary',
- name: Messages.friendRequest_accept,
- onClick: function () {
- todo(true);
- },
- keys: [13]
- }, {
- className: 'primary',
- name: Messages.friendRequest_decline,
- onClick: function () {
- todo(false);
- },
- keys: [[13, 'ctrl']]
- }];
- var modal = UI.dialog.customModal(div, {buttons: buttons});
- UI.openCustomModal(modal);
+ UI.proposal(div, todo);
+ };
+
+ UIElements.getVerifiedFriend = function (common, curve, name) {
+ var priv = common.getMetadataMgr().getPrivateData();
+ var verified = h('p');
+ var $verified = $(verified);
+
+ if (priv.friends && priv.friends[curve]) {
+ $verified.addClass('cp-notifications-requestedit-verified');
+ var f = priv.friends[curve];
+ $verified.append(h('span.fa.fa-certificate'));
+ var $avatar = $(h('span.cp-avatar')).appendTo($verified);
+ $verified.append(h('p', Messages._getKey('requestEdit_fromFriend', [f.displayName])));
+ common.displayAvatar($avatar, f.avatar, f.displayName);
+ } else {
+ $verified.append(Messages._getKey('requestEdit_fromStranger', [name]));
+ }
+ return verified;
+ };
+
+ UIElements.displayInviteTeamModal = function (common, data) {
+ var priv = common.getMetadataMgr().getPrivateData();
+ var user = common.getMetadataMgr().getUserData();
+ var sframeChan = common.getSframeChannel();
+ var msg = data.content.msg;
+
+ var name = Util.fixHTML(msg.content.user.displayName) || Messages.anonymous;
+ var teamName = Util.fixHTML(Util.find(msg, ['content', 'team', 'metadata', 'name']) || '');
+
+ var verified = UIElements.getVerifiedFriend(common, msg.author, name);
+
+ //var text = Messages._getKey('', [name, title]); // XXX
+ var text = name + " has invited you to join the team " + teamName +"";
+
+ var div = h('div', [
+ UI.setHTML(h('p'), text),
+ verified
+ ]);
+
+ var module = common.makeUniversal('team');
+
+ var answer = function (yes) {
+ common.mailbox.sendTo("INVITE_TO_TEAM_ANSWER", {
+ answer: yes,
+ teamChannel: msg.content.team.channel,
+ user: {
+ displayName: user.name,
+ avatar: user.avatar,
+ profile: user.profile,
+ notifications: user.notifications,
+ curvePublic: user.curvePublic,
+ edPublic: priv.edPublic
+ }
+ }, {
+ channel: msg.content.user.notifications,
+ curvePublic: msg.content.user.curvePublic
+ });
+ common.mailbox.dismiss(data, function (err) {
+ console.log(err);
+ });
+ };
+ var todo = function (yes) {
+ if (yes) {
+ // ACCEPT
+ module.execCommand('JOIN_TEAM', {
+ team: msg.content.team
+ }, function (obj) {
+ if (obj && obj.error) { return void UI.warn(Messages.error); }
+ answer(true);
+ });
+ return;
+ }
+
+ // DECLINE
+ answer(false);
+ };
+
+ UI.proposal(div, todo);
};
return UIElements;
diff --git a/www/common/notifications.js b/www/common/notifications.js
index 4f5d286ee..810e4b72f 100644
--- a/www/common/notifications.js
+++ b/www/common/notifications.js
@@ -145,22 +145,10 @@ define([
var link = h('a', {
href: '#'
}, Messages.requestEdit_viewPad);
- var verified = h('p');
- var $verified = $(verified);
var name = Util.fixHTML(msg.content.user.displayName) || Messages.anonymous;
var title = Util.fixHTML(msg.content.title);
-
- if (priv.friends && priv.friends[msg.author]) {
- $verified.addClass('cp-notifications-requestedit-verified');
- var f = priv.friends[msg.author];
- $verified.append(h('span.fa.fa-certificate'));
- var $avatar = $(h('span.cp-avatar')).appendTo($verified);
- $verified.append(h('p', Messages._getKey('requestEdit_fromFriend', [f.displayName])));
- common.displayAvatar($avatar, f.avatar, f.displayName);
- } else {
- $verified.append(Messages._getKey('requestEdit_fromStranger', [name]));
- }
+ var verified = UIElements.getVerifiedFriend(common, msg.author, name);
var div = h('div', [
UI.setHTML(h('p'), Messages._getKey('requestEdit_confirm', [title, name])),
@@ -268,6 +256,42 @@ define([
}
};
+ handlers['INVITE_TO_TEAM'] = function (common, data) {
+ var content = data.content;
+ var msg = content.msg;
+
+ // Display the notification
+ var name = Util.fixHTML(msg.content.user.displayName) || Messages.anonymous;
+ var teamName = Util.fixHTML(Util.find(msg, ['content', 'team', 'metadata', 'name']) || '');
+ content.getFormatText = function () {
+ var text = name + " has invited you to join the team " + teamName +"";
+ return text;
+ };
+ if (!content.archived) {
+ content.handler = function () {
+ UIElements.displayInviteTeamModal(common, data);
+ };
+ }
+ };
+
+ handlers['INVITE_TO_TEAM_ANSWER'] = function (common, data) {
+ var content = data.content;
+ var msg = content.msg;
+
+ // Display the notification
+ var name = Util.fixHTML(msg.content.user.displayName) || Messages.anonymous;
+ var teamName = Util.fixHTML(Util.find(msg, ['content', 'team', 'metadata', 'name']) || '');
+ var key = 'owner_request_' + (msg.content.answer ? 'accepted' : 'declined');
+ content.getFormatText = function () {
+ //return Messages._getKey(key, [name, title]); // XXX
+ return name +' has ' + (msg.content.answer ? 'accepted' : 'declined') + ' your offer to join the team ' + teamName + '';
+ };
+ if (!content.archived) {
+ content.dismissHandler = defaultDismiss(common, data);
+ }
+ };
+
+
// NOTE: don't forget to fixHTML everything returned by "getFormatText"
return {
diff --git a/www/common/outer/mailbox-handlers.js b/www/common/outer/mailbox-handlers.js
index 487eaecdc..cfe6fdd29 100644
--- a/www/common/outer/mailbox-handlers.js
+++ b/www/common/outer/mailbox-handlers.js
@@ -1,7 +1,8 @@
define([
'/common/common-messaging.js',
'/common/common-hash.js',
-], function (Messaging, Hash) {
+ '/common/common-util.js',
+], function (Messaging, Hash, Util) {
var getRandomTimeout = function (ctx) {
var lag = ctx.store.realtime.getLag().lag || 0;
@@ -309,6 +310,72 @@ define([
cb(false);
};
+ var invitedTo = {};
+ handlers['INVITE_TO_TEAM'] = function (ctx, box, data, cb) {
+ var msg = data.msg;
+ var content = msg.content;
+
+ if (msg.author !== content.user.curvePublic) { return void cb(true); }
+ if (!content.team) {
+ console.log('Remove invalid notification');
+ return void cb(true);
+ }
+
+ if (invitedTo[content.team.channel]) { return void cb(true); }
+
+ var myTeams = Util.find(ctx, ['store', 'proxy', 'teams'])
+ var alreadyMember = Object.keys(myTeams).some(function (k) {
+ var team = myTeams[k];
+ return team.channel === content.team.channel;
+ });
+ if (alreadyMember) { return void cb(true); }
+
+ invitedTo[content.team.channel] = true;
+
+ cb(false);
+ };
+ removeHandlers['INVITE_TO_TEAM'] = function (ctx, box, data) {
+ var channel = Util.find(data, ['content', 'team', 'channel']);
+ delete invitedTo[channel];
+ };
+
+ handlers['INVITE_TO_TEAM_ANSWER'] = function (ctx, box, data, cb) {
+ var msg = data.msg;
+ var content = msg.content;
+
+ if (msg.author !== content.user.curvePublic) { return void cb(true); }
+ if (!content.teamChannel) {
+ console.log('Remove invalid notification');
+ return void cb(true);
+ }
+
+ var myTeams = Util.find(ctx, ['store', 'proxy', 'teams'])
+ var teamId;
+ var team;
+ Object.keys(myTeams).some(function (k) {
+ var _team = myTeams[k];
+ if (_team.channel === content.teamChannel) {
+ teamId = k;
+ team = _team;
+ return true;
+ }
+ });
+ if (!teamId) { return void cb(true); }
+
+ content.team = team;
+
+ if (!content.answer) {
+ // If they declined the invitation, remove them from the roster (as a pending member)
+ try {
+ var module = ctx.store.modules['team'];
+ module.removeFromTeam(teamId, msg.author);
+ } catch (e) { console.error(e); }
+ }
+
+ cb(false);
+ };
+
+
return {
add: function (ctx, box, data, cb) {
/**
diff --git a/www/common/outer/team.js b/www/common/outer/team.js
index 2785ee806..2331d35d9 100644
--- a/www/common/outer/team.js
+++ b/www/common/outer/team.js
@@ -310,8 +310,8 @@ define([
// If you're allowed to edit the roster, try to update your data
if (!rosterData.edit) { return; }
var data = {};
- var myData = Messaging.createData(ctx.store.proxy);
- delete myData.channel;
+ var myData = Messaging.createData(ctx.store.proxy, false);
+ myData.pending = false;
data[ctx.store.proxy.curvePublic] = myData;
roster.describe(data, function (err) {
if (!err) { return; }
@@ -448,6 +448,19 @@ define([
});
};
+ var joinTeam = function (ctx, data, cId, cb) {
+ var team = data.team;
+ if (!team.hash || !team.channel || !team.password
+ || !team.keys || !team.metadata) { return void cb({error: 'EINVAL'}); }
+ var id = Util.createRandomInteger();
+ ctx.store.proxy.teams[id] = team;
+ ctx.onReadyHandlers[id] = [];
+ openChannel(ctx, team, id, function (obj) {
+ if (!(obj && obj.error)) { console.debug('Team joined:' + id); }
+ cb(obj);
+ });
+ };
+
var getTeamRoster = function (ctx, data, cId, cb) {
var teamId = data.teamId;
if (!teamId) { return void cb({error: 'EINVAL'}); }
@@ -499,6 +512,43 @@ define([
});
};
+ // TODO send guest keys only in the future
+ var getInviteData = function (ctx, teamId) {
+ var teamData = Util.find(ctx, ['store', 'proxy', 'teams', teamId]);
+ if (!teamData) { return; }
+ var data = Util.clone(teamData);
+ delete data.owner;
+ return data;
+ };
+
+ var inviteToTeam = function (ctx, data, cId, cb) {
+ var teamId = data.teamId;
+ if (!teamId) { return void cb({error: 'EINVAL'}); }
+ var team = ctx.teams[teamId];
+ if (!team) { return void cb ({error: 'ENOENT'}); }
+ if (!team.roster) { return void cb({error: 'NO_ROSTER'}); }
+ var user = data.user;
+ if (!user || !user.curvePublic || !user.notifications) { return void cb({error: 'MISSING_DATA'}); }
+ delete user.channel;
+ delete user.lastKnownHash;
+ user.pending = true;
+
+ var obj = {};
+ obj[user.curvePublic] = user;
+ team.roster.add(obj, function (err) {
+ if (err && err !== 'NO_CHANGE') { return void cb({error: err}); }
+ ctx.store.mailbox.sendTo('INVITE_TO_TEAM', {
+ user: Messaging.createData(ctx.store.proxy, false),
+ team: getInviteData(ctx, teamId)
+ }, {
+ channel: user.notifications,
+ curvePublic: user.curvePublic
+ }, function (obj) {
+ cb(obj);
+ });
+ });
+ };
+
// XXX Listen for changes to the roster pad to know if you've been removed
// XXX onReady, if you've been removed, leave the team
var removeUser = function (ctx, data, cId, cb) {
@@ -584,7 +634,8 @@ define([
pinPads: cfg.pinPads,
emit: emit,
onReadyHandlers: {},
- teams: {}
+ teams: {},
+ updateMetadata: cfg.updateMetadata
};
var teams = store.proxy.teams = store.proxy.teams || {};
@@ -608,7 +659,8 @@ define([
Object.keys(teams).forEach(function (id) {
t[id] = {
name: teams[id].metadata.name,
- edPublic: Util.find(teams[id], ['keys', 'drive', 'edPublic'])
+ edPublic: Util.find(teams[id], ['keys', 'drive', 'edPublic']),
+ avatar: Util.find(teams[id], ['metadata', 'avatar'])
};
});
return t;
@@ -616,6 +668,22 @@ define([
team.getTeams = function () {
return Object.keys(ctx.teams);
};
+ team.removeFromTeam = function (teamId, curve) {
+ if (!teams[teamId]) { return; }
+ if (ctx.onReadyHandlers[teamId]) {
+ ctx.onReadyHandlers[teamId].push({cb : function () {
+ ctx.teams[teamId].roster.remove([curve], function (err) {
+ if (err && err !== 'NO_CHANGE') { console.error(err); }
+ });
+ }});
+ return;
+ }
+ if (!ctx.teams[teamId]) { return void console.error("TEAM MODULE ERROR"); }
+ ctx.teams[teamId].roster.remove([curve], function (err) {
+ if (err && err !== 'NO_CHANGE') { console.error(err); }
+ });
+
+ };
team.removeClient = function (clientId) {
removeClient(ctx, clientId);
};
@@ -644,6 +712,12 @@ define([
if (cmd === 'DESCRIBE_USER') {
return void describeUser(ctx, data, clientId, cb);
}
+ if (cmd === 'INVITE_TO_TEAM') {
+ return void inviteToTeam(ctx, data, clientId, cb);
+ }
+ if (cmd === 'JOIN_TEAM') {
+ return void joinTeam(ctx, data, clientId, cb);
+ }
if (cmd === 'REMOVE_USER') {
return void removeUser(ctx, data, clientId, cb);
}
diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js
index aee8b8306..11061fd80 100644
--- a/www/common/sframe-common.js
+++ b/www/common/sframe-common.js
@@ -167,9 +167,11 @@ define([
// Universal direct channel
var modules = {};
funcs.makeUniversal = function (type, cfg) {
- modules[type] = {
- onEvent: cfg.onEvent || function () {}
- };
+ if (cfg && cfg.onEvent) {
+ modules[type] = {
+ onEvent: cfg.onEvent || function () {}
+ };
+ }
var sframeChan = funcs.getSframeChannel();
return {
execCommand: function (cmd, data, cb) {
diff --git a/www/team/inner.js b/www/team/inner.js
index 52b9535cf..d2eb95a76 100644
--- a/www/team/inner.js
+++ b/www/team/inner.js
@@ -397,7 +397,7 @@ define([
var theirRole = ROLES.indexOf(data.role) || 0;
// If they're a member and I have a higher role than them, I can promote them to admin
if (!isMe && myRole > theirRole && theirRole === 0) {
- var promote = h('fa.fa-angle-double-up', {
+ var promote = h('span.fa.fa-angle-double-up', {
title: 'Promote' // XXX
});
$(promote).click(function () {
@@ -410,7 +410,7 @@ define([
// If I'm not a member and I have an equal or higher role than them, I can demote them
// (if they're not already a MEMBER)
if (!isMe && myRole >= theirRole && theirRole > 0) {
- var demote = h('fa.fa-angle-double-down', {
+ var demote = h('span.fa.fa-angle-double-down', {
title: 'Demote' // XXX
});
$(demote).click(function () {
@@ -422,7 +422,7 @@ define([
}
// If I'm not a member and I have an equal or higher role than them, I can remove them
if (!isMe && myRole > 0 && myRole >= theirRole) {
- var remove = h('fa.fa-times', {
+ var remove = h('span.fa.fa-times', {
title: 'Remove' // XXX
});
$(remove).click(function () {
@@ -460,8 +460,8 @@ define([
APP.refreshRoster = function (common, roster) {
if (!roster || typeof(roster) !== "object" || Object.keys(roster) === 0) { return; }
var metadataMgr = common.getMetadataMgr();
- var privateData = metadataMgr.getPrivateData();
- var me = roster[privateData.curvePublic];
+ var userData = metadataMgr.getUserData();
+ var me = roster[userData.curvePublic] || {};
var owner = Object.keys(roster).filter(function (k) {
return roster[k].role === "OWNER";
}).map(function (k) {
@@ -479,7 +479,35 @@ define([
});
// XXX LEAVE the team button
// XXX INVITE to the team button
+ var header = h('div.cp-app-team-roster-header');
+ var $header = $(header);
+
+ // If you're an admin or an owner, you can invite your friends to the team
+ // TODO and acquaintances later?
+ if (me && (me.role === 'ADMIN' || me.role === 'OWNER')) {
+ var invite = h('button.btn.btn-primary', 'INVITE A FRIEND');
+ var inviteFriends = common.getFriends();
+ Object.keys(inviteFriends).forEach(function (curve) {
+ // Keep only friends that are not already in the team and that you can contact
+ // via their mailbox
+ if (roster[curve] && !roster[curve].pending) {
+ delete inviteFriends[curve];
+ }
+ });
+ var inviteCfg = {
+ teamId: APP.team,
+ common: common,
+ friends: inviteFriends,
+ module: APP.module
+ };
+ $(invite).click(function () {
+ UIElements.createInviteTeamModal(inviteCfg);
+ });
+ $header.append(invite);
+ }
+
return [
+ header,
h('h3', 'OWNER'), // XXX
h('div', owner),
h('h3', 'ADMINS'), // XXX