OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git-v3 / lib / web / static / pgv3-repos.js

88 lines · javascript ref:
// pgv3-repos.js — lazy "view more" for the /repos landing page.
//
// The page used to render every repo the daemon serves: 131 cards and 207KB
// of HTML on the live forge, each carrying an inline SVG sparkline. The server
// now renders the 25 most recently pushed and this script pulls the rest on
// demand, at a size the reader picks (next 25 / 50 / 100 / all remaining).
//
// Fragments come back as server-rendered HTML from GET /api/repos/page, not
// as JSON we re-render here — the card markup then has exactly one definition
// and a later page cannot drift from the first.
//
// Paging state lives in data-attributes on #repo-more, seeded server-side, so
// the first "view more" continues from where the SSR page stopped rather than
// re-deriving an offset from what happens to be in the DOM.
//
// Progressive enhancement: with this script blocked the first 25 still render
// and the nav search still reaches everything below them.

(function () {
  'use strict'

  function init () {
    var more = document.getElementById('repo-more')
    var grid = document.getElementById('repo-grid')
    var btn = document.getElementById('repo-more-btn')
    var sizeSel = document.getElementById('repo-more-size')
    var status = document.getElementById('repo-more-status')
    if (!more || !grid || !btn || !sizeSel) return

    var prefix = document.body.getAttribute('data-prefix') || ''
    var total = parseInt(more.getAttribute('data-total'), 10) || 0
    var offset = parseInt(more.getAttribute('data-next-offset'), 10) || 0
    var busy = false

    function setStatus (text) {
      if (status) status.textContent = text
    }

    async function loadMore () {
      // A second click while a fetch is in flight would append the SAME page
      // twice — the offset only advances on response.
      if (busy) return
      busy = true
      btn.disabled = true
      var label = btn.textContent
      btn.textContent = 'Loading…'

      try {
        var limit = sizeSel.value // "0" is the explicit "all remaining"
        var url = prefix + '/api/repos/page?offset=' + encodeURIComponent(offset) +
          '&limit=' + encodeURIComponent(limit)
        var res = await fetch(url, { headers: { accept: 'application/json' } })
        if (!res.ok) throw new Error('HTTP ' + res.status)
        var page = await res.json()

        if (page.html) grid.insertAdjacentHTML('beforeend', page.html)
        offset = typeof page.nextOffset === 'number' ? page.nextOffset : offset
        if (typeof page.total === 'number') total = page.total
        more.setAttribute('data-next-offset', String(offset))
        more.setAttribute('data-remaining', String(page.remaining || 0))

        if (!page.remaining) {
          // Nothing left — remove the control rather than leaving a button
          // that does nothing.
          more.hidden = true
          return
        }
        setStatus('showing ' + offset + ' of ' + total)
      } catch (err) {
        // Surface it inline: a silently dead button reads as a broken page.
        setStatus('could not load more repos — ' + (err && err.message ? err.message : 'try again'))
      } finally {
        busy = false
        btn.disabled = false
        btn.textContent = label
      }
    }

    btn.addEventListener('click', loadMore)
  }

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