data-mention-url="{U_MENTION_URL}" data-mention-names-limit="{S_MENTION_NAMES_LIMIT}" data-topic-id="{S_TOPIC_ID}">
+
data-mention-url="{U_MENTION_URL}" data-mention-names-limit="{S_MENTION_NAMES_LIMIT}" data-mention-batch-size="{S_MENTION_BATCH_SIZE}" data-topic-id="{S_TOPIC_ID}">
diff --git a/phpBB/assets/javascript/editor.js b/phpBB/assets/javascript/editor.js
index aa39c15568..f15b776889 100644
--- a/phpBB/assets/javascript/editor.js
+++ b/phpBB/assets/javascript/editor.js
@@ -386,21 +386,40 @@ function getCaretPosition(txtarea) {
(function($) {
function Mentions() {
- let $mentionDataContainer = $('[data-mention-url]:first'), cachedNames = null, filteredNamesCount = 0;
+ let $mentionDataContainer = $('[data-mention-url]:first');
+ let cachedNames = null;
+ let cachedFor = null;
+ let cachedSearchKey = 'name';
function defaultAvatar(type) {
return (type === 'group') ? '
' : '
';
}
+ function getMatchedNames(query, items, searchKey) {
+ let i, len;
+ let _results = [];
+ for (i = 0, len = items.length; i < len; i++) {
+ let item = items[i];
+ if (String(item[searchKey]).toLowerCase().indexOf(query.toLowerCase()) === 0) {
+ _results.push(item);
+ }
+ }
+ return _results;
+ }
+
+ function getNumberOfMatchedCachedNames(query) {
+ return getMatchedNames(query, cachedNames, cachedSearchKey).length;
+ }
+
this.isEnabled = function() {
return $mentionDataContainer.length;
};
this.handle = function(txtarea) {
- let mentionURL = $mentionDataContainer.data('mentionUrl'),
- mentionNamesLimit = $mentionDataContainer.data('mentionNamesLimit'),
- mentionTopicId = $mentionDataContainer.data('topicId'),
- defaultFilter = $.fn.atwho['default'].callbacks.filter;
+ let mentionURL = $mentionDataContainer.data('mentionUrl');
+ let mentionBatchSize = $mentionDataContainer.data('mentionBatchSize');
+ let mentionNamesLimit = $mentionDataContainer.data('mentionNamesLimit');
+ let mentionTopicId = $mentionDataContainer.data('topicId');
$(txtarea).atwho({
at: "@",
acceptSpaceBar: true,
@@ -413,51 +432,78 @@ function getCaretPosition(txtarea) {
insertTpl: "[mention=${type}:${id}]${name}[/mention]",
limit: mentionNamesLimit,
callbacks: {
- filter: function(query, data, searchKey) {
- data = defaultFilter(query, data, searchKey);
-
- // Update our cached statistics used by remoteFilter
- filteredNamesCount = data.length;
-
- return data;
- },
remoteFilter: function(query, callback) {
- if (cachedNames && filteredNamesCount >= mentionNamesLimit) {
+ /*
+ * Use cached values when we can:
+ * 1) There are some names in the cache
+ * 2) The cache contains relevant data for the query
+ * (it was made for the query with the same first characters)
+ * 3) We have enough names to display OR
+ * all relevant names have been fetched from the server
+ */
+ if (cachedNames &&
+ query.indexOf(cachedFor) === 0 &&
+ (getNumberOfMatchedCachedNames(query) >= mentionNamesLimit ||
+ cachedNames.length < mentionBatchSize)) {
callback(cachedNames);
return;
}
+
let params = {keyword: query, topic_id: mentionTopicId, _referer: location.href};
$.getJSON(mentionURL, params, function (data) {
cachedNames = data;
+ cachedFor = query;
callback(data);
});
},
sorter: function(query, items, searchKey) {
- let _unsorted, _results, _exactMatch, i, item, len, highestPriority = 0;
- _unsorted = {u: {}, g: {}};
- _exactMatch = [];
+ let i, len;
+ let highestPriority = 0;
+ let _unsorted = {u: {}, g: {}};
+ let _exactMatch = [];
+ let _results = [];
+
+ // Reduce the items array to the relevant ones
+ items = getMatchedNames(query, items, searchKey);
+
+ // Group names by their types and calculate priorities
for (i = 0, len = items.length; i < len; i++) {
- item = items[i];
+ let item = items[i];
+
+ // Exact matches should not be prioritised - they always come first
if (item.name === query) {
_exactMatch.push(items[i]);
continue;
}
+
+ // Check for unsupported type - in general, this should never happen
if (!_unsorted[item.type]) {
continue;
}
+
+ // If the item hasn't been added yet - add it
+ // Group names do not have priorities and are also handled here
if (!_unsorted[item.type][item.id] || item.type === 'g') {
_unsorted[item.type][item.id] = item;
continue;
}
+
+ // Priority is calculated as the sum of priorities from different sources
_unsorted[item.type][item.id].priority += parseFloat(item.priority);
+
+ // Calculate the highest priority - we'll give it to group names
highestPriority = Math.max(highestPriority, _unsorted[item.type][item.id].priority);
}
- _results = [];
+
+ // Push user names to the result array
if (_unsorted['u']) {
$.each(_unsorted['u'], function(name, value) {
_results.push(value);
});
}
+
+ // Push group names to the result array and give them the highest priority
+ // They will be sorted together with the usernames on top of the list
if (_unsorted['g']) {
$.each(_unsorted['g'], function(name, value) {
// Groups should come at the same level of importance
@@ -466,12 +512,17 @@ function getCaretPosition(txtarea) {
_results.push(value);
});
}
+
+ // Sort names by priorities - higher values come first
_results = _results.sort(function(a, b) {
- return a.priority - b.priority;
+ return b.priority - a.priority;
});
+
+ // Exact match is the most important - should come above anything else
$.each(_exactMatch, function(name, value) {
_results.unshift(value);
});
+
return _results;
}
}
diff --git a/phpBB/includes/functions.php b/phpBB/includes/functions.php
index bc9bc3dbd9..e00c2fc25d 100644
--- a/phpBB/includes/functions.php
+++ b/phpBB/includes/functions.php
@@ -3947,6 +3947,7 @@ function page_header($page_title = '', $display_online_list = false, $item_id =
'U_FEED' => $controller_helper->route('phpbb_feed_index'),
'S_ALLOW_MENTIONS' => ($config['allow_mentions'] && $auth->acl_get('u_mention') && (empty($forum_id) || $auth->acl_get('f_mention', $forum_id))) ? true : false,
+ 'S_MENTION_BATCH_SIZE' => 100, // TODO: do not hardcode the value
'S_MENTION_NAMES_LIMIT' => $config['mention_names_limit'],
'U_MENTION_URL' => $controller_helper->route('phpbb_mention_controller'),
diff --git a/phpBB/includes/functions_acp.php b/phpBB/includes/functions_acp.php
index a2ffe0b1b2..e1d3159e02 100644
--- a/phpBB/includes/functions_acp.php
+++ b/phpBB/includes/functions_acp.php
@@ -90,6 +90,7 @@ function adm_page_header($page_title)
'U_INDEX' => append_sid("{$phpbb_root_path}index.$phpEx"),
'S_ALLOW_MENTIONS' => ($config['allow_mentions'] && $auth->acl_get('u_mention')) ? true : false,
+ 'S_MENTION_BATCH_SIZE' => 100, // TODO: do not hardcode the value
'S_MENTION_NAMES_LIMIT' => $config['mention_names_limit'],
'U_MENTION_URL' => $controller_helper->route('phpbb_mention_controller'),
diff --git a/phpBB/phpbb/mention/controller/mention.php b/phpBB/phpbb/mention/controller/mention.php
index 6548a8a995..a188ea6bf8 100644
--- a/phpBB/phpbb/mention/controller/mention.php
+++ b/phpBB/phpbb/mention/controller/mention.php
@@ -55,7 +55,7 @@ class mention
foreach ($this->mention_sources as $source)
{
- $names += $source->get($keyword, $topic_id);
+ $source->get($names, $keyword, $topic_id);
}
return new JsonResponse(array_values($names));
diff --git a/phpBB/phpbb/mention/source/base_group.php b/phpBB/phpbb/mention/source/base_group.php
index b8d6c44091..d7037f77ae 100644
--- a/phpBB/phpbb/mention/source/base_group.php
+++ b/phpBB/phpbb/mention/source/base_group.php
@@ -109,7 +109,7 @@ abstract class base_group implements source_interface
/**
* {@inheritdoc}
*/
- public function get($keyword, $topic_id)
+ public function get(array &$names, $keyword, $topic_id)
{
// Grab all group IDs
$result = $this->db->sql_query($this->query($keyword, $topic_id));
@@ -128,11 +128,10 @@ abstract class base_group implements source_interface
$matches = preg_grep('/^' . preg_quote($keyword) . '.*/i', $groups['names']);
$group_ids = array_intersect($group_ids, array_flip($matches));
- $names = [];
foreach ($group_ids as $group_id)
{
$group_rank = phpbb_get_user_rank($groups[$group_id], false);
- $names[] = [
+ array_push($names, [
'name' => $groups[$group_id]['group_name'],
'type' => 'g',
'id' => $group_id,
@@ -141,7 +140,7 @@ abstract class base_group implements source_interface
'img' => phpbb_get_group_avatar($groups[$group_id]),
],
'rank' => $group_rank['title'],
- ];
+ ]);
}
return $names;
diff --git a/phpBB/phpbb/mention/source/base_user.php b/phpBB/phpbb/mention/source/base_user.php
index 45f5e3b917..7ac830fc6e 100644
--- a/phpBB/phpbb/mention/source/base_user.php
+++ b/phpBB/phpbb/mention/source/base_user.php
@@ -73,16 +73,15 @@ abstract class base_user implements source_interface
/**
* {@inheritdoc}
*/
- public function get($keyword, $topic_id)
+ public function get(array &$names, $keyword, $topic_id)
{
$keyword = utf8_clean_string($keyword);
$result = $this->db->sql_query_limit($this->query($keyword, $topic_id), self::NAMES_BATCH_SIZE);
- $names = [];
while ($row = $this->db->sql_fetchrow($result))
{
$user_rank = $this->user_loader->get_rank($row['user_id'], true);
- $names[] = [
+ array_push($names, [
'name' => $row['username'],
'type' => 'u',
'id' => $row['user_id'],
@@ -92,7 +91,7 @@ abstract class base_user implements source_interface
],
'rank' => (isset($user_rank['rank_title'])) ? $user_rank['rank_title'] : '',
'priority' => $this->get_priority($row),
- ];
+ ]);
}
$this->db->sql_freeresult($result);
diff --git a/phpBB/phpbb/mention/source/friend.php b/phpBB/phpbb/mention/source/friend.php
index 3c946dcd73..60e706b458 100644
--- a/phpBB/phpbb/mention/source/friend.php
+++ b/phpBB/phpbb/mention/source/friend.php
@@ -45,7 +45,6 @@ class friend extends base_user
]
],
'WHERE' => 'z.friend = 1 AND z.user_id = ' . (int) $this->user->data['user_id'] . '
- AND u.user_id <> ' . ANONYMOUS . '
AND ' . $this->db->sql_in_set('u.user_type', [USER_NORMAL, USER_FOUNDER]) . '
AND u.username_clean ' . $this->db->sql_like_expression($keyword . $this->db->get_any_char()),
'ORDER_BY' => 'u.user_lastvisit DESC'
diff --git a/phpBB/phpbb/mention/source/source_interface.php b/phpBB/phpbb/mention/source/source_interface.php
index ace5cc9149..43b6106363 100644
--- a/phpBB/phpbb/mention/source/source_interface.php
+++ b/phpBB/phpbb/mention/source/source_interface.php
@@ -19,9 +19,10 @@ interface source_interface
* Searches database for names to mention
* and returns and array of found items
*
+ * @param array $names Array of already fetched data with names
* @param string $keyword Search string
* @param int $topic_id Current topic ID
* @return array Array of names
*/
- public function get($keyword, $topic_id);
+ public function get(array &$names, $keyword, $topic_id);
}
diff --git a/phpBB/phpbb/mention/source/team.php b/phpBB/phpbb/mention/source/team.php
index 187f0fb691..2cfffd9f82 100644
--- a/phpBB/phpbb/mention/source/team.php
+++ b/phpBB/phpbb/mention/source/team.php
@@ -28,7 +28,6 @@ class team extends base_user
TEAMPAGE_TABLE => 't',
],
'WHERE' => 'ug.group_id = t.group_id AND ug.user_id = u.user_id AND ug.user_pending = 0
- AND u.user_id <> ' . ANONYMOUS . '
AND ' . $this->db->sql_in_set('u.user_type', [USER_NORMAL, USER_FOUNDER]) . '
AND u.username_clean ' . $this->db->sql_like_expression($keyword . $this->db->get_any_char()),
'ORDER_BY' => 'u.user_lastvisit DESC'
diff --git a/phpBB/phpbb/mention/source/topic.php b/phpBB/phpbb/mention/source/topic.php
index f8a5123c56..688495ddc7 100644
--- a/phpBB/phpbb/mention/source/topic.php
+++ b/phpBB/phpbb/mention/source/topic.php
@@ -31,7 +31,7 @@ class topic extends base_user
'ON' => 'u.user_id = p.poster_id'
]
],
- 'WHERE' => 'p.topic_id = ' . $topic_id . ' AND u.user_id <> ' . ANONYMOUS . '
+ 'WHERE' => 'p.topic_id = ' . $topic_id . '
AND ' . $this->db->sql_in_set('u.user_type', [USER_NORMAL, USER_FOUNDER]) . '
AND u.username_clean ' . $this->db->sql_like_expression($keyword . $this->db->get_any_char()),
'ORDER_BY' => 'p.post_time DESC'
diff --git a/phpBB/phpbb/mention/source/user.php b/phpBB/phpbb/mention/source/user.php
index 10f065df0d..8a03bc41a8 100644
--- a/phpBB/phpbb/mention/source/user.php
+++ b/phpBB/phpbb/mention/source/user.php
@@ -25,8 +25,7 @@ class user extends base_user
'FROM' => [
USERS_TABLE => 'u',
],
- 'WHERE' => 'u.user_id <> ' . ANONYMOUS . '
- AND ' . $this->db->sql_in_set('u.user_type', [USER_NORMAL, USER_FOUNDER]) . '
+ 'WHERE' => $this->db->sql_in_set('u.user_type', [USER_NORMAL, USER_FOUNDER]) . '
AND u.username_clean ' . $this->db->sql_like_expression($keyword . $this->db->get_any_char()),
'ORDER_BY' => 'u.user_lastvisit DESC'
]);
diff --git a/phpBB/styles/prosilver/template/posting_buttons.html b/phpBB/styles/prosilver/template/posting_buttons.html
index 5765a29dca..449f677dd9 100644
--- a/phpBB/styles/prosilver/template/posting_buttons.html
+++ b/phpBB/styles/prosilver/template/posting_buttons.html
@@ -39,7 +39,7 @@