Skip to content

Commit 6bae159

Browse files
committed
Modernize UI
* Replace Blueprint CSS reset with modern reset * Rewrite stylesheet with design tokens, system fonts, refined light/dark themes, and better contrast ratios * Replace jQuery Colorbox lightbox with native <dialog> element * Add inline coverage bar visualization to file list rows * Add ARIA attributes and proper HTML5 semantics * Remove colorbox, jquery-ui, and DataTables sort images * CSS-only sort indicators for DataTables columns Compiled assets drop from 336KB to 218KB. Closes #65.
1 parent b72d0bb commit 6bae159

37 files changed

+994
-2134
lines changed

assets/javascripts/application.js

Lines changed: 143 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -2,50 +2,54 @@
22
//= require_directory ./plugins/
33
//= require_self
44

5-
(function() {
6-
var toggle = document.getElementById('dark-mode-toggle');
7-
if (!toggle) return;
8-
9-
function isDark() {
10-
return document.documentElement.classList.contains('dark-mode') ||
11-
(!document.documentElement.classList.contains('light-mode') &&
12-
window.matchMedia('(prefers-color-scheme: dark)').matches);
13-
}
5+
/* --- Main application logic -------------------------------- */
146

15-
function updateLabel() {
16-
toggle.textContent = isDark() ? '\u2600\uFE0F Light' : '\uD83C\uDF19 Dark';
17-
}
7+
$(document).ready(function () {
188

19-
updateLabel();
9+
// --- Dark mode toggle ---
2010

21-
toggle.addEventListener('click', function() {
22-
if (isDark()) {
23-
document.documentElement.classList.remove('dark-mode');
24-
document.documentElement.classList.add('light-mode');
25-
localStorage.setItem('simplecov-dark-mode', 'light');
26-
} else {
27-
document.documentElement.classList.remove('light-mode');
28-
document.documentElement.classList.add('dark-mode');
29-
localStorage.setItem('simplecov-dark-mode', 'dark');
11+
(function() {
12+
var toggle = document.getElementById('dark-mode-toggle');
13+
if (!toggle) return;
14+
15+
function isDark() {
16+
return document.documentElement.classList.contains('dark-mode') ||
17+
(!document.documentElement.classList.contains('light-mode') &&
18+
window.matchMedia('(prefers-color-scheme: dark)').matches);
19+
}
20+
21+
function updateLabel() {
22+
toggle.textContent = isDark() ? '\u2600\uFE0F Light' : '\uD83C\uDF19 Dark';
3023
}
24+
3125
updateLabel();
32-
});
3326

34-
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function() {
35-
if (!localStorage.getItem('simplecov-dark-mode')) {
27+
toggle.addEventListener('click', function() {
28+
if (isDark()) {
29+
document.documentElement.classList.remove('dark-mode');
30+
document.documentElement.classList.add('light-mode');
31+
localStorage.setItem('simplecov-dark-mode', 'light');
32+
} else {
33+
document.documentElement.classList.remove('light-mode');
34+
document.documentElement.classList.add('dark-mode');
35+
localStorage.setItem('simplecov-dark-mode', 'dark');
36+
}
3637
updateLabel();
37-
}
38-
});
39-
})();
38+
});
4039

41-
$(document).ready(function () {
40+
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function() {
41+
if (!localStorage.getItem('simplecov-dark-mode')) {
42+
updateLabel();
43+
}
44+
});
45+
})();
4246
$('.file_list').dataTable({
4347
order: [[1, "asc"]],
4448
paging: false
4549
});
4650

47-
// Materialize a source file from its <template> tag into the .source_files container.
48-
// Returns the materialized element, or the existing one if already materialized.
51+
// --- Template materialization ---
52+
4953
function materializeSourceFile(sourceFileId) {
5054
var existing = document.getElementById(sourceFileId);
5155
if (existing) return $(existing);
@@ -57,111 +61,145 @@ $(document).ready(function () {
5761
$('.source_files').append(clone);
5862

5963
var el = $('#' + sourceFileId);
60-
61-
// Apply syntax highlighting on first materialization
6264
el.find('pre code').each(function (i, e) { hljs.highlightBlock(e, ' ') });
63-
el.addClass('highlighted');
6465

6566
return el;
6667
}
6768

68-
// Syntax highlight source files on first toggle of the file view popup
69-
$("a.src_link").click(function () {
70-
var sourceFileId = $(this).attr('href').substring(1);
71-
materializeSourceFile(sourceFileId);
72-
});
69+
// --- Native dialog for source file viewing ---
7370

71+
var dialog = document.getElementById('source-dialog');
72+
var dialogBody = document.getElementById('source-dialog-body');
73+
var dialogTitle = document.getElementById('source-dialog-title');
74+
var dialogClose = dialog.querySelector('.source-dialog__close');
7475
var prev_anchor;
7576
var curr_anchor;
7677

77-
// Set-up of popup for source file views
78-
$("a.src_link").colorbox({
79-
transition: "none",
80-
inline: true,
81-
opacity: 1,
82-
width: "95%",
83-
height: "95%",
84-
onLoad: function () {
85-
prev_anchor = curr_anchor ? curr_anchor : window.location.hash.substring(1);
86-
curr_anchor = this.href.split('#')[1];
78+
function openSourceFile(sourceFileId, linenumber) {
79+
var el = materializeSourceFile(sourceFileId);
80+
if (!el || !el.length) return;
8781

88-
// Ensure the source file is materialized before colorbox tries to inline it
89-
materializeSourceFile(curr_anchor.replace(/-L.*/, ''));
82+
// Clone the source table into the dialog
83+
var sourceTable = el[0].cloneNode(true);
9084

91-
window.location.hash = curr_anchor;
85+
// Move header content to dialog title area
86+
var header = sourceTable.querySelector('.header');
87+
if (header) {
88+
dialogTitle.innerHTML = header.innerHTML;
89+
header.remove();
90+
}
9291

93-
$('.file_list_container').hide();
94-
},
95-
onComplete: function () {
96-
$('#cboxLoadedContent').attr('tabindex', '0').focus();
97-
},
98-
onCleanup: function () {
99-
if (prev_anchor && prev_anchor != curr_anchor) {
100-
$('a[href="#' + prev_anchor + '"]').click();
101-
curr_anchor = prev_anchor;
102-
} else {
103-
$('.group_tabs a:first').click();
104-
prev_anchor = curr_anchor;
105-
curr_anchor = "#_AllFiles";
92+
dialogBody.innerHTML = '';
93+
dialogBody.appendChild(sourceTable);
94+
95+
prev_anchor = curr_anchor ? curr_anchor : window.location.hash.substring(1);
96+
curr_anchor = sourceFileId + (linenumber ? '-L' + linenumber : '');
97+
window.location.hash = curr_anchor;
98+
99+
dialog.showModal();
100+
dialogBody.focus();
101+
102+
// Scroll to line number if specified
103+
if (linenumber) {
104+
var targetLine = dialogBody.querySelector('li[data-linenumber="' + linenumber + '"]');
105+
if (targetLine) {
106+
dialogBody.scrollTop = targetLine.offsetTop;
107+
}
108+
}
109+
}
110+
111+
function closeDialog() {
112+
dialog.close();
113+
114+
if (prev_anchor && prev_anchor.substring(0, 1) === '_') {
115+
window.location.hash = prev_anchor;
116+
} else {
117+
var activeTab = $('.group_tabs li.active a').attr('href');
118+
if (activeTab) {
119+
window.location.hash = activeTab.replace('#', '#_');
106120
}
107-
window.location.hash = curr_anchor;
121+
}
122+
123+
curr_anchor = window.location.hash.substring(1);
108124

109-
var active_group = $('.group_tabs li.active a').attr('class');
125+
var active_group = $('.group_tabs li.active a').attr('class');
126+
if (active_group) {
110127
$("#" + active_group + ".file_list_container").show();
111128
}
129+
}
130+
131+
dialogClose.addEventListener('click', closeDialog);
132+
133+
dialog.addEventListener('close', function() {
134+
dialogBody.innerHTML = '';
135+
dialogTitle.innerHTML = '';
112136
});
113137

114-
// Event delegation for line number clicks (works with template-materialized elements)
115-
$(document).on('click', '.source_table li[data-linenumber]', function () {
116-
$('#cboxLoadedContent').scrollTop(this.offsetTop);
117-
var new_anchor = curr_anchor.replace(/-.*/, '') + '-L' + $(this).data('linenumber');
138+
// Close on backdrop click
139+
dialog.addEventListener('click', function(e) {
140+
if (e.target === dialog) {
141+
closeDialog();
142+
}
143+
});
144+
145+
// Source link clicks
146+
$(document).on('click', 'a.src_link', function (e) {
147+
e.preventDefault();
148+
var sourceFileId = $(this).attr('href').substring(1);
149+
openSourceFile(sourceFileId);
150+
});
151+
152+
// Clicking anywhere in a file row opens the source view
153+
$(document).on('click', 'table.file_list tbody tr', function (e) {
154+
if ($(e.target).closest('a').length) return; // let link clicks handle themselves
155+
var link = $(this).find('a.src_link');
156+
if (link.length) {
157+
openSourceFile(link.attr('href').substring(1));
158+
}
159+
});
160+
161+
// Line number clicks within dialog
162+
$(document).on('click', '.source-dialog .source_table li[data-linenumber]', function () {
163+
dialogBody.scrollTop = this.offsetTop;
164+
var linenumber = $(this).data('linenumber');
165+
var new_anchor = curr_anchor.replace(/-L.*/, '').replace(/-.*/, '') + '-L' + linenumber;
118166
window.location.replace(window.location.href.replace(/#.*/, '#' + new_anchor));
119167
curr_anchor = new_anchor;
120168
return false;
121169
});
122170

123-
window.onpopstate = function (event) {
124-
if (window.location.hash.substring(0, 2) == "#_") {
125-
$.colorbox.close();
126-
curr_anchor = window.location.hash.substring(1);
127-
} else {
128-
if ($('#colorbox').is(':hidden')) {
129-
var anchor = window.location.hash.substring(1);
130-
var ary = anchor.split('-L');
131-
var source_file_id = ary[0];
132-
var linenumber = ary[1];
133-
134-
// Materialize before opening colorbox
135-
materializeSourceFile(source_file_id);
136-
137-
$('a.src_link[href="#' + source_file_id + '"]').colorbox({ open: true });
138-
if (linenumber !== undefined) {
139-
$('#cboxLoadedContent').scrollTop($('#cboxLoadedContent .source_table li[data-linenumber="' + linenumber + '"]')[0].offsetTop);
140-
}
141-
}
171+
// --- Hash-based navigation ---
172+
173+
window.onpopstate = function () {
174+
var hash = window.location.hash.substring(1);
175+
if (!hash) return;
176+
177+
if (hash.substring(0, 1) === '_') {
178+
if (dialog.open) closeDialog();
179+
curr_anchor = hash;
180+
} else if (!dialog.open) {
181+
var parts = hash.split('-L');
182+
openSourceFile(parts[0], parts[1]);
142183
}
143184
};
144185

145-
// Hide src files and file list container after load
186+
// --- Tab system ---
187+
146188
$('.source_files').hide();
147189
$('.file_list_container').hide();
148190

149-
// Add tabs based upon existing file_list_containers
150191
$('.file_list_container h2').each(function () {
151192
var container_id = $(this).parent().attr('id');
152193
var group_name = $(this).find('.group_name').first().html();
153194
var covered_percent = $(this).find('.covered_percent').first().html();
154195

155-
$('.group_tabs').append('<li><a href="#' + container_id + '">' + group_name + ' (' + covered_percent + ')</a></li>');
196+
$('.group_tabs').append('<li role="tab"><a href="#' + container_id + '">' + group_name + ' (' + covered_percent + ')</a></li>');
156197
});
157198

158199
$('.group_tabs a').each(function () {
159200
$(this).addClass($(this).attr('href').replace('#', ''));
160201
});
161202

162-
// Make sure tabs don't get ugly focus borders when active
163-
$('.group_tabs').on('focus', 'a', function () { $(this).blur(); });
164-
165203
var favicon_path = $('link[rel="icon"]').attr('href');
166204
$('.group_tabs').on('click', 'a', function () {
167205
if (!$(this).parent().hasClass('active')) {
@@ -171,41 +209,33 @@ $(document).ready(function () {
171209
$(".file_list_container" + $(this).attr('href')).show();
172210
window.location.href = window.location.href.split('#')[0] + $(this).attr('href').replace('#', '#_');
173211

174-
// Force favicon reload - otherwise the location change containing anchor would drop the favicon...
175-
// Works only on firefox, but still... - Anyone know a better solution to force favicon on local file?
176212
$('link[rel="icon"]').remove();
177213
$('head').append('<link rel="icon" type="image/png" href="' + favicon_path + '" />');
178-
};
214+
}
179215
return false;
180216
});
181217

218+
// --- Initial state from URL hash ---
219+
182220
if (window.location.hash) {
183221
var anchor = window.location.hash.substring(1);
184222
if (anchor.length === 40) {
185-
// Materialize before clicking
186-
materializeSourceFile(anchor);
187-
$('a.src_link[href="#' + anchor + '"]').click();
223+
openSourceFile(anchor);
188224
} else if (anchor.length > 40) {
189225
var ary = anchor.split('-L');
190-
var source_file_id = ary[0];
191-
var linenumber = ary[1];
192-
193-
// Materialize before opening colorbox
194-
materializeSourceFile(source_file_id);
195-
196-
$('a.src_link[href="#' + source_file_id + '"]').colorbox({ open: true });
197-
// Scroll to anchor of linenumber
198-
$('#' + source_file_id + ' li[data-linenumber="' + linenumber + '"]').click();
226+
openSourceFile(ary[0], ary[1]);
199227
} else {
200228
$('.group_tabs a.' + anchor.replace('_', '')).click();
201229
}
202230
} else {
203231
$('.group_tabs a:first').click();
204-
};
232+
}
233+
234+
// --- Finalize loading ---
205235

206236
$("abbr.timeago").timeago();
207237
clearInterval(window._simplecovLoadingTimer);
208238
$('#loading').fadeOut();
209239
$('#wrapper').show();
210-
$('.dataTables_filter input').focus()
240+
$('.dataTables_filter input').focus();
211241
});

0 commit comments

Comments
 (0)