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