Skip to content

Commit 8e8a74b

Browse files
authored
Merge pull request #6 from cucapra/search
Added search and checkbox functionality
2 parents a9f7615 + 29b22ed commit 8e8a74b

File tree

3 files changed

+154
-0
lines changed

3 files changed

+154
-0
lines changed

src/index.njk

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ The base integer <strong>I</strong> extension is the most widely implemented set
2626
{% endfor %}
2727
</ul>
2828

29+
<p class="secondary-callout">
30+
<a href="/search/">Try the searchable instruction index.</a>
31+
</p>
32+
2933
<style>
3034
body { font-family: system-ui, sans-serif; line-height: 1.5; margin: 2rem auto; max-width: 900px; padding: 0 1rem; }
3135
ul { list-style: none; padding: 0; }
@@ -34,4 +38,5 @@ a { text-decoration: none; color: #0366d6; }
3438
a:hover { text-decoration: underline; }
3539
.primary-callout { background: #f1f8ff; border-left: 4px solid #0366d6; padding: 12px; margin: 1rem 0; }
3640
.extension-description { margin: 4px 0 0; color: #586069; font-size: 0.9rem; }
41+
.secondary-callout { margin-top: 2rem; }
3742
</style>

src/instruction-search.njk

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
---
2+
title: Instruction Search
3+
permalink: search/index.html
4+
---
5+
<h1>Instruction Search</h1>
6+
7+
<p>Search by mnemonic or long name. Hit Enter on an exact mnemonic match to jump straight to the instruction details.</p>
8+
9+
<input id="search" placeholder="Search mnemonic, e.g. addi" style="padding:8px; width: 100%; max-width: 480px;" />
10+
11+
{# Creates box divide between checkboxes and rest of page #}
12+
<fieldset id="extension-filters-fieldset" class="filters">
13+
<legend>Filter by extension</legend>
14+
<div id="extension-filters" class="filter-options"></div>
15+
</fieldset>
16+
17+
<ul id="instruction_list">
18+
{% for inst in instructions %}
19+
<li
20+
data-search="{{ (inst.name ~ ' ' ~ inst.longName) | lower }}"
21+
data-mnemonic="{{ inst.name | lower }}"
22+
data-extension="{{ inst.extension }}"
23+
data-extension-slug="{{ inst.extensionSlug }}"
24+
>
25+
<a href="/instructions/{{ inst.name }}/">{{ inst.name }}</a>
26+
<span class="ext-name" data-extension-slug="{{ inst.extensionSlug }}" title="Extension">
27+
- {{ inst.extension }}
28+
</span>
29+
</li>
30+
{% endfor %}
31+
</ul>
32+
33+
<script>
34+
const input = document.getElementById('search');
35+
const instruction_list = document.getElementById('instruction_list');
36+
const filtersFieldset = document.getElementById('extension-filters-fieldset');
37+
{# the blank area where we’ll insert checkboxes #}
38+
const filtersContainer = document.getElementById('extension-filters');
39+
const items = Array.from(instruction_list.querySelectorAll('li'));
40+
const activeExtensions = new Set();
41+
42+
// Gather unique extension names from the rendered instruction <li> items.
43+
const extensionSet = new Set();
44+
for (const li of items) {
45+
const extension = li.dataset.extension;
46+
extensionSet.add(extension);
47+
}
48+
49+
const extensions = Array.from(extensionSet).sort();
50+
for (const extension of extensions) {
51+
const id = `ext-${extension}`;
52+
const label = document.createElement('label');
53+
label.className = 'filter-option';
54+
55+
const checkbox = document.createElement('input');
56+
checkbox.type = 'checkbox';
57+
checkbox.value = extension;
58+
checkbox.id = id;
59+
checkbox.addEventListener('change', () => {
60+
if (checkbox.checked) {
61+
activeExtensions.add(extension);
62+
} else {
63+
activeExtensions.delete(extension);
64+
}
65+
applyFilters();
66+
});
67+
68+
const text = document.createElement('span');
69+
text.textContent = extension;
70+
71+
label.appendChild(checkbox);
72+
label.appendChild(text);
73+
filtersContainer.appendChild(label);
74+
}
75+
76+
77+
function applyFilters() {
78+
const q = input.value.trim().toLowerCase();
79+
const filtersActive = activeExtensions.size > 0;
80+
for (const li of items) {
81+
82+
{# Check if the item matches the text search
83+
- If the search box is empty, match everything
84+
- Otherwise, match if the search string includes the query #}
85+
const matchesQuery = !q || (li.dataset.search || '').includes(q);
86+
87+
{# Check if the item matches an active extension filter
88+
- If no filters are active, match everything
89+
- Otherwise, show only if its extension is selected #}
90+
const matchesExtension = !filtersActive || activeExtensions.has(li.dataset.extension);
91+
92+
{# Show or hide this <li> depending on whether both match conditions are true #}
93+
li.style.display = (matchesQuery && matchesExtension) ? '' : 'none';
94+
}
95+
}
96+
97+
input.addEventListener('input', applyFilters);
98+
99+
input.addEventListener('keydown', (e) => {
100+
{# Only act if Enter is pressed #}
101+
if (e.key !== 'Enter') return;
102+
103+
const q = input.value.trim().toLowerCase();
104+
105+
{# Do nothing if the box is empty #}
106+
if (!q) return;
107+
108+
const filtersActive = activeExtensions.size > 0;
109+
110+
{# Look for an exact mnemonic match among all instructions
111+
- If filters are active, also ensure it belongs to one of the selected extensions #}
112+
const exact = items.find(li => {
113+
if (li.dataset.mnemonic !== q) return false;
114+
if (!filtersActive) return true;
115+
return activeExtensions.has(li.dataset.extension);
116+
});
117+
118+
{# If an exact match is found, redirect to that instruction's detail page #}
119+
if (exact) {
120+
const link = exact.querySelector('a').getAttribute('href');
121+
location.href = link;
122+
}
123+
});
124+
125+
applyFilters();
126+
</script>
127+
128+
<style>
129+
body { font-family: system-ui, sans-serif; line-height: 1.5; margin: 2rem auto; max-width: 900px; padding: 0 1rem; }
130+
ul { instruction_list-style: none; padding: 0; }
131+
li { margin: 6px 0; }
132+
a { text-decoration: none; color: #0366d6; }
133+
a:hover { text-decoration: underline; }
134+
#extension-filters-fieldset { margin: 1rem 0; padding: 0.75rem 1rem; border: 1px solid #d0d7de; border-radius: 6px; }
135+
#extension-filters-fieldset legend { font-weight: 600; font-size: 0.95rem; padding: 0 6px; }
136+
.filter-options { display: flex; flex-wrap: wrap; gap: 0.5rem 1rem; margin-top: 0.5rem; align-items: center; }
137+
.filter-option { display: inline-flex; align-items: center; gap: 0.4rem; font-size: 0.95rem; white-space: nowrap; }
138+
.filter-option input { width: 16px; height: 16px; }
139+
</style>

src/instructions/instruction.njk

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,16 @@ eleventyComputed:
3333

3434
</section>
3535

36+
<section>
37+
<h2>RISC-V Instruction Encoder/Decoder</h2>
38+
<p>
39+
<a href="https://luplab.gitlab.io/rvcodecjs/#q={{ inst.name | urlencode }}&abi=false&isa=AUTO"
40+
target="_blank" rel="noopener">
41+
open {{ inst.name }} in rvcodecjs
42+
</a>
43+
</p>
44+
</section>
45+
3646
<section>
3747
<h2>Availability</h2>
3848
<ul>

0 commit comments

Comments
 (0)