class DoxygenAwesomeReadtheDocsSearch { static searchResultsText=[ "Sorry, no pages matching your query.", "Search finished, found 1 page matching the search query.", "Search finished, found $num pages matching the search query.", ]; static _liveResultsAlignment = 'right'; static _liveResultsPositionFrame = null; static _liveSearchAttached = false; static _projectSlug = 'doxygen-awesome-css'; static _versionSlug = 'latest'; static get serverUrl() { const serverUrlSuffix = '_/api/v3/'; const domainName = globalThis.location.hostname; console.log(`Domain name: ${domainName}`); if (domainName === 'localhost') { let tmpServerUrl = serverUrl; while (tmpServerUrl.endsWith('/')) { tmpServerUrl = tmpServerUrl.slice(0, -1); } console.warn('Localhost detected, you probably need to bypass CORS'); return `${tmpServerUrl}/${serverUrlSuffix}`; } return `https://${domainName}/${serverUrlSuffix}`; } static init(alignment = 'rightAlign', { projectSlug = 'doxygen-awesome-css', versionSlug = 'latest' } = {}) { DoxygenAwesomeReadtheDocsSearch._liveResultsAlignment = alignment === 'leftAlign' ? 'left' : 'right'; DoxygenAwesomeReadtheDocsSearch._projectSlug = projectSlug; DoxygenAwesomeReadtheDocsSearch._versionSlug = versionSlug; const realSearchBox = globalThis.SearchBox; globalThis.SearchBox = function(name, resultsPath, extension) { if (realSearchBox) { realSearchBox.call(this, name, resultsPath, extension); } this.OnSearchFieldFocus = function() {}; const originalClose = this.CloseResultsWindow; this.CloseResultsWindow = function() { if (originalClose) { originalClose.call(this); } const field = this.DOMSearchField ? this.DOMSearchField() : null; if (field?.id === document.activeElement?.id) { return; } }; }; // Re-focus the search input if it is replaced in the DOM while focused. // On mobile, viewport resize (triggered by the OSK opening) can cause // menu.js resetState() to recreate the input element, losing focus. const observer = new MutationObserver(function() { const field = document.getElementById('MSearchField'); if (field && DoxygenAwesomeReadtheDocsSearch._searchFieldHadFocus) { field.focus(); } }); if (document.body) { observer.observe(document.body, { childList: true, subtree: true }); DoxygenAwesomeReadtheDocsSearch._attachLiveSearch(); } else { document.addEventListener('DOMContentLoaded', function() { observer.observe(document.body, { childList: true, subtree: true }); DoxygenAwesomeReadtheDocsSearch._attachLiveSearch(); }); } document.addEventListener('focusin', function(e) { if (e.target?.id === 'MSearchField') { DoxygenAwesomeReadtheDocsSearch._searchFieldHadFocus = true; } }); document.addEventListener('focusout', function(e) { if (e.target?.id === 'MSearchField') { setTimeout(function() { DoxygenAwesomeReadtheDocsSearch._searchFieldHadFocus = false; }, 0); } }); document.addEventListener('click', function(e) { const dropdown = document.getElementById('RTDLiveResults'); const field = document.getElementById('MSearchField'); if (dropdown && !dropdown.contains(e.target) && e.target !== field) { DoxygenAwesomeReadtheDocsSearch._hideLiveResults(); } }); globalThis.searchFor = function(query, page, count) { const results = document.getElementById('searchresults'); // Get the title const pageTitle = document.querySelector('div.title'); const originalTitle = pageTitle ? pageTitle.textContent : ''; let pageTitleStates = ["Searching", "Searching .", "Searching ..", "Searching ..."]; let pageTitleIndex = 0; // Function to update the page title function updatePageTitle() { if (pageTitle) pageTitle.textContent = pageTitleStates[pageTitleIndex]; pageTitleIndex = (pageTitleIndex + 1) % pageTitleStates.length; } // Start the interval to update the page title let titleInterval = setInterval(updatePageTitle, 500); // The summary will be displayed at the top of the search results let resultSummary = document.createElement('p'); resultSummary.className = 'search-summary'; results.appendChild(resultSummary); // Put all results into an unordered list let resultList = document.createElement('ul'); resultList.className = 'search'; results.appendChild(resultList); // readthedocs metadata const projectSlug = DoxygenAwesomeReadtheDocsSearch.projectSlug; const projectVersion = DoxygenAwesomeReadtheDocsSearch.versionSlug; const ctx = { query, resultSummary, resultList, titleInterval, pageTitle, originalTitle, firstUrl: true }; // pull requests are not indexed, so use the default version const versionReady = /^\d+$/.test(projectVersion) ? DoxygenAwesomeReadtheDocsSearch.getReadTheDocsDefaultVersion(projectSlug) : Promise.resolve(projectVersion); versionReady.then(function(resolvedVersion) { const url = `${DoxygenAwesomeReadtheDocsSearch.serverUrl}search/?q=project:${projectSlug}/${resolvedVersion}+${query}&page=${page + 1}&page_size=${count}`; console.log(url); DoxygenAwesomeReadtheDocsSearch.fetchResults(url, ctx); }); } } static _attachLiveSearch() { if (DoxygenAwesomeReadtheDocsSearch._liveSearchAttached) return; DoxygenAwesomeReadtheDocsSearch._liveSearchAttached = true; let debounceTimer = null; function onInput() { clearTimeout(debounceTimer); const field = document.getElementById('MSearchField'); if (!field) return; const query = field.value.trim(); if (!query) { DoxygenAwesomeReadtheDocsSearch._hideLiveResults(); return; } debounceTimer = setTimeout(function() { DoxygenAwesomeReadtheDocsSearch._runLiveSearch(query); }, 300); } function attachToField() { const field = document.getElementById('MSearchField'); if (!field || field._rtdLiveSearchAttached) return; field._rtdLiveSearchAttached = true; field.setAttribute('autocomplete', 'off'); field.addEventListener('input', onInput); field.addEventListener('keydown', DoxygenAwesomeReadtheDocsSearch._handleLiveSearchKeydown); DoxygenAwesomeReadtheDocsSearch._scheduleLiveResultsPositionUpdate(); } attachToField(); // Re-attach when the DOM changes (e.g. menu.js recreates the field on mobile) const fieldObserver = new MutationObserver(attachToField); fieldObserver.observe(document.body, { childList: true, subtree: true }); window.addEventListener('resize', DoxygenAwesomeReadtheDocsSearch._scheduleLiveResultsPositionUpdate); } static _runLiveSearch(query) { const projectSlug = DoxygenAwesomeReadtheDocsSearch.projectSlug; const projectVersion = DoxygenAwesomeReadtheDocsSearch.versionSlug; const versionReady = /^\d+$/.test(projectVersion) ? DoxygenAwesomeReadtheDocsSearch.getReadTheDocsDefaultVersion(projectSlug) : Promise.resolve(projectVersion); versionReady.then(function(resolvedVersion) { const url = `${DoxygenAwesomeReadtheDocsSearch.serverUrl}search/?q=project:${projectSlug}/${resolvedVersion}+${query}&page=1&page_size=5`; fetch(url) .then(function(response) { if (!response.ok) throw new Error(response.statusText); return response.json(); }) .then(function(data) { DoxygenAwesomeReadtheDocsSearch._showLiveResults(query, data.results || []); }) .catch(function() { DoxygenAwesomeReadtheDocsSearch._hideLiveResults(); }); }); } static _showLiveResults(query, results) { let dropdown = document.getElementById('RTDLiveResults'); if (!dropdown) { dropdown = document.createElement('div'); dropdown.id = 'RTDLiveResults'; dropdown.addEventListener('keydown', DoxygenAwesomeReadtheDocsSearch._handleLiveSearchKeydown); document.body.appendChild(dropdown); } dropdown.innerHTML = ''; if (results.length === 0) { const empty = document.createElement('div'); empty.className = 'rtd-live-empty'; empty.textContent = 'No results found.'; dropdown.appendChild(empty); } else { const list = document.createElement('ul'); list.className = 'rtd-live-list'; for (const item of results) { const li = document.createElement('li'); const a = document.createElement('a'); a.className = 'rtd-live-item'; a.href = `${item.domain}${item.path}`; a.tabIndex = 0; const title = document.createElement('span'); title.className = 'rtd-live-title'; title.textContent = item.title; a.appendChild(title); if (item.blocks && item.blocks.length > 0) { const snippet = document.createElement('span'); snippet.className = 'rtd-live-snippet'; let snipHtml = item.blocks[0].highlights.content.join(' … '); snipHtml = snipHtml.replaceAll(/(.*?)<\/span>/g, '$1'); snippet.innerHTML = snipHtml; a.appendChild(snippet); } li.appendChild(a); list.appendChild(li); } dropdown.appendChild(list); } DoxygenAwesomeReadtheDocsSearch._positionLiveResults(dropdown); dropdown.style.display = 'block'; } static _positionLiveResults(dropdown) { const field = document.getElementById('MSearchField'); const box = document.getElementById('MSearchBox'); const anchor = box || field; if (!anchor) return; const rect = anchor.getBoundingClientRect(); const scrollX = window.scrollX; const scrollY = window.scrollY; const viewportWidth = document.documentElement.clientWidth; dropdown.style.top = `${rect.bottom + scrollY}px`; if (window.matchMedia('(max-width: 767px)').matches) { dropdown.style.left = ''; dropdown.style.right = ''; dropdown.style.minWidth = ''; return; } dropdown.style.minWidth = `${Math.max(rect.width, 260)}px`; if (DoxygenAwesomeReadtheDocsSearch._liveResultsAlignment === 'left') { dropdown.style.left = `${rect.left + scrollX}px`; dropdown.style.right = ''; } else { dropdown.style.left = ''; dropdown.style.right = `${viewportWidth - rect.right}px`; } } static _hideLiveResults(restoreFocus = false) { const dropdown = document.getElementById('RTDLiveResults'); if (dropdown) { dropdown.style.display = 'none'; } if (restoreFocus) { const field = document.getElementById('MSearchField'); if (field) field.focus(); } } static _scheduleLiveResultsPositionUpdate() { if (DoxygenAwesomeReadtheDocsSearch._liveResultsPositionFrame) return; DoxygenAwesomeReadtheDocsSearch._liveResultsPositionFrame = window.requestAnimationFrame(function() { DoxygenAwesomeReadtheDocsSearch._liveResultsPositionFrame = null; DoxygenAwesomeReadtheDocsSearch._updateLiveResultsPosition(); }); } static _updateLiveResultsPosition() { const dropdown = document.getElementById('RTDLiveResults'); if (!dropdown || dropdown.style.display === 'none') return; DoxygenAwesomeReadtheDocsSearch._positionLiveResults(dropdown); } static _handleLiveSearchKeydown(e) { if (e.key === 'Escape') { DoxygenAwesomeReadtheDocsSearch._hideLiveResults(true); } else if (e.key === 'ArrowDown') { e.preventDefault(); DoxygenAwesomeReadtheDocsSearch._moveLiveResultFocus(1); } else if (e.key === 'ArrowUp') { e.preventDefault(); DoxygenAwesomeReadtheDocsSearch._moveLiveResultFocus(-1); } else if (e.key === 'Enter') { const focused = document.querySelector('#RTDLiveResults .rtd-live-item:focus'); if (focused) { e.preventDefault(); focused.click(); } } } static _moveLiveResultFocus(direction) { const dropdown = document.getElementById('RTDLiveResults'); if (!dropdown || dropdown.style.display === 'none') return; const items = Array.from(dropdown.querySelectorAll('.rtd-live-item')); if (items.length === 0) return; const current = document.activeElement; const idx = items.indexOf(current); let next; if (idx === -1) { next = direction > 0 ? items[0] : items.at(-1); } else { next = items[idx + direction]; } if (next) next.focus(); } static fetchResults(url, ctx) { fetch(url) .then(function(response) { if (!response.ok) throw new Error(response.statusText); return response.json(); }) .then(function(data) { // Add the query to the search field // This seems only be working if applied in the fetch success handler... // maybe the field is not available before this point const searchField = document.getElementById('MSearchField'); if (searchField) searchField.value = ctx.query; if (ctx.firstUrl) { if (data.count === 1) { ctx.resultSummary.innerHTML = DoxygenAwesomeReadtheDocsSearch.searchResultsText[1]; } else if (data.count > 1) { ctx.resultSummary.innerHTML = DoxygenAwesomeReadtheDocsSearch.searchResultsText[2].replace(/\$num/, data.count); } else { ctx.resultSummary.innerHTML = DoxygenAwesomeReadtheDocsSearch.searchResultsText[0]; } ctx.firstUrl = false; } data.results.forEach(function(item) { DoxygenAwesomeReadtheDocsSearch.appendResultItem(ctx.resultList, item); }); if (data.next) { DoxygenAwesomeReadtheDocsSearch.fetchResults(data.next, ctx); } else { // Clear the interval when the search is complete clearInterval(ctx.titleInterval); if (ctx.pageTitle) ctx.pageTitle.textContent = ctx.originalTitle; } }); } static appendResultItem(resultList, item) { const resultItem = document.createElement('li'); resultItem.className = 'search-result'; const resultItemUrl = `${item.domain}${item.path}`; const resultItemTitleLink = document.createElement('a'); const resultItemTitleHeading = document.createElement('h3'); resultItemTitleHeading.appendChild(resultItemTitleLink); resultItemTitleLink.href = resultItemUrl; resultItemTitleLink.textContent = item.title; resultItem.append(resultItemTitleHeading); resultList.append(resultItem); const resultItemParagraph = document.createElement('p'); resultItemParagraph.className = 'context'; for (const block of item.blocks) { let blockContent = block.highlights.content.join(', '); blockContent = blockContent.replaceAll(/(.*?)<\/span>/g, '$1'); resultItemParagraph.innerHTML += blockContent; const blockName = `#${block.title.toLowerCase().replaceAll(' ', '-')}`; const blockUrl = resultItemUrl + blockName; const blockLink = document.createElement('a'); blockLink.href = blockUrl; blockLink.textContent = "More..."; resultItemParagraph.append(document.createTextNode(' ')); resultItemParagraph.append(blockLink); resultItemParagraph.append(document.createElement('br')); } resultItem.append(resultItemParagraph); } // Function to extract the value of a specified Read the Docs meta property static getMetaValue(propertyName) { const metaTags = document.getElementsByTagName('meta'); for (let meta of metaTags) { if (meta.name === propertyName) { return meta.content; } } return null; } static get projectSlug() { return DoxygenAwesomeReadtheDocsSearch.getMetaValue("readthedocs-project-slug") || DoxygenAwesomeReadtheDocsSearch._projectSlug; } static get versionSlug() { return DoxygenAwesomeReadtheDocsSearch.getMetaValue("readthedocs-version-slug") || DoxygenAwesomeReadtheDocsSearch._versionSlug; } static getReadTheDocsDefaultVersion(project) { console.log('Pull request detected, getting default version from ReadTheDocs API'); const url = `${DoxygenAwesomeReadtheDocsSearch.serverUrl}projects/${project}/`; return fetch(url) .then(function(response) { if (!response.ok) throw new Error(response.statusText); return response.json(); }) .then(function(data) { console.log(data); return data.default_version || 'latest'; }) .catch(function(err) { console.error('Error:', err); console.log(`Cannot determine default version for ${project}, assuming "latest"`); return 'latest'; }); } }