document.addEventListener("DOMContentLoaded", () => { // The search widget should only be visible if we're in the options page. Else, we // want it hidden. if (window.location.pathname.endsWith("options.html")) { console.log("Running script on options.html"); // Static const searchBar = document.createElement("div"); searchBar.id = "search-bar"; searchBar.innerHTML = ` <input type="text" id="search-input" placeholder="Search options by name..." disabled /> `; document.body.prepend(searchBar); // Floating const searchPopup = document.createElement("div"); searchPopup.id = "search-popup"; searchPopup.innerHTML = ` <div id="search-popup-content"> <input type="text" id="search-input-popup" placeholder="Search options..." /> <ul id="search-results-list"></ul> </div `; searchPopup.classList.add("hidden"); document.body.appendChild(searchPopup); // TODO: move this to the compiled stylesheet after development const style = document.createElement("style"); style.textContent = ` #search-bar { position: sticky; top: 0; background: #f9f9f9; padding: 10px; border-bottom: 1px solid #ccc; z-index: 1000; cursor: pointer; } #search-popup { position: fixed; top: 25%; left: 50%; transform: translate(-50%, -50%); background: #fff; border: 1px solid #ccc; border-radius: 5px; padding: 20px; width: 450px; max-height: 400px; /* Limit the height of the popup */ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); z-index: 1001; display: none; /* Initially hidden */ overflow: hidden; } #search-popup-content { display: flex; flex-direction: column; } #search-input-popup { padding: 8px; border: 1px solid #ccc; border-radius: 4px; margin-bottom: 10px; } #search-results-list { list-style-type: none; padding: 0; max-height: 300px; /* Limit the number of visible entries */ overflow-y: auto; /* Make it scrollable */ } #search-results-list li { margin: 5px 0; cursor: pointer; padding: 5px; } #search-results-list li:hover { background-color: #f0f0f0; } .hidden { display: none; } `; document.head.appendChild(style); // Focus the input on pop-up widget searchBar.addEventListener("click", (event) => { event.stopPropagation(); // also cancel clicks to get rid of that annoying cursor flicker console.log("Search bar clicked!"); // Visibility if (searchPopup.style.display === "block") { searchPopup.style.display = "none"; } else { searchPopup.style.display = "block"; document.getElementById("search-input-popup").focus(); } }); // Ctrl+K opens the floating search widget document.addEventListener("keydown", (event) => { if (event.ctrlKey && event.key === "k") { event.preventDefault(); console.log("Ctrl+K pressed!"); searchPopup.style.display = "block"; document.getElementById("search-input-popup").focus(); } }); // Close the popup when clicking outside document.addEventListener("click", (event) => { if ( !searchPopup.contains(event.target) && event.target !== searchBar ) { console.log("Click outside, hiding popup"); searchPopup.style.display = "none"; } }); // Close the popup with Esc key document.addEventListener("keydown", (event) => { if (event.key === "Escape") { console.log("Escape pressed!"); searchPopup.style.display = "none"; } }); // Handle input const searchInput = document.getElementById("search-input-popup"); const searchResultsList = document.getElementById( "search-results-list", ); const codeElements = document.querySelectorAll("code.option"); searchInput.addEventListener("input", (event) => { const query = event.target.value.toLowerCase(); searchResultsList.innerHTML = ""; // clear previous // Find matching const matchingOptions = []; codeElements.forEach((code) => { const optionText = code.textContent; if (optionText.toLowerCase().includes(query)) { // Search should be case-insensitive, since chances are // the user doesn't *actually* know what they're looking for. const anchorId = code.closest("a").id; matchingOptions.push({ text: optionText, id: anchorId }); } }); // Limit the number of visible entries (e.g., show max 10 matches) const maxVisibleResults = 10; const resultsToShow = matchingOptions.slice(0, maxVisibleResults); // Display matching resultsToShow.forEach(({ text, id }) => { const li = document.createElement("li"); li.textContent = text; li.addEventListener("click", () => { // Update the hash to the option name with a prefix // e.g., #vim.enableEditorconfig -> #opt-vim.enableEditorconfig const optionId = `opt-${text}`; window.location.hash = `#${optionId}`; searchPopup.style.display = "none"; const element = document.getElementById(id); if (element) { element.scrollIntoView({ behavior: "smooth" }); // doesn't really scroll very smoothly } }); searchResultsList.appendChild(li); }); // If there are items > maxVisibleResults, show a "See More" option if (matchingOptions.length > maxVisibleResults) { const li = document.createElement("li"); li.textContent = `See more (${matchingOptions.length - maxVisibleResults} more)`; li.style.fontStyle = "italic"; searchResultsList.appendChild(li); } }); } });