11// Shared probe rendering utilities
2+ ( function injectFilterCSS ( ) {
3+ if ( document . getElementById ( 'probe-filter-css' ) ) return ;
4+ var s = document . createElement ( 'style' ) ;
5+ s . id = 'probe-filter-css' ;
6+ s . textContent = ''
7+ + '.probe-filters{border:1px solid #d0d7de;border-radius:8px;padding:12px 16px;margin-bottom:16px;background:#f6f8fa}'
8+ + '.dark .probe-filters{border-color:#30363d;background:#161b22}'
9+ + '.probe-filters>div:not(:last-child){border-bottom:1px solid #d0d7de;padding-bottom:10px;margin-bottom:10px}'
10+ + '.dark .probe-filters>div:not(:last-child){border-bottom-color:#30363d}'
11+ + '.probe-filter-label{display:inline-block;width:80px;font-size:12px;font-weight:700;color:#656d76;white-space:nowrap}'
12+ + '.dark .probe-filter-label{color:#8b949e}'
13+ + '.probe-filter-btn{display:inline-block;padding:4px 12px;font-size:12px;font-weight:600;border-radius:4px;cursor:pointer;border:1px solid #d0d7de;margin-right:6px;transition:all .15s;background:#fff;color:#24292f}'
14+ + '.dark .probe-filter-btn{border-color:#30363d;background:#21262d;color:#c9d1d9}'
15+ + '.probe-filter-btn.active{background:#0969da;color:#fff;border-color:#0969da}'
16+ + '.dark .probe-filter-btn.active{background:#1f6feb;border-color:#1f6feb}' ;
17+ document . head . appendChild ( s ) ;
18+ } ) ( ) ;
19+
220window . ProbeRender = ( function ( ) {
321 var PASS_BG = '#1a7f37' ;
422 var WARN_BG = '#9a6700' ;
@@ -663,7 +681,12 @@ window.ProbeRender = (function () {
663681 } ) ;
664682
665683 var unscoredStart = scoredTests . length ;
666- var t = '<div class="probe-scroll"><table class="probe-table" style="border-collapse:collapse;font-size:13px;white-space:nowrap;">' ;
684+ var isTopLevel = ! testIdFilter && ! tableLabel ;
685+ var t = '' ;
686+ if ( isTopLevel ) {
687+ t += '<div class="probe-filter-wrap"><input class="probe-filter-input" type="text" placeholder="Filter by server or test name (comma-separated)\u2026"><span class="probe-filter-count"></span></div>' ;
688+ }
689+ t += '<div class="probe-scroll"><table class="probe-table" style="border-collapse:collapse;font-size:13px;white-space:nowrap;">' ;
667690
668691 // Column header row (horizontal labels)
669692 t += '<thead><tr>' ;
@@ -882,6 +905,60 @@ window.ProbeRender = (function () {
882905 document . addEventListener ( 'keydown' , onKey ) ;
883906 } ) ;
884907 } ) ;
908+
909+ // Wire filter input (top-level only)
910+ if ( isTopLevel ) {
911+ var filterInput = el . querySelector ( '.probe-filter-input' ) ;
912+ var filterCount = el . querySelector ( '.probe-filter-count' ) ;
913+ if ( filterInput ) {
914+ function matchesAny ( text , keywords ) {
915+ for ( var k = 0 ; k < keywords . length ; k ++ ) {
916+ if ( text . indexOf ( keywords [ k ] ) !== - 1 ) return true ;
917+ }
918+ return false ;
919+ }
920+ filterInput . addEventListener ( 'input' , function ( ) {
921+ var raw = filterInput . value . toLowerCase ( ) ;
922+ var keywords = raw . split ( ',' ) . map ( function ( s ) { return s . trim ( ) ; } ) . filter ( Boolean ) ;
923+ var fRows = el . querySelectorAll ( '.probe-server-row' ) ;
924+ var allCols = el . querySelectorAll ( '[data-test-label]' ) ;
925+ var thCols = el . querySelectorAll ( 'thead [data-test-label]' ) ;
926+ if ( keywords . length === 0 ) {
927+ fRows . forEach ( function ( r ) { r . style . display = '' ; } ) ;
928+ allCols . forEach ( function ( c ) { c . style . display = '' ; } ) ;
929+ if ( filterCount ) filterCount . textContent = '' ;
930+ return ;
931+ }
932+ var serverMatches = 0 ;
933+ fRows . forEach ( function ( r ) {
934+ var name = ( r . getAttribute ( 'data-server' ) || '' ) . toLowerCase ( ) ;
935+ var lang = ( r . getAttribute ( 'data-language' ) || '' ) . toLowerCase ( ) ;
936+ if ( matchesAny ( name , keywords ) || matchesAny ( lang , keywords ) ) serverMatches ++ ;
937+ } ) ;
938+ var colMatchSet = { } ;
939+ thCols . forEach ( function ( th ) {
940+ var label = th . getAttribute ( 'data-test-label' ) . toLowerCase ( ) ;
941+ if ( matchesAny ( label , keywords ) ) colMatchSet [ th . getAttribute ( 'data-test-label' ) ] = true ;
942+ } ) ;
943+ var colMatches = Object . keys ( colMatchSet ) . length ;
944+ fRows . forEach ( function ( r ) {
945+ var name = ( r . getAttribute ( 'data-server' ) || '' ) . toLowerCase ( ) ;
946+ var lang = ( r . getAttribute ( 'data-language' ) || '' ) . toLowerCase ( ) ;
947+ r . style . display = ( serverMatches === 0 || matchesAny ( name , keywords ) || matchesAny ( lang , keywords ) ) ? '' : 'none' ;
948+ } ) ;
949+ allCols . forEach ( function ( c ) {
950+ var label = c . getAttribute ( 'data-test-label' ) ;
951+ c . style . display = ( colMatches === 0 || colMatchSet [ label ] ) ? '' : 'none' ;
952+ } ) ;
953+ if ( filterCount ) {
954+ var parts = [ ] ;
955+ if ( serverMatches > 0 ) parts . push ( serverMatches + ' server' + ( serverMatches !== 1 ? 's' : '' ) ) ;
956+ if ( colMatches > 0 ) parts . push ( colMatches + ' test' + ( colMatches !== 1 ? 's' : '' ) ) ;
957+ filterCount . textContent = parts . length > 0 ? parts . join ( ', ' ) : 'No matches' ;
958+ }
959+ } ) ;
960+ }
961+ }
885962 }
886963
887964 // ── Collapsible-group wiring helper ────────────────────────────
@@ -1037,42 +1114,21 @@ window.ProbeRender = (function () {
10371114 var langList = Object . keys ( langs ) . sort ( ) ;
10381115 if ( langList . length === 0 ) return ;
10391116
1040- var isDark = document . documentElement . classList . contains ( 'dark' ) ;
1041- var baseBg = isDark ? '#21262d' : '#f6f8fa' ;
1042- var baseFg = isDark ? '#c9d1d9' : '#24292f' ;
1043- var baseBorder = isDark ? '#30363d' : '#d0d7de' ;
1044- var activeBg = isDark ? '#1f6feb' : '#0969da' ;
1045-
1046- var btnStyle = 'display:inline-block;padding:4px 12px;font-size:12px;font-weight:600;'
1047- + 'border-radius:20px;cursor:pointer;border:1px solid ' + baseBorder + ';'
1048- + 'margin-right:6px;margin-bottom:6px;transition:all 0.15s;' ;
1049-
1050- var labelStyle = 'font-size:12px;font-weight:700;color:#656d76;margin-right:10px;white-space:nowrap;' ;
1051- var html = '<div style="display:flex;align-items:center;flex-wrap:wrap;margin-bottom:4px;">' ;
1052- html += '<span style="' + labelStyle + '">Language:</span>' ;
1053- html += '<button class="probe-lang-btn" data-lang="" style="' + btnStyle
1054- + 'background:' + activeBg + ';color:#fff;border-color:' + activeBg + ';">All</button>' ;
1117+ var html = '<div style="display:flex;align-items:center;flex-wrap:wrap;">' ;
1118+ html += '<span class="probe-filter-label">Language</span>' ;
1119+ html += '<button class="probe-filter-btn active" data-lang="">All</button>' ;
10551120 langList . forEach ( function ( lang ) {
1056- html += '<button class="probe-lang-btn" data-lang="' + lang + '" style="' + btnStyle
1057- + 'background:' + baseBg + ';color:' + baseFg + ';">' + lang + '</button>' ;
1121+ html += '<button class="probe-filter-btn" data-lang="' + lang + '">' + lang + '</button>' ;
10581122 } ) ;
10591123 html += '</div>' ;
10601124 el . innerHTML = html ;
10611125
1062- var buttons = el . querySelectorAll ( '.probe-lang -btn' ) ;
1126+ var buttons = el . querySelectorAll ( '.probe-filter -btn' ) ;
10631127 buttons . forEach ( function ( btn ) {
10641128 btn . addEventListener ( 'click' , function ( ) {
10651129 var lang = btn . getAttribute ( 'data-lang' ) ;
10661130 buttons . forEach ( function ( b ) {
1067- if ( b === btn ) {
1068- b . style . background = activeBg ;
1069- b . style . color = '#fff' ;
1070- b . style . borderColor = activeBg ;
1071- } else {
1072- b . style . background = baseBg ;
1073- b . style . color = baseFg ;
1074- b . style . borderColor = baseBorder ;
1075- }
1131+ b . classList . toggle ( 'active' , b === btn ) ;
10761132 } ) ;
10771133 if ( ! lang ) {
10781134 onChange ( { commit : data . commit , servers : allServers } ) ;
@@ -1117,16 +1173,6 @@ window.ProbeRender = (function () {
11171173 var el = document . getElementById ( targetId ) ;
11181174 if ( ! el ) return ;
11191175
1120- var isDark = document . documentElement . classList . contains ( 'dark' ) ;
1121- var baseBg = isDark ? '#21262d' : '#f6f8fa' ;
1122- var baseFg = isDark ? '#c9d1d9' : '#24292f' ;
1123- var baseBorder = isDark ? '#30363d' : '#d0d7de' ;
1124- var activeBg = isDark ? '#1f6feb' : '#0969da' ;
1125-
1126- var btnStyle = 'display:inline-block;padding:4px 12px;font-size:12px;font-weight:600;'
1127- + 'border-radius:20px;cursor:pointer;border:1px solid ' + baseBorder + ';'
1128- + 'margin-right:6px;margin-bottom:6px;transition:all 0.15s;' ;
1129-
11301176 var filters = [
11311177 { label : 'All' , categories : null } ,
11321178 { label : 'Compliance' , categories : [ 'Compliance' ] } ,
@@ -1135,32 +1181,20 @@ window.ProbeRender = (function () {
11351181 { label : 'Normalization' , categories : [ 'Normalization' ] }
11361182 ] ;
11371183
1138- var labelStyle = 'font-size:12px;font-weight:700;color:#656d76;margin-right:10px;white-space:nowrap;' ;
1139- var html = '<div style="display:flex;align-items:center;flex-wrap:wrap;margin-bottom:4px;">' ;
1140- html += '<span style="' + labelStyle + '">Category:</span>' ;
1184+ var html = '<div style="display:flex;align-items:center;flex-wrap:wrap;">' ;
1185+ html += '<span class="probe-filter-label">Category</span>' ;
11411186 filters . forEach ( function ( f , i ) {
1142- var isActive = i === 0 ;
1143- html += '<button class="probe-cat-btn" data-idx="' + i + '" style="' + btnStyle
1144- + 'background:' + ( isActive ? activeBg : baseBg ) + ';color:' + ( isActive ? '#fff' : baseFg )
1145- + ';border-color:' + ( isActive ? activeBg : baseBorder ) + ';">' + f . label + '</button>' ;
1187+ html += '<button class="probe-filter-btn' + ( i === 0 ? ' active' : '' ) + '" data-idx="' + i + '">' + f . label + '</button>' ;
11461188 } ) ;
11471189 html += '</div>' ;
11481190 el . innerHTML = html ;
11491191
1150- var buttons = el . querySelectorAll ( '.probe-cat -btn' ) ;
1192+ var buttons = el . querySelectorAll ( '.probe-filter -btn' ) ;
11511193 buttons . forEach ( function ( btn ) {
11521194 btn . addEventListener ( 'click' , function ( ) {
11531195 var idx = parseInt ( btn . getAttribute ( 'data-idx' ) ) ;
11541196 buttons . forEach ( function ( b ) {
1155- if ( b === btn ) {
1156- b . style . background = activeBg ;
1157- b . style . color = '#fff' ;
1158- b . style . borderColor = activeBg ;
1159- } else {
1160- b . style . background = baseBg ;
1161- b . style . color = baseFg ;
1162- b . style . borderColor = baseBorder ;
1163- }
1197+ b . classList . toggle ( 'active' , b === btn ) ;
11641198 } ) ;
11651199 onChange ( filters [ idx ] . categories ) ;
11661200 } ) ;
@@ -1208,42 +1242,21 @@ window.ProbeRender = (function () {
12081242 var methods = Object . keys ( methodSet ) . sort ( ) ;
12091243 if ( methods . length === 0 ) return ;
12101244
1211- var isDark = document . documentElement . classList . contains ( 'dark' ) ;
1212- var baseBg = isDark ? '#21262d' : '#f6f8fa' ;
1213- var baseFg = isDark ? '#c9d1d9' : '#24292f' ;
1214- var baseBorder = isDark ? '#30363d' : '#d0d7de' ;
1215- var activeBg = isDark ? '#1f6feb' : '#0969da' ;
1216-
1217- var btnStyle = 'display:inline-block;padding:4px 12px;font-size:12px;font-weight:600;'
1218- + 'border-radius:20px;cursor:pointer;border:1px solid ' + baseBorder + ';'
1219- + 'margin-right:6px;margin-bottom:6px;transition:all 0.15s;' ;
1220-
1221- var labelStyle = 'font-size:12px;font-weight:700;color:#656d76;margin-right:10px;white-space:nowrap;' ;
1222- var html = '<div style="display:flex;align-items:center;flex-wrap:wrap;margin-bottom:4px;">' ;
1223- html += '<span style="' + labelStyle + '">Method:</span>' ;
1224- html += '<button class="probe-method-btn" data-method="" style="' + btnStyle
1225- + 'background:' + activeBg + ';color:#fff;border-color:' + activeBg + ';">All</button>' ;
1245+ var html = '<div style="display:flex;align-items:center;flex-wrap:wrap;">' ;
1246+ html += '<span class="probe-filter-label">Method</span>' ;
1247+ html += '<button class="probe-filter-btn active" data-method="">All</button>' ;
12261248 methods . forEach ( function ( m ) {
1227- html += '<button class="probe-method-btn" data-method="' + m + '" style="' + btnStyle
1228- + 'background:' + baseBg + ';color:' + baseFg + ';">' + m + '</button>' ;
1249+ html += '<button class="probe-filter-btn" data-method="' + m + '">' + m + '</button>' ;
12291250 } ) ;
12301251 html += '</div>' ;
12311252 el . innerHTML = html ;
12321253
1233- var buttons = el . querySelectorAll ( '.probe-method -btn' ) ;
1254+ var buttons = el . querySelectorAll ( '.probe-filter -btn' ) ;
12341255 buttons . forEach ( function ( btn ) {
12351256 btn . addEventListener ( 'click' , function ( ) {
12361257 var method = btn . getAttribute ( 'data-method' ) ;
12371258 buttons . forEach ( function ( b ) {
1238- if ( b === btn ) {
1239- b . style . background = activeBg ;
1240- b . style . color = '#fff' ;
1241- b . style . borderColor = activeBg ;
1242- } else {
1243- b . style . background = baseBg ;
1244- b . style . color = baseFg ;
1245- b . style . borderColor = baseBorder ;
1246- }
1259+ b . classList . toggle ( 'active' , b === btn ) ;
12471260 } ) ;
12481261 onChange ( method || null ) ;
12491262 } ) ;
@@ -1298,42 +1311,21 @@ window.ProbeRender = (function () {
12981311 var visibleLevels = levels . filter ( function ( l ) { return presentLevels [ l . key ] ; } ) ;
12991312 if ( visibleLevels . length === 0 ) return ;
13001313
1301- var isDark = document . documentElement . classList . contains ( 'dark' ) ;
1302- var baseBg = isDark ? '#21262d' : '#f6f8fa' ;
1303- var baseFg = isDark ? '#c9d1d9' : '#24292f' ;
1304- var baseBorder = isDark ? '#30363d' : '#d0d7de' ;
1305- var activeBg = isDark ? '#1f6feb' : '#0969da' ;
1306-
1307- var btnStyle = 'display:inline-block;padding:4px 12px;font-size:12px;font-weight:600;'
1308- + 'border-radius:20px;cursor:pointer;border:1px solid ' + baseBorder + ';'
1309- + 'margin-right:6px;margin-bottom:6px;transition:all 0.15s;' ;
1310-
1311- var labelStyle = 'font-size:12px;font-weight:700;color:#656d76;margin-right:10px;white-space:nowrap;' ;
1312- var html = '<div style="display:flex;align-items:center;flex-wrap:wrap;margin-bottom:4px;">' ;
1313- html += '<span style="' + labelStyle + '">RFC Level:</span>' ;
1314- html += '<button class="probe-rfc-btn" data-level="" style="' + btnStyle
1315- + 'background:' + activeBg + ';color:#fff;border-color:' + activeBg + ';">All</button>' ;
1314+ var html = '<div style="display:flex;align-items:center;flex-wrap:wrap;">' ;
1315+ html += '<span class="probe-filter-label">RFC Level</span>' ;
1316+ html += '<button class="probe-filter-btn active" data-level="">All</button>' ;
13161317 visibleLevels . forEach ( function ( l ) {
1317- html += '<button class="probe-rfc-btn" data-level="' + l . key + '" style="' + btnStyle
1318- + 'background:' + baseBg + ';color:' + baseFg + ';">' + l . label + '</button>' ;
1318+ html += '<button class="probe-filter-btn" data-level="' + l . key + '">' + l . label + '</button>' ;
13191319 } ) ;
13201320 html += '</div>' ;
13211321 el . innerHTML = html ;
13221322
1323- var buttons = el . querySelectorAll ( '.probe-rfc -btn' ) ;
1323+ var buttons = el . querySelectorAll ( '.probe-filter -btn' ) ;
13241324 buttons . forEach ( function ( btn ) {
13251325 btn . addEventListener ( 'click' , function ( ) {
13261326 var level = btn . getAttribute ( 'data-level' ) ;
13271327 buttons . forEach ( function ( b ) {
1328- if ( b === btn ) {
1329- b . style . background = activeBg ;
1330- b . style . color = '#fff' ;
1331- b . style . borderColor = activeBg ;
1332- } else {
1333- b . style . background = baseBg ;
1334- b . style . color = baseFg ;
1335- b . style . borderColor = baseBorder ;
1336- }
1328+ b . classList . toggle ( 'active' , b === btn ) ;
13371329 } ) ;
13381330 onChange ( level || null ) ;
13391331 } ) ;
0 commit comments