X-Git-Url: http://source.bookstackapp.com/website/blobdiff_plain/f98b02bb765e400462f3d8f76d5c0f261e0b0896..154bf3bd500a0799525489728217e7e94aba2ec8:/themes/bookstack/static/js/script.js diff --git a/themes/bookstack/static/js/script.js b/themes/bookstack/static/js/script.js index a6783ea..1cc52bb 100644 --- a/themes/bookstack/static/js/script.js +++ b/themes/bookstack/static/js/script.js @@ -1,29 +1,26 @@ // Mobile menu -var menuButton = document.getElementById('menu-button'); -var menuDropDown = document.querySelector('header div.inner'); - -menuButton.onclick = function(event) { - var menuClass = menuDropDown.className; - var visible = menuClass.indexOf('showing') !== -1; - if (visible) { - menuDropDown.className = menuClass.replace('showing', ''); - } else { - menuDropDown.className += ' showing'; - } - event.stopPropagation(); -}; +const menuButton = document.getElementById('menu-button'); +const menuDropDown = document.querySelector('#header .main-nav'); + +menuButton.addEventListener('click', function(event) { + menuDropDown.classList.toggle('showing'); + event.stopPropagation(); +}); -document.body.onclick = function(event) { - menuDropDown.className = menuDropDown.className.replace('showing', ''); - event.stopPropagation(); -}; +document.body.addEventListener('click', function(event) { + const isShown = menuDropDown.classList.contains('showing'); + if (isShown) { + menuDropDown.classList.remove('showing'); + event.stopPropagation(); + } +}); // Handle video click to play -var videos = document.querySelectorAll('video'); -for (var i = 0; i < videos.length; i++) { +const videos = document.querySelectorAll('video'); +for (let i = 0; i < videos.length; i++) { videos[i].addEventListener('click', videoClick) } @@ -32,34 +29,164 @@ function videoClick() { this.paused ? this.play() : this.pause(); } +// Header double click URL reference +document.body.addEventListener('dblclick', event => { + const isHeader = event.target.matches('h1, h2, h3, h4, h5, h6'); + if (isHeader && event.target.id) { + window.location.hash = event.target.id; + } +}); + +function el(tag, attributes = {}, children = []) { + const elem = document.createElement(tag); + for (const attr of Object.keys(attributes)) { + elem.setAttribute(attr, attributes[attr]); + } + for (let child of children) { + if (typeof child === 'string') { + child = new Text(child); + } + elem.append(child); + } + return elem; +} + +// Site search +const searchForm = document.getElementById('site-search-form'); +const searchInput = document.getElementById('site-search-input'); +const searchDialog = searchForm.querySelector('dialog'); + +async function runSearch() { + const searchTerm = searchInput.value.toLowerCase(); + + let pages = []; + try { + pages = await window.webidx.search({ + dbfile:'/search.db', + query: searchTerm, + }); + } catch (error) { + searchDialog.innerHTML = 'Failed to load search results'; + console.error(error); + return; + } -// Codemirror Setup - -var modeMap = { - 'language-html': 'htmlmixed', - 'language-bash': 'shell', - 'language-js': 'javascript', - 'language-shell': 'bash', - 'language-nginx': 'nginx', - 'language-apache': 'apache' -}; - -var codeBlocks = document.querySelectorAll('pre'); -for (var i = 0; i < codeBlocks.length; i++) { - var block = codeBlocks[i]; - var codeElem = block.querySelector('code'); - if (codeElem === null) continue; - - var langClass = codeElem.className; - var mode = (typeof modeMap[langClass] !== 'undefined') ? modeMap[langClass] : 'htmlmixed'; - var content = codeElem.textContent.trim(); - CodeMirror(function(cmElem) { - block.parentNode.replaceChild(cmElem, block); - }, { - theme: 'base16-light', - lineNumbers: true, - mode: mode, - readOnly: true, - value: content + // Sort pages to prioritise those with word in title + pages.sort((a, b) => { + const aScore = (a.url.includes(searchTerm) || a.title.toLowerCase().includes(searchTerm)) ? 1 : 0; + const bScore = (b.url.includes(searchTerm) || b.title.toLowerCase().includes(searchTerm)) ? 1 : 0; + return bScore - aScore; }); + + // Categorizes pages to display + const categorised = { + docs: {title: 'Documentation', filter: '/docs/', pages: []}, + hacks: {title: 'Hacks', filter: '/hacks/', pages: []}, + blog: {title: 'From the blog', filter: '/blog/', pages: []}, + other: {title: 'Site Pages', filter: '', pages: []}, + }; + const categoryNames = Object.keys(categorised); + + for (const page of pages) { + for (const categoryName of categoryNames) { + const category = categorised[categoryName]; + if (page.url.includes(category.filter)) { + category.pages.push(page); + break; + } + } + } + + const categoryResults = categoryNames.map(name => { + const category = categorised[name]; + if (category.pages.length === 0) { + return null; + } + return el('div', {}, [ + el('strong', {class: 'search-category-title'}, [category.title]), + el('div', {}, category.pages.slice(0, 5).map(page => { + return el('a', {href: page.url}, [page.title]); + })), + ]); + }).filter(Boolean); + + const emptyResult = el('strong', {class: 'search-category-title'}, [el('em', {}, 'No results found')]); + const resultList = categoryResults.length ? categoryResults : [emptyResult]; + const results = el('div', {}, resultList); + + for (const child of searchDialog.children) { + child.remove(); + } + + searchDialog.append(results); + showSearchDialog(); } + +function showSearchDialog() { + if (searchDialog.open) { + return; + } + searchDialog.show(); + searchInput.focus(); + + const clickListener = e => { + if(!e.target.closest('dialog')) { + closeListener(); + } + }; + + const escListener = e => { + if (e.key === 'Escape') { + closeListener(); + } + }; + + const mouseLeaveListener = e => { + closeListener(); + } + + const closeListener = () => { + searchDialog.close(); + document.removeEventListener('click', clickListener); + document.removeEventListener('keydown', escListener); + searchForm.removeEventListener('mouseleave', mouseLeaveListener); + }; + + document.addEventListener('click', clickListener); + document.addEventListener('keydown', escListener); + searchForm.addEventListener('mouseleave', mouseLeaveListener); +} + +function showSearchLoading() { + searchDialog.innerHTML = `