Search form enhancement 1
This commit is contained in:
@@ -4,4 +4,5 @@ Dokuwiki template specifically designed for luxtools plugins.
|
|||||||
This is a fork of the original default dokuwiki template.
|
This is a fork of the original default dokuwiki template.
|
||||||
|
|
||||||
## Changes from the original template
|
## Changes from the original template
|
||||||
- Default font changed to "Perfect DOS VGA 437 Win"
|
- Default font changed to "Perfect DOS VGA 437 Win"
|
||||||
|
- Enhanced header search with DokuWiki quicksearch suggestions, keyboard navigation, and disabled browser autofill
|
||||||
@@ -179,6 +179,44 @@
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
display: none;
|
display: none;
|
||||||
|
|
||||||
|
&.is-open {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-loading {
|
||||||
|
opacity: .9;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.search-suggestions {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.search-suggestions li {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.search-suggestions li a {
|
||||||
|
display: block;
|
||||||
|
padding: .15em .25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.search-suggestions li.is-active > a,
|
||||||
|
ul.search-suggestions li a:focus {
|
||||||
|
background-color: __highlight__;
|
||||||
|
color: @ini_text;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.search-suggestions li.is-loading,
|
||||||
|
ul.search-suggestions li.is-empty {
|
||||||
|
padding: .15em .25em;
|
||||||
|
color: @ini_text_alt;
|
||||||
|
}
|
||||||
|
|
||||||
strong {
|
strong {
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: .3em;
|
margin-bottom: .3em;
|
||||||
|
|||||||
@@ -243,6 +243,10 @@ body {
|
|||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#dokuwiki__sitetools form.search div.ajax_qsearch.is-open {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* action dropdown is alternative for all hidden tools */
|
/* action dropdown is alternative for all hidden tools */
|
||||||
#dokuwiki__header .mobileTools {
|
#dokuwiki__header .mobileTools {
|
||||||
display: block;
|
display: block;
|
||||||
|
|||||||
306
script.js
306
script.js
@@ -86,4 +86,310 @@ jQuery(function(){
|
|||||||
jQuery('#dokuwiki__pagetools div.tools>ul>li>a').on('click', function(){
|
jQuery('#dokuwiki__pagetools div.tools>ul>li>a').on('click', function(){
|
||||||
this.blur();
|
this.blur();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// enhance header search with suggestions and keyboard navigation
|
||||||
|
(function enhanceSearch(){
|
||||||
|
var $input = jQuery('#qsearch__in');
|
||||||
|
var $output = jQuery('#qsearch__out');
|
||||||
|
|
||||||
|
if ($input.length === 0 || $output.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var $form = $input.closest('form');
|
||||||
|
var debounceMs = 150;
|
||||||
|
var minChars = 2;
|
||||||
|
var maxSuggestions = 10;
|
||||||
|
var requestTimer = null;
|
||||||
|
var curRequest = null;
|
||||||
|
var items = [];
|
||||||
|
var activeIndex = -1;
|
||||||
|
var listId = 'qsearch__listbox';
|
||||||
|
|
||||||
|
$form
|
||||||
|
.addClass('search-enhanced')
|
||||||
|
.attr('autocomplete', 'off');
|
||||||
|
|
||||||
|
$input
|
||||||
|
.attr('autocomplete', 'off')
|
||||||
|
.attr('spellcheck', 'false')
|
||||||
|
.attr('aria-autocomplete', 'list')
|
||||||
|
.attr('aria-expanded', 'false')
|
||||||
|
.attr('aria-controls', listId)
|
||||||
|
.attr('aria-haspopup', 'listbox');
|
||||||
|
|
||||||
|
$output
|
||||||
|
.attr('aria-hidden', 'true');
|
||||||
|
|
||||||
|
// remove default quicksearch handlers
|
||||||
|
$input.off('keyup');
|
||||||
|
$output.off('click');
|
||||||
|
|
||||||
|
function clearActive() {
|
||||||
|
activeIndex = -1;
|
||||||
|
$input.removeAttr('aria-activedescendant');
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeList() {
|
||||||
|
if (curRequest) {
|
||||||
|
curRequest.abort();
|
||||||
|
curRequest = null;
|
||||||
|
}
|
||||||
|
if (requestTimer) {
|
||||||
|
window.clearTimeout(requestTimer);
|
||||||
|
requestTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
items = [];
|
||||||
|
clearActive();
|
||||||
|
$output
|
||||||
|
.removeClass('is-open is-loading')
|
||||||
|
.attr('aria-hidden', 'true')
|
||||||
|
.hide()
|
||||||
|
.empty();
|
||||||
|
$form.removeClass('is-searching');
|
||||||
|
$input.attr('aria-expanded', 'false');
|
||||||
|
}
|
||||||
|
|
||||||
|
function openList() {
|
||||||
|
$output
|
||||||
|
.addClass('is-open')
|
||||||
|
.attr('aria-hidden', 'false')
|
||||||
|
.show();
|
||||||
|
$input.attr('aria-expanded', 'true');
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderList(content) {
|
||||||
|
$output.empty().append(content);
|
||||||
|
openList();
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderLoading() {
|
||||||
|
var $list = jQuery('<ul />', {
|
||||||
|
'class': 'search-suggestions',
|
||||||
|
'id': listId,
|
||||||
|
'role': 'listbox',
|
||||||
|
'aria-busy': 'true'
|
||||||
|
});
|
||||||
|
$list.append(jQuery('<li />', {
|
||||||
|
'class': 'is-loading',
|
||||||
|
'role': 'option',
|
||||||
|
'aria-disabled': 'true',
|
||||||
|
'text': 'Loading…'
|
||||||
|
}));
|
||||||
|
$output.addClass('is-loading');
|
||||||
|
renderList($list);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderNoMatches() {
|
||||||
|
var $list = jQuery('<ul />', {
|
||||||
|
'class': 'search-suggestions',
|
||||||
|
'id': listId,
|
||||||
|
'role': 'listbox',
|
||||||
|
'aria-busy': 'false'
|
||||||
|
});
|
||||||
|
$list.append(jQuery('<li />', {
|
||||||
|
'class': 'is-empty',
|
||||||
|
'role': 'option',
|
||||||
|
'aria-disabled': 'true',
|
||||||
|
'text': 'No Matches'
|
||||||
|
}));
|
||||||
|
$output.removeClass('is-loading');
|
||||||
|
renderList($list);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateActive() {
|
||||||
|
var $options = $output.find('li[data-index]');
|
||||||
|
$options.removeClass('is-active').attr('aria-selected', 'false');
|
||||||
|
|
||||||
|
if (activeIndex < 0 || activeIndex >= items.length) {
|
||||||
|
clearActive();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var $active = $options.filter('[data-index="' + activeIndex + '"]');
|
||||||
|
$active.addClass('is-active').attr('aria-selected', 'true');
|
||||||
|
$input.attr('aria-activedescendant', $active.attr('id'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveActive(delta) {
|
||||||
|
if (!items.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (activeIndex < 0) {
|
||||||
|
activeIndex = delta > 0 ? 0 : items.length - 1;
|
||||||
|
} else {
|
||||||
|
activeIndex = (activeIndex + delta + items.length) % items.length;
|
||||||
|
}
|
||||||
|
updateActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectActive() {
|
||||||
|
if (activeIndex < 0 || activeIndex >= items.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
window.location.assign(items[activeIndex].href);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderResults(results) {
|
||||||
|
var $list = jQuery('<ul />', {
|
||||||
|
'class': 'search-suggestions',
|
||||||
|
'id': listId,
|
||||||
|
'role': 'listbox',
|
||||||
|
'aria-busy': 'false'
|
||||||
|
});
|
||||||
|
|
||||||
|
results.forEach(function (item, index) {
|
||||||
|
var optionId = 'qsearch__option_' + index;
|
||||||
|
var $link = jQuery('<a />', {
|
||||||
|
'href': item.href,
|
||||||
|
'text': item.text
|
||||||
|
});
|
||||||
|
var $option = jQuery('<li />', {
|
||||||
|
'id': optionId,
|
||||||
|
'role': 'option',
|
||||||
|
'data-index': index,
|
||||||
|
'aria-selected': 'false'
|
||||||
|
});
|
||||||
|
$option.append($link);
|
||||||
|
$list.append($option);
|
||||||
|
});
|
||||||
|
|
||||||
|
$output.removeClass('is-loading');
|
||||||
|
renderList($list);
|
||||||
|
clearActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleResults(data) {
|
||||||
|
$form.removeClass('is-searching');
|
||||||
|
curRequest = null;
|
||||||
|
|
||||||
|
var $wrapper = jQuery('<div />').html(data || '');
|
||||||
|
var $links = $wrapper.find('a');
|
||||||
|
items = [];
|
||||||
|
|
||||||
|
$links.each(function () {
|
||||||
|
if (items.length >= maxSuggestions) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
items.push({
|
||||||
|
href: this.href,
|
||||||
|
text: jQuery(this).text()
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!items.length) {
|
||||||
|
renderNoMatches();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderResults(items);
|
||||||
|
}
|
||||||
|
|
||||||
|
function scheduleSearch() {
|
||||||
|
if (requestTimer) {
|
||||||
|
window.clearTimeout(requestTimer);
|
||||||
|
requestTimer = null;
|
||||||
|
}
|
||||||
|
requestTimer = window.setTimeout(runSearch, debounceMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
function runSearch() {
|
||||||
|
var term = jQuery.trim($input.val());
|
||||||
|
|
||||||
|
if (term.length < minChars) {
|
||||||
|
closeList();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curRequest) {
|
||||||
|
curRequest.abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
$form.addClass('is-searching');
|
||||||
|
renderLoading();
|
||||||
|
|
||||||
|
var base = (typeof DOKU_BASE !== 'undefined') ? DOKU_BASE : '/';
|
||||||
|
curRequest = jQuery.post(
|
||||||
|
base + 'lib/exe/ajax.php',
|
||||||
|
{
|
||||||
|
call: 'qsearch',
|
||||||
|
q: encodeURI(term)
|
||||||
|
},
|
||||||
|
handleResults,
|
||||||
|
'html'
|
||||||
|
).fail(function () {
|
||||||
|
$form.removeClass('is-searching');
|
||||||
|
curRequest = null;
|
||||||
|
renderNoMatches();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$input.on('input', scheduleSearch);
|
||||||
|
|
||||||
|
$input.on('keydown', function (event) {
|
||||||
|
var key = event.key;
|
||||||
|
var isOpen = $output.hasClass('is-open');
|
||||||
|
|
||||||
|
if (!isOpen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === 'Escape') {
|
||||||
|
event.preventDefault();
|
||||||
|
closeList();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!items.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === 'ArrowDown') {
|
||||||
|
event.preventDefault();
|
||||||
|
moveActive(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === 'ArrowUp') {
|
||||||
|
event.preventDefault();
|
||||||
|
moveActive(-1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === 'Tab') {
|
||||||
|
event.preventDefault();
|
||||||
|
moveActive(event.shiftKey ? -1 : 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === 'Enter') {
|
||||||
|
if (selectActive()) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$output.on('mouseenter', 'li[data-index]', function () {
|
||||||
|
activeIndex = parseInt(jQuery(this).attr('data-index'), 10);
|
||||||
|
updateActive();
|
||||||
|
});
|
||||||
|
|
||||||
|
$output.on('mouseleave', 'li[data-index]', function () {
|
||||||
|
clearActive();
|
||||||
|
});
|
||||||
|
|
||||||
|
$output.on('mousedown', 'li[data-index] > a', function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
window.location.assign(this.href);
|
||||||
|
});
|
||||||
|
|
||||||
|
jQuery(document).on('click', function (event) {
|
||||||
|
if (!jQuery(event.target).closest($form).length) {
|
||||||
|
closeList();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ if (!defined('DOKU_INC')) die();
|
|||||||
<!-- SITE TOOLS -->
|
<!-- SITE TOOLS -->
|
||||||
<div id="dokuwiki__sitetools">
|
<div id="dokuwiki__sitetools">
|
||||||
<h3 class="a11y"><?php echo $lang['site_tools']; ?></h3>
|
<h3 class="a11y"><?php echo $lang['site_tools']; ?></h3>
|
||||||
<?php tpl_searchform(); ?>
|
<?php tpl_searchform(true, false); ?>
|
||||||
<div class="mobileTools">
|
<div class="mobileTools">
|
||||||
<?php echo (new \dokuwiki\Menu\MobileMenu())->getDropdown($lang['tools']); ?>
|
<?php echo (new \dokuwiki\Menu\MobileMenu())->getDropdown($lang['tools']); ?>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user