1var ResultPagination = ( function () { 2 'use strict'; 3 4 function keydownPaginate( event, page, listID, nodePath, selector ) { 5 if ( event.key === 'Enter' ) { 6 paginate( page, listID, nodePath, selector ); 7 } 8 } 9 10 function paginate( page, listID, nodePath, selector ) { 11 var params = ListUtil.updateQueryString( { 'page': page }, listID ); 12 ListUtil.applyParameters( params, listID, nodePath, selector ); 13 } 14 15 return { 16 keydownPaginate: keydownPaginate, 17 paginate: paginate 18 } 19})(); 20var ListFiltering = ( function () { 21 'use strict'; 22 23 // TODO: Connect to value in ListUtil 24 var filterDelimiter = '$'; 25 26 function keydownSearch( event, el, listID, nodePath, selector, searchURL ) { 27 if ( event.key === 'Enter' ) { 28 search( el, listID, nodePath, selector, searchURL ); 29 } 30 } 31 32 function search( el, listID, nodePath, selector, searchURL ) { 33 const searchParams = searchURL ? { q: el.value, page: 1 } : { query: el.value, page: 1}; 34 var params = /** @type {URLSearchParams} */ ListUtil.updateQueryString( searchParams, searchURL ? '' : listID, searchURL ); 35 if (!searchURL) { 36 ListUtil.applyParameters( params, listID, nodePath, selector ); 37 } else { 38 const q = params.get("q") || params.get("query"); 39 window.location = searchURL + '.html?' + (q && `q=${q}`); 40 } 41 } 42 43 function keydownFilter( event, el, listID, nodePath, selector ) { 44 if ( event.key === 'Enter' || event.key == 'Space' ) { 45 filter( el, listID, nodePath, selector ); 46 } 47 } 48 49 function filter( el, listID, nodePath, selector ) { 50 switch ( el.tagName.toLowerCase() ) { 51 case 'input': 52 // Key input on dropdown checkbox 53 var value = el.value; 54 break; 55 case 'i': 56 // Key input on pill X 57 var value = el.dataset.value; 58 break; 59 default: 60 // Clicks 61 var value = $( el ).find( 'input[type="checkbox"]' ).val(); 62 } 63 64 var params = ListUtil.updateQueryString( { 'filters': value, 'page': 1 }, listID ); 65 ListUtil.applyParameters( params, listID, nodePath, selector ); 66 } 67 68 function prepopulateFilters( listID ) { 69 var filters = new URLSearchParams( window.location.search ).get( listID + ':filters' ); 70 71 if ( filters ) { 72 var $filterPanel = $( '#' + listID + ' .uscb-list__filters' ); 73 filters.split( filterDelimiter ).forEach( tag => { 74 // Easiest to just use CSS selectors to find elements with the correct value. Otherwise, several nested loops would be necessary 75 // and would duplicate checks against multiple filters if they shared items 76 var $matchingItems = $filterPanel.find( 'input[type="checkbox"][value="' + tag + '"]' ); 77 if ( $matchingItems ) { 78 $matchingItems.prop( 'checked', true ); 79 $matchingItems.attr( 'aria-checked', true ); 80 } 81 }); 82 } 83 } 84 85 function prepopulateSearch( listID ) { 86 var query = new URLSearchParams( window.location.search ).get( listID + ':query' ); 87 $( '#' + listID + ' .uscb-form-text__search-container input' ).val( query ); 88 } 89 90 function keydownClearFilters( event, listID, nodePath, selector ) { 91 if ( event.key === 'Enter' ) { 92 clearFilters( listID, nodePath, selector ); 93 } 94 } 95 96 function clearFilters( listID, nodePath, selector ) { 97 var params = ListUtil.updateQueryString( { 'filters': undefined, 'page': undefined, 'query': undefined }, listID ); 98 ListUtil.applyParameters( params, listID, nodePath, selector ); 99 100 togglePanel( listID + '_panel', false, false ); 101 } 102 103 function keydownTogglePanel( event, panelID, open, submitSearch ) { 104 if ( event.key === 'Enter' ) { 105 togglePanel( panelID, open, submitSearch ); 106 } 107 } 108 109 function togglePanel( panelID, open, submitSearch ) { 110 var $panel = $( '#' + panelID ); 111 112 if ( open ) { 113 $panel.removeClass( 'uscb-hide' ); 114 } else { 115 if( submitSearch ) { 116 // Closing the panel, apply the search term if provided 117 const searchInputEle = $panel.find('.uscb-form-text__search-container input').get(0); 118 if( searchInputEle.value?.trim().length ) { 119 // Faking an "Enter" on the input, to leverage existing attribute listeners 120 const enterEvent = new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true });
121 searchInputEle.dispatchEvent(enterEvent); 122 } 123 } 124 125 // Close the panel 126 $panel.addClass( 'uscb-hide' ); 127 } 128 } 129 130 function toggleToUpdateProps(ele, panelID) { 131 var $dropDownButton = $( ele ); 132 var $panel = $( '#' + panelID ); 133 134 if ( $panel[0] ) { 135 if ( $panel[0].className.includes('uscb-hide')) { 136 $dropDownButton.attr('aria-expanded', 'false'); 137 } else { 138 $dropDownButton.attr('aria-expanded', 'true'); 139 } 140 } 141 } 142 143 return { 144 keydownSearch: keydownSearch, 145 search: search, 146 prepopulateSearch: prepopulateSearch, 147 148 keydownFilter: keydownFilter, 149 filter: filter, 150 prepopulateFilters: prepopulateFilters, 151 152 clearFilters: clearFilters, 153 keydownClearFilters: keydownClearFilters, 154 155 togglePanel: togglePanel, 156 keydownTogglePanel: keydownTogglePanel, 157 toggleToUpdateProps: toggleToUpdateProps 158 } 159})(); 160var ResultTabs = ( function() { 161 'use strict'; 162 163 function keydownChangeTab( event, tab, listID, nodePath, selector ) { 164 if ( event.key === 'Enter' ) { 165 changeTab( tab, listID, nodePath, selector ); 166 } 167 } 168 169 function changeTab( tab, listID, nodePath, selector ) { 170 var params = ListUtil.updateQueryString( { 'tab': tab, 'page': undefined }, listID ); 171 ListUtil.applyParameters( params, listID, nodePath, selector ); 172 } 173 174 return { 175 changeTab: changeTab, 176 keydownChangeTab: keydownChangeTab 177 } 178})(); 179var ListUtil = ( function() { 180 'use strict'; 181 182 var filterDelimiter = '$'; 183 184 function updateQueryString( updates, listID, noRedirect ) { 185 var params = new URLSearchParams( window.location.search ); 186 187 var updateKeys = Object.keys( updates ); 188 for ( var i = 0; i < Object.keys( updates ).length; i++ ) { 189 var key = updateKeys[ i ]; 190 var namespacedKey = !listID ? key : listID + ':' + key; 191 192 // If the incoming value is undefined, clear the key 193 if ( updates[ key ] == undefined ) { 194 params.delete( namespacedKey ); 195 continue; 196 } 197 198 // Tags are a special case. If they already exist as a parameter, they will be removed. If they don't exist, they will be appended, not replaced. 199 if ( key === 'filters' && params.get( namespacedKey ) ) { 200 var tagParam = params.get( namespacedKey ); 201 202 if ( tagParam.includes( updates[ key ] ) ) { 203 // Remove the existing tag 204 // If it's the only tag, remove the whole parameter 205 if ( tagParam === updates[ key ] ) { 206 params.delete( namespacedKey ); 207 } else { 208 // Determine where the delimiter is relative to the incoming tag 209 var pattern = tagParam.startsWith( updates[ key ] ) ? updates[ key ] + filterDelimiter : filterDelimiter + updates[ key ]; 210 params.set( namespacedKey, tagParam.replace( pattern, '' ).trim() ); 211 } 212 } else { 213 // Add new tag 214 params.set( namespacedKey, tagParam + filterDelimiter + updates[ key ] ); 215 } 216 } else { 217 params.set( namespacedKey, updates[ key ] ); 218 } 219 } 220 if (noRedirect) return params; 221 var url = window.location.protocol + "//" + window.location.host + window.location.pathname + '?' + params.toString(); 222 window.history.pushState( { path: url }, '', url ); 223 224 return params; 225 } 226 227 function applyParameters( params, listID, nodePath, selector ) { 228 var url = nodePath + '.' + selector + '.html?' + params.toString(); 229 230 // Add loading indicator on 500ms delay. If added immediately, some variants of lists are fast enough to return almost immediately 231 // so content appears to flash. Adding the delay means that quick-responding lists won't do this but slower ones will still give user feedback 232 var timer = setTimeout( () => { 233 replaceHTML( '<div id="' + listID + '" class="uscb-loading-circular-indeterminate" style="margin: 0 auto" aria-label="Loading"></div>', listID ); 234 document.dispatchEvent( new Event( 'activate-loading-indicators' ) ); 235 }, 500 ); 236 237 $.get( url ).then( response => { 238 clearTimeout( timer ); 239 replaceHTML( response, listID ); 240 241 // Updates 242 ListFiltering.prepopulateSearch( listID ); 243 ListFiltering.prepopulateFilters( listID ); 244 245 document.dispatchEvent( new Event( 'adjust-lists' ) ); 246 window.location.hash = listID; 247 }); 248 } 249 250 function replaceHTML( html, listID ) { 251 var $root = $( '#' + listID ); 252 if ( $root.length > 0 ) { 253 // Create contextual fragment so that scripts in resulting HTML are executed properly 254 var htmlFragment = document.createRange().createContextualFragment( html ); 255 $root[0].replaceWith( htmlFragment ); 256 } 257 } 258 259 return { 260 updateQueryString: updateQueryString, 261 applyParameters: applyParameters 262 }; 263})(); 264 265 // TODO: Genericize this entire utility and move to other clientlib 266 var DropdownUtil = ( function() { 267 268 function keydownToggle( event, dropdownID ) { 269 if ( event.key === 'ArrowDown' ) { 270 toggle( dropdownID ); 271 event.preventDefault(); 272 } 273 } 274 275 function clickOutside( dropdownID ) { 276 let dropdownMenu = $('#' + dropdownID ); 277 let dropdownContainer = dropdownMenu.siblings('.uscb-dropdown-container, .uscb-result__tabs-tab-more'); 278 279 if( dropdownMenu.length === 0 || dropdownContainer.length === 0 || dropdownMenu.hasClass('uscb-hide') ) { 280 return; 281 } 282 283 let namespace = 'click.dropdownUtil.' + dropdownID.replace(/[^a-zA-Z0-9_-]/g, ''); 284 $('body').off(namespace); 285 $('body').on(namespace, function(event) { 286 const hasClickedContainer = $(event.target).closest(dropdownContainer).length > 0;
287 const hasClickedMenu = $(event.target).closest(dropdownMenu).length > 0; 288 289 if( hasClickedContainer === false && hasClickedMenu === false ) { 290 if( !dropdownMenu.hasClass('uscb-hide') ) { 291 toggle(dropdownID); 292 } 293 294 $('body').off(namespace); 295 } 296 297 if( hasClickedMenu === true && hasClickedContainer === false ) { 298 $('body').off(namespace); 299 toggle(dropdownID); 300 } 301 }); 302 303 } //outerfunction 304 305 function toggle( dropdownID ) { 306 var dropdownMenu = $( '.' + dropdownID ); 307 dropdownMenu.toggleClass( 'uscb-hide' ); 308 309 var isOpen = dropdownMenu.length > 0 && !dropdownMenu.first().hasClass( 'uscb-hide' ); 310 dropdownMenu.each(function() { 311 $(this).siblings('.uscb-dropdown-container, .uscb-result__tabs-tab-more').attr('aria-expanded', isOpen ? 'true' : 'false'); 312 }); 313 } 314 315 return { 316 keydownToggle: keydownToggle, 317 toggle: toggle, 318 clickOutside : clickOutside 319 } 320})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.