@@ -41,15 +41,34 @@ function populateFormFromURL() {
4141 const features = hashParams . get ( "features" ) . split ( "," ) ;
4242 setTimeout ( ( ) => {
4343 features . forEach ( feature => {
44- const tagContainer = document . getElementById ( 'tag-container ' ) ;
45- const tagInput = document . getElementById ( 'tag -input' ) ;
44+ const tagContainer = document . getElementById ( 'hwcaps-tags ' ) ;
45+ const tagInput = document . getElementById ( 'hwcaps -input' ) ;
4646
4747 const tagElement = document . createElement ( 'span' ) ;
4848 tagElement . classList . add ( 'tag' ) ;
4949 tagElement . textContent = feature ;
5050 tagElement . onclick = ( ) => {
51- const selectedTags = [ ...document . querySelectorAll ( '.tag' ) ] . map ( tag => tag . textContent ) ;
52- selectedTags . splice ( selectedTags . indexOf ( feature ) , 1 ) ;
51+ tagElement . remove ( ) ;
52+ filterBoards ( ) ;
53+ } ;
54+ tagContainer . insertBefore ( tagElement , tagInput ) ;
55+ } ) ;
56+ filterBoards ( ) ;
57+ } , 0 ) ;
58+ }
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 = ( ) => {
5372 tagElement . remove ( ) ;
5473 filterBoards ( ) ;
5574 } ;
@@ -83,9 +102,13 @@ function updateURL() {
83102 } ) ;
84103
85104 // Add supported features to URL
86- const selectedTags = [ ...document . querySelectorAll ( '.tag' ) ] . map ( tag => tag . textContent ) ;
105+ const selectedTags = [ ...document . querySelectorAll ( '#hwcaps-tags .tag' ) ] . map ( tag => tag . textContent ) ;
87106 selectedTags . length ? hashParams . set ( "features" , selectedTags . join ( "," ) ) : hashParams . delete ( "features" ) ;
88107
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+
89112 window . history . replaceState ( { } , "" , `#${ hashParams . toString ( ) } ` ) ;
90113}
91114
@@ -126,8 +149,8 @@ function fillSocSocSelect(families, series = undefined, selectOnFill = false) {
126149function setupHWCapabilitiesField ( ) {
127150 let selectedTags = [ ] ;
128151
129- const tagContainer = document . getElementById ( 'tag-container ' ) ;
130- const tagInput = document . getElementById ( 'tag -input' ) ;
152+ const tagContainer = document . getElementById ( 'hwcaps-tags ' ) ;
153+ const tagInput = document . getElementById ( 'hwcaps -input' ) ;
131154 const datalist = document . getElementById ( 'tag-list' ) ;
132155
133156 const tagCounts = Array . from ( document . querySelectorAll ( '.board-card' ) ) . reduce ( ( acc , board ) => {
@@ -198,6 +221,86 @@ function setupHWCapabilitiesField() {
198221 updateDatalist ( ) ;
199222}
200223
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+ // Add compatible when pressing the Enter key (supports wildcards)
287+ tagInput . addEventListener ( 'keydown' , ( e ) => {
288+ if ( e . key === 'Enter' && tagInput . value ) {
289+ addCompatible ( tagInput . value ) ;
290+ e . preventDefault ( ) ;
291+ }
292+ } ) ;
293+
294+ // Delete compatible when pressing the Backspace key
295+ tagInput . addEventListener ( 'keydown' , ( e ) => {
296+ if ( e . key === 'Backspace' && tagInput . value === '' && selectedCompatibles . length > 0 ) {
297+ removeCompatible ( selectedCompatibles [ selectedCompatibles . length - 1 ] ) ;
298+ }
299+ } ) ;
300+
301+ updateDatalist ( ) ;
302+ }
303+
201304document . addEventListener ( "DOMContentLoaded" , function ( ) {
202305 const form = document . querySelector ( ".filter-form" ) ;
203306
@@ -218,6 +321,7 @@ document.addEventListener("DOMContentLoaded", function () {
218321 populateFormFromURL ( ) ;
219322
220323 setupHWCapabilitiesField ( ) ;
324+ setupCompatiblesField ( ) ;
221325
222326 socFamilySelect = document . getElementById ( "family" ) ;
223327 socFamilySelect . addEventListener ( "change" , ( ) => {
@@ -272,8 +376,12 @@ function resetForm() {
272376 document . getElementById ( "show-shields" ) . checked = true ;
273377
274378 // Clear supported features
275- document . querySelectorAll ( '.tag' ) . forEach ( tag => tag . remove ( ) ) ;
276- document . getElementById ( 'tag-input' ) . value = '' ;
379+ document . querySelectorAll ( '#hwcaps-tags .tag' ) . forEach ( tag => tag . remove ( ) ) ;
380+ document . getElementById ( 'hwcaps-input' ) . value = '' ;
381+
382+ // Clear compatibles
383+ document . querySelectorAll ( '#compatibles-tags .tag' ) . forEach ( tag => tag . remove ( ) ) ;
384+ document . getElementById ( 'compatibles-input' ) . value = '' ;
277385
278386 filterBoards ( ) ;
279387}
@@ -289,6 +397,16 @@ function updateBoardCount() {
289397 + ` ${ visibleShields . length } of ${ shields . length } shields` ;
290398}
291399
400+ function wildcardMatch ( pattern , str ) {
401+ // Convert wildcard pattern to regex
402+ // Escape special regex characters except *
403+ const regexPattern = pattern
404+ . replace ( / [ . + ? ^ $ { } ( ) | [ \] \\ ] / g, '\\$&' )
405+ . replace ( / \* / g, '.*' ) ;
406+ const regex = new RegExp ( `^${ regexPattern } $` ) ;
407+ return regex . test ( str ) ;
408+ }
409+
292410function filterBoards ( ) {
293411 const nameInput = document . getElementById ( "name" ) . value . toLowerCase ( ) ;
294412 const archSelect = document . getElementById ( "arch" ) . value ;
@@ -297,10 +415,14 @@ function filterBoards() {
297415 const showBoards = document . getElementById ( "show-boards" ) . checked ;
298416 const showShields = document . getElementById ( "show-shields" ) . checked ;
299417
300- const selectedTags = [ ...document . querySelectorAll ( '.tag' ) ] . map ( tag => tag . textContent ) ;
418+ // Get selected hardware capability tags
419+ const selectedTags = [ ...document . querySelectorAll ( '#hwcaps-tags .tag' ) ] . map ( tag => tag . textContent ) ;
420+
421+ // Get selected compatible tags
422+ const selectedCompatibles = [ ...document . querySelectorAll ( '#compatibles-tags .tag' ) ] . map ( tag => tag . textContent ) ;
301423
302424 const resetFiltersBtn = document . getElementById ( "reset-filters" ) ;
303- if ( nameInput || archSelect || vendorSelect || socSocSelect . selectedOptions . length || selectedTags . length || ! showBoards || ! showShields ) {
425+ if ( nameInput || archSelect || vendorSelect || socSocSelect . selectedOptions . length || selectedTags . length || selectedCompatibles . length || ! showBoards || ! showShields ) {
304426 resetFiltersBtn . classList . remove ( "btn-disabled" ) ;
305427 } else {
306428 resetFiltersBtn . classList . add ( "btn-disabled" ) ;
@@ -314,6 +436,7 @@ function filterBoards() {
314436 const boardVendor = board . getAttribute ( "data-vendor" ) || "" ;
315437 const boardSocs = ( board . getAttribute ( "data-socs" ) || "" ) . split ( " " ) . filter ( Boolean ) ;
316438 const boardSupportedFeatures = ( board . getAttribute ( "data-supported-features" ) || "" ) . split ( " " ) . filter ( Boolean ) ;
439+ const boardCompatibles = ( board . getAttribute ( "data-compatibles" ) || "" ) . split ( " " ) . filter ( Boolean ) ;
317440 const isShield = board . classList . contains ( "shield" ) ;
318441
319442 let matches = true ;
@@ -323,12 +446,19 @@ function filterBoards() {
323446 if ( ( isShield && ! showShields ) || ( ! isShield && ! showBoards ) ) {
324447 matches = false ;
325448 } else {
449+ // Check if board matches all selected compatibles (with wildcard support)
450+ const compatiblesMatch = selectedCompatibles . length === 0 ||
451+ selectedCompatibles . every ( ( pattern ) =>
452+ boardCompatibles . some ( ( compatible ) => wildcardMatch ( pattern , compatible ) )
453+ ) ;
454+
326455 matches =
327456 ! ( nameInput && ! boardName . includes ( nameInput ) ) &&
328457 ! ( archSelect && ! boardArchs . includes ( archSelect ) ) &&
329458 ! ( vendorSelect && boardVendor !== vendorSelect ) &&
330459 ( selectedSocs . length === 0 || selectedSocs . some ( ( soc ) => boardSocs . includes ( soc ) ) ) &&
331- ( selectedTags . length === 0 || selectedTags . every ( ( tag ) => boardSupportedFeatures . includes ( tag ) ) ) ;
460+ ( selectedTags . length === 0 || selectedTags . every ( ( tag ) => boardSupportedFeatures . includes ( tag ) ) ) &&
461+ compatiblesMatch ;
332462 }
333463
334464 board . classList . toggle ( "hidden" , ! matches ) ;
0 commit comments