@@ -56,6 +56,19 @@ function populateFormFromURL() {
5656 filterBoards ( ) ;
5757 } , 0 ) ;
5858 }
59+
60+ // Restore compatibles from URL
61+ if ( hashParams . has ( "compatibles" ) ) {
62+ const compatibles = hashParams . get ( "compatibles" ) . split ( "|" ) ;
63+ setTimeout ( ( ) => {
64+ compatibles . forEach ( compatible => {
65+ const tagContainer = document . getElementById ( 'compatibles-tags' ) ;
66+ const tagInput = document . getElementById ( 'compatibles-input' ) ;
67+
68+ const tagElement = document . createElement ( 'span' ) ;
69+ tagElement . classList . add ( 'tag' ) ;
70+ tagElement . textContent = compatible ;
71+ tagElement . onclick = ( ) => {
5972 tagElement . remove ( ) ;
6073 filterBoards ( ) ;
6174 } ;
@@ -92,6 +105,10 @@ function updateURL() {
92105 const selectedHWTags = [ ...document . querySelectorAll ( '#hwcaps-tags .tag' ) ] . map ( tag => tag . textContent ) ;
93106 selectedHWTags . length ? hashParams . set ( "features" , selectedHWTags . join ( "," ) ) : hashParams . delete ( "features" ) ;
94107
108+ // Add compatibles to URL
109+ const selectedCompatibles = [ ...document . querySelectorAll ( '#compatibles-tags .tag' ) ] . map ( tag => tag . textContent ) ;
110+ selectedCompatibles . length ? hashParams . set ( "compatibles" , selectedCompatibles . join ( "|" ) ) : hashParams . delete ( "compatibles" ) ;
111+
95112 window . history . replaceState ( { } , "" , `#${ hashParams . toString ( ) } ` ) ;
96113}
97114
@@ -204,6 +221,80 @@ function setupHWCapabilitiesField() {
204221 updateDatalist ( ) ;
205222}
206223
224+ function setupCompatiblesField ( ) {
225+ let selectedCompatibles = [ ] ;
226+
227+ const tagContainer = document . getElementById ( 'compatibles-tags' ) ;
228+ const tagInput = document . getElementById ( 'compatibles-input' ) ;
229+ const datalist = document . getElementById ( 'compatibles-list' ) ;
230+
231+ // Collect all unique compatibles from boards
232+ const allCompatibles = Array . from ( document . querySelectorAll ( '.board-card' ) ) . reduce ( ( acc , board ) => {
233+ ( board . getAttribute ( 'data-compatibles' ) || '' ) . split ( ' ' ) . forEach ( compat => {
234+ if ( compat && ! acc . includes ( compat ) ) {
235+ acc . push ( compat ) ;
236+ }
237+ } ) ;
238+ return acc ;
239+ } , [ ] ) ;
240+
241+ allCompatibles . sort ( ) ;
242+
243+ function addCompatible ( compatible ) {
244+ if ( selectedCompatibles . includes ( compatible ) || compatible === "" ) return ;
245+ selectedCompatibles . push ( compatible ) ;
246+
247+ const tagElement = document . createElement ( 'span' ) ;
248+ tagElement . classList . add ( 'tag' ) ;
249+ tagElement . textContent = compatible ;
250+ tagElement . onclick = ( ) => removeCompatible ( compatible ) ;
251+ tagContainer . insertBefore ( tagElement , tagInput ) ;
252+
253+ tagInput . value = '' ;
254+ updateDatalist ( ) ;
255+ }
256+
257+ function removeCompatible ( compatible ) {
258+ selectedCompatibles = selectedCompatibles . filter ( c => c !== compatible ) ;
259+ document . querySelectorAll ( '.tag' ) . forEach ( el => {
260+ if ( el . textContent === compatible && el . parentElement === tagContainer ) {
261+ el . remove ( ) ;
262+ }
263+ } ) ;
264+ updateDatalist ( ) ;
265+ }
266+
267+ function updateDatalist ( ) {
268+ datalist . innerHTML = '' ;
269+ const filteredCompatibles = allCompatibles . filter ( c => ! selectedCompatibles . includes ( c ) ) ;
270+
271+ filteredCompatibles . forEach ( compatible => {
272+ const option = document . createElement ( 'option' ) ;
273+ option . value = compatible ;
274+ datalist . appendChild ( option ) ;
275+ } ) ;
276+
277+ filterBoards ( ) ;
278+ }
279+
280+ tagInput . addEventListener ( 'input' , ( ) => {
281+ if ( allCompatibles . includes ( tagInput . value ) ) {
282+ addCompatible ( tagInput . value ) ;
283+ }
284+ } ) ;
285+
286+ tagInput . addEventListener ( 'keydown' , ( e ) => {
287+ if ( e . key === 'Enter' && tagInput . value ) {
288+ addCompatible ( tagInput . value ) ;
289+ e . preventDefault ( ) ;
290+ } else if ( e . key === 'Backspace' && tagInput . value === '' && selectedCompatibles . length > 0 ) {
291+ removeCompatible ( selectedCompatibles [ selectedCompatibles . length - 1 ] ) ;
292+ }
293+ } ) ;
294+
295+ updateDatalist ( ) ;
296+ }
297+
207298document . addEventListener ( "DOMContentLoaded" , function ( ) {
208299 const form = document . querySelector ( ".filter-form" ) ;
209300
@@ -224,6 +315,7 @@ document.addEventListener("DOMContentLoaded", function () {
224315 populateFormFromURL ( ) ;
225316
226317 setupHWCapabilitiesField ( ) ;
318+ setupCompatiblesField ( ) ;
227319
228320 socFamilySelect = document . getElementById ( "family" ) ;
229321 socFamilySelect . addEventListener ( "change" , ( ) => {
@@ -281,6 +373,10 @@ function resetForm() {
281373 document . querySelectorAll ( '#hwcaps-tags .tag' ) . forEach ( tag => tag . remove ( ) ) ;
282374 document . getElementById ( 'hwcaps-input' ) . value = '' ;
283375
376+ // Clear compatibles
377+ document . querySelectorAll ( '#compatibles-tags .tag' ) . forEach ( tag => tag . remove ( ) ) ;
378+ document . getElementById ( 'compatibles-input' ) . value = '' ;
379+
284380 filterBoards ( ) ;
285381}
286382
@@ -295,6 +391,16 @@ function updateBoardCount() {
295391 + ` ${ visibleShields . length } of ${ shields . length } shields` ;
296392}
297393
394+ function wildcardMatch ( pattern , str ) {
395+ // Convert wildcard pattern to regex
396+ // Escape special regex characters except *
397+ const regexPattern = pattern
398+ . replace ( / [ . + ? ^ $ { } ( ) | [ \] \\ ] / g, '\\$&' )
399+ . replace ( / \* / g, '.*' ) ;
400+ const regex = new RegExp ( `^${ regexPattern } $` , "i" ) ;
401+ return regex . test ( str ) ;
402+ }
403+
298404function filterBoards ( ) {
299405 const nameInput = document . getElementById ( "name" ) . value . toLowerCase ( ) ;
300406 const archSelect = document . getElementById ( "arch" ) . value ;
@@ -306,8 +412,11 @@ function filterBoards() {
306412 // Get selected hardware capability tags
307413 const selectedHWTags = [ ...document . querySelectorAll ( '#hwcaps-tags .tag' ) ] . map ( tag => tag . textContent ) ;
308414
415+ // Get selected compatible tags
416+ const selectedCompatibles = [ ...document . querySelectorAll ( '#compatibles-tags .tag' ) ] . map ( tag => tag . textContent ) ;
417+
309418 const resetFiltersBtn = document . getElementById ( "reset-filters" ) ;
310- if ( nameInput || archSelect || vendorSelect || socSocSelect . selectedOptions . length || selectedHWTags . length || ! showBoards || ! showShields ) {
419+ if ( nameInput || archSelect || vendorSelect || socSocSelect . selectedOptions . length || selectedHWTags . length || selectedCompatibles . length || ! showBoards || ! showShields ) {
311420 resetFiltersBtn . classList . remove ( "btn-disabled" ) ;
312421 } else {
313422 resetFiltersBtn . classList . add ( "btn-disabled" ) ;
@@ -321,6 +430,7 @@ function filterBoards() {
321430 const boardVendor = board . getAttribute ( "data-vendor" ) || "" ;
322431 const boardSocs = ( board . getAttribute ( "data-socs" ) || "" ) . split ( " " ) . filter ( Boolean ) ;
323432 const boardSupportedFeatures = ( board . getAttribute ( "data-supported-features" ) || "" ) . split ( " " ) . filter ( Boolean ) ;
433+ const boardCompatibles = ( board . getAttribute ( "data-compatibles" ) || "" ) . split ( " " ) . filter ( Boolean ) ;
324434 const isShield = board . classList . contains ( "shield" ) ;
325435
326436 let matches = true ;
@@ -330,12 +440,19 @@ function filterBoards() {
330440 if ( ( isShield && ! showShields ) || ( ! isShield && ! showBoards ) ) {
331441 matches = false ;
332442 } else {
443+ // Check if board matches all selected compatibles (with wildcard support)
444+ const compatiblesMatch = selectedCompatibles . length === 0 ||
445+ selectedCompatibles . every ( ( pattern ) =>
446+ boardCompatibles . some ( ( compatible ) => wildcardMatch ( pattern , compatible ) )
447+ ) ;
448+
333449 matches =
334450 ! ( nameInput && ! boardName . includes ( nameInput ) ) &&
335451 ! ( archSelect && ! boardArchs . includes ( archSelect ) ) &&
336452 ! ( vendorSelect && boardVendor !== vendorSelect ) &&
337453 ( selectedSocs . length === 0 || selectedSocs . some ( ( soc ) => boardSocs . includes ( soc ) ) ) &&
338- ( selectedHWTags . length === 0 || selectedHWTags . every ( ( tag ) => boardSupportedFeatures . includes ( tag ) ) ) ;
454+ ( selectedHWTags . length === 0 || selectedHWTags . every ( ( tag ) => boardSupportedFeatures . includes ( tag ) ) ) &&
455+ compatiblesMatch ;
339456 }
340457
341458 board . classList . toggle ( "hidden" , ! matches ) ;
0 commit comments