use Bugzilla::Extension::Gravatar::Data qw( %gravatar_user_map );
use Bugzilla::User::Setting;
+use Bugzilla::WebService::Util qw(filter_wants);
use Digest::MD5 qw(md5_hex);
+use Scalar::Util qw(blessed);
use constant DEFAULT_URL => 'extensions/Gravatar/web/default.jpg';
});
}
+#
+# hooks
+#
+
+sub webservice_user_get {
+ my ($self, $args) = @_;
+ my ($webservice, $params, $users) = @$args{qw(webservice params users)};
+
+ return unless filter_wants($params, 'gravatar');
+
+ my $ids = [
+ map { blessed($_->{id}) ? $_->{id}->value : $_->{id} }
+ grep { exists $_->{id} }
+ @$users
+ ];
+
+ return unless @$ids;
+
+ my %user_map = map { $_->id => $_ } @{ Bugzilla::User->new_from_list($ids) };
+ foreach my $user (@$users) {
+ my $id = blessed($user->{id}) ? $user->{id}->value : $user->{id};
+ my $user_obj = $user_map{$id};
+ $user->{gravatar} = $user_obj->gravatar;
+ }
+}
+
+sub webservice_user_suggest {
+ my ($self, $args) = @_;
+ $self->webservice_user_get($args);
+}
+
__PACKAGE__->NAME;
*/
$(function() {
-
- // single user
-
function searchComplete() {
var that = $(this);
that.data('counter', that.data('counter') - 1);
noCache: true,
tabDisabled: true,
autoSelectFirst: true,
+ preserveInput: true,
triggerSelectOnValidInput: false,
transformResult: function(response) {
response = $.parseJSON(response);
return {
- suggestions: $.map(response.users, function(dataItem) {
+ suggestions: $.map(response.users, function({ name, real_name, requests, gravatar } = {}) {
return {
- value: dataItem.name,
- data : { login: dataItem.name, name: dataItem.real_name }
+ value: name,
+ data : { email: name, real_name, requests, gravatar }
};
})
};
},
- formatResult: function(suggestion, currentValue) {
- return (suggestion.data.name === '' ?
- suggestion.data.login : suggestion.data.name + ' (' + suggestion.data.login + ')')
- .htmlEncode();
+ formatResult: function(suggestion) {
+ const $input = this;
+ const user = suggestion.data;
+ const request_type = $input.getAttribute('data-request-type');
+ const blocked = user.requests && request_type ? user.requests[request_type].blocked : false;
+ const pending = user.requests && request_type ? user.requests[request_type].pending : 0;
+ const image = user.gravatar ? `<img itemprop="image" alt="" src="${user.gravatar}">` : '';
+ const description = blocked ? '<span class="icon" aria-hidden="true"></span> Requests blocked' :
+ pending ? `${pending} pending ${request_type}${pending === 1 ? '' : 's'}` : '';
+
+ return `<div itemscope itemtype="http://schema.org/Person">${image} ` +
+ `<span itemprop="name">${user.real_name.htmlEncode()}</span> ` +
+ `<span class="minor" itemprop="email">${user.email.htmlEncode()}</span> ` +
+ `<span class="minor${blocked ? ' blocked' : ''}" itemprop="description">${description}</span></div>`;
+ },
+ onSelect: function (suggestion) {
+ const $input = this;
+ const user = suggestion.data;
+ const is_multiple = !!$input.getAttribute('data-multiple');
+ const request_type = $input.getAttribute('data-request-type');
+ const blocked = user.requests && request_type ? user.requests[request_type].blocked : false;
+
+ if (blocked) {
+ window.alert(`${user.real_name} is not accepting ${request_type} requests at this time. ` +
+ 'If you’re in a hurry, ask someone else for help.');
+ } else if (is_multiple) {
+ const _values = $input.value.split(',').map(value => value.trim());
+
+ _values.pop();
+ _values.push(suggestion.value);
+ $input.value = _values.join(', ') + ', ';
+ } else {
+ $input.value = suggestion.value;
+ }
+
+ $input.focus();
},
onSearchStart: function(params) {
var that = $(this);
onSearchError: searchComplete
};
- // multiple users (based on single user)
- var options_users = {
- delimiter: /,\s*/,
- onSelect: function() {
- this.value = this.value + ', ';
- this.focus();
- },
- };
- $.extend(options_users, options_user);
-
// init user autocomplete fields
$('.bz_autocomplete_user')
.each(function() {
- var that = $(this);
- that.data('counter', 0);
- if (that.data('multiple')) {
- that.devbridgeAutocomplete(options_users);
- }
- else {
- that.devbridgeAutocomplete(options_user);
- }
- that.addClass('bz_autocomplete');
+ const $input = this;
+ const is_multiple = !!$input.getAttribute('data-multiple');
+ const options = Object.assign({}, options_user);
+
+ options.delimiter = is_multiple ? /,\s*/ : undefined;
+ // Override `this` in the relevant functions
+ options.formatResult = options.formatResult.bind($input);
+ options.onSelect = options.onSelect.bind($input);
+
+ $input.dataset.counter = 0;
+ $input.classList.add('bz_autocomplete');
+ $(this).devbridgeAutocomplete(options);
});
// init autocomplete fields with array of values
.autocomplete-suggestions {
border: 1px solid #999;
+ border-radius: 4px;
background: #fff;
color: #000;
+ box-shadow: 0 2px 8px rgba(0, 0, 0, .3);
overflow-x: hidden;
overflow-y: auto;
cursor: pointer;
}
.autocomplete-suggestion {
- padding: 2px 5px;
+ padding: 4px 6px;
white-space: nowrap;
overflow: hidden;
width: 100%;
- margin-right: 1.5em;
+ box-sizing: border-box;
+}
+
+.autocomplete-suggestion [itemtype] {
+ display: flex;
+ align-items: center;
+ padding: 2px 2px 2px 0;
+ font-size: 14px;
+ pointer-events: none;
+}
+
+.autocomplete-suggestion [itemtype] > span {
+ margin-left: 12px;
+}
+
+.autocomplete-suggestion [itemtype] > span:first-of-type,
+.autocomplete-suggestion [itemtype] > span:empty {
+ margin-left: 0;
+}
+
+.autocomplete-suggestion [itemtype] img {
+ margin-right: 6px;
+ border-radius: 50%;
+ width: 20px;
+ height: 20px;
+}
+
+.autocomplete-suggestion [itemtype] .minor {
+ opacity: .7;
+ font-size: 13px;
+}
+
+.autocomplete-suggestion [itemtype] .blocked {
+ margin-left: 8px;
+ border-radius: 12px;
+ padding: 1px 6px 1px 3px;
+ color: #C00;
+ background-color: #FFF;
+ opacity: 1;
+}
+
+.autocomplete-suggestion [itemtype] .blocked .icon::before {
+ font-size: 15px;
+ font-family: 'Material Icons';
+ vertical-align: -3px;
+ content: '\E033';
}
.autocomplete-selected {