1<!DOCTYPE html> 2<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"><head> 3 4<meta charset="utf-8"> 5<meta name="generator" content="quarto-1.10.18"> 6 7<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes"> 8 9 10<title>About The Warehouse â The Warehouse</title> 11<style> 12/* Default styles provided by pandoc. 13** See https://pandoc.org/MANUAL.html#variables-for-html for config info. 14*/ 15span.smallcaps{font-variant: small-caps;} 16div.columns{display: flex; gap: 1.5em;} 17div.column{flex: auto;} 18@media screen { 19div.columns{gap: min(4vw, 1.5em);} 20div.column{overflow-x: auto;} 21} 22div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;} 23ul.task-list{list-style: none;} 24ul.task-list li input[type="checkbox"] { 25 width: 0.8em; 26 margin: 0 0.8em 0.2em -1em; /* quarto-specific, see https://github.com/quarto-dev/quarto-cli/issues/4556 */ 27 vertical-align: middle; 28} 29</style> 30 31
32<script src="site_libs/quarto-nav/quarto-nav.js"></script>
vendor: 1 bytes, line 32
32
33<script src="site_libs/quarto-nav/headroom.min.js"></script>
vendor: 1 bytes, line 33
33
34<script src="site_libs/clipboard/clipboard.min.js"></script>
vendor: 1 bytes, line 34
34
35<script src="site_libs/quarto-search/autocomplete.umd.js"></script>
vendor: 1 bytes, line 35
35
36<script src="site_libs/quarto-search/fuse.min.js"></script>
vendor: 1 bytes, line 36
36
37<script src="site_libs/quarto-search/quarto-search.js"></script>
37 38<meta name="quarto:offset" content="./">
39<script src="site_libs/quarto-html/quarto.js" type="module"></script>
vendor: 1 bytes, line 39
39
40<script src="site_libs/quarto-html/tabsets/tabsets.js" type="module"></script>
vendor: 1 bytes, line 40
40
41<script src="site_libs/quarto-html/popper.min.js"></script>
vendor: 1 bytes, line 41
41
42<script src="site_libs/quarto-html/tippy.umd.min.js"></script>
vendor: 1 bytes, line 42
42
43<script src="site_libs/quarto-html/anchor.min.js"></script>
43 44<link href="site_libs/quarto-html/tippy.css" rel="stylesheet"> 45<link href="site_libs/quarto-html/quarto-syntax-highlighting-64a204ab8560d761af7679ceaba22944.css" rel="stylesheet" id="quarto-text-highlighting-styles">
46<script src="site_libs/bootstrap/bootstrap.min.js"></script>
46 47<link href="site_libs/bootstrap/bootstrap-icons.css" rel="stylesheet"> 48<link href="site_libs/bootstrap/bootstrap-bfcc276a1845d57ef5c1d60264490f1b.min.css" rel="stylesheet" append-hash="true" id="quarto-bootstrap" data-mode="light">
49<script id="quarto-search-options" type="application/json">{ 50 "location": "navbar", 51 "copy-button": false, 52 "collapse-after": 3, 53 "panel-placement": "end", 54 "type": "overlay", 55 "limit": 50, 56 "keyboard-shortcut": [ 57 "f", 58 "/", 59 "s" 60 ], 61 "show-item-context": false, 62 "language": { 63 "search-no-results-text": "No results", 64 "search-matching-documents-text": "matching documents", 65 "search-copy-link-title": "Copy link to search", 66 "search-hide-matches-text": "Hide additional matches", 67 "search-more-match-text": "more match in this document", 68 "search-more-matches-text": "more matches in this document", 69 "search-clear-button-title": "Clear", 70 "search-text-placeholder": "", 71 "search-detached-cancel-button-title": "Cancel", 72 "search-submit-button-title": "Submit", 73 "search-label": "Search" 74 } 75}</script>
75 76 77 78<link rel="stylesheet" href="styles.css"> 79</head> 80 81<body class="nav-fixed quarto-light"> 82 83<div id="quarto-search-results"></div> 84 <header id="quarto-header" class="headroom fixed-top"> 85 <nav class="navbar navbar-expand-lg " data-bs-theme="dark"> 86 <div class="navbar-container container-fluid"> 87 <div class="navbar-brand-container mx-auto"> 88 <a class='navbar-brand navbar-brand-logo' href='/'> 89 <img src="./images/the_warehouse_logo.png" alt="" class="navbar-logo light-content"> 90 <img src="./images/the_warehouse_logo.png" alt="" class="navbar-logo dark-content"> 91 </a> 92 <a class='navbar-brand' href='/'> 93 <span class="navbar-title">The Warehouse</span> 94 </a> 95 </div> 96 <div id="quarto-search" class="" title="Search"></div> 97 <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse" aria-controls="navbarCollapse" role="menu" aria-expanded="false" aria-label="Toggle navigation" onclick="if (window.quartoToggleHeadroom) { window.quartoToggleHeadroom(); }"> 98 <span class="navbar-toggler-icon"></span> 99</button> 100 <div class="collapse navbar-collapse" id="navbarCollapse"> 101 <ul class="navbar-nav navbar-nav-scroll me-auto"> 102 <li class="nav-item"> 103 <a class='nav-link' href='/'> 104<span class="menu-text">Home</span></a> 105 </li> 106 <li class="nav-item"> 107 <a class='nav-link' href='/categories/'> 108<span class="menu-text">Browse</span></a> 109 </li> 110 <li class="nav-item"> 111 <a class='nav-link' href='/reviews'> 112<span class="menu-text">Reviews</span></a> 113 </li> 114 <li class="nav-item"> 115 <a aria-current='page' class='nav-link active' href='/about'> 116<span class="menu-text">About</span></a> 117 </li> 118</ul> 119 <ul class="navbar-nav navbar-nav-scroll ms-auto"> 120 <li class="nav-item"> 121 <a class='nav-link' href='/submit-review'> <i class="bi bi-star" role="img"> 122</i> 123<span class="menu-text">Write Review</span></a> 124 </li> 125 <li class="nav-item"> 126 <a class='nav-link' href='/submit'> <i class="bi bi-plus-circle" role="img"> 127</i> 128<span class="menu-text">Submit Package</span></a> 129 </li> 130</ul> 131 </div> <!-- /navcollapse --> 132 <div class="quarto-navbar-tools"> 133</div> 134 </div> <!-- /container-fluid --> 135 </nav> 136</header> 137<!-- content --> 138<div id="quarto-content" class="quarto-container page-columns page-rows-contents page-layout-article page-navbar"> 139<!-- sidebar --> 140<!-- margin-sidebar --> 141 <div id="quarto-margin-sidebar" class="sidebar margin-sidebar"> 142 <nav id="TOC" role="doc-toc" class="toc-active"> 143 <h2 id="toc-title">On this page</h2> 144 145 <ul> 146 <li><a href="#what-is-the-warehouse" id="toc-what-is-the-warehouse" class="nav-link active" data-scroll-target="#what-is-the-warehouse">What is The Warehouse?</a> 147 <ul class="collapse"> 148 <li><a href="#the-problem" id="toc-the-problem" class="nav-link" data-scroll-target="#the-problem">The Problem</a></li> 149 <li><a href="#our-solution" id="toc-our-solution" class="nav-link" data-scroll-target="#our-solution">Our Solution</a></li> 150 </ul></li> 151 <li><a href="#how-it-works" id="toc-how-it-works" class="nav-link" data-scroll-target="#how-it-works">How It Works</a></li> 152 <li><a href="#search-strategy" id="toc-search-strategy" class="nav-link" data-scroll-target="#search-strategy">Search Strategy</a></li> 153 <li><a href="#resources-credits" id="toc-resources-credits" class="nav-link" data-scroll-target="#resources-credits">Resources & Credits</a></li> 154 <li><a href="#quality-scores" id="toc-quality-scores" class="nav-link" data-scroll-target="#quality-scores">Quality Scores</a></li> 155 <li><a href="#open-source-free" id="toc-open-source-free" class="nav-link" data-scroll-target="#open-source-free">Open Source & Free</a></li> 156 <li><a href="#get-involved" id="toc-get-involved" class="nav-link" data-scroll-target="#get-involved">Get Involved</a></li> 157 <li><a href="#contact" id="toc-contact" class="nav-link" data-scroll-target="#contact">Contact</a></li> 158 </ul> 159</nav> 160 </div> 161<!-- main --> 162<main class="content" id="quarto-document-content"> 163 164<header id="title-block-header" class="quarto-title-block default"> 165<div class="quarto-title"> 166<h1 class="title">About The Warehouse</h1> 167</div> 168 169 170 171<div class="quarto-title-meta"> 172 173 174 175 176 </div> 177 178 179 180</header> 181 182 183<section id="what-is-the-warehouse" class="level2"> 184<h2 class="anchored" data-anchor-id="what-is-the-warehouse">
184What is The Warehouse?</h2> 185<p>The Warehouse is a <strong>function-first R package directory</strong> that helps you find packages by what they do, not just by their names.</p> 186<section id="the-problem" class="level3"> 187<h3 class="anchored" data-anchor-id="the-problem">The Problem</h3> 188<p>With over 23,000 packages on CRAN alone, finding the right package for your task is overwhelming:</p> 189<ul> 190<li>Searching by keywords often misses relevant packages</li> 191<li>No easy way to compare similar packages</li> 192<li>Quality indicators are scattered or missing</li> 193<li>GitHub-only packages are hard to discover</li> 194</ul> 195</section> 196<section id="our-solution" class="level3"> 197<h3 class="anchored" data-anchor-id="our-solution">Our Solution</h3> 198<p>The Warehouse provides:</p> 199<ul> 200<li><strong>Function-first search</strong>: âestimate serial intervalâ â find all relevant packages</li> 201<li><strong>Quality scores</strong>: Automated assessment of tests, documentation, and maintenance</li> 202<li><strong>All sources</strong>: CRAN, GitHub, Bioconductor in one place</li> 203<li><strong>Community reviews</strong>: Real user experiences and recommendations</li> 204<li><strong>Smart categorization</strong>: Browse by what packages actually do</li> 205</ul> 206</section> 207</section> 208<section id="how-it-works" class="level2"> 209<h2 class="anchored" data-anchor-id="how-it-works">How It Works</h2> 210<div class="quarto-figure quarto-figure-center"> 211<figure class="figure"> 212<p><img src="images/architecture.png" class="img-fluid figure-img" alt="Architecture diagram showing the data flow from sources through the weekly pipeline to the website and end user"></p> 213<figcaption>The Warehouse â How It Works</figcaption> 214</figure> 215</div> 216<p>The Warehouse is built around a weekly automated pipeline and a static website, with AI used at two points in the process.</p> 217<p><strong>Data pipeline (weekly, automated)</strong> A GitHub Actions workflow runs every week and scrapes package metadata from the <a href="https://r-universe.dev">R-Universe API</a>, which indexes packages from CRAN, Bioconductor, and GitHub. Metadata â name, title, description, topics, quality scores, and author information â is stored in a local SQLite database. Community-submitted packages and user reviews (collected via Google Sheets) are merged in at this stage. Claude then reads each package description and automatically assigns it to one or more categories (e.g. <em>epidemiology</em>, <em>spatial analysis</em>, <em>machine learning</em>). Finally, everything is exported to a flat JSON file that the website reads directly.</p> 218<p><strong>Website</strong> The site is a static <a href="https://quarto.org">Quarto</a> site hosted on Netlify. Because the full package index is exported as a JSON file and loaded in the browser, basic search works instantly without any server round-trip. The site also has a Discover section that surfaces hidden gems (high-quality packages with low download counts) and packages featured in <a href="https://rweekly.org">R Weekly</a>.</p> 219<p><strong>AI</strong> Claude is used in two places: during the pipeline to auto-label packages with categories, and at search time to expand queries and power the chatbot. See the <a href="#search-strategy">Search Strategy</a> section below for details.</p> 220</section> 221<section id="search-strategy" class="level2"> 222<h2 class="anchored" data-anchor-id="search-strategy">Search Strategy</h2> 223<div class="quarto-figure quarto-figure-center"> 224<figure class="figure"> 225<p><img src="images/search-strategy.png" class="img-fluid figure-img" alt="Diagram showing the five-step search flow: query expansion via Claude, BM25 ranking, and result merging, with exact-match shortcut and Fuse.js fallback paths"></p> 226<figcaption>The Warehouse â Search Strategy</figcaption> 227</figure> 228</div> 229<p>Every search is handled by a Netlify serverless function that combines Claude with a BM25 ranking engine.</p> 230<p><strong>1. Query expansion via Claude</strong> The raw query is sent to Claude, which returns two things: a list of R packages likely to match, and a set of related search terms (synonyms, method n
230ames, related concepts). For example, âfit a mixed modelâ might expand to terms like <em>random effects</em>, <em>lme4</em>, <em>repeated measures</em>, and <em>REML</em>.</p> 231<p><strong>2. BM25 search</strong> The original query and Claudeâs expanded terms are run through a BM25 scoring engine over the full package index. BM25 (Best Match 25) is a standard information retrieval algorithm that ranks documents by term frequency â how often a search term appears â while penalising two things that naive term-counting gets wrong: (1) diminishing returns from repeated terms (finding a word 20 times isnât 20Ã better than finding it twice), and (2) long documents that mention a term incidentally. Fields are weighted so a match in the package name counts far more than a match in the description.</p> 232<p><strong>3. Merge</strong> Claudeâs suggested packages are placed first in the results (verified against the database), followed by the BM25-ranked matches. This means well-known packages Claude directly identifies appear at the top, while the BM25 search catches relevant packages Claude may not have named explicitly.</p> 233<p><strong>Shortcuts and fallbacks</strong> If the query exactly matches a package name, Claude is skipped and the result is returned immediately. If the API call fails or times out, the client falls back to a local <a href="https://fusejs.io">Fuse.js</a> fuzzy search over the same package data.</p> 234</section> 235<section id="resources-credits" class="level2"> 236<h2 class="anchored" data-anchor-id="resources-credits">Resources & Credits</h2> 237<p>The Warehouse doesnât aim to reinvent the wheel. The R ecosystem already has excellent resources for package discovery and distribution. We build upon these foundations, adding a unified search experience focused on <em>what packages do</em> rather than what theyâre called.</p> 238<p><strong>Our data sources:</strong></p> 239<table class="caption-top table"> 240<thead> 241<tr class="header"> 242<th>Resource</th> 243<th>What it provides</th> 244</tr> 245</thead> 246<tbody> 247<tr class="odd"> 248<td><a href="https://r-universe.dev">R-Universe</a></td> 249<td>Package metadata, binaries, and quality metrics</td> 250</tr> 251<tr class="even"> 252<td><a href="https://cran.r-project.org">CRAN</a></td> 253<td>The official R package repository with 23,000+ packages</td> 254</tr> 255<tr class="odd"> 256<td><a href="https://github.com">GitHub</a></td> 257<td>R packages hosted on GitHub, discovered via the GitHub API</td> 258</tr> 259<tr class="even"> 260<td><a href="https://bioconductor.org">Bioconductor</a></td> 261<td>Curated packages for bioinformatics and computational biology</td> 262</tr> 263<tr class="odd"> 264<td><a href="https://ropensci.org">rOpenSci</a></td> 265<td>Peer-reviewed packages for reproducible research</td> 266</tr> 267<tr class="even"> 268<td><a href="https://rweekly.org">R Weekly</a></td> 269<td>Weekly highlights of new and updated R packages</td> 270</tr> 271</tbody> 272</table> 273<p><strong>What The Warehouse adds:</strong></p> 274<ul> 275<li><strong>Unified search</strong> across all sources with AI-powered semantic matching</li> 276<li><strong>Function-first discovery</strong> â search by what you want to do, not package names</li> 277<li><strong>Curated categories</strong> for domain-specific workflows (epidemiology, pharma, geospatial, etc.)</li> 278<li><strong>Community reviews</strong> to share real-world experiences</li> 279<li><strong>Quality comparisons</strong> to help choose between similar packages</li> 280</ul> 281<p>Weâre grateful to the maintainers of these resources and the broader R community for making package discovery possible.</p> 282</section> 283<section id="quality-scores" class="level2"> 284<h2 class="anchored" data-anchor-id="quality-scores">Quality Scores</h2> 285<p>Quality scores displayed on packages come from <a href="https://r-universe.dev">R-Universe</a>, which calculates them based on factors like documentation, testing, dependencies, and maintenance activity.</p> 286<p><a href="https://docs.r-universe.dev/browse/search.html">Learn more about R-Universe scoring â</a></p> 287</section> 288<section id="open-source-free" class="level2"> 289<h2 class="anchored" data-anchor-id="open-source-free">Open Source & Free</h2> 290<p>The Warehouse is:</p> 291<ul> 292<li>â Completely free to use</li> 293<li>â Open source (MIT license)</li> 294<li>â Community-driven</li> 295</ul> 296</section> 297<section id="get-involved" class="level2"> 298<h2 class="anchored" data-anchor-id="get-involved">Get Involved</h2> 299<ul> 300<li><strong>Submit packages</strong>: <a href='/submit'>Add your package â</a></li> 301<li><strong>Leave reviews</strong>: <a href='/submit-review'>Share your experience â</a></li> 302<li><strong>
302Contribute</strong>: <a href="https://github.com/kylieainslie/warehouse">GitHub repository â</a></li> 303<li><strong>Report issues</strong>: Found a problem? Let us know!</li> 304</ul> 305</section> 306<section id="contact" class="level2"> 307<h2 class="anchored" data-anchor-id="contact">Contact</h2> 308<p>Questions or feedback? Reach out:</p> 309<ul> 310<li>GitHub: <a href="https://github.com/kylieainslie/warehouse">github.com/kylieainslie/warehouse</a></li> 311</ul> 312<hr> 313<p><em>The Warehouse is a prototype project demonstrating function-first package discovery for R and beyond.</em></p> 314<hr> 315<p><small>This website was built with <a href="https://quarto.org">Quarto</a> and created with the help of <a href="https://claude.ai/code">Claude Code</a>.</small></p> 316 317 318</section> 319 320</main> <!-- /main -->
321<script id="quarto-html-after-body" type="application/javascript"> 322 window.document.addEventListener("DOMContentLoaded", function (event) { 323 const icon = "î§"; 324 const anchorJS = new window.AnchorJS(); 325 anchorJS.options = { 326 placement: 'right', 327 icon: icon 328 }; 329 anchorJS.add('.anchored'); 330 const isCodeAnnotation = (el) => { 331 for (const clz of el.classList) { 332 if (clz.startsWith('code-annotation-')) { 333 return true; 334 } 335 } 336 return false; 337 } 338 const onCopySuccess = function(e) { 339 // button target 340 const button = e.trigger; 341 // don't keep focus 342 button.blur(); 343 // flash "checked" 344 button.classList.add('code-copy-button-checked'); 345 var currentTitle = button.getAttribute("title"); 346 button.setAttribute("title", "Copied!"); 347 let tooltip; 348 if (window.bootstrap) { 349 button.setAttribute("data-bs-toggle", "tooltip"); 350 button.setAttribute("data-bs-placement", "left"); 351 button.setAttribute("data-bs-title", "Copied!"); 352 tooltip = new bootstrap.Tooltip(button, 353 { trigger: "manual", 354 customClass: "code-copy-button-tooltip", 355 offset: [0, -8]}); 356 tooltip.show(); 357 } 358 setTimeout(function() { 359 if (tooltip) { 360 tooltip.hide(); 361 button.removeAttribute("data-bs-title"); 362 button.removeAttribute("data-bs-toggle"); 363 button.removeAttribute("data-bs-placement"); 364 } 365 button.setAttribute("title", currentTitle); 366 button.classList.remove('code-copy-button-checked'); 367 }, 1000); 368 // clear code selection 369 e.clearSelection(); 370 } 371 const getTextToCopy = function(trigger) { 372 const outerScaffold = trigger.parentElement.cloneNode(true); 373 const codeEl = outerScaffold.querySelector('code'); 374 for (const childEl of codeEl.children) { 375 if (isCodeAnnotation(childEl)) { 376 childEl.remove(); 377 } 378 } 379 return codeEl.innerText; 380 } 381 const clipboard = new window.ClipboardJS('.code-copy-button:not([data-in-quarto-modal])', { 382 text: getTextToCopy 383 }); 384 clipboard.on('success', onCopySuccess); 385 if (window.document.getElementById('quarto-embedded-source-code-modal')) { 386 const clipboardModal = new window.ClipboardJS('.code-copy-button[data-in-quarto-modal]', { 387 text: getTextToCopy, 388 container: window.document.getElementById('quarto-embedded-source-code-modal') 389 }); 390 clipboardModal.on('success', onCopySuccess); 391 } 392 var localhostRegex = new RegExp(/^(?:http|https):\/\/localhost\:?[0-9]*\//); 393 var mailtoRegex = new RegExp(/^mailto:/); 394 var filterRegex = new RegExp('/' + window.location.host + '/'); 395 var isInternal = (href) => { 396 return filterRegex.test(href) || localhostRegex.test(href) || mailtoRegex.test(href); 397 } 398 // Inspect non-navigation links and adorn them if external 399 var links = window.document.querySelectorAll('a[href]:not(.nav-link):not(.navbar-brand):not(.toc-action):not(.sidebar-link):not(.sidebar-item-toggle):not(.pagination-link):not(.no-external):not([aria-hidden]):not(.dropdown-item):not(.quarto-navigation-tool):not(.about-link)'); 400 for (var i=0; i<links.length; i++) { 401 const link = links[i]; 402 if (!isInternal(link.href)) { 403 // undo the damage that might have been done by quarto-nav.js in the case of 404 // links that we want to consider external 405 if (link.dataset.originalHref !== undefined) { 406 link.href = link.dataset.originalHref; 407 } 408 } 409 } 410 function tippyHover(el, contentFn, onTriggerFn, onUntriggerFn) { 411 const config = { 412 allowHTML: true, 413 maxWidth: 500, 414 delay: 100, 415 arrow: false, 416 appendTo: function(el) { 417 return el.parentElement; 418 }, 419 interactive: true, 420 interactiveBorder: 10, 421 theme: 'quarto', 422 placement: 'bottom-start', 423 }; 424 if (contentFn) { 425 config.content = contentFn; 426 } 427 if (onTriggerFn) { 428 config.onTrigger = onTriggerFn; 429 } 430 if (onUntriggerFn) { 431 config.onUntrigger = onUntriggerFn; 432 } 433 window.tippy(el, config); 434 } 435 const noterefs = window.document.querySelectorAll('a[role="doc-noteref"]'); 436 for (var i=0; i<noterefs.length; i++) { 437 const ref = noterefs[i]; 438 tippyHover(ref, function() { 439 // use id or data attribute instead here 440 let href = ref.getAttribute('data-footnote-href') || ref.getAttribute('href'); 441 try { href = new URL(href).hash; } catch {} 442 const id = href.replace(/^#\/?/, ""); 443 const note = window.document.getElementById(id); 444 if (note) { 445 return note.innerHTML; 446 } else { 447 return ""; 448 } 449 }); 450 } 451 const xrefs = window.document.querySelectorAll('a.quarto-xref'); 452 const processXRef = (id, note) => { 453 // Strip column container classes 454 const stripColumnClz = (el) => { 455 el.classList.remove("page-full", "page-columns"); 456 if (el.children) { 457 for (const child of el.children) {
458 stripColumnClz(child); 459 } 460 } 461 } 462 stripColumnClz(note) 463 if (id === null || id.startsWith('sec-')) { 464 // Special case sections, only their first couple elements 465 const container = document.createElement("div"); 466 if (note.children && note.children.length > 2) { 467 container.appendChild(note.children[0].cloneNode(true)); 468 for (let i = 1; i < note.children.length; i++) { 469 const child = note.children[i]; 470 if (child.tagName === "P" && child.innerText === "") { 471 continue; 472 } else { 473 container.appendChild(child.cloneNode(true)); 474 break; 475 } 476 } 477 if (window.Quarto?.typesetMath) { 478 window.Quarto.typesetMath(container); 479 } 480 return container.innerHTML 481 } else { 482 if (window.Quarto?.typesetMath) { 483 window.Quarto.typesetMath(note); 484 } 485 return note.innerHTML; 486 } 487 } else { 488 // Remove any anchor links if they are present 489 const anchorLink = note.querySelector('a.anchorjs-link'); 490 if (anchorLink) { 491 anchorLink.remove(); 492 } 493 if (window.Quarto?.typesetMath) { 494 window.Quarto.typesetMath(note); 495 } 496 if (note.classList.contains("callout")) { 497 return note.outerHTML; 498 } else { 499 return note.innerHTML; 500 } 501 } 502 } 503 for (var i=0; i<xrefs.length; i++) { 504 const xref = xrefs[i]; 505 tippyHover(xref, undefined, function(instance) { 506 instance.disable(); 507 let url = xref.getAttribute('href'); 508 let hash = undefined; 509 if (url.startsWith('#')) { 510 hash = url; 511 } else { 512 try { hash = new URL(url).hash; } catch {} 513 } 514 if (hash) { 515 const id = hash.replace(/^#\/?/, ""); 516 const note = window.document.getElementById(id); 517 if (note !== null) { 518 try { 519 const html = processXRef(id, note.cloneNode(true)); 520 instance.setContent(html); 521 } finally { 522 instance.enable(); 523 instance.show(); 524 } 525 } else { 526 // See if we can fetch this 527 fetch(url.split('#')[0]) 528 .then(res => res.text()) 529 .then(html => { 530 const parser = new DOMParser(); 531 const htmlDoc = parser.parseFromString(html, "text/html"); 532 const note = htmlDoc.getElementById(id); 533 if (note !== null) { 534 const html = processXRef(id, note); 535 instance.setContent(html); 536 } 537 }).finally(() => { 538 instance.enable(); 539 instance.show(); 540 }); 541 } 542 } else { 543 // See if we can fetch a full url (with no hash to target) 544 // This is a special case and we should probably do some content thinning / targeting 545 fetch(url) 546 .then(res => res.text()) 547 .then(html => { 548 const parser = new DOMParser(); 549 const htmlDoc = parser.parseFromString(html, "text/html"); 550 const note = htmlDoc.querySelector('main.content'); 551 if (note !== null) { 552 // This should only happen for chapter cross references 553 // (since there is no id in the URL) 554 // remove the first header 555 if (note.children.length > 0 && note.children[0].tagName === "HEADER") { 556 note.children[0].remove(); 557 } 558 const html = processXRef(null, note); 559 instance.setContent(html); 560 } 561 }).finally(() => { 562 instance.enable(); 563 instance.show(); 564 }); 565 } 566 }, function(instance) { 567 }); 568 } 569 let selectedAnnoteEl; 570 const selectorForAnnotation = ( cell, annotation) => { 571 let cellAttr = 'data-code-cell="' + cell + '"'; 572 let lineAttr = 'data-code-annotation="' + annotation + '"'; 573 const selector = 'span[' + cellAttr + '][' + lineAttr + ']'; 574 return selector; 575 } 576 const selectCodeLines = (annoteEl) => { 577 const doc = window.document; 578 const targetCell = annoteEl.getAttribute("data-target-cell"); 579 const targetAnnotation = annoteEl.getAttribute("data-target-annotation"); 580 const annoteSpan = window.document.querySelector(selectorForAnnotation(targetCell, targetAnnotation)); 581 const lines = annoteSpan.getAttribute("data-code-lines").split(","); 582 const lineIds = lines.map((line) => { 583 return targetCell + "-" + line; 584 }) 585 let top = null; 586 let height = null; 587 let parent = null; 588 if (lineIds.length > 0) { 589 //compute the position of the single el (top and bottom and make a div) 590 const el = window.document.getElementById(lineIds[0]); 591 top = el.offsetTop; 592 height = el.offsetHeight; 593 parent = el.parentElement.parentElement; 594 if (lineIds.length > 1) { 595 const lastEl = window.document.getElementById(lineIds[lineIds.length - 1]); 596 const bottom = lastEl.offsetTop + lastEl.offsetHeight; 597 height = bottom - top; 598 } 599 if (top !== null && height !== null && parent !== null) { 600 // cook up a div (if necessary) and position it 601 let div = window.document.getElementById("code-annotation-line-highlight"); 602 if (div === null) { 603 div = window.document.createElement("div"); 604 div.setAttribute("id", "code-annotation-line-highlight"); 605 div.style.position = 'absolute'; 606 parent.appendChild(div); 607 } 608 div.style.top = top - 2 + "px"; 609 div.style.height = height + 4 + "px"; 610 div.style.left = 0; 611 let gutterDiv = window.document.getElementById("code-annotation-line-highlight-gutter"); 612 if (gutterDiv === null) { 613 gutterDiv = window.document.createElement("div"); 614 gutterDiv.setAttribute("id", "code-annotation-line-highlight-gutter"); 615 gutterDiv.style.position = 'absolute'; 616 const codeCell = window.document.getElementById(targetCell); 617 const gutter = codeCell.querySelector('.code-annotation-gutter'); 618 gutter.appendChild(gutterDiv); 619 } 620 gutterDiv.style.top = top - 2 + "px"; 621 gutterDiv.style.height = height + 4 + "px"; 622 } 623 selectedAnnoteEl = annoteEl; 624 } 625 }; 626 const unselectCodeLines = () => { 627 const elementsIds = ["code-annotation-line-highlight", "code-annotation-line-highlight-gutter"]; 628 elementsIds.forEach((elId) => { 629 const div = window.document.getElementById(elId); 630 if (div) { 631 div.remove(); 632 } 633 }); 634 selectedAnnoteEl = undefined; 635 }; 636 // Handle positioning of the toggle 637 window.addEventListener( 638 "resize", 639 throttle(() => { 640 elRect = undefined; 641 if (selectedAnnoteEl) { 642 selectCodeLines(selectedAnnoteEl); 643 } 644 }, 10) 645 ); 646 function throttle(fn, ms) { 647 let throttle = false;
648 let timer; 649 return (...args) => { 650 if(!throttle) { // first call gets through 651 fn.apply(this, args); 652 throttle = true; 653 } else { // all the others get throttled 654 if(timer) clearTimeout(timer); // cancel #2 655 timer = setTimeout(() => { 656 fn.apply(this, args); 657 timer = throttle = false; 658 }, ms); 659 } 660 }; 661 } 662 // Attach click handler to the DT 663 const annoteDls = window.document.querySelectorAll('dt[data-target-cell]'); 664 for (const annoteDlNode of annoteDls) { 665 annoteDlNode.addEventListener('click', (event) => { 666 const clickedEl = event.target; 667 if (clickedEl !== selectedAnnoteEl) { 668 unselectCodeLines(); 669 const activeEl = window.document.querySelector('dt[data-target-cell].code-annotation-active'); 670 if (activeEl) { 671 activeEl.classList.remove('code-annotation-active'); 672 } 673 selectCodeLines(clickedEl); 674 clickedEl.classList.add('code-annotation-active'); 675 } else { 676 // Unselect the line 677 unselectCodeLines(); 678 clickedEl.classList.remove('code-annotation-active'); 679 } 680 }); 681 } 682 const findCites = (el) => { 683 const parentEl = el.parentElement; 684 if (parentEl) { 685 const cites = parentEl.dataset.cites; 686 if (cites) { 687 return { 688 el, 689 cites: cites.split(' ') 690 }; 691 } else { 692 return findCites(el.parentElement) 693 } 694 } else { 695 return undefined; 696 } 697 }; 698 var bibliorefs = window.document.querySelectorAll('a[role="doc-biblioref"]'); 699 for (var i=0; i<bibliorefs.length; i++) { 700 const ref = bibliorefs[i]; 701 const citeInfo = findCites(ref); 702 if (citeInfo) { 703 tippyHover(citeInfo.el, function() { 704 var popup = window.document.createElement('div'); 705 citeInfo.cites.forEach(function(cite) { 706 var citeDiv = window.document.createElement('div'); 707 citeDiv.classList.add('hanging-indent'); 708 citeDiv.classList.add('csl-entry'); 709 var biblioDiv = window.document.getElementById('ref-' + cite); 710 if (biblioDiv) { 711 citeDiv.innerHTML = biblioDiv.innerHTML; 712 } 713 popup.appendChild(citeDiv); 714 }); 715 return popup.innerHTML; 716 }); 717 } 718 } 719 }); 720 </script>
720 721</div> <!-- /content --> 722 723 724 725 726</body></html>
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.