Compare commits

...

2 Commits

Author SHA1 Message Date
02e9932f1e Serach enhancement refinement 2026-02-09 09:57:26 +01:00
1eb50d8969 Search form enhancement 1 2026-02-09 09:43:37 +01:00
7 changed files with 363 additions and 3 deletions

View File

@@ -5,3 +5,4 @@ This is a fork of the original default dokuwiki template.
## Changes from the original template
- Default font changed to "Perfect DOS VGA 437 Win"
- Enhanced header search with DokuWiki quicksearch suggestions, keyboard navigation, and disabled browser autofill

View File

@@ -179,6 +179,48 @@
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-tabbed > a {
box-shadow: inset 0 0 0 1px @ini_border;
}
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;

View File

@@ -143,7 +143,7 @@ form.search {
}
button {
background: transparent url(images/search.png) no-repeat 0 0;
background: transparent no-repeat 0 0;
border-width: 0;
width: 19px;
height: 14px;

View File

@@ -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;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 307 B

313
script.js
View File

@@ -86,4 +86,317 @@ 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 lastNavigation = null;
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;
lastNavigation = null;
$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 is-tabbed').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');
if (lastNavigation === 'tab') {
$active.addClass('is-tabbed');
}
$input.attr('aria-activedescendant', $active.attr('id'));
}
function moveActive(delta, source) {
if (!items.length) {
return;
}
lastNavigation = source || null;
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, 'arrow');
return;
}
if (key === 'ArrowUp') {
event.preventDefault();
moveActive(-1, 'arrow');
return;
}
if (key === 'Tab') {
event.preventDefault();
moveActive(event.shiftKey ? -1 : 1, 'tab');
return;
}
if (key === 'Enter') {
if (selectActive()) {
event.preventDefault();
}
}
});
$output.on('mouseenter', 'li[data-index]', function () {
activeIndex = parseInt(jQuery(this).attr('data-index'), 10);
lastNavigation = 'mouse';
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();
}
});
})();
});

View File

@@ -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>