Skip to content

Commit 697711a

Browse files
committed
Add URL auto-complete to cart search
1 parent f132200 commit 697711a

2 files changed

Lines changed: 110 additions & 43 deletions

File tree

docs/index.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ If you'd like to improve this resource or add new carts, feel free to create a p
1313

1414
## Cart Search
1515

16-
Don't know which page matches your cart? You can search for a matching cart by typing in the URL that appears on the label of your cart into the search box below (results will appear after typing in the entire URL).
16+
Don't know which page matches your cart? You can search for a matching cart by typing in the URL that appears on the label of your cart into the search box below.
1717

18-
Note that some URLs may have many hardware variants. If multiple results are returned for your cart's URL, you will need to check each page to see which one accurately matches the cart you have. Results are sorted by most common to least common carts, so starting at the first result is a good bet.
18+
Note that some manufacturers can have many hardware variants. If multiple results are returned for your cart's URL, you will need to check each page to see which one accurately matches the cart you have. Results are sorted by most common to least common carts, so starting at the first result is a good bet.
1919

2020
<div class="cart-search" data-cart-search data-index-url="assets/search/cart-search.json">
2121

docs/javascripts/cart-search.js

Lines changed: 108 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
const indexUrl = root.dataset.indexUrl;
88

99
let searchData = null;
10+
let allKnownUrls = [];
1011
let activeIndex = -1;
1112

1213
function normalizeCartUrl(value) {
@@ -29,41 +30,57 @@
2930
}
3031

3132
searchData = await response.json();
33+
34+
// Precompute a sorted list of all unique normalized URLs for quick filtering
35+
const urlSet = new Set();
36+
for (const page of Object.values(searchData.pages)) {
37+
(page.matches || []).forEach(url => {
38+
urlSet.add(normalizeCartUrl(url));
39+
});
40+
}
41+
allKnownUrls = Array.from(urlSet).sort();
42+
3243
return searchData;
3344
}
3445

3546
function findResults(query, data) {
3647
const normalizedQuery = normalizeCartUrl(query);
3748
if (normalizedQuery.length < 2) return [];
3849

39-
const results = [];
40-
41-
for (const [pageId, page] of Object.entries(data.pages)) {
42-
const normalizedMatches = (page.matches || []).map(normalizeCartUrl);
43-
44-
const isMatch = normalizedMatches.some((url) => {
45-
return url === normalizedQuery;
46-
});
47-
48-
if (!isMatch) continue;
49-
50-
results.push({
51-
id: pageId,
52-
...page
50+
// Exact match, display the search results
51+
if (allKnownUrls.includes(normalizedQuery)) {
52+
const pageResults = [];
53+
for (const [pageId, page] of Object.entries(data.pages)) {
54+
const normalizedMatches = (page.matches || []).map(normalizeCartUrl);
55+
if (normalizedMatches.includes(normalizedQuery)) {
56+
pageResults.push({ type: "page", id: pageId, ...page });
57+
}
58+
}
59+
60+
pageResults.sort((a, b) => {
61+
const rankA = a.rank ?? 9999;
62+
const rankB = b.rank ?? 9999;
63+
if (rankA !== rankB) return rankA - rankB;
64+
return a.title.localeCompare(b.title);
5365
});
66+
67+
// Prepend header for page results
68+
pageResults.unshift({ type: "header", value: normalizedQuery });
69+
70+
return pageResults;
5471
}
55-
56-
results.sort((a, b) => {
57-
const rankA = a.rank ?? 9999;
58-
const rankB = b.rank ?? 9999;
59-
60-
if (rankA !== rankB) {
61-
return rankA - rankB;
62-
}
63-
64-
return a.title.localeCompare(b.title);
65-
});
66-
72+
73+
// Partial match, display URL suggestions
74+
const suggestions = allKnownUrls.filter(url => url.startsWith(normalizedQuery));
75+
76+
// Limit to top 5 suggestions
77+
const results = suggestions.slice(0, 5).map(url => ({ type: "suggestion", value: url }));
78+
79+
// Add footnote if there are more than 5 matches
80+
if (suggestions.length > 5) {
81+
results.push({ type: "footnote", value: "Suggestions Being Limited to Top 5 Matches" });
82+
}
83+
6784
return results;
6885
}
6986

@@ -76,25 +93,75 @@
7693
return;
7794
}
7895

79-
for (const page of results) {
80-
const link = document.createElement("a");
81-
link.className = "cart-search__result";
82-
link.href = page.url;
83-
84-
const title = document.createElement("span");
85-
title.className = "cart-search__title";
86-
title.textContent = page.title;
96+
for (const item of results) {
97+
// Footnotes and headers should not be clickable links
98+
const el = document.createElement((item.type === "footnote" || item.type === "header") ? "div" : "a");
99+
100+
if (item.type === "header") {
101+
el.style.padding = "0.45rem 0.9rem";
102+
el.style.borderBottom = "1px solid var(--md-default-fg-color--lighter)";
103+
el.style.fontSize = "0.8rem";
104+
105+
const prefix = document.createTextNode("Matches for ");
106+
const boldText = document.createElement("strong");
107+
boldText.textContent = item.value;
108+
109+
el.appendChild(prefix);
110+
el.appendChild(boldText);
111+
112+
resultsBox.appendChild(el);
113+
continue; // Skip the rest of the styling for this item
114+
}
87115

88-
link.appendChild(title);
116+
if (item.type === "footnote") {
117+
el.style.padding = "0.5rem 0.9rem";
118+
el.style.textAlign = "center";
119+
el.style.fontSize = "0.60rem";
120+
el.style.color = "var(--md-default-fg-color--light)";
121+
el.style.fontStyle = "italic";
122+
el.textContent = item.value;
123+
resultsBox.appendChild(el);
124+
continue; // Skip the rest of the styling for this item
125+
}
89126

90-
if (page.subtitle) {
91-
const subtitle = document.createElement("span");
92-
subtitle.className = "cart-search__subtitle";
93-
subtitle.textContent = page.subtitle;
94-
link.appendChild(subtitle);
127+
el.className = "cart-search__result";
128+
129+
if (item.type === "page") {
130+
// Standard cart page result
131+
el.href = item.url;
132+
133+
const title = document.createElement("span");
134+
title.className = "cart-search__title";
135+
title.textContent = item.title;
136+
el.appendChild(title);
137+
138+
if (item.subtitle) {
139+
const subtitle = document.createElement("span");
140+
subtitle.className = "cart-search__subtitle";
141+
subtitle.textContent = item.subtitle;
142+
el.appendChild(subtitle);
143+
}
144+
} else if (item.type === "suggestion") {
145+
// URL autocomplete suggestion
146+
el.style.cursor = "pointer";
147+
148+
const title = document.createElement("span");
149+
title.className = "cart-search__title";
150+
title.textContent = item.value;
151+
el.appendChild(title);
152+
153+
// Accept a suggestion
154+
el.addEventListener("click", (e) => {
155+
e.preventDefault();
156+
e.stopPropagation(); // Prevent the document click listener from hiding the results box
157+
158+
input.value = item.value;
159+
input.dispatchEvent(new Event("input")); // Trigger exact match state
160+
input.focus();
161+
});
95162
}
96163

97-
resultsBox.appendChild(link);
164+
resultsBox.appendChild(el);
98165
}
99166

100167
resultsBox.hidden = false;

0 commit comments

Comments
 (0)