Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,22 @@
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
line-height: 32px;
vertical-align: middle;
}

.customgroups-autocomplete-item .autocomplete-item-displayname {
margin-right: 5px;
}

.customgroups-autocomplete-item .autocomplete-item-typeInfo {
font-size: smaller;
font-style: italic;
}

.customgroups-autocomplete-item .avatardiv {
flex-shrink: 0;
}

.custom-group-buttons {
margin-top: 5px;
}
Expand Down
3 changes: 2 additions & 1 deletion js/GroupModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@

davProperties: {
'displayName': NS + 'display-name',
'role': NS + 'role'
'role': NS + 'role',
'userTypeInfo': NS + 'user-type-info'
},

initialize: function() {
Expand Down
3 changes: 2 additions & 1 deletion js/MemberModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@

davProperties: {
'role': NS + 'role',
'userDisplayName': NS + 'user-display-name'
'userDisplayName': NS + 'user-display-name',
'userTypeInfo': NS + 'user-type-info'
}
});

Expand Down
171 changes: 156 additions & 15 deletions js/MembersInputView.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@

className: 'member-input-view',

/** @type {string} **/
batchActionSeparator: ',',

template: function(data) {
return OCA.CustomGroups.Templates.membersInput(data);
},
Expand Down Expand Up @@ -77,16 +80,43 @@
autocompleteHandler: function (search, response) {
var self = this;
var $loading = this.$el.find('.loading');
var trimmedSearch = search.term.trim();
this.$field.tooltip('hide');
$loading.removeClass('hidden');
$loading.addClass('inlineblock');

if (trimmedSearch.indexOf(this.batchActionSeparator) !== -1) {
return this._getUsersForBatchAction(trimmedSearch).then(function (res) {
$loading.addClass('hidden');
$loading.removeClass('inlineblock');
if (res.found.length) {
var labelArray = [];
for (var i = 0; i < res.found.length; i++) {
labelArray.push(res.found[i].displayName);
}
return response({
osc: {
batch: res.found,
failedBatch: res.notFound,
displayName: labelArray.join(', '),
userId: labelArray.join(', '),
typeInfo: t('core', 'Add multiple users and guests')
}
});
}

self._displayError(t('core', 'No users found'));
return response();
})
}

$.ajax({
url: OC.generateUrl('/apps/customgroups/members'),
contentType: 'application/json',
dataType: 'json',
data: {
group: this.groupUri,
pattern: search.term.trim(),
pattern: trimmedSearch,
limit: 200
}
}).done(function (result, type, xhr) {
Expand Down Expand Up @@ -114,15 +144,7 @@
response(entries);
} else {
var title = t('core', 'No users found for {search}', {search: self.$field.val()});
self.$field.addClass('error')
.attr('data-original-title', title)
.tooltip('hide')
.tooltip({
placement: 'bottom',
trigger: 'manual'
})
.tooltip('fixTitle')
.tooltip('show');
self._displayError(title);
response();
}
}
Expand All @@ -134,9 +156,17 @@
},

autocompleteRenderItem: function($ul, item) {
var typeInfo;
if (item.typeInfo) {
typeInfo = item.typeInfo;
} else {
typeInfo = item.type === 'guest' ? t('customgroups', 'Guest') : t('customgroups', 'User');
}

var $item = $(this.itemTemplate({
displayName: item.displayName,
userId: item.userId
userId: item.userId,
typeInfo: typeInfo
}));

/* jshint camelcase:false */
Expand All @@ -149,11 +179,122 @@

_onSelect: function(e, s) {
e.preventDefault();
this.trigger('select', {
userId: s.item.userId,
displayName: s.item.displayName
});
var members = s.item.batch || [s.item];

if (s.item.failedBatch && s.item.failedBatch.length) {
var failedUsersStr = s.item.failedBatch.join(', ');
OC.Notification.show(
t('core', 'Could not add the following users: {users}', {users: failedUsersStr}),
{type: 'error'}
);
}

for (var i = 0; i < members.length; i++) {
var member = members[i];
this.trigger('select', {
userId: member.userId,
displayName: member.displayName,
type: member.type
});
$(e.target).val(member.userId).blur();
}
$(e.target).val(s.item.userId).blur();
},

/**
* Displays an error, e.g. when the autocomplete doesn't have results.
*
* @param {string} title - title of the error
* @private
*/
_displayError: function(title) {
this.$field.addClass('error')
.attr('data-original-title', title)
.tooltip('hide')
.tooltip({
placement: 'bottom',
trigger: 'manual'
})
.tooltip('fixTitle')
.tooltip('show');
},

/**
* Returns a promise which includes all fetched users for batch actions once resolved.
*
* @param {string} search - trimmed search term
* @returns {Promise}
* @private
*/
_getUsersForBatchAction: function(search) {
var foundUsers = [];
var notFound = [];
var promises = [];
var users = Array.from(new Set(search.split(this.batchActionSeparator)));

for (var i = 0; i < users.length; i++) {
if (!users[i]) {
continue;
}
var user = users[i].trim();
promises.push(
$.ajax({
url: OC.generateUrl('/apps/customgroups/members'),
contentType: 'application/json',
dataType: 'json',
context: { user: user },
data: {
group: this.groupUri,
pattern: user,
limit: 200
},
}).done(function (result) {
for (var j = 0; j < result.results.length; j++) {
var userToAdd = result.results[j];
var addUser = true;

// only add users that match exact with the search term
if (this.user.toLowerCase() !== userToAdd.userId.toLowerCase()
&& this.user.toLowerCase() !== userToAdd.displayName.toLowerCase()) {
continue;
}

// only add new users
for (var k = 0; k < foundUsers.length; k++) {
if (foundUsers[k].userId.toLowerCase() === userToAdd.userId.toLowerCase()) {
addUser = false;
break;
}
}

if (addUser) {
foundUsers.push(userToAdd);
}
}
})
)
}

return Promise.all(promises).then(function() {
for (var i = 0; i < users.length; i++) {
if (!users[i]) continue;
var user = users[i].trim();
var userAdded = false;

for (var j = 0; j < foundUsers.length; j++) {
var search = user.toLowerCase();
if (search === foundUsers[j].userId.toLowerCase()
|| search === foundUsers[j].displayName.toLowerCase()) {
userAdded = true;
break;
}
}
if (!userAdded) {
notFound.push(user);
}
}
return {found: foundUsers, notFound: notFound};
});
}
});

Expand Down
20 changes: 16 additions & 4 deletions js/MembersView.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,9 +202,16 @@
var $loading = this.$('.member-input-view .loading');
$loading.removeClass('hidden');

var displayName = data.displayName || userId
var role = null
if (data.type === 'guest') {
displayName = userId
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment from JP:

we might need a comment here. The displayname should be already initialized above prioritizing the displayname over the userid. If guests will use the userid anyway I think this needs a comment to explain it's intentional, otherwise it might be a bug because one type of users will use the displayname while others the userid.

role = 'guest'
}
this.collection.create({
id: userId,
userDisplayName: data.displayName || userId
userDisplayName: displayName,
userTypeInfo: role
}, {
wait: true,
success: function() {
Expand Down Expand Up @@ -335,6 +342,13 @@
},

_formatMember: function(member) {
var roleDisplayName = t('customgroups', 'Member');
if (member.get('role') === OCA.CustomGroups.ROLE_ADMIN) {
roleDisplayName = t('customgroups', 'Group owner');
} else if (member.get('userTypeInfo') === 'guest') {
roleDisplayName = t('customgroups', 'Member (Guest)');
}

return {
id: member.id,
displayName: member.get('userDisplayName'),
Expand All @@ -344,9 +358,7 @@
t('customgroups', 'Change role to "group owner"'),
deleteLabel: t('customgroups', 'Remove member'),
canAdmin: OC.isUserAdmin() || this.model.get('role') === OCA.CustomGroups.ROLE_ADMIN,
roleDisplayName: (member.get('role') === OCA.CustomGroups.ROLE_ADMIN) ?
t('customgroups', 'Group owner') :
t('customgroups', 'Member')
roleDisplayName: roleDisplayName
};
},

Expand Down
6 changes: 5 additions & 1 deletion js/templates/membersInputItem.handlebars
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
<a>
<div class="customgroups-autocomplete-item" title="{{userId}}">
<div class='avatardiv'></div>
<div class='autocomplete-item-text'>{{displayName}}</div>
<div class="autocomplete-item-text">
<span class="autocomplete-item-displayname">{{displayName}}</span>
<br/>
<span class="autocomplete-item-typeInfo">{{typeInfo}}</span>
</div>
</div>
</a>
</li>
25 changes: 23 additions & 2 deletions lib/Controller/PageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,11 @@

namespace OCA\CustomGroups\Controller;

use OCA\CustomGroups\Service\GuestIntegrationHelper;
use OCA\Guests\Controller\UsersController;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\Constants;
use OCP\IUser;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
Expand Down Expand Up @@ -59,6 +62,10 @@ class PageController extends Controller {
* @var IUserManager
*/
private $userManager;
/**
* @var GuestIntegrationHelper
*/
private $guestIntegrationHelper;

public function __construct(
$appName,
Expand All @@ -67,14 +74,16 @@ public function __construct(
IUserSession $userSession,
IUserManager $userManager,
IGroupManager $groupManager,
CustomGroupsDatabaseHandler $handler
CustomGroupsDatabaseHandler $handler,
GuestIntegrationHelper $guestIntegrationHelper
) {
parent::__construct($appName, $request);
$this->handler = $handler;
$this->config = $config;
$this->userSession = $userSession;
$this->userManager = $userManager;
$this->groupManager = $groupManager;
$this->guestIntegrationHelper = $guestIntegrationHelper;
}

/**
Expand Down Expand Up @@ -280,10 +289,22 @@ public function searchUsers($group, $pattern, $limit = 200) {
$results = \array_map(function (IUser $entry) {
return [
'userId' => $entry->getUID(),
'displayName' => $entry->getDisplayName()
'displayName' => $entry->getDisplayName(),
'type' => 'user'
];
}, $results);

# guest integration
Copy link
Copy Markdown
Contributor

@JammingBen JammingBen Jul 18, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment from JP:

I'm a bit confused here and maybe we need a comment clarifying.
Aren't guests shown as part of the regular result? or is this additional result a placeholder so the user can click on it to add that new user as a guest?

if (!$results && $this->guestIntegrationHelper->canBeGuest($pattern)) {
$results = [
[
'userId' => $pattern,
'displayName' => $pattern,
'type' => 'guest'
]
];
}

return new DataResponse(['results' => $results], Http::STATUS_OK);
}
}
Loading