}
-input[type="text"].doc-search-input {
+header input[type="search"] {
background-color: transparent;
border: 2px solid #FFF;
color: #FFF;
background-color: transparent;
border-radius: 30px;
- width: 300px;
- margin-bottom: $-m;
- margin-top: $-xs;
+ width: 280px;
+ margin-block: $-m;
+ font-size: 14px;
+ padding: 7.2px 12px;
&::-webkit-input-placeholder { /* Chrome */
color: rgba(255, 255, 255, 0.7);
}
outline: none;
}
}
+#site-search-form {
+ position: relative;
+}
+#site-search-form dialog {
+ position: absolute;
+ left: -10px;
+ top: 100%;
+ margin-top: -14px;
+ border: 0;
+ margin-inline: 0;
+ background-color: #FFF;
+ box-shadow: 0 2px 12px 1px rgba(0, 0, 0, 0.2);
+ border-radius: 4px;
+ width: 300px;
+ padding: 0;
+ min-height: 42px;
+ a {
+ display: block;
+ padding: 0.25rem 1rem 0.25rem 2rem;
+ border-bottom: 1px solid #EEE;
+ color: #222;
+ font-size: 15px;
+ }
+ a:hover {
+ color: $primary;
+ text-decoration: none;
+ background-color: rgba($primary, 0.1);
+ }
+ a:last-child {
+ border-bottom: 0;
+ }
+ strong.search-category-title {
+ padding: .5rem 1rem .25rem 1rem;
+ color: #666;
+ display: block;
+ font-size: 14px;
+ }
+}
+
+// Loader from https://loading.io/css/
+.lds-ellipsis {
+ display: block;
+ position: relative;
+ width: 80px;
+ height: 80px;
+ margin: 0 auto;
+}
+.lds-ellipsis div {
+ position: absolute;
+ top: 33px;
+ width: 13px;
+ height: 13px;
+ border-radius: 50%;
+ background: $primary;
+ animation-timing-function: cubic-bezier(0, 1, 1, 0);
+}
+.lds-ellipsis div:nth-child(1) {
+ left: 8px;
+ animation: lds-ellipsis1 0.6s infinite;
+}
+.lds-ellipsis div:nth-child(2) {
+ left: 8px;
+ animation: lds-ellipsis2 0.6s infinite;
+}
+.lds-ellipsis div:nth-child(3) {
+ left: 32px;
+ animation: lds-ellipsis2 0.6s infinite;
+}
+.lds-ellipsis div:nth-child(4) {
+ left: 56px;
+ animation: lds-ellipsis3 0.6s infinite;
+}
+@keyframes lds-ellipsis1 {
+ 0% {
+ transform: scale(0);
+ }
+ 100% {
+ transform: scale(1);
+ }
+}
+@keyframes lds-ellipsis3 {
+ 0% {
+ transform: scale(1);
+ }
+ 100% {
+ transform: scale(0);
+ }
+}
+@keyframes lds-ellipsis2 {
+ 0% {
+ transform: translate(0, 0);
+ }
+ 100% {
+ transform: translate(24px, 0);
+ }
+}
.youtube-embed-wrap {
margin-bottom: 1rem;
});
document.body.addEventListener('click', function(event) {
- menuDropDown.classList.remove('showing');
- event.stopPropagation();
+ const isShown = menuDropDown.classList.contains('showing');
+ if (isShown) {
+ menuDropDown.classList.remove('showing');
+ event.stopPropagation();
+ }
});
if (isHeader && event.target.id) {
window.location.hash = event.target.id;
}
-});
\ No newline at end of file
+});
+
+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 = '<strong class="search-category-title">Failed to load search results</strong>';
+ console.error(error);
+ return;
+ }
+
+ // 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 = `<div class="lds-ellipsis"><div></div><div></div><div></div><div></div></div>`;
+ showSearchDialog();
+}
+
+searchForm.addEventListener('submit', event => {
+ event.preventDefault();
+ showSearchLoading();
+ runSearch();
+});
+
+searchInput.addEventListener('input', event => {
+ const termLength = searchInput.value.length;
+ if (termLength === 0) {
+ searchDialog.close();
+ } else if (termLength > 2) {
+ showSearchLoading();
+ runSearch();
+ }
+})
\ No newline at end of file
// BSD 3-Clause License
// Copyright (c) 2024, Gavin Brown
// Full license: https://github.com/gbxyz/webidx/blob/a28a984d38fd546d1bec4d6a4a5a47ab86cb08f8/LICENSE
+// Modified since copied.
window.webidx = {};
-webidx = window.webidx;
+const webidx = window.webidx;
webidx.search = async function (params) {
if (!webidx.sql) {
}
if (webidx.hasOwnProperty('db')) {
- webidx.displayResults(webidx.query(params.query), params);
-
+ return webidx.query(params.query);
+ } else if (webidx.loadingPromise) {
+ await webidx.loadingPromise;
+ await new Promise((res, rej) => setTimeout(res, 10));
+ return webidx.query(params.query);
} else {
- webidx.loadDB(params);
+ webidx.loadingPromise = webidx.loadDB(params);
+
+ webidx.loadingPromise.then(() => {
+ webidx.loadingPromise = null;
+ });
+ return webidx.loadingPromise
}
};
webidx.loadDB = function (params) {
- var xhr = new XMLHttpRequest();
+ return new Promise((res, rej) => {
+ var xhr = new XMLHttpRequest();
- xhr.open('GET', params.dbfile);
- xhr.timeout = params.timeout ?? 5000;
- xhr.responseType = 'arraybuffer';
+ xhr.open('GET', params.dbfile);
+ xhr.timeout = params.timeout ?? 5000;
+ xhr.responseType = 'arraybuffer';
- xhr.ontimeout = function() {
- if (params.hasOwnProperty('errorCallback')) {
- params.errorCallback('Unable to load index, please refresh the page.');
- }
- };
+ xhr.ontimeout = function() {
+ rej('Unable to load index, please refresh the page.');
+ };
- xhr.onload = function() {
- webidx.initializeDB(this.response);
- webidx.displayResults(webidx.query(params.query), params);
- };
+ xhr.onload = function() {
+ webidx.initializeDB(this.response);
+ const results = webidx.query(params.query);
+ res(results);
+ };
- xhr.send();
+ xhr.send();
+ });
};
webidx.initializeDB = function (arrayBuffer) {