src/html.cpp

changeset 61
d77763d2fdda
parent 60
9b1cbc665851
child 62
89b12ef5e190
--- a/src/html.cpp	Sun Aug 10 11:59:03 2025 +0200
+++ b/src/html.cpp	Sun Aug 10 15:22:25 2025 +0200
@@ -86,20 +86,21 @@
         <style>
             table.heatmap {
                 table-layout: fixed;
-                border-collapse: collapse;
+                border-collapse: separate;
+                border-spacing: 4px;
                 font-family: sans-serif;
             }
 
             table.heatmap td, table.heatmap th {
                 text-align: center;
-                border: solid 1px lightgray;
-                height: 1.5em;
+                font-size: 0.75rem;
+                border: solid 2px transparent;
+                border-radius: 4px;
+                height: 1rem;
             }
 
             table.heatmap td {
-                border: solid 1px lightgray;
-                width: 1.5em;
-                height: 1.5em;
+                width: 1rem;
             }
 
             table.heatmap td:hover, table.heatmap td.popup-open {
@@ -134,6 +135,10 @@
                 background-color: #008000;
             }
 
+            table.heatmap td[data-tags]:not([data-tags=""]) {
+                border-color: gold;
+            }
+
             /* Popup styles */
             .commit-popup {
                 position: absolute;
@@ -156,6 +161,15 @@
                 padding-bottom: 5px;
             }
 
+            .commit-popup h4 {
+                margin-top: .5em;
+                margin-bottom: .5em;
+            }
+
+            .commit-popup h5 {
+                margin-top: 0;
+            }
+
             .commit-popup ul {
                 margin: 0;
                 padding-left: 20px;
@@ -191,6 +205,9 @@
                         let content = '<span class="close-btn">×</span>';
                         if (Array.isArray(summaries)) {
                             content += `<h3>${date}: ${summaries.length} commit${summaries.length !== 1 ? 's' : ''}</h3>`;
+                            if (this.dataset.tags) {
+                                content += `<h5>Tags: ${this.dataset.tags}</h5>`;
+                            }
                             content += '<ul>';
                             summaries.forEach(summary => {
                                 content += `<li>${summary}</li>`;
@@ -198,6 +215,7 @@
                             content += '</ul>';
                         } else {
                             const repos = Object.keys(summaries).sort();
+                            const tags = this.dataset.tags ? JSON.parse(this.dataset.tags) : {};
                             let total_commits = 0;
                             for (const repo of repos) total_commits += summaries[repo].length;
                             content += `<h3>${date}: ${total_commits} commit${total_commits !== 1 ? 's' : ''}</h3>`;
@@ -208,6 +226,9 @@
                                 } else {
                                     content += `<h4>${repo}</h4>`;
                                 }
+                                if (tags[repo]) {
+                                    content += `<h5>Tags: ${tags[repo]}</h5>`;
+                                }
                                 content += '<ul>';
                                 commits.forEach(commit => {
                                     content += `<li>${commit}</li>`;
@@ -372,7 +393,7 @@
         static_cast<unsigned>(ymd.month()),
         static_cast<unsigned>(ymd.day()));
 
-    // Build a JSON object of commit summaries
+    // Utility function to escape strings in JSON
     auto escape_json = [](std::string str) static {
         size_t pos = str.find('\"');
         if (pos == std::string::npos) return str;
@@ -383,6 +404,8 @@
         } while ((pos = escaped.find('\"', pos)) != std::string::npos);
         return escaped;
     };
+
+    // Build a JSON object of commit summaries
     auto add_summaries = [escape_json](std::string &json, const std::vector<std::string> &summaries) {
         // We have to iterate in reverse order to sort the summaries chronologically
         for (const auto &summary :  summaries | std::views::reverse) {
@@ -410,13 +433,47 @@
         summaries_json += '}';
     }
 
+    // Build a JSON object of tags
+    std::string tags_json;
+    if (hide_repo_names) {
+        for (const auto &tags_vector: commits.tags | std::views::values) {
+            for (const auto &tag: tags_vector) {
+                tags_json += escape_json(tag);
+                tags_json += ' ';
+            }
+        }
+        if (!tags_json.empty()) {
+            tags_json.pop_back();
+        }
+    } else {
+        tags_json += '{';
+        for (const auto &[repo, tags_vector] : commits.tags) {
+            tags_json += "\"" + escape_json(repo) + "\":\"";
+            for (const auto &tag: tags_vector) {
+                tags_json += escape_json(tag);
+                tags_json += ' ';
+            }
+            if (!tags_vector.empty()) {
+                tags_json.pop_back();
+            }
+            tags_json += "\",";
+        }
+        // note: in contrast to summaries, we want an empty string here when there's nothing to report
+        tags_json.pop_back();
+        if (!commits.tags.empty()) {
+            tags_json += '}';
+        }
+    }
+
+    // Output
     indent();
-    printf("<td class=\"%s\" title=\"%s: %u %s\" data-date=\"%s\" data-summaries=\"%s\"></td>\n",
+    printf("<td class=\"%s\" title=\"%s: %u %s\" data-date=\"%s\" data-summaries=\"%s\" data-tags=\"%s\"></td>\n",
         color_class,
         date_str,
         commits.count(),
         commits.count() == 1 ? "commit" : "commits",
         date_str,
-        encode(summaries_json).c_str()
+        encode(summaries_json).c_str(),
+        encode(tags_json).c_str()
     );
 }

mercurial