Skip to content

Commit 9b8c68d

Browse files
authored
Add new results landing page for website search (opensearch-project#7942)
* Add new results landing page for website Signed-off-by: Zelin Hao <zelinhao@amazon.com> * Update some features Signed-off-by: Zelin Hao <zelinhao@amazon.com> * Update the margin between search results Signed-off-by: Zelin Hao <zelinhao@amazon.com> * Update display when no checkbox selected Signed-off-by: Zelin Hao <zelinhao@amazon.com> --------- Signed-off-by: Zelin Hao <zelinhao@amazon.com>
1 parent e1fc065 commit 9b8c68d

File tree

4 files changed

+349
-4
lines changed

4 files changed

+349
-4
lines changed

_layouts/search_layout.html

+195
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
---
2+
layout: table_wrappers
3+
---
4+
5+
<!DOCTYPE html>
6+
7+
<html lang="{{ site.lang | default: 'en-US' }}">
8+
{% include head.html %}
9+
<body>
10+
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
11+
<symbol id="svg-arrow-right" viewBox="0 0 24 24">
12+
<title>Expand</title>
13+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chevron-right">
14+
<polyline points="9 18 15 12 9 6"></polyline>
15+
</svg>
16+
</symbol>
17+
</svg>
18+
19+
{% include header.html %}
20+
21+
<div class="main-content-wrap-home">
22+
<div id="main-content" class="main-content" role="main">
23+
<head>
24+
<meta charset="UTF-8">
25+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
26+
<title>Results Page Head from layout</title>
27+
</head>
28+
<div class="search-page">
29+
<aside class="search-page--sidebar">
30+
<h2>Filter results</h2>
31+
<div class="version-selector-wrapper" id="version-panel">
32+
<version-selector selected="{{ site.data.versions.current }}"></version-selector>
33+
</div>
34+
<form class="search-page--sidebar--category-filter">
35+
<div class="search-page--sidebar--category-filter--checkbox">
36+
<div>
37+
<input type="checkbox" id="categoryAll" name="categoryGroup" value="All" class="category-checkbox" checked>
38+
<label for="categoryAll">All</label>
39+
</div>
40+
<div class="search-page--sidebar--category-filter--checkbox-child">
41+
<input type="checkbox" id="categoryDocumentation" name="categoryGroup" value="Documentation" class="category-checkbox">
42+
<label for="categoryDocumentation">Documentation</label>
43+
</div>
44+
<div class="search-page--sidebar--category-filter--checkbox-child">
45+
<input type="checkbox" id="categoryNews" name="categoryGroup" value="News" class="category-checkbox">
46+
<label for="categoryNews">News</label>
47+
</div>
48+
</div>
49+
</form>
50+
</aside>
51+
<div class="search-page--results">
52+
<div class="search-page--results--input">
53+
<input type="text" class="search-page--results--input-box" id="searchPageInput" placeholder="Search for anything" aria-label="Search {{ site.title }}" autocomplete="off">
54+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" class="search-page--results--input-icon" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-search">
55+
<circle cx="11" cy="11" r="8"></circle>
56+
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
57+
</svg>
58+
</div>
59+
<main class="search-page--results--diplay">
60+
<h1 class="search-page--results--diplay--header" id="searchPageResultsHeader"></h1>
61+
<div class="search-page--results--diplay--container" id="searchPageResultsContainer">
62+
<!-- API results will be displayed here -->
63+
</div>
64+
</main>
65+
</div>
66+
</div>
67+
<a class="top-link" href="#top">
68+
<svg viewBox="0 0 24 24"><use xlink:href="#svg-arrow-right"></use></svg>
69+
</a>
70+
</div>
71+
</div>
72+
73+
{% include footer.html %}
74+
<script src="{{ '/assets/js/search.js' | relative_url }}"></script>
75+
<script src="{{ '/assets/js/home-listener.js' | relative_url }}"></script>
76+
<script>
77+
function truncateSentence(sentence, maxLength) {
78+
if (sentence.length <= maxLength) return sentence;
79+
let words = sentence.split(' ');
80+
let truncatedSentence = '';
81+
82+
for (let i = 0; i < words.length; i++) {
83+
if (truncatedSentence.length + words[i].length + 1 > maxLength) {
84+
break;
85+
}
86+
if (i > 0) {
87+
truncatedSentence += ' ';
88+
}
89+
truncatedSentence += words[i];
90+
}
91+
return truncatedSentence + (truncatedSentence.length < sentence.length ? '...' : '');
92+
}
93+
</script>
94+
<script>
95+
const searchInput = document.getElementById('searchPageInput');
96+
const checkboxes = document.querySelectorAll('.category-checkbox');
97+
98+
function getSelectedCategory() {
99+
const allCheckbox = document.getElementById('categoryAll');
100+
if (allCheckbox.checked) {
101+
return "All";
102+
}
103+
return document.querySelector('input[name="categoryGroup"]:checked');
104+
}
105+
106+
function triggerSearch(query) {
107+
const searchResultsHeader = document.getElementById('searchPageResultsHeader');
108+
109+
if (query) {
110+
trucatedQuery = truncateSentence(query, 20);
111+
if (getSelectedCategory() == null) {
112+
searchResultsHeader.textContent = 'Select the result type for your search.';
113+
document.getElementById('searchPageResultsContainer').innerHTML = '';
114+
return;
115+
}
116+
const selectedCategory = getSelectedCategory().value;
117+
const searchType = selectedCategory == ("Documentation") ? "docs" : selectedCategory == ("News") ? "proj" : "all";
118+
const urlPath = window.location.pathname;
119+
const versionMatch = urlPath.match(/(\d+\.\d+)/);
120+
const docsVersion = versionMatch ? versionMatch[1] : "latest";
121+
122+
searchResultsHeader.textContent = `Search results for "${trucatedQuery}"`;
123+
doResultsPageSearch(query, searchType, docsVersion);
124+
} else {
125+
searchResultsHeader.textContent = 'Please input a Query for searching.';
126+
document.getElementById('searchPageResultsContainer').innerHTML = '';
127+
}
128+
}
129+
130+
searchInput.addEventListener('keydown', function(event) {
131+
if (event.key === 'Enter') {
132+
event.preventDefault();
133+
triggerSearch(searchInput.value.trim());
134+
}
135+
});
136+
137+
</script>
138+
<script>
139+
document.addEventListener('DOMContentLoaded', function() {
140+
const categoryAll = document.getElementById('categoryAll');
141+
const categoryDocumentation = document.getElementById('categoryDocumentation');
142+
const categoryNews = document.getElementById('categoryNews');
143+
const searchInput = document.getElementById('searchPageInput');
144+
145+
function updateAllCheckbox() {
146+
if (categoryDocumentation.checked && categoryNews.checked) {
147+
categoryAll.checked = true;
148+
} else {
149+
categoryAll.checked = false;
150+
}
151+
}
152+
153+
function updateChildCheckboxes() {
154+
if (categoryAll.checked) {
155+
categoryDocumentation.checked = true;
156+
categoryNews.checked = true;
157+
} else {
158+
categoryDocumentation.checked = false;
159+
categoryNews.checked = false;
160+
}
161+
}
162+
163+
categoryAll.addEventListener('change', () => {
164+
updateChildCheckboxes();
165+
triggerSearch(searchInput.value.trim());
166+
});
167+
categoryDocumentation.addEventListener('change', () => {
168+
updateAllCheckbox();
169+
triggerSearch(searchInput.value.trim());
170+
});
171+
categoryNews.addEventListener('change', () => {
172+
updateAllCheckbox();
173+
triggerSearch(searchInput.value.trim());
174+
});
175+
});
176+
</script>
177+
<script>
178+
document.addEventListener('DOMContentLoaded', () => {
179+
const searchInput = document.getElementById('searchPageInput');
180+
181+
const getQueryParam = (param) => {
182+
const urlParams = new URLSearchParams(window.location.search);
183+
return urlParams.get(param);
184+
};
185+
186+
const searchQuery = getQueryParam('q');
187+
188+
if (searchQuery) {
189+
searchInput.value = decodeURIComponent(searchQuery);
190+
triggerSearch(searchInput.value.trim());
191+
}
192+
});
193+
</script>
194+
</body>
195+
</html>

_sass/custom/custom.scss

+65
Original file line numberDiff line numberDiff line change
@@ -1035,6 +1035,71 @@ body {
10351035
border-bottom: 1px solid #eeebee;
10361036
}
10371037

1038+
.search-page {
1039+
display: flex;
1040+
align-items: flex-start;
1041+
justify-content: center;
1042+
gap: 20px;
1043+
margin: 0 auto;
1044+
}
1045+
1046+
.search-page--sidebar {
1047+
flex: 1;
1048+
max-width: 200px;
1049+
flex: 0 0 200px;
1050+
}
1051+
1052+
.search-page--sidebar--category-filter--checkbox-child {
1053+
padding-left: 20px;
1054+
}
1055+
1056+
.search-page--results {
1057+
flex: 3;
1058+
display: flex;
1059+
flex-direction: column;
1060+
align-items: center;
1061+
max-width: 60%;
1062+
}
1063+
1064+
.search-page--results--input {
1065+
width: 100%;
1066+
position: relative;
1067+
}
1068+
1069+
.search-page--results--input-box {
1070+
width: 100%;
1071+
padding: 10px;
1072+
margin-bottom: 20px;
1073+
border: 1px solid #ccc;
1074+
border-radius: 4px;
1075+
}
1076+
1077+
.search-page--results--input-icon {
1078+
position: absolute;
1079+
top: 35%;
1080+
right: 10px;
1081+
transform: translateY(-50%);
1082+
pointer-events: none;
1083+
color: #333;
1084+
}
1085+
1086+
.search-page--results--diplay {
1087+
width: 100%;
1088+
position: relative;
1089+
flex-flow: column nowrap;
1090+
}
1091+
1092+
.search-page--results--diplay--header {
1093+
text-align: center;
1094+
margin-bottom: 20px;
1095+
background-color: transparent;
1096+
}
1097+
1098+
.search-page--results--diplay--container--item {
1099+
margin-bottom: 1%;
1100+
display: block;
1101+
}
1102+
10381103
@mixin body-text($color: #000) {
10391104
color: $color;
10401105
font-family: 'Open Sans';

assets/js/search.js

+77-4
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@
1313
const CLASSNAME_HIGHLIGHTED = 'highlighted';
1414

1515
const canSmoothScroll = 'scrollBehavior' in document.documentElement.style;
16-
const docsVersion = elInput.getAttribute('data-docs-version');
16+
17+
//Extract version from the URL path
18+
const urlPath = window.location.pathname;
19+
const versionMatch = urlPath.match(/(\d+\.\d+)/);
20+
const docsVersion = versionMatch ? versionMatch[1] : elInput.getAttribute('data-docs-version');
1721

1822
let _showingResults = false,
1923
animationFrame,
@@ -46,7 +50,7 @@
4650

4751
case 'Enter':
4852
e.preventDefault();
49-
navToHighlightedResult();
53+
navToResult();
5054
break;
5155
}
5256
});
@@ -247,9 +251,19 @@
247251
}
248252
};
249253

250-
const navToHighlightedResult = () => {
254+
const navToResultsPage = () => {
255+
const query = encodeURIComponent(elInput.value);
256+
window.location.href = `/docs/${docsVersion}/search.html?q=${query}`;
257+
}
258+
259+
const navToResult = () => {
251260
const searchResultClassName = 'top-banner-search--field-with-results--field--wrapper--search-component--search-results--result';
252-
elResults.querySelector(`.${searchResultClassName}.highlighted a[href]`)?.click?.();
261+
const element = elResults.querySelector(`.${searchResultClassName}.highlighted a[href]`);
262+
if (element) {
263+
element.click?.();
264+
} else {
265+
navToResultsPage();
266+
}
253267
};
254268

255269
const recordEvent = (name, data) => {
@@ -261,3 +275,62 @@
261275
};
262276
});
263277
})();
278+
279+
280+
window.doResultsPageSearch = async (query, type, version) => {
281+
console.log("Running results page search!");
282+
283+
const searchResultsContainer = document.getElementById('searchPageResultsContainer');
284+
285+
try {
286+
const response = await fetch(`https://search-api.opensearch.org/search?q=${query}&v=${version}&t=${type}`);
287+
const data = await response.json();
288+
// Clear any previous search results
289+
searchResultsContainer.innerHTML = '';
290+
291+
if (data.results && data.results.length > 0) {
292+
data.results.forEach(result => {
293+
const resultElement = document.createElement('div');
294+
resultElement.classList.add('search-page--results--diplay--container--item');
295+
296+
const contentCite = document.createElement('cite');
297+
const crumbs = [...result.ancestors];
298+
if (result.type === 'DOCS') crumbs.unshift(`OpenSearch ${result.versionLabel || result.version}`);
299+
else if (result.type) crumbs.unshift(result.type);
300+
contentCite.textContent = crumbs.join(' › ')?.replace?.(/</g, '&lt;');
301+
contentCite.style.fontSize = '.8em';
302+
303+
const titleLink = document.createElement('a');
304+
titleLink.href = result.url;
305+
titleLink.textContent = result.title;
306+
titleLink.style.fontSize = '1.5em';
307+
titleLink.style.fontWeight = 'bold';
308+
titleLink.style.display = 'block';
309+
310+
const contentSpan = document.createElement('span');
311+
contentSpan.textContent = result.content;
312+
contentSpan.style.display = 'block';
313+
314+
resultElement.appendChild(contentCite);
315+
resultElement.appendChild(titleLink);
316+
resultElement.appendChild(contentSpan);
317+
318+
// Append the result element to the searchResultsContainer
319+
searchResultsContainer.appendChild(resultElement);
320+
321+
const breakline = document.createElement('hr');
322+
breakline.style.border = '.5px solid #ccc';
323+
breakline.style.margin = 'auto';
324+
searchResultsContainer.appendChild(breakline);
325+
});
326+
} else {
327+
const noResultsElement = document.createElement('div');
328+
noResultsElement.textContent = 'No results found.';
329+
noResultsElement.style.fontSize = '2em';
330+
searchResultsContainer.appendChild(noResultsElement);
331+
}
332+
} catch (error) {
333+
console.error('Error fetching search results:', error);
334+
searchResultsContainer.innerHTML = 'An error occurred while fetching search results. Please try again later.';
335+
}
336+
}

search.md

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
layout: search_layout
3+
title: OpenSearch Documentation Search Results Page
4+
nav_order: 1
5+
has_children: false
6+
nav_exclude: true
7+
permalink: /search.html
8+
---
9+
10+
{% include banner.html %}
11+
12+
{% include cards.html %}

0 commit comments

Comments
 (0)