|
36 | 36 | /* Defined inline so the factory is on window before Alpine evaluates x-data |
37 | 37 | on this page — defer-script load ordering can otherwise race Alpine.start. */ |
38 | 38 | window.libraryFilter = function () { |
39 | | - function cppNum(value) { |
40 | | - const n = parseInt(value, 10); |
41 | | - return Number.isFinite(n) ? n : 0; |
| 39 | + const CPP_STANDARDS = [ |
| 40 | + { key: '98', year: 1998, label: 'C++98' }, |
| 41 | + { key: '03', year: 2003, label: 'C++03' }, |
| 42 | + { key: '11', year: 2011, label: 'C++11' }, |
| 43 | + { key: '14', year: 2014, label: 'C++14' }, |
| 44 | + { key: '17', year: 2017, label: 'C++17' }, |
| 45 | + { key: '20', year: 2020, label: 'C++20' }, |
| 46 | + { key: '23', year: 2023, label: 'C++23' }, |
| 47 | + ]; |
| 48 | + const CPP_OPTION_VALUES = CPP_STANDARDS.map((s) => s.key); |
| 49 | + const CPP_LABEL_MAP = Object.fromEntries( |
| 50 | + [['all', 'All'], ...CPP_STANDARDS.map((s) => [s.key, s.label])] |
| 51 | + ); |
| 52 | + function cppRank(value) { |
| 53 | + const digits = String(value || '').match(/\d+/)?.[0]; |
| 54 | + return CPP_STANDARDS.find((s) => s.key === digits)?.year ?? 0; |
42 | 55 | } |
43 | 56 | return { |
44 | 57 | dataset: [], |
|
98 | 111 | this.$el.addEventListener('field-change', (event) => { |
99 | 112 | const { name, value } = event.detail; |
100 | 113 | if (name === 'grading') this.grading = value || 'all'; |
101 | | - else if (name === 'min_cpp') this.minCpp = value || 'all'; |
102 | | - else if (name === 'max_cpp') this.maxCpp = value || 'all'; |
| 114 | + else if (name === 'min_cpp') { |
| 115 | + this.minCpp = value || 'all'; |
| 116 | + if (this.minCpp !== 'all' && this.maxCpp !== 'all' |
| 117 | + && cppRank(this.minCpp) > cppRank(this.maxCpp)) { |
| 118 | + this.resetField('max_cpp', 'all'); |
| 119 | + } |
| 120 | + this.updateCppConstraints(); |
| 121 | + } |
| 122 | + else if (name === 'max_cpp') { |
| 123 | + this.maxCpp = value || 'all'; |
| 124 | + if (this.maxCpp !== 'all' && this.minCpp !== 'all' |
| 125 | + && cppRank(this.maxCpp) < cppRank(this.minCpp)) { |
| 126 | + this.resetField('min_cpp', 'all'); |
| 127 | + } |
| 128 | + this.updateCppConstraints(); |
| 129 | + } |
103 | 130 | else if (name === 'q') this.query = value || ''; |
104 | 131 | else if (name === 'sort') this.sort = value || this.defaults.sort; |
105 | 132 | else if (name === 'view') { |
|
109 | 136 | this.apply(); |
110 | 137 | }); |
111 | 138 |
|
| 139 | + // Auto-correct invalid combinations loaded from URL params, then push |
| 140 | + // initial disabled-value constraints to both cpp dropdowns. Defer to |
| 141 | + // $nextTick so the dropdown components have mounted their listeners. |
| 142 | + if (this.minCpp !== 'all' && this.maxCpp !== 'all' |
| 143 | + && cppRank(this.minCpp) > cppRank(this.maxCpp)) { |
| 144 | + this.maxCpp = 'all'; |
| 145 | + } |
| 146 | + this.$nextTick(() => { |
| 147 | + if (this.maxCpp === 'all') { |
| 148 | + window.dispatchEvent(new CustomEvent('field-set', |
| 149 | + { detail: { name: 'max_cpp', value: 'all', label: 'All' } })); |
| 150 | + } |
| 151 | + this.updateCppConstraints(); |
| 152 | + }); |
| 153 | + |
112 | 154 | this.observeCategorySelections(); |
113 | 155 | document.querySelectorAll('[data-slug]').forEach((el) => { |
114 | 156 | const siblings = Array.from(el.parentElement.children).filter((c) => c.dataset.slug); |
|
117 | 159 | this.apply(); |
118 | 160 | }, |
119 | 161 |
|
| 162 | + updateCppConstraints() { |
| 163 | + const minDisabled = (this.maxCpp && this.maxCpp !== 'all') |
| 164 | + ? CPP_OPTION_VALUES.filter((v) => cppRank(v) > cppRank(this.maxCpp)) |
| 165 | + : []; |
| 166 | + const maxDisabled = (this.minCpp && this.minCpp !== 'all') |
| 167 | + ? CPP_OPTION_VALUES.filter((v) => cppRank(v) < cppRank(this.minCpp)) |
| 168 | + : []; |
| 169 | + window.dispatchEvent(new CustomEvent('field-disabled-values', |
| 170 | + { detail: { name: 'min_cpp', values: minDisabled } })); |
| 171 | + window.dispatchEvent(new CustomEvent('field-disabled-values', |
| 172 | + { detail: { name: 'max_cpp', values: maxDisabled } })); |
| 173 | + }, |
| 174 | + |
| 175 | + resetField(name, value) { |
| 176 | + if (name === 'min_cpp') this.minCpp = value; |
| 177 | + else if (name === 'max_cpp') this.maxCpp = value; |
| 178 | + window.dispatchEvent(new CustomEvent('field-set', |
| 179 | + { detail: { name, value, label: CPP_LABEL_MAP[value] || '' } })); |
| 180 | + }, |
| 181 | + |
120 | 182 | handleViewChange(value) { |
121 | 183 | if (!value) return; |
122 | 184 | const url = new URL(window.location.href); |
|
252 | 314 | matches(data) { |
253 | 315 | if (this.searchHits && !this.searchHits.has(data.slug)) return false; |
254 | 316 | if (this.grading !== 'all' && data.tier !== this.grading) return false; |
255 | | - if (this.minCpp !== 'all' && data.cpp_min && cppNum(data.cpp_min) > cppNum(this.minCpp)) return false; |
256 | | - if (this.maxCpp !== 'all' && data.cpp_max && cppNum(data.cpp_max) < cppNum(this.maxCpp)) return false; |
| 317 | + if (this.minCpp !== 'all' && data.cpp_min && cppRank(data.cpp_min) > cppRank(this.minCpp)) return false; |
| 318 | + if (this.maxCpp !== 'all' && data.cpp_max && cppRank(data.cpp_max) < cppRank(this.maxCpp)) return false; |
257 | 319 | if (this.categories.length > 0) { |
258 | 320 | const hit = data.category_slugs.some((s) => this.categories.includes(s)); |
259 | 321 | if (!hit) return false; |
|
0 commit comments