Files
luxtools-template/script.js
2026-02-09 09:57:26 +01:00

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();
}
});
})();
});