From ccc21a8c94d9ee3d9cb2d1f869837bd03336ac9d Mon Sep 17 00:00:00 2001
From: mrgoldy <gijsmartens1@gmail.com>
Date: Mon, 9 Sep 2019 16:27:16 +0200
Subject: [PATCH] [ticket/12539] Live member search improvements

PHPBB3-12539
---
 phpBB/assets/javascript/core.js               | 96 ++++++++++++++++++-
 .../prosilver/template/memberlist_search.html |  2 +-
 phpBB/styles/prosilver/theme/colours.css      |  5 +
 phpBB/styles/prosilver/theme/forms.css        |  2 +-
 4 files changed, 100 insertions(+), 5 deletions(-)

diff --git a/phpBB/assets/javascript/core.js b/phpBB/assets/javascript/core.js
index 2ce20a3b1a..817adecfc4 100644
--- a/phpBB/assets/javascript/core.js
+++ b/phpBB/assets/javascript/core.js
@@ -11,7 +11,9 @@ phpbb.alertTime = 100;
 var keymap = {
 	TAB: 9,
 	ENTER: 13,
-	ESC: 27
+	ESC: 27,
+	ARROW_UP: 38,
+	ARROW_DOWN: 40
 };
 
 var $dark = $('#darkenwrapper');
@@ -561,7 +563,8 @@ phpbb.search.setValue = function($input, value, multiline) {
 phpbb.search.setValueOnClick = function($input, value, $row, $container) {
 	$row.click(function() {
 		phpbb.search.setValue($input, value.result, $input.attr('data-multiline'));
-		$container.hide();
+
+		phpbb.search.closeResults($input, $container);
 	});
 };
 
@@ -584,9 +587,16 @@ phpbb.search.filter = function(data, event, sendRequest) {
 		searchID = $this.attr('data-results'),
 		keyword = phpbb.search.getKeyword($this, data[dataName], $this.attr('data-multiline')),
 		cache = phpbb.search.cache.get(searchID),
+		key = event.keyCode || event.which,
 		proceed = true;
 	data[dataName] = keyword;
 
+	// No need to search if enter was pressed
+	// for selecting a value from the results.
+	if (key === keymap.ENTER) {
+		return false;
+	}
+
 	if (cache.timeout) {
 		clearTimeout(cache.timeout);
 	}
@@ -697,6 +707,8 @@ phpbb.search.showResults = function(results, $input, $container, callback) {
 		row.appendTo($resultContainer).show();
 	});
 	$container.show();
+
+	phpbb.search.navigateResults($input, $container, $resultContainer);
 };
 
 /**
@@ -708,11 +720,89 @@ phpbb.search.clearResults = function($container) {
 	$container.children(':not(.search-result-tpl)').remove();
 };
 
+/**
+ * Close search results.
+ *
+ * @param {jQuery} $input Search input|textarea.
+ * @param {jQuery} $container Search results container.
+ */
+phpbb.search.closeResults = function($input, $container) {
+	$input.off('.searchNavigation');
+	$container.hide();
+};
+
+/**
+ * Navigate search results.
+ *
+ * @param {jQuery} $input Search input|textarea.
+ * @param {jQuery} $container Search results container.
+ * @param {jQuery} $resultContainer	Search results list container.
+ */
+phpbb.search.navigateResults = function($input, $container, $resultContainer) {
+	// Add a namespace to the event (.searchNavigation),
+	// so it can be unbound specifically later on.
+	$input.on('keydown.searchNavigation', function(event) {
+		let key = event.keyCode || event.which,
+			$active = $resultContainer.children('.active');
+
+		switch (key) {
+			// Set the value for the selected result
+			case keymap.ENTER:
+				if ($active.length) {
+					let value = $active.find('.search-result > span').text();
+
+					phpbb.search.setValue($input, value, $input.attr('data-multiline'));
+				}
+
+				phpbb.search.closeResults($input, $container);
+
+				// Do not submit the form
+				event.preventDefault();
+			break;
+
+			// Close the results
+			case keymap.ESC:
+				phpbb.search.closeResults($input, $container);
+			break;
+
+			// Navigate the results
+			case keymap.ARROW_DOWN:
+			case keymap.ARROW_UP:
+				let up = key === keymap.ARROW_UP;
+
+				if (!$active.length) {
+					if (up) {
+						$resultContainer.children().last().addClass('active');
+					} else {
+						$resultContainer.children().first().addClass('active');
+					}
+				} else {
+					if (up) {
+						if ($active.is(':first-child')) {
+							$resultContainer.children().last().addClass('active');
+						} else {
+							$active.prev().addClass('active');
+						}
+					} else {
+						if ($active.is(':last-child')) {
+							$resultContainer.children().first().addClass('active');
+						} else {
+							$active.next().addClass('active');
+						}
+					}
+
+					$active.removeClass('active');
+				}
+			break;
+		}
+	});
+};
+
 $('#phpbb').click(function() {
 	var $this = $(this);
 
 	if (!$this.is('.live-search') && !$this.parents().is('.live-search')) {
-		$('.live-search').hide();
+		phpbb.search.closeResults($('input, textarea'), $('.live-search'));
 	}
 });
 
diff --git a/phpBB/styles/prosilver/template/memberlist_search.html b/phpBB/styles/prosilver/template/memberlist_search.html
index b1c7a81709..34915ebc41 100644
--- a/phpBB/styles/prosilver/template/memberlist_search.html
+++ b/phpBB/styles/prosilver/template/memberlist_search.html
@@ -12,7 +12,7 @@
 		<dt><label for="username">{L_USERNAME}{L_COLON}</label></dt>
 		<dd>
 			<!-- IF U_LIVE_SEARCH --><div class="dropdown-container dropdown-{S_CONTENT_FLOW_END}"><!-- ENDIF -->
-			<input type="text" name="username" id="username" value="{USERNAME}" class="inputbox"<!-- IF U_LIVE_SEARCH --> autocomplete="off" data-filter="phpbb.search.filter" data-ajax="member_search" data-min-length="3" data-url="{U_LIVE_SEARCH}" data-results="#user-search" data-overlay="false"<!-- ENDIF --> />
+			<input type="text" name="username" id="username" value="{USERNAME}" class="inputbox"<!-- IF U_LIVE_SEARCH --> autocomplete="off" data-filter="phpbb.search.filter" data-ajax="member_search" data-min-length="3" data-url="{U_LIVE_SEARCH}" data-results="#user-search"<!-- ENDIF --> />
 			<!-- IF U_LIVE_SEARCH -->
 				<div class="dropdown live-search hidden" id="user-search">
 					<div class="pointer"><div class="pointer-inner"></div></div>
diff --git a/phpBB/styles/prosilver/theme/colours.css b/phpBB/styles/prosilver/theme/colours.css
index ffaa71034f..1ead493926 100644
--- a/phpBB/styles/prosilver/theme/colours.css
+++ b/phpBB/styles/prosilver/theme/colours.css
@@ -672,6 +672,11 @@ Colours and backgrounds for buttons.css
 	box-shadow: 0 0 10px #0075B0;
 }
 
+.search-results li:hover,
+.search-results li.active {
+	background-color: #CFE1F6;
+}
+
 /* Icon images
 ---------------------------------------- */
 
diff --git a/phpBB/styles/prosilver/theme/forms.css b/phpBB/styles/prosilver/theme/forms.css
index 5646a7d6c7..99c898f41e 100644
--- a/phpBB/styles/prosilver/theme/forms.css
+++ b/phpBB/styles/prosilver/theme/forms.css
@@ -355,7 +355,7 @@ input.button3 {
 	font-variant: small-caps;
 }
 
-input[type="button"], input[type="submit"], input[type="reset"], input[type="checkbox"], input[type="radio"] {
+input[type="button"], input[type="submit"], input[type="reset"], input[type="checkbox"], input[type="radio"], .search-results li {
 	cursor: pointer;
 }