2 const heatmap = $(".heatmap").removeClass("d-none").addClass("d-grid");
3 const weekInfo = getWeekInfo();
4 const maxPerDay = heatmap.data("max-per-day");
5 const weekdayLabels = heatmap.find("[data-weekday]");
7 let previousMonth = null;
9 for (const day of weekdayLabels) {
11 const weekday = $day.data("weekday");
12 if (weekday < weekInfo.firstDay % 7) {
13 $day.insertAfter(weekdayLabels.last());
15 const weekdayRow = getWeekdayRow(weekday);
16 if (weekdayRow % 2 === 0) $day.remove();
17 $day.css("grid-area", weekdayRow + " / 1");
20 for (const day of heatmap.find("[data-date]")) {
22 const date = new Date($day.data("date"));
23 if (date.getUTCDay() === weekInfo.firstDay % 7) {
25 const currentMonth = getMonthOfThisWeek(date);
26 if (previousMonth === null) {
27 previousMonth = currentMonth;
28 heatmap.find(`[data-month]:has( ~ [data-month="${previousMonth}"])`).remove();
29 heatmap.find("[data-month]").first().css("grid-column-start", 2);
31 if (previousMonth % 12 !== currentMonth % 12) {
32 heatmap.find(`[data-month="${previousMonth}"]`).css("grid-column-end", weekColumn);
34 heatmap.find(`[data-month="${previousMonth}"]`).css("grid-column-start", weekColumn);
37 if (weekColumn === 1) {
41 const count = $day.data("count") ?? 0;
42 const tooltipText = getTooltipText(date, count);
44 .css("grid-area", getWeekdayRow(date.getUTCDay()) + " / " + weekColumn)
45 .attr("aria-label", tooltipText)
49 delay: { show: 0, hide: 0 }
52 .css("opacity", Math.sqrt(count / maxPerDay));
54 heatmap.find(`[data-month="${previousMonth}"] ~ [data-month]`).remove();
55 heatmap.find("[data-month]").last().css("grid-column-end", weekColumn + 1);
57 function getMonthOfThisWeek(date) {
58 const nextDate = new Date(date);
59 nextDate.setUTCDate(date.getUTCDate() + weekInfo.minimalDays - 1);
60 return nextDate.getUTCMonth() + 1;
63 function getWeekdayRow(weekday) {
64 return ((weekday - weekInfo.firstDay + 7) % 7) + 2;
67 function getTooltipText(date, value) {
68 const localizedDate = OSM.i18n.l("date.formats.heatmap", date);
71 return OSM.i18n.t("javascripts.heatmap.tooltip.contributions", { count: value, date: localizedDate });
74 return OSM.i18n.t("javascripts.heatmap.tooltip.no_contributions", { date: localizedDate });
77 function getWeekInfo() {
78 const weekInfo = { firstDay: 1, minimalDays: 4 }; // ISO 8601
79 const locale = new Intl.Locale(OSM.i18n.locale);
80 return { ...weekInfo, ...locale.weekInfo, ...locale.getWeekInfo?.() };