vole
Clone
Read only, no credentials:
https://rodent.software/vole.git
Write over SSH with a registered key:
git clone ssh://git@rodent.software: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/templates/chrome.html +23−3
| @@ -93,6 +93,18 @@ | |||
| 93 | 93 | </html> | |
| 94 | 94 | {{end}} | |
| 95 | 95 | ||
| 96 | {{/* The open counts beside the Issues and Changes tabs. They are templates of | ||
| 97 | their own so the header's poll can replace the badges and nothing else: the | ||
| 98 | tab the reader is on, the clone menu and any open form stay where they are. | ||
| 99 | The ids are what the out-of-band swap matches, so the page and the poll | ||
| 100 | agree on where a count goes. */}} | ||
| 101 | {{define "repo_issue_count"}}<span class="chip-count" id="repo-issue-count"{{if .Live}} hx-swap-oob="outerHTML"{{end}}>{{.OpenIssues}}</span>{{end}} | ||
| 102 | {{define "repo_request_count"}}<span class="chip-count" id="repo-request-count"{{if .Live}} hx-swap-oob="outerHTML"{{end}}>{{.OpenRequests}}</span>{{end}} | ||
| 103 | {{define "repo_counts_live"}} | ||
| 104 | {{template "repo_issue_count" .}} | ||
| 105 | {{template "repo_request_count" .}} | ||
| 106 | {{end}} | ||
| 107 | |||
| 96 | 108 | {{define "repohead"}} | |
| 97 | 109 | <div class="repohead"> | |
| 98 | 110 | <div class="repo-title"> | |
| @@ -119,12 +131,20 @@ | |||
| 119 | 131 | </div> | |
| 120 | 132 | {{if .Description}}<p class="repo-desc">{{.Description}}</p>{{end}} | |
| 121 | 133 | </div> | |
| 122 | <nav class="tabs" aria-label="Repository"> | ||
| 134 | {{/* One poll for the header's counts. The response is out-of-band updates to | ||
| 135 | the two badges, so the tabs are not re-rendered and the page keeps its | ||
| 136 | state. It pauses while the tab is hidden, so a forgotten page does not | ||
| 137 | keep asking. */}} | ||
| 138 | <nav class="tabs" aria-label="Repository" | ||
| 139 | hx-get="/r/{{.Name}}/counts" | ||
| 140 | hx-trigger="every 30s [document.visibilityState === 'visible']" | ||
| 141 | hx-sync="this:replace" | ||
| 142 | hx-swap="none"> | ||
| 123 | 143 | <a class="repotab{{if eq .Tab "overview"}} active{{end}}" href="/r/{{.Name}}"{{if eq .Tab "overview"}} aria-current="page"{{end}}>{{icon "layout-dashboard"}}Overview</a> | |
| 124 | 144 | <a href="/r/{{.Name}}/activity"{{if eq .Tab "activity"}} class="active" aria-current="page"{{end}}>{{icon "activity"}}Activity</a> | |
| 125 | 145 | <a href="/r/{{.Name}}/code"{{if eq .Tab "code"}} class="active" aria-current="page"{{end}}>{{icon "code"}}Code</a> | |
| 126 | <a href="/r/{{.Name}}/issues"{{if eq .Tab "issues"}} class="active" aria-current="page"{{end}}>{{icon "circle-dot"}}Issues <span class="chip-count">{{.OpenIssues}}</span></a> | ||
| 127 | <a href="/r/{{.Name}}/requests"{{if eq .Tab "requests"}} class="active" aria-current="page"{{end}}>{{icon "git-pull-request"}}Changes <span class="chip-count">{{.OpenRequests}}</span></a> | ||
| 146 | <a href="/r/{{.Name}}/issues"{{if eq .Tab "issues"}} class="active" aria-current="page"{{end}}>{{icon "circle-dot"}}Issues {{template "repo_issue_count" .}}</a> | ||
| 147 | <a href="/r/{{.Name}}/requests"{{if eq .Tab "requests"}} class="active" aria-current="page"{{end}}>{{icon "git-pull-request"}}Changes {{template "repo_request_count" .}}</a> | ||
| 128 | 148 | <a href="/r/{{.Name}}/releases"{{if eq .Tab "releases"}} class="active" aria-current="page"{{end}}>{{icon "tag"}}Releases</a> | |
| 129 | 149 | <a href="/r/{{.Name}}/runs"{{if eq .Tab "runs"}} class="active" aria-current="page"{{end}}>{{icon "play"}}Runs</a> | |
| 130 | 150 | <a href="/r/{{.Name}}/locks"{{if eq .Tab "locks"}} class="active" aria-current="page"{{end}}>{{icon "lock"}}Locks</a> | |