OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git-v3 / lib / web / static / pgv3-docs-switch.js

99 lines · javascript ref:
/**
 * Repo Docs tab (`GET /repos/:name/docs`) sidebar switcher.
 *
 * Every doc is already rendered into its own `.docs-doc` pane by
 * pgv3-markdown.js, so switching is purely a class toggle — no markdown
 * re-parse, no source embedded in JS. Selection is mirrored into the URL
 * hash so a pane is deep-linkable.
 *
 * This lives in a real .js file rather than inline in the page template
 * on purpose: the inline version was where the page's fatal bug lived. A
 * regex written `/…|\/\/|#/` inside a template literal loses its
 * backslashes on the way out, so the emitted regex was invalid, the whole
 * block failed to parse, and the docs viewer rendered empty for every
 * repo. There is no template layer here to escape through.
 */

(function () {
  function panes () {
    return document.querySelectorAll('.docs-doc[data-doc-path]')
  }

  function sidebarLinks () {
    return document.querySelectorAll('.docs-sidebar a[data-doc-path]')
  }

  function rawLinkFor (path) {
    var link = document.getElementById('docs-raw-link')
    if (!link) return null
    // Rebuild from the server-rendered href's base so the URL prefix
    // (e.g. /git) and repo name never have to be duplicated here.
    var base = link.getAttribute('data-blob-base')
    if (!base) return null
    return base + path.split('/').map(encodeURIComponent).join('/')
  }

  function show (path) {
    var found = false
    var all = panes()
    for (var i = 0; i < all.length; i++) {
      var match = all[i].getAttribute('data-doc-path') === path
      if (match) found = true
      all[i].classList.toggle('active', match)
    }
    if (!found) return false

    var links = sidebarLinks()
    for (var j = 0; j < links.length; j++) {
      links[j].classList.toggle('active', links[j].getAttribute('data-doc-path') === path)
    }

    var title = document.getElementById('docs-pane-title')
    if (title) title.textContent = path

    var raw = rawLinkFor(path)
    if (raw) document.getElementById('docs-raw-link').href = raw

    return true
  }

  function currentPath () {
    var hash = window.location.hash.slice(1)
    if (!hash) return null
    try { return decodeURIComponent(hash) } catch (e) { return null }
  }

  function onSidebarClick (ev) {
    var a = ev.target.closest ? ev.target.closest('a[data-doc-path]') : null
    if (!a) return
    ev.preventDefault()
    var path = a.getAttribute('data-doc-path')
    // Assigning the hash fires hashchange, which does the switching —
    // but do it directly too so a repeat click on the active doc (same
    // hash, no event) still behaves.
    window.location.hash = '#' + encodeURIComponent(path)
    show(path)
  }

  function init () {
    var side = document.querySelector('.docs-sidebar')
    if (side) side.addEventListener('click', onSidebarClick)

    window.addEventListener('hashchange', function () {
      var p = currentPath()
      if (p) show(p)
    })

    // Deep link wins; otherwise the server already marked the first pane
    // active, so there is nothing to do.
    var start = currentPath()
    if (start) show(start)
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', init)
  } else {
    init()
  }
})()