e94d2f6481
Extends the JSON response to include per-day repo names via array_agg on action.repo_name, and surfaces them in the hover tooltip below the contribution count. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
102 lines
3.4 KiB
Cheetah
102 lines
3.4 KiB
Cheetah
{{/*
|
|
Drop this into $GITEA_CUSTOM/templates/user/profile.tmpl as part of a full
|
|
profile.tmpl override. Place near the existing heatmap section (search for
|
|
"heatmap" in the upstream profile.tmpl for your Gitea version).
|
|
|
|
The {{if eq .ContextUser.LowerName ...}} guard means only the named user
|
|
gets the override; everyone else keeps the stock heatmap.
|
|
|
|
The HEATMAP_BASE_URL placeholder must be replaced at install time with the
|
|
reverse-proxied URL of the sidecar service, e.g. https://heatmap.dideric.is
|
|
*/}}
|
|
|
|
{{if eq .ContextUser.LowerName "didericis"}}
|
|
<div class="ui attached segment private-heatmap-wrap">
|
|
<h4 class="ui header">Contributions (incl. private)</h4>
|
|
<div id="private-heatmap"
|
|
data-user="{{.ContextUser.LowerName}}"
|
|
data-source="HEATMAP_BASE_URL/heatmap/{{.ContextUser.LowerName}}.json">
|
|
<noscript>JavaScript is required to render this heatmap.</noscript>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.private-heatmap-wrap #private-heatmap {
|
|
display: grid;
|
|
grid-template-columns: repeat(53, 11px);
|
|
grid-template-rows: repeat(7, 11px);
|
|
gap: 2px;
|
|
overflow-x: auto;
|
|
padding: 4px 0;
|
|
}
|
|
.private-heatmap-wrap .cell {
|
|
border-radius: 2px;
|
|
background: var(--color-secondary, #161b22);
|
|
}
|
|
.private-heatmap-wrap .cell[data-level="1"] { background: #0e4429; }
|
|
.private-heatmap-wrap .cell[data-level="2"] { background: #006d32; }
|
|
.private-heatmap-wrap .cell[data-level="3"] { background: #26a641; }
|
|
.private-heatmap-wrap .cell[data-level="4"] { background: #39d353; }
|
|
.private-heatmap-wrap .cell.future { background: transparent; }
|
|
</style>
|
|
|
|
<script>
|
|
(async () => {
|
|
const el = document.getElementById('private-heatmap');
|
|
if (!el) return;
|
|
|
|
let data = [];
|
|
try {
|
|
const res = await fetch(el.dataset.source, { credentials: 'omit' });
|
|
if (!res.ok) throw new Error('HTTP ' + res.status);
|
|
data = await res.json();
|
|
} catch (e) {
|
|
el.textContent = 'Heatmap unavailable.';
|
|
console.warn('private-heatmap fetch failed:', e);
|
|
return;
|
|
}
|
|
|
|
const byDate = Object.fromEntries(data.map(d => [d.date, d]));
|
|
const bucket = c =>
|
|
c === 0 ? 0 : c < 3 ? 1 : c < 6 ? 2 : c < 10 ? 3 : 4;
|
|
|
|
const today = new Date();
|
|
today.setUTCHours(0, 0, 0, 0);
|
|
|
|
// Anchor rightmost column to the most recent Saturday (>= today).
|
|
const end = new Date(today);
|
|
while (end.getUTCDay() !== 6) end.setUTCDate(end.getUTCDate() + 1);
|
|
|
|
const start = new Date(end);
|
|
start.setUTCDate(start.getUTCDate() - (53 * 7 - 1));
|
|
|
|
const fmt = d => d.toISOString().slice(0, 10);
|
|
const frag = document.createDocumentFragment();
|
|
|
|
for (let i = 0; i < 53 * 7; i++) {
|
|
const d = new Date(start);
|
|
d.setUTCDate(d.getUTCDate() + i);
|
|
|
|
const cell = document.createElement('div');
|
|
cell.className = 'cell';
|
|
cell.style.gridColumn = Math.floor(i / 7) + 1;
|
|
cell.style.gridRow = (i % 7) + 1;
|
|
|
|
if (d > today) {
|
|
cell.classList.add('future');
|
|
} else {
|
|
const entry = byDate[fmt(d)];
|
|
const c = entry ? entry.count : 0;
|
|
const repos = entry && entry.repos && entry.repos.length ? entry.repos : [];
|
|
cell.dataset.level = String(bucket(c));
|
|
// Native browser tooltip. For richer ones, swap in Tippy.js etc.
|
|
const repoLine = repos.length ? '\n' + repos.join('\n') : '';
|
|
cell.title = `${fmt(d)}: ${c} contribution${c === 1 ? '' : 's'}${repoLine}`;
|
|
}
|
|
frag.appendChild(cell);
|
|
}
|
|
el.appendChild(frag);
|
|
})();
|
|
</script>
|
|
{{end}}
|