mirror of
https://github.com/roundcube/roundcubemail.git
synced 2025-12-27 23:45:58 +00:00
Move autocomplete list rendering to client side (#9832)
* basic support for autocomplete list rendering on client side * remove 'contact_search_name' config var, add 'rcube_addressbook::compose_autocomplete_fields()' * add contactlist_name_template config replacement for contact_search_name
This commit is contained in:
parent
dec1d668ed
commit
39821c8a56
@ -1290,10 +1290,11 @@ $config['addressbook_search_mode'] = 0;
|
||||
// Warning: These are field names not LDAP attributes (see 'fieldmap' setting)!
|
||||
$config['contactlist_fields'] = ['name', 'firstname', 'surname', 'email'];
|
||||
|
||||
// Template of contact entry on the autocompletion list.
|
||||
// You can use contact fields as: name, email, organization, department, etc.
|
||||
// See program/actions/contacts/index.php for a list
|
||||
$config['contact_search_name'] = '{name} <{email}>';
|
||||
// Template of contact entry on contacts and autocompletion list.
|
||||
// You can use any field listed in contactlist_fields.
|
||||
// Example: '{name} ({organization})'
|
||||
// Default: '{name}'.
|
||||
$config['contactlist_name_template'] = '{name}';
|
||||
|
||||
// Contact mode. If your contacts are mostly business, switch it to 'business'.
|
||||
// This will prioritize form fields related to 'work' (instead of 'home').
|
||||
|
||||
@ -101,10 +101,10 @@ class acl extends rcube_plugin
|
||||
}
|
||||
|
||||
if ($user) {
|
||||
$display = rcube_addressbook::compose_search_name($record);
|
||||
$user = ['name' => $user, 'display' => $display];
|
||||
$fields = rcube_addressbook::compose_search_fields($record);
|
||||
$user = ['name' => $user, 'fields' => $fields];
|
||||
$users[] = $user;
|
||||
$keys[] = $display ?: $user['name'];
|
||||
$keys[] = $fields['name'] ?? $user['name'];
|
||||
}
|
||||
}
|
||||
|
||||
@ -118,7 +118,7 @@ class acl extends rcube_plugin
|
||||
$group_id = is_array($record[$group_field]) ? $record[$group_field][0] : $record[$group_field];
|
||||
|
||||
if ($group) {
|
||||
$users[] = ['name' => ($prefix ?: '') . $group_id, 'display' => $group, 'type' => 'group'];
|
||||
$users[] = ['name' => ($prefix ?: '') . $group_id, 'fields' => ['name' => $group], 'type' => 'group'];
|
||||
$keys[] = $group;
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,18 +83,14 @@ class rcmail_action_mail_autocomplete extends rcmail_action
|
||||
'source' => $abook_id,
|
||||
];
|
||||
|
||||
$display = rcube_addressbook::compose_search_name($record, $email, $name);
|
||||
|
||||
if ($display && $display != $contact['name']) {
|
||||
$contact['display'] = $display;
|
||||
}
|
||||
$contact['fields'] = rcube_addressbook::compose_search_fields($record, $email, $name);
|
||||
|
||||
// groups with defined email address will not be expanded to its members' addresses
|
||||
if ($contact['type'] == 'group') {
|
||||
$contact['email'] = $email;
|
||||
}
|
||||
|
||||
$name = !empty($contact['display']) ? $contact['display'] : $name;
|
||||
$name = !empty($contact['fields']['name']) ? $contact['fields']['name'] : $name;
|
||||
$contacts[$index] = $contact;
|
||||
$sort_keys[$index] = sprintf('%s %03d', $name, $idx++);
|
||||
|
||||
@ -130,6 +126,7 @@ class rcmail_action_mail_autocomplete extends rcmail_action
|
||||
$contacts[$index] = [
|
||||
'name' => $index,
|
||||
'email' => $email,
|
||||
'fields' => ['name' => $index, 'email' => $email],
|
||||
'type' => 'group',
|
||||
'id' => $group['ID'],
|
||||
'source' => $abook_id,
|
||||
@ -147,6 +144,7 @@ class rcmail_action_mail_autocomplete extends rcmail_action
|
||||
$sort_keys[$group['name']] = $group['name'];
|
||||
$contacts[$group['name']] = [
|
||||
'name' => $group['name'] . ' (' . intval($result->count) . ')',
|
||||
'fields' => ['name' => $group['name'] . ' (' . intval($result->count) . ')'],
|
||||
'type' => 'group',
|
||||
'id' => $group['ID'],
|
||||
'source' => $abook_id,
|
||||
|
||||
@ -6345,10 +6345,11 @@ function rcube_webmail() {
|
||||
if (results && (len = results.length)) {
|
||||
for (i = 0; i < len && maxlen > 0; i++) {
|
||||
text = typeof results[i] === 'object' ? (results[i].display || results[i].name) : results[i];
|
||||
fields = typeof results[i] === 'object' && results[i].fields ? results[i].fields : { name: text };
|
||||
type = typeof results[i] === 'object' ? results[i].type : '';
|
||||
id = i + this.env.contacts.length;
|
||||
$('<li>').attr({ id: 'rcmkSearchItem' + id, role: 'option' })
|
||||
.html('<i class="icon"></i>' + this.quote_html(text.replace(new RegExp('(' + RegExp.escape(value) + ')', 'ig'), '##$1%%')).replace(/##([^%]+)%%/g, '<b>$1</b>'))
|
||||
.html(this.ksearch_results_display(fields, value))
|
||||
.addClass(type || '')
|
||||
.appendTo(ul)
|
||||
.mouseover(function () {
|
||||
@ -6384,6 +6385,24 @@ function rcube_webmail() {
|
||||
}
|
||||
};
|
||||
|
||||
this.ksearch_results_display = function (fields, search_term) {
|
||||
line = "<i class='icon'></i>{name} <{email}>";
|
||||
|
||||
$.each(fields, function (key, data) {
|
||||
line = line.replace('{' + key + '}', data ? ref.ksearch_results_highlight(data, search_term) : '');
|
||||
});
|
||||
line = line.replace(/\{[a-z]+\}/ug, '');
|
||||
line = line.replace(/\s*<>/ug, '');
|
||||
line = line.replace(/\s+/ug, ' ');
|
||||
line = line.trim();
|
||||
|
||||
return line;
|
||||
};
|
||||
|
||||
this.ksearch_results_highlight = function (haystack, needle) {
|
||||
return this.quote_html(haystack.replace(new RegExp('(' + RegExp.escape(needle) + ')', 'ig'), '##$1%%')).replace(/##([^%]+)%%/g, '<b>$1</b>');
|
||||
};
|
||||
|
||||
// Getter for input value
|
||||
// returns a string from the last comma to current cursor position
|
||||
this.ksearch_input_get = function () {
|
||||
|
||||
@ -679,7 +679,7 @@ abstract class rcube_addressbook
|
||||
*/
|
||||
public static function compose_list_name($contact)
|
||||
{
|
||||
static $compose_mode;
|
||||
static $compose_mode, $template;
|
||||
|
||||
if (!isset($compose_mode)) {
|
||||
$compose_mode = (int) rcube::get_instance()->config->get('addressbook_name_listing', 0);
|
||||
@ -745,6 +745,16 @@ abstract class rcube_addressbook
|
||||
}
|
||||
}
|
||||
|
||||
if ($fn !== '') {
|
||||
if (!isset($template)) { // cache this
|
||||
$template = rcube::get_instance()->config->get('contactlist_name_template', '{name}');
|
||||
}
|
||||
|
||||
if ($template !== '{name}') {
|
||||
$fn = self::compose_search_name($contact, null, $fn, $template);
|
||||
}
|
||||
}
|
||||
|
||||
return $fn;
|
||||
}
|
||||
|
||||
@ -754,64 +764,76 @@ abstract class rcube_addressbook
|
||||
* @param array $contact Hash array with contact data as key-value pairs
|
||||
* @param string $email Optional email address
|
||||
* @param string $name Optional name (self::compose_list_name() result)
|
||||
* @param string $templ Optional template to use (defaults to the 'contact_search_name' config option)
|
||||
* @param string $templ Optional template to use (defaults to '{name} <{email}>')
|
||||
*
|
||||
* @return string Display name
|
||||
*/
|
||||
public static function compose_search_name($contact, $email = null, $name = null, $templ = null)
|
||||
public static function compose_search_name($contact, $email = null, $name = null, $templ = '{name} <{email}>')
|
||||
{
|
||||
static $template;
|
||||
|
||||
if (empty($templ) && !isset($template)) { // cache this
|
||||
$template = rcube::get_instance()->config->get('contact_search_name');
|
||||
if (empty($template)) {
|
||||
$template = '{name} <{email}>';
|
||||
if (preg_match_all('/\{([a-z]+)\}/', $templ, $matches)) {
|
||||
$values = self::compose_search_fields($contact, $email, $name, $matches[1]);
|
||||
foreach ($values as $key => $value) {
|
||||
$templ = str_replace('{' . $key . '}', $value, $templ);
|
||||
}
|
||||
}
|
||||
|
||||
$result = $templ ?: $template;
|
||||
$templ = preg_replace('/\s+/u', ' ', $templ);
|
||||
$templ = preg_replace('/\s*(<>|\(\)|\[\])/u', '', $templ);
|
||||
$templ = trim($templ, '/ ');
|
||||
|
||||
if (preg_match_all('/\{[a-z]+\}/', $result, $matches)) {
|
||||
foreach ($matches[0] as $key) {
|
||||
$key = trim($key, '{}');
|
||||
$value = '';
|
||||
return $templ;
|
||||
}
|
||||
|
||||
switch ($key) {
|
||||
case 'name':
|
||||
$value = $name ?: self::compose_list_name($contact);
|
||||
/**
|
||||
* Build contact display name for search result listing
|
||||
*
|
||||
* @param array $contact Hash array with contact data as key-value pairs
|
||||
* @param string $email Optional email address
|
||||
* @param string $name Optional name (self::compose_list_name() result)
|
||||
* @param array $fields Optional fields to return (defaults to ['name', 'email'])
|
||||
*
|
||||
* @return array Fields
|
||||
*/
|
||||
public static function compose_search_fields($contact, $email = null, $name = null, $fields = ['name', 'email'])
|
||||
{
|
||||
$result = [];
|
||||
|
||||
// If name(s) are undefined compose_list_name() may return an email address
|
||||
// here we prevent from returning the same name and email
|
||||
if ($name === $email && str_contains($result, '{email}')) {
|
||||
$value = '';
|
||||
}
|
||||
foreach ($fields as $key) {
|
||||
$value = '';
|
||||
|
||||
break;
|
||||
case 'email':
|
||||
$value = $email;
|
||||
break;
|
||||
}
|
||||
switch ($key) {
|
||||
case 'name':
|
||||
$value = $name ?: self::compose_list_name($contact);
|
||||
|
||||
if (empty($value)) {
|
||||
$value = strpos($key, ':') ? $contact[$key] : self::get_col_values($key, $contact, true);
|
||||
if (is_array($value) && isset($value[0])) {
|
||||
$value = $value[0];
|
||||
// If name(s) are undefined compose_list_name() may return an email address
|
||||
// here we prevent from returning the same name and email
|
||||
if ($name === $email && in_array('email', $fields) !== false) {
|
||||
$value = '';
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_string($value)) {
|
||||
$value = '';
|
||||
}
|
||||
|
||||
$result = str_replace('{' . $key . '}', $value, $result);
|
||||
break;
|
||||
case 'email':
|
||||
$value = $email;
|
||||
break;
|
||||
}
|
||||
|
||||
if (empty($value)) {
|
||||
$value = strpos($key, ':') ? $contact[$key] : self::get_col_values($key, $contact, true);
|
||||
if (is_array($value) && isset($value[0])) {
|
||||
$value = $value[0];
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_string($value)) {
|
||||
$value = '';
|
||||
}
|
||||
|
||||
$result[$key] = $value;
|
||||
}
|
||||
|
||||
$result = preg_replace('/\s+/u', ' ', $result);
|
||||
$result = preg_replace('/\s*(<>|\(\)|\[\])/u', '', $result);
|
||||
$result = trim($result, '/ ');
|
||||
$plugin = rcube::get_instance()->plugins->exec_hook('compose_search_fields', ['contact' => $contact, 'email' => $email, 'name' => $name, 'fields' => $result]);
|
||||
|
||||
return $result;
|
||||
return $plugin['fields'];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -335,12 +335,35 @@ html.touch {
|
||||
&:extend(.font-icon-class);
|
||||
content: @fa-var-user;
|
||||
margin-left: .5rem;
|
||||
line-height: normal;
|
||||
}
|
||||
&.group > i:before {
|
||||
content: @fa-var-users;
|
||||
}
|
||||
}
|
||||
|
||||
#rcmKSearchpane > ul > li {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
padding: .5em 0;
|
||||
|
||||
& > span.fields {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
width: auto;
|
||||
overflow: hidden;
|
||||
margin: 0 .25em;
|
||||
|
||||
& > span.field {
|
||||
flex: 0 0 100%;
|
||||
font-size: 1rem;
|
||||
line-height: normal;
|
||||
.overflow-ellipsis();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
html.ie11 .listing.iconized li a:before {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
@ -4450,6 +4450,21 @@ if (window.rcmail) {
|
||||
// delegate to rcube_elastic_ui
|
||||
return rcmail.triggerEvent('menu-close', { name: name, props: { menu: name }, originalEvent: event });
|
||||
};
|
||||
|
||||
/**
|
||||
* Elastic version of ksearch_results_display with small screen support
|
||||
*/
|
||||
rcmail.ksearch_results_display = function (fields, search_term) {
|
||||
var line = $('<li>')
|
||||
.append($('<i>').addClass('icon'))
|
||||
.append($('<span>').addClass('fields'));
|
||||
|
||||
$.each(fields, function (key, data) {
|
||||
line.children('span.fields').append($('<span>').addClass('field ' + key).html(data ? rcmail.ksearch_results_highlight(data, search_term) : ''));
|
||||
});
|
||||
|
||||
return line.html();
|
||||
};
|
||||
} else {
|
||||
// rcmail does not exists e.g. on the error template inside a frame
|
||||
// we fake the engine a little
|
||||
|
||||
Loading…
Reference in New Issue
Block a user