403 lines
12 KiB
JavaScript
Executable File
403 lines
12 KiB
JavaScript
Executable File
/**
|
|
* We handle several device classes based on browser width.
|
|
*
|
|
* - desktop: > __tablet_width__ (as set in style.ini)
|
|
* - mobile:
|
|
* - tablet <= __tablet_width__
|
|
* - phone <= __phone_width__
|
|
*/
|
|
var device_class = ''; // not yet known
|
|
var device_classes = 'desktop mobile tablet phone';
|
|
|
|
function tpl_dokuwiki_mobile(){
|
|
|
|
// the z-index in mobile.css is (mis-)used purely for detecting the screen mode here
|
|
var screen_mode = jQuery('#screen__mode').css('z-index') + '';
|
|
|
|
// determine our device pattern
|
|
// TODO: consider moving into dokuwiki core
|
|
switch (screen_mode) {
|
|
case '1':
|
|
if (device_class.match(/tablet/)) return;
|
|
device_class = 'mobile tablet';
|
|
break;
|
|
case '2':
|
|
if (device_class.match(/phone/)) return;
|
|
device_class = 'mobile phone';
|
|
break;
|
|
default:
|
|
if (device_class == 'desktop') return;
|
|
device_class = 'desktop';
|
|
}
|
|
|
|
jQuery('html').removeClass(device_classes).addClass(device_class);
|
|
|
|
// handle some layout changes based on change in device
|
|
var $handle = jQuery('#dokuwiki__aside h3.toggle');
|
|
var $toc = jQuery('#dw__toc h3');
|
|
|
|
if (device_class == 'desktop') {
|
|
// reset for desktop mode
|
|
if($handle.length) {
|
|
$handle[0].setState(1);
|
|
$handle.hide();
|
|
}
|
|
if($toc.length) {
|
|
$toc[0].setState(1);
|
|
}
|
|
}
|
|
if (device_class.match(/mobile/)){
|
|
// toc and sidebar hiding
|
|
if($handle.length) {
|
|
$handle.show();
|
|
$handle[0].setState(-1);
|
|
}
|
|
if($toc.length) {
|
|
$toc[0].setState(-1);
|
|
}
|
|
}
|
|
}
|
|
|
|
jQuery(function(){
|
|
var resizeTimer;
|
|
dw_page.makeToggle('#dokuwiki__aside h3.toggle','#dokuwiki__aside div.content');
|
|
|
|
tpl_dokuwiki_mobile();
|
|
jQuery(window).on('resize',
|
|
function(){
|
|
if (resizeTimer) clearTimeout(resizeTimer);
|
|
resizeTimer = setTimeout(tpl_dokuwiki_mobile,200);
|
|
}
|
|
);
|
|
|
|
// increase sidebar length to match content (desktop mode only)
|
|
var sidebar_height = jQuery('.desktop #dokuwiki__aside').height();
|
|
var pagetool_height = jQuery('.desktop #dokuwiki__pagetools ul:first').height();
|
|
// pagetools div has no height; ul has a height
|
|
var content_min = Math.max(sidebar_height || 0, pagetool_height || 0);
|
|
|
|
var content_height = jQuery('#dokuwiki__content div.page').height();
|
|
if(content_min && content_min > content_height) {
|
|
var $content = jQuery('#dokuwiki__content div.page');
|
|
$content.css('min-height', content_min);
|
|
}
|
|
|
|
// blur when clicked
|
|
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();
|
|
}
|
|
});
|
|
})();
|
|
});
|