rfheadless 0.1.0

A headless browsing engine API for Rust
Documentation
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width,initial-scale=1" />
  <title>rfheadless docs</title>
  <link rel="stylesheet" href="assets/site.css">
</head>
<body>
  <header class="site-header">
    <div class="wrapper header-row">
      <button id="sidebar-toggle" class="sidebar-toggle" aria-label="Toggle sidebar"></button>
      <div class="site-title"><a href="#/">rfheadless</a></div>
    </div>
  </header>

  <div class="docs-layout">
    <aside class="sidebar" id="sidebar" aria-label="Documentation navigation">
      <div class="sidebar-header">
        <div class="logo"><img src="assets/images/logo.png" alt="rfheadless logo"></div>
        <div class="brand">rfheadless</div>
      </div>
      <div class="sidebar-search">
        <input id="nav-search" placeholder="Filter pages…" aria-label="Filter pages" />
      </div>
      <nav id="nav" class="nav-list" role="navigation"></nav>
    </aside>

    <main class="content" id="content">
      <h1>rfheadless</h1>
      <p>Welcome to the rfheadless documentation. Use the navigation to browse pages.</p>
    </main>
  </div>

  <footer class="site-footer wrapper">
    <small>© rfheadless — docs generated from repository Markdown</small>
  </footer>

  <script src="assets/vendor/marked.min.js"></script>
  <script src="assets/vendor/dompurify.min.js"></script>
  <script>
    // Simple SPA that loads markdown pages listed in docs/docs.json and builds a nested nav
    function makeLink(title, path) {
      const a = document.createElement('a');
      a.href = '#/' + path;
      a.className = 'nav-link';
      a.textContent = title;
      a.addEventListener('click', (e) => { e.preventDefault(); navigate(path); });
      a.dataset.path = path;
      return a;
    }

    function makeSection(section) {
      const div = document.createElement('div');
      div.className = 'nav-section';
      const header = document.createElement('div');
      header.className = 'nav-section-header';
      const title = document.createElement('span');
      title.textContent = section.title;
      header.appendChild(title);
      const toggle = document.createElement('button');
      toggle.className = 'nav-toggle';
      toggle.setAttribute('aria-expanded','true');
      toggle.innerHTML = '';
      header.appendChild(toggle);
      div.appendChild(header);

      const ul = document.createElement('ul');
      ul.className = 'nav-sub';
      (section.children || []).forEach(child => {
        const li = document.createElement('li');
        li.appendChild(makeLink(child.title, child.path));
        ul.appendChild(li);
      });
      div.appendChild(ul);

      toggle.addEventListener('click', () => {
        const expanded = toggle.getAttribute('aria-expanded') === 'true';
        toggle.setAttribute('aria-expanded', String(!expanded));
        ul.style.display = expanded ? 'none' : 'block';
        toggle.innerHTML = expanded ? '' : '';
      });

      return div;
    }

    async function loadIndex() {
      const res = await fetch('./docs.json');
      const pages = await res.json();
      const nav = document.getElementById('nav');
      pages.forEach(p => {
        if (p.children) {
          nav.appendChild(makeSection(p));
        } else {
          const div = document.createElement('div');
          div.className = 'nav-item';
          div.appendChild(makeLink(p.title, p.path));
          nav.appendChild(div);
        }
      });
      // wire up search box to filter nav
      const search = document.getElementById('nav-search');
      search.addEventListener('input', (e) => {
        const q = e.target.value.trim().toLowerCase();
        filterNav(q);
      });

      // keyboard shortcut: press '/' to focus search
      window.addEventListener('keydown', (ev) => {
        if (ev.key === '/' && document.activeElement.tagName !== 'INPUT') {
          ev.preventDefault();
          document.getElementById('nav-search').focus();
        }
      });
    }

    function filterNav(q) {
      document.querySelectorAll('.nav-item, .nav-section').forEach(el => {
        if (el.classList.contains('nav-section')) {
          // hide entire section if none of its children match
          const sub = Array.from(el.querySelectorAll('.nav-link'));
          const matched = sub.some(a => a.textContent.toLowerCase().includes(q));
          el.style.display = matched ? '' : 'none';
        } else {
          const a = el.querySelector('.nav-link');
          if (a) el.style.display = a.textContent.toLowerCase().includes(q) ? '' : 'none';
        }
      });    }

    function setActive(path) {
      document.querySelectorAll('.nav-link').forEach(n => {
        if (n.dataset.path === path) n.classList.add('active'); else n.classList.remove('active');
      });
    }

    async function navigate(path, push=true) {
      const content = document.getElementById('content');
      content.innerHTML = '<p>Loading…</p>';
      try {
        const mdRes = await fetch(path);
        if (!mdRes.ok) { content.innerHTML = '<p>Page not found</p>'; return; }
        const md = await mdRes.text();
        const rawHtml = marked.parse(md);
        const safeHtml = DOMPurify.sanitize(rawHtml);
        content.innerHTML = safeHtml;
        setActive(path);
        if (push) history.pushState({path}, '', '#/' + path);
        window.scrollTo(0,0);
        // on small screens hide sidebar
        if (window.innerWidth < 900) document.getElementById('sidebar').classList.remove('open');
      } catch (e) {
        content.innerHTML = '<p>Failed to load page</p>';
        console.error(e);
      }
    }

    window.addEventListener('popstate', (ev) => {
      const state = ev.state;
      if (state && state.path) navigate(state.path, false);
      else {
        document.getElementById('content').innerHTML = '<h1>rfheadless</h1><p>Welcome to the rfheadless documentation. Use the navigation to browse pages.</p>';
      }
    });

    document.getElementById('sidebar-toggle').addEventListener('click', () => {
      document.getElementById('sidebar').classList.toggle('open');
    });

    // On load: build nav and load initial page if present in hash
    (async function(){ await loadIndex();
      const hash = location.hash.replace('#/','');
      if (hash) navigate(hash, false);
    })();
  </script>
</body>
</html>