src/html.cpp

changeset 56
3d2720f95cfb
parent 54
586dcd606e47
equal deleted inserted replaced
54:586dcd606e47 56:3d2720f95cfb
185 cell.addEventListener('click', function(e) { 185 cell.addEventListener('click', function(e) {
186 const date = this.dataset.date; 186 const date = this.dataset.date;
187 const summaries = JSON.parse(this.dataset.summaries); 187 const summaries = JSON.parse(this.dataset.summaries);
188 188
189 // Create popup content 189 // Create popup content
190 let content = `<span class="close-btn">×</span> 190 let content = '<span class="close-btn">×</span>';
191 <h3>${date}: ${summaries.length} commit${summaries.length !== '1' ? 's' : ''}</h3>`; 191 if (Array.isArray(summaries)) {
192 content += '<ul>'; 192 content += `<h3>${date}: ${summaries.length} commit${summaries.length !== 1 ? 's' : ''}</h3>`;
193 summaries.forEach(summary => { 193 content += '<ul>';
194 content += `<li>${summary}</li>`; 194 summaries.forEach(summary => {
195 }); 195 content += `<li>${summary}</li>`;
196 content += '</ul>'; 196 });
197 content += '</ul>';
198 } else {
199 const repos = Object.keys(summaries).sort();
200 let total_commits = 0;
201 for (const repo of repos) total_commits += summaries[repo].length;
202 content += `<h3>${date}: ${total_commits} commit${total_commits !== 1 ? 's' : ''}</h3>`;
203 for (const repo of repos) {
204 const commits = summaries[repo];
205 if (repos.length > 1) {
206 content += `<h4>${repo} (${commits.length} commit${commits.length !== 1 ? 's' : ''})</h4>`;
207 } else {
208 content += `<h4>${repo}</h4>`;
209 }
210 content += '<ul>';
211 commits.forEach(commit => {
212 content += `<li>${commit}</li>`;
213 });
214 content += '</ul>';
215 }
216 }
197 popup.innerHTML = content; 217 popup.innerHTML = content;
198 218
199 // Position popup near the cell 219 // Position popup near the cell
200 const rect = this.getBoundingClientRect(); 220 const rect = this.getBoundingClientRect();
201 popup.style.left = rect.left + window.scrollX + 'px'; 221 popup.style.left = rect.left + window.scrollX + 'px';
324 void html::cell_out_of_range() { 344 void html::cell_out_of_range() {
325 indent(); 345 indent();
326 puts("<td class=\"out-of-range\"></td>"); 346 puts("<td class=\"out-of-range\"></td>");
327 } 347 }
328 348
329 void html::cell(year_month_day ymd, const fm::commits &commits) { 349 void html::cell(year_month_day ymd, bool hide_repo_names, const fm::commits &commits) {
330 const char *color_class; 350 const char *color_class;
331 if (commits.count() == 0) { 351 if (commits.count() == 0) {
332 color_class = "zero-commits"; 352 color_class = "zero-commits";
333 } else if (commits.count() == 1) { 353 } else if (commits.count() == 1) {
334 color_class = "one-commit"; 354 color_class = "one-commit";
349 weekdays[weekday(ymd).iso_encoding() - 1], 369 weekdays[weekday(ymd).iso_encoding() - 1],
350 static_cast<int>(ymd.year()), 370 static_cast<int>(ymd.year()),
351 static_cast<unsigned>(ymd.month()), 371 static_cast<unsigned>(ymd.month()),
352 static_cast<unsigned>(ymd.day())); 372 static_cast<unsigned>(ymd.day()));
353 373
354 // Build a JSON array of commit summaries 374 // Build a JSON object of commit summaries
355 // We have to iterate in reverse order to sort the summaries chronologically 375 auto escape_json = [](std::string str) static {
356 std::string summaries_json = "["; 376 size_t pos = str.find('\"');
357 for (const auto &summary : commits.summaries | std::views::reverse) { 377 if (pos == std::string::npos) return str;
358 // Escape quotes in JSON 378 std::string escaped = std::move(str);
359 size_t pos = summary.find('\"'); 379 do {
360 if (pos == std::string::npos) { 380 escaped.replace(pos, 1, "\\\"");
361 summaries_json += "\"" + summary + "\","; 381 pos += 2;
362 } else { 382 } while ((pos = escaped.find('\"', pos)) != std::string::npos);
363 std::string escaped = summary; 383 return escaped;
364 do { 384 };
365 escaped.replace(pos, 1, "\\\""); 385 auto add_summaries = [escape_json](std::string &json, const std::vector<std::string> &summaries) {
366 pos += 2; 386 // We have to iterate in reverse order to sort the summaries chronologically
367 } while ((pos = escaped.find('\"', pos)) != std::string::npos); 387 for (const auto &summary : summaries | std::views::reverse) {
368 summaries_json += "\"" + escaped + "\","; 388 json += "\"" + escape_json(summary) + "\",";
369 } 389 }
370 } 390 json.pop_back();
371 summaries_json.pop_back(); 391 };
372 summaries_json += "]"; 392 std::string summaries_json;
393 if (hide_repo_names) {
394 summaries_json += '[';
395 for (const auto &summaries: commits.summaries | std::views::values) {
396 add_summaries(summaries_json, summaries);
397 }
398 summaries_json += ']';
399 } else {
400 summaries_json += '{';
401 for (const auto &[repo, summaries] : commits.summaries) {
402 summaries_json += "\"" + escape_json(repo) + "\":[";
403 add_summaries(summaries_json, summaries);
404 summaries_json += "],";
405 }
406 summaries_json.pop_back();
407 summaries_json += '}';
408 }
373 409
374 indent(); 410 indent();
375 printf("<td class=\"%s\" title=\"%s: %u %s\" data-date=\"%s\" data-summaries=\"%s\"></td>\n", 411 printf("<td class=\"%s\" title=\"%s: %u %s\" data-date=\"%s\" data-summaries=\"%s\"></td>\n",
376 color_class, 412 color_class,
377 date_str, 413 date_str,

mercurial