mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-01-05 23:30:36 +01:00
closes #3741
This commit is contained in:
@@ -77,7 +77,8 @@
|
||||
"group-name-too-short": "Group name too short",
|
||||
"group-already-exists": "Group already exists",
|
||||
"group-name-change-not-allowed": "Group name change not allowed",
|
||||
"group-already-member": "You are already part of this group",
|
||||
"group-already-member": "Already part of this group",
|
||||
"group-not-member": "Not a member of this group",
|
||||
"group-needs-owner": "This group requires at least one owner",
|
||||
"group-already-invited": "This user has already been invited",
|
||||
"group-already-requested": "Your membership request has already been submitted",
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
.group {
|
||||
.current_members {
|
||||
[component="groups/members"] {
|
||||
padding: 0;
|
||||
tbody {
|
||||
max-height: 500px;
|
||||
display: block;
|
||||
overflow-y: auto;
|
||||
padding-bottom: 100px;
|
||||
.member-name {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
img {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
"use strict";
|
||||
/*global define, templates, socket, ajaxify, app, admin, bootbox, utils, config */
|
||||
/*global define, templates, socket, ajaxify, app, bootbox, translator */
|
||||
|
||||
define('admin/manage/group', [
|
||||
'forum/groups/memberlist',
|
||||
'iconSelect',
|
||||
'admin/modules/colorpicker'
|
||||
], function(iconSelect, colorpicker) {
|
||||
], function(memberList, iconSelect, colorpicker) {
|
||||
var Groups = {};
|
||||
|
||||
Groups.init = function() {
|
||||
var groupDetailsSearch = $('#group-details-search'),
|
||||
groupDetailsSearchResults = $('#group-details-search-results'),
|
||||
groupMembersEl = $('ul.current_members'),
|
||||
groupIcon = $('#group-icon'),
|
||||
changeGroupUserTitle = $('#change-group-user-title'),
|
||||
changeGroupLabelColor = $('#change-group-label-color'),
|
||||
@@ -20,6 +20,8 @@ define('admin/manage/group', [
|
||||
|
||||
var groupName = ajaxify.data.group.name;
|
||||
|
||||
memberList.init(true);
|
||||
|
||||
changeGroupUserTitle.keyup(function() {
|
||||
groupLabelPreview.text(changeGroupUserTitle.val());
|
||||
});
|
||||
@@ -46,10 +48,17 @@ define('admin/manage/group', [
|
||||
}
|
||||
|
||||
groupDetailsSearchResults.empty();
|
||||
|
||||
for (x = 0; x < numResults; x++) {
|
||||
foundUser = $('<li />');
|
||||
foundUser
|
||||
.attr({title: results.users[x].username, 'data-uid': results.users[x].uid})
|
||||
.attr({title:
|
||||
results.users[x].username,
|
||||
'data-uid': results.users[x].uid,
|
||||
'data-username': results.users[x].username,
|
||||
'data-userslug': results.users[x].userslug,
|
||||
'data-picture': results.users[x].picture
|
||||
})
|
||||
.append($('<img />').attr('src', results.users[x].picture))
|
||||
.append($('<span />').html(results.users[x].username));
|
||||
|
||||
@@ -64,45 +73,74 @@ define('admin/manage/group', [
|
||||
|
||||
groupDetailsSearchResults.on('click', 'li[data-uid]', function() {
|
||||
var userLabel = $(this),
|
||||
uid = parseInt(userLabel.attr('data-uid'), 10),
|
||||
members = [];
|
||||
uid = parseInt(userLabel.attr('data-uid'), 10);
|
||||
|
||||
groupMembersEl.find('li[data-uid]').each(function() {
|
||||
members.push(parseInt($(this).attr('data-uid'), 10));
|
||||
});
|
||||
|
||||
if (members.indexOf(uid) === -1) {
|
||||
socket.emit('admin.groups.join', {
|
||||
groupName: groupName,
|
||||
uid: uid
|
||||
}, function(err, data) {
|
||||
if (!err) {
|
||||
groupMembersEl.append(userLabel.clone(true));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
groupMembersEl.on('click', 'li[data-uid]', function() {
|
||||
var uid = $(this).attr('data-uid');
|
||||
|
||||
bootbox.confirm('Are you sure you want to remove this user?', function(confirm) {
|
||||
if (!confirm) {
|
||||
return;
|
||||
socket.emit('admin.groups.join', {
|
||||
groupName: groupName,
|
||||
uid: uid
|
||||
}, function(err) {
|
||||
if (err) {
|
||||
return app.alertError(err.message);
|
||||
}
|
||||
|
||||
socket.emit('admin.groups.leave', {
|
||||
groupName: groupName,
|
||||
uid: uid
|
||||
}, function(err, data) {
|
||||
if (err) {
|
||||
return app.alertError(err.message);
|
||||
}
|
||||
groupMembersEl.find('li[data-uid="' + uid + '"]').remove();
|
||||
var member = {
|
||||
uid: userLabel.attr('data-uid'),
|
||||
username: userLabel.attr('data-username'),
|
||||
userslug: userLabel.attr('data-userslug'),
|
||||
picture: userLabel.attr('data-picture')
|
||||
};
|
||||
|
||||
templates.parse('partials/groups/memberlist', 'members', {group: {isOwner: ajaxify.data.group.isOwner, members: [member]}}, function(html) {
|
||||
translator.translate(html, function(html) {
|
||||
$('[component="groups/members"] tr').first().before(html);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
$('[component="groups/members"]').on('click', '[data-action]', function() {
|
||||
var btnEl = $(this),
|
||||
userRow = btnEl.parents('[data-uid]'),
|
||||
ownerFlagEl = userRow.find('.member-name i'),
|
||||
isOwner = !ownerFlagEl.hasClass('invisible') ? true : false,
|
||||
uid = userRow.attr('data-uid'),
|
||||
action = btnEl.attr('data-action');
|
||||
|
||||
switch(action) {
|
||||
case 'toggleOwnership':
|
||||
socket.emit('groups.' + (isOwner ? 'rescind' : 'grant'), {
|
||||
toUid: uid,
|
||||
groupName: groupName
|
||||
}, function(err) {
|
||||
if (err) {
|
||||
return app.alertError(err.message);
|
||||
}
|
||||
ownerFlagEl.toggleClass('invisible');
|
||||
});
|
||||
break;
|
||||
|
||||
case 'kick':
|
||||
bootbox.confirm('Are you sure you want to remove this user?', function(confirm) {
|
||||
if (!confirm) {
|
||||
return;
|
||||
}
|
||||
socket.emit('admin.groups.leave', {
|
||||
uid: uid,
|
||||
groupName: groupName
|
||||
}, function(err) {
|
||||
if (err) {
|
||||
return app.alertError(err.message);
|
||||
}
|
||||
userRow.slideUp().remove();
|
||||
});
|
||||
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
$('#group-icon').on('click', function() {
|
||||
iconSelect.init(groupIcon);
|
||||
});
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
"use strict";
|
||||
/* globals define, socket, ajaxify, app, bootbox, RELATIVE_PATH, utils */
|
||||
/* globals define, socket, ajaxify, app, bootbox, utils */
|
||||
|
||||
define('forum/groups/details', [
|
||||
'forum/groups/memberlist',
|
||||
'iconSelect',
|
||||
'components',
|
||||
'vendor/colorpicker/colorpicker',
|
||||
'vendor/jquery/draggable-background/backgroundDraggable'
|
||||
], function(memberList, iconSelect, components) {
|
||||
|
||||
define('forum/groups/details', ['iconSelect', 'components', 'forum/infinitescroll', 'vendor/colorpicker/colorpicker', 'vendor/jquery/draggable-background/backgroundDraggable'], function(iconSelect, components, infinitescroll) {
|
||||
var Details = {
|
||||
cover: {}
|
||||
};
|
||||
|
||||
var searchInterval;
|
||||
var groupName;
|
||||
|
||||
Details.init = function() {
|
||||
var detailsPage = components.get('groups/container'),
|
||||
settingsFormEl = detailsPage.find('form');
|
||||
var detailsPage = components.get('groups/container');
|
||||
|
||||
groupName = ajaxify.data.group.name;
|
||||
|
||||
@@ -20,8 +25,8 @@ define('forum/groups/details', ['iconSelect', 'components', 'forum/infinitescrol
|
||||
Details.initialiseCover();
|
||||
}
|
||||
|
||||
handleMemberSearch();
|
||||
handleMemberInfiniteScroll();
|
||||
memberList.init();
|
||||
|
||||
handleMemberInvitations();
|
||||
|
||||
components.get('groups/activity').find('.content img:not(.not-responsive)').addClass('img-responsive');
|
||||
@@ -291,44 +296,6 @@ define('forum/groups/details', ['iconSelect', 'components', 'forum/infinitescrol
|
||||
});
|
||||
};
|
||||
|
||||
function handleMemberSearch() {
|
||||
$('[component="groups/members/search"]').on('keyup', function() {
|
||||
var query = $(this).val();
|
||||
if (searchInterval) {
|
||||
clearInterval(searchInterval);
|
||||
searchInterval = 0;
|
||||
}
|
||||
|
||||
searchInterval = setTimeout(function() {
|
||||
socket.emit('groups.searchMembers', {groupName: groupName, query: query}, function(err, results) {
|
||||
if (err) {
|
||||
return app.alertError(err.message);
|
||||
}
|
||||
|
||||
infinitescroll.parseAndTranslate('groups/details', 'members', {
|
||||
group: {
|
||||
members: results.users,
|
||||
isOwner: ajaxify.data.group.isOwner
|
||||
}
|
||||
}, function(html) {
|
||||
$('[component="groups/members"] tbody').html(html);
|
||||
$('[component="groups/members"]').attr('data-nextstart', 20);
|
||||
});
|
||||
});
|
||||
}, 250);
|
||||
});
|
||||
}
|
||||
|
||||
function handleMemberInfiniteScroll() {
|
||||
$('[component="groups/members"] tbody').on('scroll', function() {
|
||||
var $this = $(this);
|
||||
var bottom = ($this[0].scrollHeight - $this.height()) * 0.9;
|
||||
if ($this.scrollTop() > bottom) {
|
||||
loadMoreMembers();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleMemberInvitations() {
|
||||
if (ajaxify.data.group.isOwner) {
|
||||
var searchInput = $('[component="groups/members/invite"]');
|
||||
@@ -349,48 +316,5 @@ define('forum/groups/details', ['iconSelect', 'components', 'forum/infinitescrol
|
||||
}
|
||||
}
|
||||
|
||||
function loadMoreMembers() {
|
||||
|
||||
var members = $('[component="groups/members"]');
|
||||
if (members.attr('loading')) {
|
||||
return;
|
||||
}
|
||||
|
||||
members.attr('loading', 1);
|
||||
socket.emit('groups.loadMoreMembers', {
|
||||
groupName: groupName,
|
||||
after: members.attr('data-nextstart')
|
||||
}, function(err, data) {
|
||||
if (err) {
|
||||
return app.alertError(err.message);
|
||||
}
|
||||
|
||||
if (data && data.users.length) {
|
||||
onMembersLoaded(data.users, function() {
|
||||
members.removeAttr('loading');
|
||||
members.attr('data-nextstart', data.nextStart);
|
||||
});
|
||||
} else {
|
||||
members.removeAttr('loading');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function onMembersLoaded(users, callback) {
|
||||
users = users.filter(function(user) {
|
||||
return !$('[component="groups/members"] [data-uid="' + user.uid + '"]').length;
|
||||
});
|
||||
|
||||
infinitescroll.parseAndTranslate('groups/details', 'members', {
|
||||
group: {
|
||||
members: users,
|
||||
isOwner: ajaxify.data.group.isOwner
|
||||
}
|
||||
}, function(html) {
|
||||
$('[component="groups/members"] tbody').append(html);
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
return Details;
|
||||
});
|
||||
97
public/src/client/groups/memberlist.js
Normal file
97
public/src/client/groups/memberlist.js
Normal file
@@ -0,0 +1,97 @@
|
||||
"use strict";
|
||||
/* globals define, socket, ajaxify, app */
|
||||
|
||||
define('forum/groups/memberlist', ['components', 'forum/infinitescroll'], function(components, infinitescroll) {
|
||||
|
||||
var MemberList = {};
|
||||
var searchInterval;
|
||||
var groupName;
|
||||
|
||||
MemberList.init = function() {
|
||||
groupName = ajaxify.data.group.name;
|
||||
|
||||
handleMemberSearch();
|
||||
handleMemberInfiniteScroll();
|
||||
};
|
||||
|
||||
function handleMemberSearch() {
|
||||
$('[component="groups/members/search"]').on('keyup', function() {
|
||||
var query = $(this).val();
|
||||
if (searchInterval) {
|
||||
clearInterval(searchInterval);
|
||||
searchInterval = 0;
|
||||
}
|
||||
|
||||
searchInterval = setTimeout(function() {
|
||||
socket.emit('groups.searchMembers', {groupName: groupName, query: query}, function(err, results) {
|
||||
if (err) {
|
||||
return app.alertError(err.message);
|
||||
}
|
||||
parseAndTranslate(results.users, function(html) {
|
||||
$('[component="groups/members"] tbody').html(html);
|
||||
$('[component="groups/members"]').attr('data-nextstart', 20);
|
||||
});
|
||||
});
|
||||
}, 250);
|
||||
});
|
||||
}
|
||||
|
||||
function handleMemberInfiniteScroll() {
|
||||
$('[component="groups/members"] tbody').on('scroll', function() {
|
||||
var $this = $(this);
|
||||
var bottom = ($this[0].scrollHeight - $this.innerHeight()) * 0.9;
|
||||
|
||||
if ($this.scrollTop() > bottom) {
|
||||
loadMoreMembers();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadMoreMembers() {
|
||||
var members = $('[component="groups/members"]');
|
||||
if (members.attr('loading')) {
|
||||
return;
|
||||
}
|
||||
|
||||
members.attr('loading', 1);
|
||||
socket.emit('groups.loadMoreMembers', {
|
||||
groupName: groupName,
|
||||
after: members.attr('data-nextstart')
|
||||
}, function(err, data) {
|
||||
if (err) {
|
||||
return app.alertError(err.message);
|
||||
}
|
||||
|
||||
if (data && data.users.length) {
|
||||
onMembersLoaded(data.users, function() {
|
||||
members.removeAttr('loading');
|
||||
members.attr('data-nextstart', data.nextStart);
|
||||
});
|
||||
} else {
|
||||
members.removeAttr('loading');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function onMembersLoaded(users, callback) {
|
||||
users = users.filter(function(user) {
|
||||
return !$('[component="groups/members"] [data-uid="' + user.uid + '"]').length;
|
||||
});
|
||||
|
||||
parseAndTranslate(users, function(html) {
|
||||
$('[component="groups/members"] tbody').append(html);
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
function parseAndTranslate(users, callback) {
|
||||
infinitescroll.parseAndTranslate('groups/details', 'members', {
|
||||
group: {
|
||||
members: users,
|
||||
isOwner: ajaxify.data.group.isOwner
|
||||
}
|
||||
}, callback);
|
||||
}
|
||||
|
||||
return MemberList;
|
||||
});
|
||||
@@ -59,12 +59,13 @@ groupsController.get = function(req, res, callback) {
|
||||
if (!exists) {
|
||||
return callback();
|
||||
}
|
||||
groups.get(groupName, {uid: req.uid}, next);
|
||||
groups.get(groupName, {uid: req.uid, truncateUserList: true, userListCount: 20}, next);
|
||||
}
|
||||
], function(err, group) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
group.isOwner = true;
|
||||
res.render('admin/manage/group', {group: group});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use strict";
|
||||
|
||||
var async = require('async');
|
||||
var groups = require('../../groups'),
|
||||
Groups = {};
|
||||
|
||||
@@ -20,7 +21,17 @@ Groups.join = function(socket, data, callback) {
|
||||
return callback(new Error('[[error:invalid-data]]'));
|
||||
}
|
||||
|
||||
groups.join(data.groupName, data.uid, callback);
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
groups.isMember(data.uid, data.groupName, next);
|
||||
},
|
||||
function (isMember, next) {
|
||||
if (isMember) {
|
||||
return next(new Error('[[error:group-already-member]]'));
|
||||
}
|
||||
groups.join(data.groupName, data.uid, next);
|
||||
}
|
||||
], callback);
|
||||
};
|
||||
|
||||
Groups.leave = function(socket, data, callback) {
|
||||
@@ -32,7 +43,17 @@ Groups.leave = function(socket, data, callback) {
|
||||
return callback(new Error('[[error:cant-remove-self-as-admin]]'));
|
||||
}
|
||||
|
||||
groups.leave(data.groupName, data.uid, callback);
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
groups.isMember(data.uid, data.groupName, next);
|
||||
},
|
||||
function (isMember, next) {
|
||||
if (!isMember) {
|
||||
return next(new Error('[[error:group-not-member]]'));
|
||||
}
|
||||
groups.leave(data.groupName, data.uid, next);
|
||||
}
|
||||
], callback);
|
||||
};
|
||||
|
||||
Groups.update = function(socket, data, callback) {
|
||||
|
||||
@@ -62,8 +62,11 @@ SocketGroups.leave = function(socket, data, callback) {
|
||||
|
||||
function isOwner(next) {
|
||||
return function (socket, data, callback) {
|
||||
groups.ownership.isOwner(socket.uid, data.groupName, function(err, isOwner) {
|
||||
if (err || !isOwner) {
|
||||
async.parallel({
|
||||
isAdmin: async.apply(user.isAdmin, socket.uid),
|
||||
isOwner: async.apply(groups.ownership.isOwner, socket.uid, data.groupName)
|
||||
}, function(err, results) {
|
||||
if (err || (!isOwner && !results.isAdmin)) {
|
||||
return callback(err || new Error('[[error:no-privileges]]'));
|
||||
}
|
||||
next(socket, data, callback);
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
waitSeconds: 3,
|
||||
urlArgs: "{cache-buster}",
|
||||
paths: {
|
||||
'forum': '../client',
|
||||
'admin': '../admin',
|
||||
'vendor': '../../vendor',
|
||||
'buzz': '../../vendor/buzz/buzz.min'
|
||||
|
||||
@@ -54,24 +54,20 @@
|
||||
<label for="add-member">Add User to Group</label>
|
||||
<input type="text" class="form-control" id="group-details-search" placeholder="Search Users" />
|
||||
<ul class="members user-list" id="group-details-search-results"></ul>
|
||||
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<label>Members</label>
|
||||
<p>Click on a user to remove them from the group</p>
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title"><i class="fa fa-users"></i> [[groups:details.members]]</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<ul class="members current_members user-list">
|
||||
<!-- BEGIN group.members -->
|
||||
<li data-uid="{group.members.uid}">
|
||||
<img src="{group.members.picture}" />
|
||||
<span>{group.members.username}</span>
|
||||
</li>
|
||||
<!-- END group.members -->
|
||||
</ul>
|
||||
<!-- IMPORT partials/groups/memberlist.tpl -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user