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.
|
||||
|
||||
## 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;
|
||||
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 {
|
||||
display: block;
|
||||
margin-bottom: .3em;
|
||||
|
||||
@@ -243,6 +243,10 @@ body {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#dokuwiki__sitetools form.search div.ajax_qsearch.is-open {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
/* action dropdown is alternative for all hidden tools */
|
||||
#dokuwiki__header .mobileTools {
|
||||
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(){
|
||||
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 -->
|
||||
<div id="dokuwiki__sitetools">
|
||||
<h3 class="a11y"><?php echo $lang['site_tools']; ?></h3>
|
||||
<?php tpl_searchform(); ?>
|
||||
<?php tpl_searchform(true, false); ?>
|
||||
<div class="mobileTools">
|
||||
<?php echo (new \dokuwiki\Menu\MobileMenu())->getDropdown($lang['tools']); ?>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user