4 const menuButton = document.getElementById('menu-button');
5 const menuDropDown = document.querySelector('#header .main-nav');
7 menuButton.addEventListener('click', function(event) {
8 menuDropDown.classList.toggle('showing');
9 event.stopPropagation();
12 document.body.addEventListener('click', function(event) {
13 const isShown = menuDropDown.classList.contains('showing');
15 menuDropDown.classList.remove('showing');
16 event.stopPropagation();
21 // Handle video click to play
22 const videos = document.querySelectorAll('video');
23 for (let i = 0; i < videos.length; i++) {
24 videos[i].addEventListener('click', videoClick)
27 function videoClick() {
28 if (typeof InstallTrigger !== 'undefined') return;
29 this.paused ? this.play() : this.pause();
32 // Header double click URL reference
33 document.body.addEventListener('dblclick', event => {
34 const isHeader = event.target.matches('h1, h2, h3, h4, h5, h6');
35 if (isHeader && event.target.id) {
36 window.location.hash = event.target.id;
40 function el(tag, attributes = {}, children = []) {
41 const elem = document.createElement(tag);
42 for (const attr of Object.keys(attributes)) {
43 elem.setAttribute(attr, attributes[attr]);
45 for (let child of children) {
46 if (typeof child === 'string') {
47 child = new Text(child);
55 const searchForm = document.getElementById('site-search-form');
56 const searchInput = document.getElementById('site-search-input');
57 const searchDialog = searchForm.querySelector('dialog');
59 async function runSearch() {
60 const searchTerm = searchInput.value.toLowerCase();
64 pages = await window.webidx.search({
69 searchDialog.innerHTML = '<strong class="search-category-title">Failed to load search results</strong>';
74 // Sort pages to prioritise those with word in title
75 pages.sort((a, b) => {
76 const aScore = (a.url.includes(searchTerm) || a.title.toLowerCase().includes(searchTerm)) ? 1 : 0;
77 const bScore = (b.url.includes(searchTerm) || b.title.toLowerCase().includes(searchTerm)) ? 1 : 0;
78 return bScore - aScore;
81 // Categorizes pages to display
83 docs: {title: 'Documentation', filter: '/docs/', pages: []},
84 hacks: {title: 'Hacks', filter: '/hacks/', pages: []},
85 blog: {title: 'From the blog', filter: '/blog/', pages: []},
86 other: {title: 'Site Pages', filter: '', pages: []},
88 const categoryNames = Object.keys(categorised);
90 for (const page of pages) {
91 for (const categoryName of categoryNames) {
92 const category = categorised[categoryName];
93 if (page.url.includes(category.filter)) {
94 category.pages.push(page);
100 const categoryResults = categoryNames.map(name => {
101 const category = categorised[name];
102 if (category.pages.length === 0) {
105 return el('div', {}, [
106 el('strong', {class: 'search-category-title'}, [category.title]),
107 el('div', {}, category.pages.slice(0, 5).map(page => {
108 return el('a', {href: page.url}, [page.title]);
113 const emptyResult = el('strong', {class: 'search-category-title'}, [el('em', {}, 'No results found')]);
114 const resultList = categoryResults.length ? categoryResults : [emptyResult];
115 const results = el('div', {}, resultList);
117 for (const child of searchDialog.children) {
121 searchDialog.append(results);
125 function showSearchDialog() {
126 if (searchDialog.open) {
132 const clickListener = e => {
133 if(!e.target.closest('dialog')) {
138 const escListener = e => {
139 if (e.key === 'Escape') {
144 const mouseLeaveListener = e => {
148 const closeListener = () => {
149 searchDialog.close();
150 document.removeEventListener('click', clickListener);
151 document.removeEventListener('keydown', escListener);
152 searchForm.removeEventListener('mouseleave', mouseLeaveListener);
155 document.addEventListener('click', clickListener);
156 document.addEventListener('keydown', escListener);
157 searchForm.addEventListener('mouseleave', mouseLeaveListener);
160 function showSearchLoading() {
161 searchDialog.innerHTML = `<div class="lds-ellipsis"><div></div><div></div><div></div><div></div></div>`;
165 searchForm.addEventListener('submit', event => {
166 event.preventDefault();
171 searchInput.addEventListener('input', event => {
172 const termLength = searchInput.value.length;
173 if (termLength === 0) {
174 searchDialog.close();
175 } else if (termLength > 2) {
183 const emailDisplayLinks = document.querySelectorAll('a.email-display');
184 const eb64 = 'ZW1haWxAYm9v' + 'a3N0YWNrYXBwLmNvbQ==';
185 for (const link of emailDisplayLinks) {
186 const email = atob(eb64);
187 link.addEventListener('click', e => {
189 e.target.textContent = email;
190 e.target.href = 'mailto:' + email;