1(() => { 2 "use strict"; 3 4 // Keep Quarto's autocomplete, ranking, search.json, and shareable search URLs. 5 const loadIndex = window.readSearchData; 6 if (!loadIndex) return; 7 let pendingIndex; 8 let failed = false; 9 let overlay; 10 let background = []; 11 12 window.readSearchData = () => { 13 if (!pendingIndex) { 14 failed = false; 15 document.body.classList.add("book-search-loading"); 16 pendingIndex = loadIndex().catch(() => { 17 failed = true; 18 pendingIndex = null; 19 // The native collection remains empty, but our error state replaces 20 // "No results" and leaves useful links available. Next input retries. 21 return new window.Fuse([]); 22 }).finally(() => requestAnimationFrame(() => { 23 document.body.classList.remove("book-search-loading"); 24 enhance(); 25 })); 26 } 27 return pendingIndex; 28 }; 29 30 function category(href) { 31 const slug = new URL(href, location.href).pathname.split("/").at(-1); 32 for (const [prefix, label] of [["careers-", "Guide"], ["principles-", "Principle"], ["strategy-", "Strategy"], ["tactics-", "Tactic"], ["concepts-", "Concept"]]) { 33 if (slug.startsWith(prefix)) return label; 34 } 35 return "Book"; 36 } 37 38 function mountOverlay(container) { 39 container.setAttribute("role", "dialog"); 40 container.setAttribute("aria-modal", "true"); 41 container.setAttribute("aria-label", "Search the book"); 42 const input = container.querySelector(".aa-Input"); 43 input.removeAttribute("aria-labelledby"); 44 input.setAttribute("aria-label", "Search chapters and ideas"); 45 input.setAttribute("role", "combobox"); 46 input.setAttribute("aria-expanded", "true"); 47 input.setAttribute("aria-haspopup", "listbox"); 48 49 if (!container.querySelector(".book-search-destinations")) { 50 const status = document.createElement("p"); 51 status.className = "book-search-status"; 52 status.setAttribute("role", "status"); 53 container.querySelector(".aa-DetachedFormContainer").after(status); 54 const destinations = document.createElement("nav"); 55 destinations.className = "book-search-destinations"; 56 destinations.setAttribute("aria-label", "Start reading"); 57 const heading = document.createElement("p"); 58 heading.textContent = "Start reading"; 59 destinations.append(heading); 60 // Select recommendations from Quarto's authoritative book navigation. 61 for (const suffix of ["/quickstart.html", "/chapters/careers-beyond-coding.html", "/chapters/principles-learn-in-public.html", "/chapters/strategy-megatrends.html", "/chapters/tactics-marketing-yourself.html", "/audiobook.html"]) { 62 const source = [...document.querySelectorAll("#quarto-sidebar .sidebar-item a[href]")].find(link => new URL(link.href).pathname === suffix); 63 if (!source) continue; 64 const link = document.createElement("a"); 65 link.href = source.href; 66 link.textContent = source.querySelector(".menu-text")?.textContent.trim().replace(/^\d+\s*/, "") || source.textContent.trim(); 67 destinations.append(link); 68 } 69 container.append(destinations); 70 } 71 background = [...document.querySelectorAll("#quarto-content, #quarto-sidebar, .book-masthead, .audiobook-player, .nav-footer")].map(element => [element, element.inert]); 72 for (const [element] of background) element.inert = true; 73 } 74 75 function enhance() { 76 const container = document.querySelector(".aa-DetachedContainer"); 77 if (container !== overlay) { 78 for (const [element, wasInert] of background) element.inert = wasInert; 79 background = []; 80 overlay = container; 81 if (container) mountOverlay(container); 82 } 83 if (!container) return; 84 const input = container.querySelector(".aa-Input"); 85 // Autocomplete rewrites this association while rendering collections. 86 input.removeAttribute("aria-labelledby"); 87 const query = input.value.trim(); 88 const loading = document.body.classList.contains("book-search-loading"); 89 const message = failed ? "Search couldnât load. Check your connection and try typing again, or use a link below." : loading && query ? "Searching the bookâ¦" : ""; 90 const status = container.querySelector(".book-search-status"); 91 if (status.textContent !== message) status.textContent = message; 92 status.hidden = !message; 93 container.classList.toggle("book-search-failed", failed); 94 container.classList.toggle("book-search-empty", !query); 95 container.querySelector(".book-search-destinations").hidden = Boolean(query) && !failed; 96 for (const result of container.querySelectorAll(".search-result-doc")) { 97 let badge = result.querySelector(".reader-search-category"); 98 if (!badge) { 99 badge = document.createElement("span"); 100 badge.className = "reader-search-category"; 101 result.querySelector(".search-result-title-container").prepend(badge); 102 } 103 const label = category(result.querySelector("a[href]").href); 104 // Autocomplete can reuse a result node for a different chapter. 105 if (badge.textContent !== label) badge.textContent = label; 106 } 107 } 108 109 new MutationObserver(enhance).observe(document.body, { childList: true, subtree: true }); 110 document.addEventListener("input", event => { 111 if (event.target.matches?.(".aa-Input")) enhance(); 112 }); 113 document.addEventListener("click", event => { 114 if (event.target.closest?.("[data-book-search], .book-panel-search")) window.readSearchData(); 115 }, { capture: true }); 116 document.addEventListener("keydown", event => { 117 if (event.isComposing || event.keyCode === 229) return; 118 if (overlay && event.key === "Tab") { 119 const controls = [...overlay.querySelectorAll("input, button, a[href]")].filter(element => !element.disabled && element.tabIndex >= 0 && element.getClientRects().length); 120 const first = controls[0]; 121 const last = controls.at(-1); 122 if (event.shiftKey && (document.activeElement === first || !control
122s.includes(document.activeElement))) { 123 event.preventDefault(); last?.focus(); 124 } else if (!event.shiftKey && document.activeElement === last) { 125 event.preventDefault(); first?.focus(); 126 } 127 return; 128 } 129 const editable = event.target.closest?.("input, textarea, select, [contenteditable]:not([contenteditable='false']), [role='textbox']"); 130 const dialog = document.querySelector("dialog[open], [role='dialog'][aria-modal='true']"); 131 const slash = event.key === "/" && !event.metaKey && !event.ctrlKey && !event.altKey; 132 const command = event.key.toLowerCase() === "k" && (event.metaKey || event.ctrlKey) && !event.altKey && !event.shiftKey; 133 if ((!slash && !command) || editable || dialog || event.defaultPrevented) return; 134 event.preventDefault(); 135 document.querySelector("[data-book-search]")?.click(); 136 }, { capture: true }); 137 enhance(); 138})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.