@@ -34,14 +34,13 @@ function on_click(sel, fn) {
3434
3535// Helpers for table sorting
3636function getCellValue ( row , column = 0 ) {
37- const cell = row . cells [ column ] // nosemgrep: eslint.detect-object-injection
37+ const cell = row . cells [ column ]
3838 if ( cell . childElementCount == 1 ) {
39- var child = cell . firstElementChild ;
40- if ( child . tagName === "A" ) {
41- child = child . firstElementChild ;
42- }
43- if ( child instanceof HTMLDataElement && child . value ) {
44- return child . value ;
39+ const child = cell . firstElementChild
40+ if ( child instanceof HTMLTimeElement && child . dateTime ) {
41+ return child . dateTime
42+ } else if ( child instanceof HTMLDataElement && child . value ) {
43+ return child . value
4544 }
4645 }
4746 return cell . innerText || cell . textContent ;
@@ -51,62 +50,28 @@ function rowComparator(rowA, rowB, column = 0) {
5150 let valueA = getCellValue ( rowA , column ) ;
5251 let valueB = getCellValue ( rowB , column ) ;
5352 if ( ! isNaN ( valueA ) && ! isNaN ( valueB ) ) {
54- return valueA - valueB ;
53+ return valueA - valueB
5554 }
5655 return valueA . localeCompare ( valueB , undefined , { numeric : true } ) ;
5756}
5857
5958function sortColumn ( th ) {
6059 // Get the current sorting direction of the selected header,
61- // clear state on other headers and then set the new sorting direction.
60+ // clear state on other headers and then set the new sorting direction
6261 const currentSortOrder = th . getAttribute ( "aria-sort" ) ;
6362 [ ...th . parentElement . cells ] . forEach ( header => header . setAttribute ( "aria-sort" , "none" ) ) ;
64- var direction ;
6563 if ( currentSortOrder === "none" ) {
66- direction = th . dataset . defaultSortOrder || "ascending" ;
67- }
68- else if ( currentSortOrder === "ascending" ) {
69- direction = "descending" ;
70- }
71- else {
72- direction = "ascending" ;
64+ th . setAttribute ( "aria-sort" , th . dataset . defaultSortOrder || "ascending" ) ;
65+ } else {
66+ th . setAttribute ( "aria-sort" , currentSortOrder === "ascending" ? "descending" : "ascending" ) ;
7367 }
74- th . setAttribute ( "aria-sort" , direction ) ;
7568
7669 const column = [ ...th . parentElement . cells ] . indexOf ( th )
7770
78- // Sort all rows and afterwards append them in order to move them in the DOM.
71+ // Sort all rows and afterwards append them in order to move them in the DOM
7972 Array . from ( th . closest ( "table" ) . querySelectorAll ( "tbody tr" ) )
80- . sort ( ( rowA , rowB ) => rowComparator ( rowA , rowB , column ) * ( direction === "ascending" ? 1 : - 1 ) )
81- . forEach ( tr => tr . parentElement . appendChild ( tr ) ) ;
82-
83- // Save the sort order for next time.
84- if ( th . id !== "region" ) {
85- let th_id = "file" ; // Sort by file if we don't have a column id
86- let current_direction = direction ;
87- const stored_list = localStorage . getItem ( coverage . INDEX_SORT_STORAGE ) ;
88- if ( stored_list ) {
89- ( { th_id, direction} = JSON . parse ( stored_list ) )
90- }
91- localStorage . setItem ( coverage . INDEX_SORT_STORAGE , JSON . stringify ( {
92- "th_id" : th . id ,
93- "direction" : current_direction
94- } ) ) ;
95- if ( th . id !== th_id || document . getElementById ( "region" ) ) {
96- // Sort column has changed, unset sorting by function or class.
97- localStorage . setItem ( coverage . SORTED_BY_REGION , JSON . stringify ( {
98- "by_region" : false ,
99- "region_direction" : current_direction
100- } ) ) ;
101- }
102- }
103- else {
104- // Sort column has changed to by function or class, remember that.
105- localStorage . setItem ( coverage . SORTED_BY_REGION , JSON . stringify ( {
106- "by_region" : true ,
107- "region_direction" : direction
108- } ) ) ;
109- }
73+ . sort ( ( rowA , rowB ) => rowComparator ( rowA , rowB , column ) * ( th . getAttribute ( "aria-sort" ) === "ascending" ? 1 : - 1 ) )
74+ . forEach ( tr => tr . parentElement . appendChild ( tr ) ) ;
11075}
11176
11277// Find all the elements with data-shortcut attribute, and use them to assign a shortcut key.
@@ -125,60 +90,21 @@ coverage.assign_shortkeys = function () {
12590
12691// Create the events for the filter box.
12792coverage . wire_up_filter = function ( ) {
128- // Populate the filter and hide100 inputs if there are saved values for them.
129- const saved_filter_value = localStorage . getItem ( coverage . FILTER_STORAGE ) ;
130- if ( saved_filter_value ) {
131- document . getElementById ( "filter" ) . value = saved_filter_value ;
132- }
133- const saved_hide100_value = localStorage . getItem ( coverage . HIDE100_STORAGE ) ;
134- if ( saved_hide100_value ) {
135- document . getElementById ( "hide100" ) . checked = JSON . parse ( saved_hide100_value ) ;
136- }
137-
13893 // Cache elements.
13994 const table = document . querySelector ( "table.index" ) ;
14095 const table_body_rows = table . querySelectorAll ( "tbody tr" ) ;
14196 const no_rows = document . getElementById ( "no_rows" ) ;
14297
14398 // Observe filter keyevents.
144- const filter_handler = ( event => {
99+ document . getElementById ( "filter" ) . addEventListener ( "input" , debounce ( event => {
145100 // Keep running total of each metric, first index contains number of shown rows
146101 const totals = new Array ( table . rows [ 0 ] . cells . length ) . fill ( 0 ) ;
147102 // Accumulate the percentage as fraction
148- totals [ totals . length - 1 ] = { "numer" : 0 , "denom" : 0 } ; // nosemgrep: eslint.detect-object-injection
149-
150- var text = document . getElementById ( "filter" ) . value ;
151- // Store filter value
152- localStorage . setItem ( coverage . FILTER_STORAGE , text ) ;
153- const casefold = ( text === text . toLowerCase ( ) ) ;
154- const hide100 = document . getElementById ( "hide100" ) . checked ;
155- // Store hide value.
156- localStorage . setItem ( coverage . HIDE100_STORAGE , JSON . stringify ( hide100 ) ) ;
103+ totals [ totals . length - 1 ] = { "numer" : 0 , "denom" : 0 } ;
157104
158105 // Hide / show elements.
159106 table_body_rows . forEach ( row => {
160- var show = false ;
161- // Check the text filter.
162- for ( let column = 0 ; column < totals . length ; column ++ ) {
163- cell = row . cells [ column ] ;
164- if ( cell . classList . contains ( "name" ) ) {
165- var celltext = cell . textContent ;
166- if ( casefold ) {
167- celltext = celltext . toLowerCase ( ) ;
168- }
169- if ( celltext . includes ( text ) ) {
170- show = true ;
171- }
172- }
173- }
174-
175- // Check the "hide covered" filter.
176- if ( show && hide100 ) {
177- const [ numer , denom ] = row . cells [ row . cells . length - 1 ] . dataset . ratio . split ( " " ) ;
178- show = ( numer !== denom ) ;
179- }
180-
181- if ( ! show ) {
107+ if ( ! row . cells [ 0 ] . textContent . includes ( event . target . value ) ) {
182108 // hide
183109 row . classList . add ( "hidden" ) ;
184110 return ;
@@ -188,20 +114,16 @@ coverage.wire_up_filter = function () {
188114 row . classList . remove ( "hidden" ) ;
189115 totals [ 0 ] ++ ;
190116
191- for ( let column = 0 ; column < totals . length ; column ++ ) {
117+ for ( let column = 1 ; column < totals . length ; column ++ ) {
192118 // Accumulate dynamic totals
193- cell = row . cells [ column ] // nosemgrep: eslint.detect-object-injection
194- if ( cell . classList . contains ( "name" ) ) {
195- continue ;
196- }
119+ cell = row . cells [ column ]
197120 if ( column === totals . length - 1 ) {
198121 // Last column contains percentage
199122 const [ numer , denom ] = cell . dataset . ratio . split ( " " ) ;
200- totals [ column ] [ "numer" ] += parseInt ( numer , 10 ) ; // nosemgrep: eslint.detect-object-injection
201- totals [ column ] [ "denom" ] += parseInt ( denom , 10 ) ; // nosemgrep: eslint.detect-object-injection
202- }
203- else {
204- totals [ column ] += parseInt ( cell . textContent , 10 ) ; // nosemgrep: eslint.detect-object-injection
123+ totals [ column ] [ "numer" ] += parseInt ( numer , 10 ) ;
124+ totals [ column ] [ "denom" ] += parseInt ( denom , 10 ) ;
125+ } else {
126+ totals [ column ] += parseInt ( cell . textContent , 10 ) ;
205127 }
206128 }
207129 } ) ;
@@ -220,89 +142,64 @@ coverage.wire_up_filter = function () {
220142
221143 const footer = table . tFoot . rows [ 0 ] ;
222144 // Calculate new dynamic sum values based on visible rows.
223- for ( let column = 0 ; column < totals . length ; column ++ ) {
145+ for ( let column = 1 ; column < totals . length ; column ++ ) {
224146 // Get footer cell element.
225- const cell = footer . cells [ column ] ; // nosemgrep: eslint.detect-object-injection
226- if ( cell . classList . contains ( "name" ) ) {
227- continue ;
228- }
147+ const cell = footer . cells [ column ] ;
229148
230149 // Set value into dynamic footer cell element.
231150 if ( column === totals . length - 1 ) {
232151 // Percentage column uses the numerator and denominator,
233152 // and adapts to the number of decimal places.
234153 const match = / \. ( [ 0 - 9 ] + ) / . exec ( cell . textContent ) ;
235154 const places = match ? match [ 1 ] . length : 0 ;
236- const { numer, denom } = totals [ column ] ; // nosemgrep: eslint.detect-object-injection
155+ const { numer, denom } = totals [ column ] ;
237156 cell . dataset . ratio = `${ numer } ${ denom } ` ;
238157 // Check denom to prevent NaN if filtered files contain no statements
239158 cell . textContent = denom
240159 ? `${ ( numer * 100 / denom ) . toFixed ( places ) } %`
241160 : `${ ( 100 ) . toFixed ( places ) } %` ;
242- }
243- else {
244- cell . textContent = totals [ column ] ; // nosemgrep: eslint.detect-object-injection
161+ } else {
162+ cell . textContent = totals [ column ] ;
245163 }
246164 }
247- } ) ;
248-
249- document . getElementById ( "filter" ) . addEventListener ( "input" , debounce ( filter_handler ) ) ;
250- document . getElementById ( "hide100" ) . addEventListener ( "input" , debounce ( filter_handler ) ) ;
165+ } ) ) ;
251166
252167 // Trigger change event on setup, to force filter on page refresh
253168 // (filter value may still be present).
254169 document . getElementById ( "filter" ) . dispatchEvent ( new Event ( "input" ) ) ;
255- document . getElementById ( "hide100" ) . dispatchEvent ( new Event ( "input" ) ) ;
256170} ;
257- coverage . FILTER_STORAGE = "COVERAGE_FILTER_VALUE" ;
258- coverage . HIDE100_STORAGE = "COVERAGE_HIDE100_VALUE" ;
259171
260- // Set up the click-to-sort columns.
261- coverage . wire_up_sorting = function ( ) {
172+ coverage . INDEX_SORT_STORAGE = "COVERAGE_INDEX_SORT_2" ;
173+
174+ // Loaded on index.html
175+ coverage . index_ready = function ( ) {
176+ coverage . assign_shortkeys ( ) ;
177+ coverage . wire_up_filter ( ) ;
262178 document . querySelectorAll ( "[data-sortable] th[aria-sort]" ) . forEach (
263179 th => th . addEventListener ( "click" , e => sortColumn ( e . target ) )
264180 ) ;
265181
266182 // Look for a localStorage item containing previous sort settings:
267- let th_id = "file" , direction = "ascending" ;
268183 const stored_list = localStorage . getItem ( coverage . INDEX_SORT_STORAGE ) ;
269- if ( stored_list ) {
270- ( { th_id, direction} = JSON . parse ( stored_list ) ) ;
271- }
272- let by_region = false , region_direction = "ascending" ;
273- const sorted_by_region = localStorage . getItem ( coverage . SORTED_BY_REGION ) ;
274- if ( sorted_by_region ) {
275- ( {
276- by_region,
277- region_direction
278- } = JSON . parse ( sorted_by_region ) ) ;
279- }
280184
281- const region_id = "region" ;
282- if ( by_region && document . getElementById ( region_id ) ) {
283- direction = region_direction ;
284- }
285- // If we are in a page that has a column with id of "region", sort on
286- // it if the last sort was by function or class.
287- let th ;
288- if ( document . getElementById ( region_id ) ) {
289- th = document . getElementById ( by_region ? region_id : th_id ) ;
290- }
291- else {
292- th = document . getElementById ( th_id ) ;
185+ if ( stored_list ) {
186+ const { column, direction} = JSON . parse ( stored_list ) ;
187+ const th = document . querySelector ( "[data-sortable]" ) . tHead . rows [ 0 ] . cells [ column ] ;
188+ th . setAttribute ( "aria-sort" , direction === "ascending" ? "descending" : "ascending" ) ;
189+ th . click ( )
293190 }
294- th . setAttribute ( "aria-sort" , direction === "ascending" ? "descending" : "ascending" ) ;
295- th . click ( )
296- } ;
297-
298- coverage . INDEX_SORT_STORAGE = "COVERAGE_INDEX_SORT_2" ;
299- coverage . SORTED_BY_REGION = "COVERAGE_SORT_REGION" ;
300191
301- // Loaded on index.html
302- coverage . index_ready = function ( ) {
303- coverage . assign_shortkeys ( ) ;
304- coverage . wire_up_filter ( ) ;
305- coverage . wire_up_sorting ( ) ;
192+ // Watch for page unload events so we can save the final sort settings:
193+ window . addEventListener ( "unload" , function ( ) {
194+ const th = document . querySelector ( '[data-sortable] th[aria-sort="ascending"], [data-sortable] [aria-sort="descending"]' ) ;
195+ if ( ! th ) {
196+ return ;
197+ }
198+ localStorage . setItem ( coverage . INDEX_SORT_STORAGE , JSON . stringify ( {
199+ column : [ ...th . parentElement . cells ] . indexOf ( th ) ,
200+ direction : th . getAttribute ( "aria-sort" ) ,
201+ } ) ) ;
202+ } ) ;
306203
307204 on_click ( ".button_prev_file" , coverage . to_prev_file ) ;
308205 on_click ( ".button_next_file" , coverage . to_next_file ) ;
@@ -320,8 +217,7 @@ coverage.pyfile_ready = function () {
320217 if ( frag . length > 2 && frag [ 1 ] === "t" ) {
321218 document . querySelector ( frag ) . closest ( ".n" ) . classList . add ( "highlight" ) ;
322219 coverage . set_sel ( parseInt ( frag . substr ( 2 ) , 10 ) ) ;
323- }
324- else {
220+ } else {
325221 coverage . set_sel ( 0 ) ;
326222 }
327223
@@ -354,7 +250,7 @@ coverage.pyfile_ready = function () {
354250 }
355251
356252 for ( cls in coverage . filters ) {
357- coverage . set_line_visibilty ( cls , coverage . filters [ cls ] ) ; // nosemgrep: eslint.detect-object-injection
253+ coverage . set_line_visibilty ( cls , coverage . filters [ cls ] ) ;
358254 }
359255
360256 coverage . assign_shortkeys ( ) ;
@@ -545,8 +441,7 @@ coverage.to_next_chunk_nicely = function () {
545441 if ( line . parentElement !== document . getElementById ( "source" ) ) {
546442 // The element is not a source line but the header or similar
547443 coverage . select_line_or_chunk ( 1 ) ;
548- }
549- else {
444+ } else {
550445 // We extract the line number from the id
551446 coverage . select_line_or_chunk ( parseInt ( line . id . substring ( 1 ) , 10 ) ) ;
552447 }
@@ -565,8 +460,7 @@ coverage.to_prev_chunk_nicely = function () {
565460 if ( line . parentElement !== document . getElementById ( "source" ) ) {
566461 // The element is not a source line but the header or similar
567462 coverage . select_line_or_chunk ( coverage . lines_len ) ;
568- }
569- else {
463+ } else {
570464 // We extract the line number from the id
571465 coverage . select_line_or_chunk ( parseInt ( line . id . substring ( 1 ) , 10 ) ) ;
572466 }
@@ -668,8 +562,7 @@ coverage.build_scroll_markers = function () {
668562 if ( line_number === previous_line + 1 ) {
669563 // If this solid missed block just make previous mark higher.
670564 last_mark . style . height = `${ line_top + line_height - last_top } px` ;
671- }
672- else {
565+ } else {
673566 // Add colored line in scroll_marker block.
674567 last_mark = document . createElement ( "div" ) ;
675568 last_mark . id = `m${ line_number } ` ;
@@ -697,8 +590,7 @@ coverage.wire_up_sticky_header = function () {
697590 function updateHeader ( ) {
698591 if ( window . scrollY > header_bottom ) {
699592 header . classList . add ( "sticky" ) ;
700- }
701- else {
593+ } else {
702594 header . classList . remove ( "sticky" ) ;
703595 }
704596 }
@@ -726,8 +618,7 @@ coverage.expand_contexts = function (e) {
726618document . addEventListener ( "DOMContentLoaded" , ( ) => {
727619 if ( document . body . classList . contains ( "indexfile" ) ) {
728620 coverage . index_ready ( ) ;
729- }
730- else {
621+ } else {
731622 coverage . pyfile_ready ( ) ;
732623 }
733624} ) ;
0 commit comments