-
Notifications
You must be signed in to change notification settings - Fork 0
Added search and checkbox functionality #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
20f8d2d
9351c66
acd00b9
6cb9630
8939759
3c20dde
29b22ed
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,9 +8,20 @@ permalink: search/index.html | |
|
|
||
| <input id="search" placeholder="Search mnemonic, e.g. addi" style="padding:8px; width: 100%; max-width: 480px;" /> | ||
|
|
||
| {# Creates box divide between checkboxes and rest of page #} | ||
| <fieldset id="extension-filters-fieldset" class="filters"> | ||
| <legend>Filter by extension</legend> | ||
| <div id="extension-filters" class="filter-options"></div> | ||
| </fieldset> | ||
|
|
||
| <ul id="list"> | ||
| {% for inst in instructions %} | ||
| <li data-name="{{ inst.name }} {{ inst.longName | lower }}"> | ||
| <li | ||
| data-search="{{ (inst.name ~ ' ' ~ inst.longName) | lower }}" | ||
| data-mnemonic="{{ inst.name | lower }}" | ||
| data-extension="{{ inst.extension }}" | ||
| data-extension-slug="{{ inst.extensionSlug }}" | ||
| > | ||
| <a href="/instructions/{{ inst.name }}/">{{ inst.name }}</a> | ||
| </li> | ||
| {% endfor %} | ||
|
|
@@ -19,25 +30,101 @@ permalink: search/index.html | |
| <script> | ||
| const input = document.getElementById('search'); | ||
| const list = document.getElementById('list'); | ||
| const filtersFieldset = document.getElementById('extension-filters-fieldset'); | ||
| {# the blank area where we’ll insert checkboxes #} | ||
| const filtersContainer = document.getElementById('extension-filters'); | ||
| const items = Array.from(list.querySelectorAll('li')); | ||
| input.addEventListener('input', () => { | ||
| const activeExtensions = new Set(); | ||
|
|
||
| const extensionMap = new Map(); | ||
| for (const li of items) { | ||
| const slug = li.dataset.extensionSlug; | ||
| const extension = li.dataset.extension; | ||
| if (slug && extension && !extensionMap.has(slug)) { | ||
| extensionMap.set(slug, { extension, slug }); | ||
| } | ||
| } | ||
|
|
||
| const extensions = Array.from(extensionMap.values()).sort((a, b) => a.extension.localeCompare(b.extension)); | ||
sq86-qazi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if (extensions.length === 0 && filtersFieldset) { | ||
sq86-qazi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| filtersFieldset.style.display = 'none'; | ||
| } else { | ||
| for (const { extension, slug } of extensions) { | ||
| const id = `ext-${slug}`; | ||
| const label = document.createElement('label'); | ||
| label.className = 'filter-option'; | ||
|
|
||
| const checkbox = document.createElement('input'); | ||
| checkbox.type = 'checkbox'; | ||
| checkbox.value = extension; | ||
| checkbox.id = id; | ||
| checkbox.addEventListener('change', () => { | ||
| if (checkbox.checked) { | ||
| activeExtensions.add(extension); | ||
| } else { | ||
| activeExtensions.delete(extension); | ||
| } | ||
| applyFilters(); | ||
| }); | ||
|
|
||
| const text = document.createElement('span'); | ||
| text.textContent = extension; | ||
|
|
||
| label.appendChild(checkbox); | ||
| label.appendChild(text); | ||
| filtersContainer.appendChild(label); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Overall, it looks like the strategy here is to populate the list of extension in the client, rather than when generating the HTML. Rather like the situation where we were generating SVG in the browser before, I think this could be a lot simpler if we simply generate the HTML for the checkboxes up front. This would avoid all the I strongly suggest that we leave that change to a future PR as well! In the interest of getting this one merged, so the next step will be clear. |
||
| } | ||
| } | ||
|
|
||
| function applyFilters() { | ||
| const q = input.value.trim().toLowerCase(); | ||
| const filtersActive = activeExtensions.size > 0; | ||
| for (const li of items) { | ||
| const txt = li.getAttribute('data-name'); | ||
| li.style.display = (!q || txt.includes(q)) ? '' : 'none'; | ||
|
|
||
| {# Check if the item matches the text search | ||
| - If the search box is empty, match everything | ||
| - Otherwise, match if the search string includes the query #} | ||
| const matchesQuery = !q || (li.dataset.search || '').includes(q); | ||
|
|
||
| {# Check if the item matches an active extension filter | ||
| - If no filters are active, match everything | ||
| - Otherwise, show only if its extension is selected #} | ||
| const matchesExtension = !filtersActive || activeExtensions.has(li.dataset.extension); | ||
|
|
||
| {# Show or hide this <li> depending on whether both match conditions are true #} | ||
| li.style.display = (matchesQuery && matchesExtension) ? '' : 'none'; | ||
| } | ||
| // If exact mnemonic match, navigate on Enter | ||
| }); | ||
| } | ||
|
|
||
| input.addEventListener('input', applyFilters); | ||
|
|
||
| input.addEventListener('keydown', (e) => { | ||
| if (e.key === 'Enter') { | ||
| const q = input.value.trim().toLowerCase(); | ||
| if (!q) return; | ||
| const exact = items.find(li => li.querySelector('a').textContent.toLowerCase() === q); | ||
| if (exact) { | ||
| location.href = exact.querySelector('a').getAttribute('href'); | ||
| } | ||
| {# Only act if Enter is pressed #} | ||
| if (e.key !== 'Enter') return; | ||
|
|
||
| const q = input.value.trim().toLowerCase(); | ||
|
|
||
| {# Do nothing if the box is empty #} | ||
| if (!q) return; | ||
|
|
||
| const filtersActive = activeExtensions.size > 0; | ||
sq86-qazi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| {# Look for an exact mnemonic match among all instructions | ||
| - If filters are active, also ensure it belongs to one of the selected extensions #} | ||
| const exact = items.find(li => { | ||
| if (li.dataset.mnemonic !== q) return false; | ||
| if (!filtersActive) return true; | ||
| return activeExtensions.has(li.dataset.extension); | ||
| }); | ||
|
|
||
| {# If an exact match is found, redirect to that instruction's detail page #} | ||
| if (exact) { | ||
| const link = exact.querySelector('a').getAttribute('href'); | ||
| location.href = link; | ||
| } | ||
| }); | ||
|
|
||
| applyFilters(); | ||
| </script> | ||
|
|
||
| <style> | ||
|
|
@@ -46,5 +133,9 @@ ul { list-style: none; padding: 0; } | |
| li { margin: 6px 0; } | ||
| a { text-decoration: none; color: #0366d6; } | ||
| a:hover { text-decoration: underline; } | ||
| #extension-filters-fieldset { margin: 1rem 0; padding: 0.75rem 1rem; border: 1px solid #d0d7de; border-radius: 6px; } | ||
| #extension-filters-fieldset legend { font-weight: 600; font-size: 0.95rem; padding: 0 6px; } | ||
| .filter-options { display: flex; flex-wrap: wrap; gap: 0.5rem 1rem; margin-top: 0.5rem; align-items: center; } | ||
| .filter-option { display: inline-flex; align-items: center; gap: 0.4rem; font-size: 0.95rem; white-space: nowrap; } | ||
| .filter-option input { width: 16px; height: 16px; } | ||
| </style> | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.