Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

Refresh the repository tab counts while the page is open

a2ec95616f4216c743cb1ae358ef8e9449ad8d1f · Keenan Nemetz · 9d ago · parent 0789f22e

Refresh the repository tab counts while the page is open

The header's open counts were only as fresh as the page load, so a reader
watching a repository had to reload to find out that an issue or a change had
been opened.

The tabs now poll /r/{name}/counts every 30s while the page is visible. The
response is the two badges as out-of-band updates, so the current tab, the
clone menu and anything the reader has typed stay where they are. A badge that
moves keeps one short ring, because a number changing elsewhere on the screen
is easy to miss. The endpoint answers 404 for a repository the viewer cannot
read, like every other page under /r/{name}.

server/web/static/app.js +27

@@ -747,3 +747,30 @@ const copyText = (value, done) => {
747 747 select(rows().length ? 0 : -1);
748 748 });
749 749 })();
750
751 // The repository tabs poll their open counts, and a number changing while the
752 // reader is looking somewhere else is easy to miss. A badge that moved keeps one
753 // short ring; the page's first counts are the baseline, so only a later poll that
754 // changes a number marks the tab. An out-of-band response replaces one badge at a
755 // time, so every swap reads all of them: the badge that has not been replaced yet
756 // is still the value already recorded and is left alone.
757 (() => {
758 const seen = new Map();
759 const read = () => {
760 document.querySelectorAll('.tabs .chip-count[id]').forEach((badge) => {
761 const value = badge.textContent.trim();
762 if (seen.get(badge.id) === value) return;
763 const first = !seen.has(badge.id);
764 seen.set(badge.id, value);
765 if (first) return;
766 // The class is removed and the layout read in between so a second change
767 // restarts the animation instead of finding the class already applied.
768 badge.classList.remove('count-changed');
769 void badge.offsetWidth;
770 badge.classList.add('count-changed');
771 badge.addEventListener('animationend', () => badge.classList.remove('count-changed'), { once: true });
772 });
773 };
774 read();
775 document.body.addEventListener('htmx:oobAfterSwap', read);
776 })();