Create new role of leader. Admin can set or remove a leader. Display leader at top of chat when user scrolls up, along with a "Chat Now" button which opens a new direct message with the leader.

pull/7526/head
Daniel Schreiber 9 years ago
parent 3fb761a1f6
commit 741608e37b
  1. 26
      packages/rocketchat-api/server/v1/groups.js
  2. 2
      packages/rocketchat-authorization/server/startup.js
  3. 4
      packages/rocketchat-i18n/i18n/en.i18n.json
  4. 10
      packages/rocketchat-lib/server/models/Messages.js
  5. 3
      packages/rocketchat-lib/server/models/Subscriptions.js
  6. 71
      packages/rocketchat-theme/client/imports/base.css
  7. 3
      packages/rocketchat-ui-flextab/client/flexTabBar.js
  8. 7
      packages/rocketchat-ui-flextab/client/tabs/userInfo.html
  9. 47
      packages/rocketchat-ui-flextab/client/tabs/userInfo.js
  10. 2
      packages/rocketchat-ui/client/lib/RoomManager.js
  11. 20
      packages/rocketchat-ui/client/views/app/room.html
  12. 26
      packages/rocketchat-ui/client/views/app/room.js
  13. 67
      server/methods/addRoomLeader.js
  14. 67
      server/methods/removeRoomLeader.js

@ -64,6 +64,18 @@ RocketChat.API.v1.addRoute('groups.addOwner', { authRequired: true }, {
}
});
RocketChat.API.v1.addRoute('groups.addLeader', { authRequired: true }, {
post() {
const findResult = findPrivateGroupByIdOrName({ params: this.requestParams(), userId: this.userId });
const user = this.getUserFromParams();
Meteor.runAsUser(this.userId, () => {
Meteor.call('addRoomLeader', findResult.rid, user._id);
});
return RocketChat.API.v1.success();
}
});
//Archives a private group only if it wasn't
RocketChat.API.v1.addRoute('groups.archive', { authRequired: true }, {
post() {
@ -373,6 +385,20 @@ RocketChat.API.v1.addRoute('groups.removeOwner', { authRequired: true }, {
}
});
RocketChat.API.v1.addRoute('groups.removeLeader', { authRequired: true }, {
post() {
const findResult = findPrivateGroupByIdOrName({ params: this.requestParams(), userId: this.userId });
const user = this.getUserFromParams();
Meteor.runAsUser(this.userId, () => {
Meteor.call('removeRoomLeader', findResult.rid, user._id);
});
return RocketChat.API.v1.success();
}
});
RocketChat.API.v1.addRoute('groups.rename', { authRequired: true }, {
post() {
if (!this.bodyParams.name || !this.bodyParams.name.trim()) {

@ -47,6 +47,7 @@ Meteor.startup(function() {
{ _id: 'set-moderator', roles : ['admin', 'owner'] },
{ _id: 'set-owner', roles : ['admin', 'owner'] },
{ _id: 'send-many-messages', roles : ['admin', 'bot'] },
{ _id: 'set-leader', roles : ['admin', 'owner'] },
{ _id: 'unarchive-room', roles : ['admin'] },
{ _id: 'view-c-room', roles : ['admin', 'user', 'bot', 'anonymous'] },
{ _id: 'user-generate-access-token', roles : ['admin'] },
@ -74,6 +75,7 @@ Meteor.startup(function() {
const defaultRoles = [
{ name: 'admin', scope: 'Users', description: 'Admin' },
{ name: 'moderator', scope: 'Subscriptions', description: 'Moderator' },
{ name: 'leader', scope: 'Subscriptions', description: 'Leader' },
{ name: 'owner', scope: 'Subscriptions', description: 'Owner' },
{ name: 'user', scope: 'Users', description: '' },
{ name: 'bot', scope: 'Users', description: '' },

@ -1397,6 +1397,7 @@
"Remove_Admin": "Remove Admin",
"Remove_as_moderator": "Remove as moderator",
"Remove_as_owner": "Remove as owner",
"Remove_as_leader": "Remove as leader",
"Remove_custom_oauth": "Remove custom oauth",
"Remove_from_room": "Remove from room",
"Remove_last_admin": "Removing last admin",
@ -1509,6 +1510,7 @@
"Service_account_key": "Service account key",
"Set_as_moderator": "Set as moderator",
"Set_as_owner": "Set as owner",
"Set_as_leader": "Set as leader",
"Settings": "Settings",
"Settings_updated": "Settings updated",
"Share_Location_Title": "Share Location?",
@ -1741,8 +1743,10 @@
"Use_User_Preferences_or_Global_Settings": "Use User Preferences or Global Settings",
"User__username__is_now_a_moderator_of__room_name_": "User __username__ is now a moderator of __room_name__",
"User__username__is_now_a_owner_of__room_name_": "User __username__ is now a owner of __room_name__",
"User__username__is_now_a_leader_of__room_name_": "User __username__ is now a leader of __room_name__",
"User__username__removed_from__room_name__moderators": "User __username__ removed from __room_name__ moderators",
"User__username__removed_from__room_name__owners": "User __username__ removed from __room_name__ owners",
"User__username__removed_from__room_name__leaders": "User __username__ removed from __room_name__ leaders",
"User_added": "User added",
"User_added_by": "User <em>__user_added__</em> added by <em>__user_by__</em>.",
"User_added_successfully": "User added successfully",

@ -545,6 +545,16 @@ RocketChat.models.Messages = new class extends RocketChat.models._Base {
return this.createWithTypeRoomIdMessageAndUser('owner-removed', roomId, message, user, extraData);
}
createNewLeaderWithRoomIdAndUser(roomId, user, extraData) {
const message = user.username;
return this.createWithTypeRoomIdMessageAndUser('new-leader', roomId, message, user, extraData);
}
createLeaderRemovedWithRoomIdAndUser(roomId, user, extraData) {
const message = user.username;
return this.createWithTypeRoomIdMessageAndUser('leader-removed', roomId, message, user, extraData);
}
createSubscriptionRoleAddedWithRoomIdAndUser(roomId, user, extraData) {
const message = user.username;
return this.createWithTypeRoomIdMessageAndUser('subscription-role-added', roomId, message, user, extraData);

@ -546,7 +546,8 @@ class ModelSubscriptions extends RocketChat.models._Base {
t: room.t,
u: {
_id: user._id,
username: user.username
username: user.username,
name: user.name
}
};

@ -4915,3 +4915,74 @@ a + br.only-after-a {
max-height: 50px !important;
}
}
.room-leader a.chat-now {
position: absolute;
right: 25px;
width: 80px;
top: 15px;
height: 30px;
border: 1px solid #eaeaea;
text-align: center;
border-radius: 4px;
font-family: arial;
font-size: 14px;
text-decoration: none;
color: #555555;
cursor: pointer;
padding-top: 4px;
}
.room-leader a.chat-now:hover {
color: #555555;
}
.room-leader-container {
height: 54px;
}
.room-leader {
position: fixed;
top: 60px;
left: 0;
z-index: 1;
background: #ffffff;
width: calc(100% - 40px);
color: #555555;
border-bottom: solid 1px #eaeaea;
height: 54px;
}
.room-leader .thumb {
top: 6px;
}
.room-leader .right {
position: absolute;
top: 10px;
left: 70px;
}
.leader-status .status-text {
text-transform: capitalize;
padding-left: 15px;
font-size: 14px;
}
.leader-status .color-ball {
width: 10px;
height: 10px;
position: absolute;
border-radius: 5px;
margin-top: 5px;
background: grey;
}
.leader-status .color-ball.online {
background: green;
}
.leader-info .leader-name {
font-size: 18px;
}

@ -41,9 +41,12 @@ Template.flexTabBar.helpers({
Template.flexTabBar.events({
'click .tab-button'(e, instance) {
e.preventDefault();
const $flexTab = $('.flex-tab-container .flex-tab');
if (instance.tabBar.getState() === 'opened' && instance.tabBar.getTemplate() === this.template) {
$flexTab.attr('template', '');
return instance.tabBar.close();
} else {
$flexTab.attr('template', this.template);
return instance.tabBar.open(this);
}
}

@ -63,6 +63,13 @@
<button class="button button-block tertiary set-owner"><span>{{_ "Set_as_owner"}}</span></button>
{{/if}}
{{/if}}
{{#if canSetLeader}}
{{#if isLeader}}
<button class="button button-block danger unset-leader"><span>{{_ "Remove_as_leader"}}</span></button>
{{else}}
<button class="button button-block tertiary set-leader"><span>{{_ "Set_as_leader"}}</span></button>
{{/if}}
{{/if}}
{{#if canSetModerator}}
{{#if isModerator}}
<button class="button button-block danger unset-moderator"><span>{{_ "Remove_as_moderator"}}</span></button>

@ -101,6 +101,10 @@ Template.userInfo.helpers({
return RocketChat.authz.hasAllPermission('set-owner', Session.get('openedRoom'));
},
canSetLeader() {
return RocketChat.authz.hasAllPermission('set-leader', Session.get('openedRoom'));
},
isOwner() {
const user = Template.instance().user.get();
if (user && user._id) {
@ -108,6 +112,13 @@ Template.userInfo.helpers({
}
},
isLeader() {
const user = Template.instance().user.get();
if (user && user._id) {
return !!RoomRoles.findOne({ rid: Session.get('openedRoom'), 'u._id': user._id, roles: 'leader' });
}
},
user() {
return Template.instance().user.get();
},
@ -367,6 +378,42 @@ Template.userInfo.events({
}
},
'click .set-leader'(e, t) {
e.preventDefault();
const user = t.user.get();
if (user) {
const userLeader = RoomRoles.findOne({ rid: Session.get('openedRoom'), 'u._id': user._id, roles: 'leader' }, { fields: { _id: 1 } });
if (userLeader == null) {
return Meteor.call('addRoomLeader', Session.get('openedRoom'), user._id, (err) => {
if (err) {
return handleError(err);
}
const room = ChatRoom.findOne(Session.get('openedRoom'));
return toastr.success(TAPi18n.__('User__username__is_now_a_leader_of__room_name_', { username: this.username, room_name: room.name }));
});
}
}
},
'click .unset-leader'(e, t) {
e.preventDefault();
const user = t.user.get();
if (user) {
const userLeader = RoomRoles.findOne({ rid: Session.get('openedRoom'), 'u._id': user._id, roles: 'leader' }, { fields: { _id: 1 } });
if (userLeader != null) {
return Meteor.call('removeRoomLeader', Session.get('openedRoom'), user._id, (err) => {
if (err) {
return handleError(err);
}
const room = ChatRoom.findOne(Session.get('openedRoom'));
return toastr.success(TAPi18n.__('User__username__removed_from__room_name__leaders', { username: this.username, room_name: room.name }));
});
}
}
},
'click .deactivate'(e, instance) {
e.stopPropagation();
e.preventDefault();

@ -50,7 +50,7 @@ const RoomManager = new function() {
msg.roles = _.union.apply(_.union, roles);
ChatMessage.upsert({ _id: msg._id }, msg);
}
msg.name = room.name;
Meteor.defer(() => RoomManager.updateMentionsMarksOfRoom(typeName));
RocketChat.callbacks.run('streamMessage', msg);

@ -28,7 +28,7 @@
<span class="room-topic">{{{RocketChatMarkdown roomTopic}}}</span>
</h2>
</header>
{{/unless}}
<div class="messages-container-wrapper">
<div class="messages-container-main">
@ -100,6 +100,24 @@
{{/unless}}
<div class="wrapper {{#if hasMoreNext}}has-more-next{{/if}} {{hideUsername}} {{hideAvatar}}">
<ul aria-live="polite">
{{#if roomLeader}}
<li class="message room-leader-container">
<div class="room-leader">
<button class="thumb user-card-message">
<div class="avatar"><div class="avatar-image" style="background-image:url(/avatar/{{roomLeader.username}}?_dc=undefined);"></div></div></button>
<div class="right">
<div class="leader-info">
<div class="leader-name">{{roomLeader.name}}</div>
<div class="leader-status userStatus">
<span class="color-ball leader-status {{roomLeader.status}}"></span>
<span class="status-text leader-status-text">{{roomLeader.status}}</span>
</div>
</div>
</div>
<a class="chat-now" href="/direct/{{roomLeader.username}}">Chat Now</a>
</div>
</li>
{{/if}}
{{#if canPreview}}
{{#if hasMore}}
<li class="load-more">

@ -97,6 +97,18 @@ Template.room.helpers({
return Session.get('uploading');
},
roomLeader() {
const roles = RoomRoles.find({rid: this._id, roles: 'leader'}).fetch();
if (roles.length > 0) {
const u = roles[0].u;
if (u._id === Meteor.user()._id) { return null; }
const currUser = RocketChat.models.Users.find({ _id: u._id}).fetch();
u['status'] = currUser.length > 0 ? 'online' : 'offline';
return u;
}
return null;
},
roomName() {
const roomData = Session.get(`roomData${ this._id }`);
if (!roomData) { return ''; }
@ -241,6 +253,7 @@ let isSocialSharingOpen = false;
let touchMoved = false;
let lastTouchX = null;
let lastTouchY = null;
let lastScrollTop;
Template.room.events({
'click, touchend'(e, t) {
@ -255,6 +268,16 @@ Template.room.events({
}
},
'scroll .messages-box .wrapper'() {
const $wrapper = $('.messages-box .wrapper');
if ($wrapper.scrollTop() < lastScrollTop) {
$('.room-leader').removeClass('hidden');
} else if ($wrapper.scrollTop() > $('.room-leader-container').height()) {
$('.room-leader').addClass('hidden');
}
lastScrollTop = $wrapper.scrollTop();
},
'touchstart .message'(e, t) {
const touches = e.originalEvent.touches;
if (touches && touches.length) {
@ -418,7 +441,7 @@ Template.room.events({
},
'click .user-card-message'(e, instance) {
if (!Meteor.userId()) {
if (!Meteor.userId() || !this._arguments) {
return;
}
const roomData = Session.get(`roomData${ this._arguments[1].rid }`);
@ -805,6 +828,7 @@ Template.room.onRendered(function() {
$('.flex-tab-bar').on('click', (/*e, t*/) =>
Meteor.setTimeout(() => template.sendToBottomIfNecessaryDebounced(), 50)
);
lastScrollTop = $('.messages-box .wrapper').scrollTop();
const rtl = $('html').hasClass('rtl');

@ -0,0 +1,67 @@
Meteor.methods({
addRoomLeader(rid, userId) {
check(rid, String);
check(userId, String);
if (!Meteor.userId()) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', {
method: 'addRoomLeader'
});
}
if (!RocketChat.authz.hasPermission(Meteor.userId(), 'set-leader', rid)) {
throw new Meteor.Error('error-not-allowed', 'Not allowed', {
method: 'addRoomLeader'
});
}
const user = RocketChat.models.Users.findOneById(userId);
if (!user || !user.username) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', {
method: 'addRoomLeader'
});
}
const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(rid, user._id);
if (!subscription) {
throw new Meteor.Error('error-user-not-in-room', 'User is not in this room', {
method: 'addRoomLeader'
});
}
if (Array.isArray(subscription.roles) === true && subscription.roles.includes('leader') === true) {
throw new Meteor.Error('error-user-already-leader', 'User is already a leader', {
method: 'addRoomLeader'
});
}
RocketChat.models.Subscriptions.addRoleById(subscription._id, 'leader');
const fromUser = RocketChat.models.Users.findOneById(Meteor.userId());
RocketChat.models.Messages.createSubscriptionRoleAddedWithRoomIdAndUser(rid, user, {
u: {
_id: fromUser._id,
username: fromUser.username
},
role: 'leader'
});
if (RocketChat.settings.get('UI_DisplayRoles')) {
RocketChat.Notifications.notifyLogged('roles-change', {
type: 'added',
_id: 'leader',
u: {
_id: user._id,
username: user.username,
name: user.name
},
scope: rid
});
}
return true;
}
});

@ -0,0 +1,67 @@
Meteor.methods({
removeRoomLeader(rid, userId) {
check(rid, String);
check(userId, String);
if (!Meteor.userId()) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', {
method: 'removeRoomLeader'
});
}
if (!RocketChat.authz.hasPermission(Meteor.userId(), 'set-leader', rid)) {
throw new Meteor.Error('error-not-allowed', 'Not allowed', {
method: 'removeRoomLeader'
});
}
const user = RocketChat.models.Users.findOneById(userId);
if (!user || !user.username) {
throw new Meteor.Error('error-invalid-user', 'Invalid user', {
method: 'removeRoomLeader'
});
}
const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(rid, user._id);
if (!subscription) {
throw new Meteor.Error('error-user-not-in-room', 'User is not in this room', {
method: 'removeRoomLeader'
});
}
if (Array.isArray(subscription.roles) === true && subscription.roles.includes('leader') === false) {
throw new Meteor.Error('error-user-not-leader', 'User is not a leader', {
method: 'removeRoomLeader'
});
}
RocketChat.models.Subscriptions.removeRoleById(subscription._id, 'leader');
const fromUser = RocketChat.models.Users.findOneById(Meteor.userId());
RocketChat.models.Messages.createSubscriptionRoleRemovedWithRoomIdAndUser(rid, user, {
u: {
_id: fromUser._id,
username: fromUser.username
},
role: 'leader'
});
if (RocketChat.settings.get('UI_DisplayRoles')) {
RocketChat.Notifications.notifyLogged('roles-change', {
type: 'removed',
_id: 'leader',
u: {
_id: user._id,
username: user.username,
name: user.name
},
scope: rid
});
}
return true;
}
});
Loading…
Cancel
Save