Skip to content

Commit be14543

Browse files
Meta: Add full-text search for multipage output
This changes adds a search widget to each page of multipage output, to allow readers to do a full-text search of the spec across all multipage output files.
1 parent 126a60e commit be14543

File tree

1 file changed

+358
-0
lines changed

1 file changed

+358
-0
lines changed

link-fixup.js

Lines changed: 358 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,361 @@
3232
};
3333
xhr.send();
3434
})();
35+
36+
// embedded full-spec search form
37+
(function() {
38+
const headerElement = document.querySelector("header"),
39+
searchForm = document.createElement("form"),
40+
searchInput = document.createElement("input"),
41+
searchSubmit = document.createElement("input"),
42+
searchSelectDiv = document.createElement("div"),
43+
searchSelect = document.createElement("select"),
44+
searchButton = document.createElement("p"),
45+
searchWidget = document.createElement("div"),
46+
searchWidgetClose = document.createElement("div"),
47+
searchWidgetStyle = document.createElement("style"),
48+
searchWidgetStyleProperties = `
49+
#searchbutton {
50+
position: fixed;
51+
top: 0;
52+
right: 18px;
53+
background: #eee;
54+
font-size: 12px;
55+
padding: 2px 5px 2px 5px;
56+
border-radius: 0 0 6px 6px;
57+
z-index: 10;
58+
cursor: pointer;
59+
}
60+
#searchwidget {
61+
display: none;
62+
width: 308px;
63+
position: fixed;
64+
top: 0;
65+
right: 0;
66+
z-index: 11;
67+
padding-right: 12px;
68+
padding-bottom: 20px;
69+
background-color: #eee;
70+
box-shadow: 0 0 3px #999;
71+
}
72+
#searchwidget select:empty {
73+
display: none;
74+
}
75+
input#txtAutoComplete {
76+
width: 240px;
77+
height: 18px;
78+
vertical-align: middle;
79+
}
80+
#searchwidget form {
81+
margin-top: 24px;
82+
margin-left: 20px
83+
}
84+
#searchwidget input[type="submit"] {
85+
vertical-align: middle;
86+
border: none;
87+
font-size: 120%;
88+
cursor: pointer;
89+
background-color: #eee;
90+
}
91+
#searchwidgetclose {
92+
position: fixed;
93+
top: 0px;
94+
right: 18px;
95+
color: #999;
96+
font-size: 24px;
97+
font-family: sans-serif;
98+
line-height: 22px;
99+
z-index: 12;
100+
cursor: pointer;
101+
}
102+
// move doc title and WHATWG logo down to make room for search button
103+
.head { padding: 1.5em 0 0 0 }
104+
.head .logo img { top: 1.5em }`,
105+
resultsOverlay = document.createElement("div"),
106+
resultsClose = document.createElement("div"),
107+
results = document.createElement("div"),
108+
resultsModalBackground = document.createElement("div"),
109+
resultsStyle = document.createElement("style"),
110+
resultsStyleProperties = `
111+
.results-wrapper-visible {
112+
opacity: 1 !important;
113+
visibility: visible !important;
114+
}
115+
.results-wrapper-overlay {
116+
border: none;
117+
margin: auto;
118+
border-radius: 1px;
119+
overflow: auto;
120+
height: 80%;
121+
box-shadow: 0px 3px 10px rgba(34, 25, 25, 0.4);
122+
border-collapse: separate;
123+
background: white;
124+
padding: 30px;
125+
width: 70%;
126+
position: fixed !important;
127+
top: 5%;
128+
left: 12%;
129+
opacity: 0;
130+
z-index: 100002;
131+
visibility: hidden;
132+
-webkit-transition: all 0.25s linear;
133+
-moz-transition: all 0.25s linear;
134+
-ms-transition: all 0.25s linear;
135+
-o-transition: all 0.25s linear;
136+
transition: all 0.25s linear;
137+
-ms-filter: "progid:DXImageTransform.Microsoft.Shadow(Strength=7, Direction=135, Color='#888888')";
138+
filter: progid:DXImageTransform.Microsoft.Shadow(Strength=7, Direction=135, Color='#888888');
139+
}
140+
#resultsclose {
141+
position: fixed;
142+
top: 6.5%;
143+
right: 15%;
144+
color: #999;
145+
font-size: 24px;
146+
font-family: sans-serif;
147+
line-height: 22px;
148+
cursor: pointer;
149+
}
150+
@media (max-width: 1410px) { #resultsclose { right: 14.8%; } }
151+
@media (max-width: 1360px) { #resultsclose { right: 14.6%; } }
152+
@media (max-width: 1310px) { #resultsclose { right: 14.4%; } }
153+
@media (max-width: 1260px) { #resultsclose { right: 14.2%; } }
154+
@media (max-width: 1210px) { #resultsclose { right: 14.0%; } }
155+
@media (max-width: 1160px) { #resultsclose { right: 13.8%; } }
156+
@media (max-width: 1110px) { #resultsclose { right: 13.7%; } }
157+
@media (max-width: 1050px) { #resultsclose { right: 13.6%; } }
158+
@media (max-width: 960px) { #resultsclose { right: 13.5%; } }
159+
@media (max-width: 910px) { #resultsclose { right: 13.4%; } }
160+
@media (max-width: 860px) { #resultsclose { right: 13.3%; } }
161+
@media (max-width: 810px) { #resultsclose { right: 13.2%; } }
162+
@media (max-width: 785px) { #resultsclose { right: 13.1%; } }
163+
@media (max-width: 760px) { #resultsclose { right: 13.0%; } }
164+
@media (max-width: 710px) { #resultsclose { right: 12.0%; } }
165+
@media (max-width: 785px) { #resultsclose { right: 11.5%; } }
166+
@media (max-width: 660px) { #resultsclose { right: 11%; } }
167+
@media (max-width: 610px) { #resultsclose { right: 10%; } }
168+
@media (max-width: 560px) { #resultsclose { right: 9%; } }
169+
@media (max-width: 510px) { #resultsclose { right: 8%; } }
170+
@media (max-width: 460px) { #resultsclose { right: 7%; } }
171+
@media (max-width: 410px) { #resultsclose { right: 6%; } }
172+
@media (max-width: 360px) { #resultsclose { right: 5%; } }
173+
.results-modal-background {
174+
position: fixed !important;
175+
top: 0px;
176+
left: 0px;
177+
height: 130%;
178+
width: 100%;
179+
z-index: 100001;
180+
background-color: white;
181+
opacity: 0;
182+
-ms-filter: "alpha(opacity=0)";
183+
filter: alpha(opacity=0);
184+
display: none;
185+
-webkit-transition: all 0.25s linear;
186+
-moz-transition: all 0.25s linear;
187+
-ms-transition: all 0.25s linear;
188+
-o-transition: all 0.25s linear;
189+
transition: all 0.25s linear;
190+
}
191+
.results-modal-background-visible {
192+
opacity: 0.8;
193+
-ms-filter: "alpha(opacity=80)";
194+
filter: alpha(opacity=80);
195+
display: block;
196+
}
197+
.result {
198+
margin-bottom: 26px;
199+
}
200+
.result h4 {
201+
font-size: 18px;
202+
margin-top: 0px;
203+
margin-bottom: 3px;
204+
font-weight: normal;
205+
}
206+
.resulturl {
207+
color: #3c790a;
208+
}
209+
.resultsnippet em {
210+
font-style: normal;
211+
font-weight: bold;
212+
}
213+
.result p {
214+
font-size: 13px;
215+
}
216+
#results {
217+
padding-left: 8px;
218+
margin-bottom: 40px;
219+
}
220+
#resultsCount {
221+
font-size: 13px;
222+
line-height: 43px;
223+
color: #808080;
224+
margin-bottom: 12px;
225+
}`,
226+
searchbase = "https://search.sideshowbarker.net/solr/select",
227+
searchparams = "&fl=id,inboundlinks_anchortext_txt,sku&wt=json&rows=99&hl=true";
228+
229+
searchForm.action = "javascript:void(0)";
230+
searchInput.id = "txtAutoComplete";
231+
searchInput.name = "query";
232+
searchSubmit.type = "submit";
233+
searchSubmit.value = "🔍";
234+
searchSelect.id="suggestionslist";
235+
searchSelect.name="choice";
236+
searchSelect.size="0";
237+
placeholderText = "Search the full text of the spec";
238+
searchButton.id = "searchbutton";
239+
searchButton.textContent = "Search";
240+
searchButton.title = placeholderText;
241+
searchWidget.id = "searchwidget";
242+
searchWidgetClose.id = "searchwidgetclose";
243+
searchWidgetClose.textContent = "×";
244+
searchWidgetStyle.textContent = searchWidgetStyleProperties;
245+
resultsOverlay.className = "results-wrapper-overlay";
246+
resultsClose.id = "resultsclose";
247+
resultsClose.textContent = "×";
248+
resultsModalBackground.className = "results-modal-background";
249+
resultsStyle.textContent = resultsStyleProperties;
250+
results.id = "results";
251+
252+
headerElement.appendChild(searchButton);
253+
headerElement.appendChild(searchWidget);
254+
headerElement.appendChild(resultsOverlay);
255+
headerElement.appendChild(resultsModalBackground);
256+
searchForm.appendChild(searchInput);
257+
searchForm.appendChild(searchSubmit);
258+
searchSelectDiv.appendChild(searchSelect);
259+
searchForm.appendChild(searchSelectDiv);
260+
searchWidget.appendChild(searchWidgetClose);
261+
searchWidget.appendChild(searchWidgetStyle);
262+
searchWidget.appendChild(searchForm);
263+
resultsOverlay.appendChild(resultsStyle);
264+
resultsOverlay.appendChild(resultsClose);
265+
resultsOverlay.appendChild(results);
266+
267+
searchWidgetClose.addEventListener("click",
268+
e => searchWidget.style.display = "none");
269+
searchButton.addEventListener("click",
270+
function() {
271+
searchWidget.style.display = "block";
272+
searchInput.placeholder = placeholderText;
273+
searchInput.focus();
274+
});
275+
resultsClose.addEventListener("click",
276+
function() {
277+
resultsOverlay.classList.remove('results-wrapper-visible');
278+
resultsModalBackground.classList.remove('results-modal-background-visible');
279+
});
280+
281+
const suggestionslist = document.querySelector("#suggestionslist");
282+
var suggestbase = "https://search.sideshowbarker.net/suggest.json?q=";
283+
searchInput.addEventListener("keyup",
284+
function (e) { showSuggestions(e) }, false);
285+
suggestionslist.addEventListener("keyup",
286+
function(e) { changeFocus(e) }, true);
287+
suggestionslist.addEventListener("change",
288+
function() { searchInput.value = suggestionslist.value }, false);
289+
290+
searchForm.addEventListener("submit", fetchSearchResults, false);
291+
292+
function fetchSearchResults() {
293+
const searchString = encodeURIComponent(searchInput.value);
294+
fetch(searchbase + "?query=" + searchString + searchparams)
295+
.then(response => response.json())
296+
.then(data => showSearchResults(data))
297+
}
298+
299+
function showSearchResults(data) {
300+
results.innerHTML = "";
301+
resultsOverlay.classList.add("results-wrapper-visible");
302+
resultsModalBackground.classList.add("results-modal-background-visible");
303+
const docs = data.response.docs,
304+
highlighting = data.highlighting,
305+
resultsCount = document.createElement("p");
306+
resultsCount.id = "resultsCount";
307+
results.appendChild(resultsCount);
308+
if (docs.length === 0) {
309+
resultsCount.textContent = "No results";
310+
} else if (docs.length === 1) {
311+
resultsCount.textContent = "1 result";
312+
} else {
313+
resultsCount.textContent = "About " + docs.length + " results";
314+
}
315+
for (var i = 0; i < docs.length; i += 1) {
316+
const doc = docs[i];
317+
result = document.createElement("div"),
318+
resultTitle = document.createElement("h4"),
319+
resultLink = document.createElement("a"),
320+
resultURL = document.createElement("p"),
321+
resultSnippet = document.createElement("p");
322+
323+
result.className = "result";
324+
resultURL.className = "resulturl";
325+
resultSnippet.className = "resultsnippet";
326+
resultLink.href = doc.sku;
327+
resultURL.textContent = doc.sku;
328+
resultLink.innerHTML = "HTML Standard";
329+
if (doc.inboundlinks_anchortext_txt) {
330+
if (doc.inboundlinks_anchortext_txt[0] === "Table of Contents"
331+
&& doc.inboundlinks_anchortext_txt[2]) {
332+
resultLink.innerHTML = doc.inboundlinks_anchortext_txt[2];
333+
} else if (doc.inboundlinks_anchortext_txt[3]) {
334+
resultLink.innerHTML = doc.inboundlinks_anchortext_txt[3];
335+
}
336+
}
337+
resultTitle.appendChild(resultLink);
338+
result.appendChild(resultTitle);
339+
result.appendChild(resultURL);
340+
if (highlighting
341+
&& highlighting[doc.id]
342+
&& highlighting[doc.id].text_t) {
343+
resultSnippet.innerHTML = highlighting[doc.id].text_t[0];
344+
result.appendChild(resultSnippet);
345+
}
346+
results.appendChild(result);
347+
}
348+
}
349+
350+
function showSuggestions(e) {
351+
if (searchInput.value === "") {
352+
suggestionslist.innerHTML = "";
353+
return;
354+
}
355+
if (e.keyCode === 38 || e.keyCode === 40) {
356+
suggestionslist.focus();
357+
return;
358+
}
359+
fetch(suggestbase + encodeURIComponent(searchInput.value))
360+
.then(response => response.json())
361+
.then(data => updateDataList(data))
362+
}
363+
function updateDataList(data) {
364+
var suggestions = data[1];
365+
if (searchInput.value === "") {
366+
suggestionslist.innerHTML = "";
367+
return;
368+
}
369+
if (suggestions.length > 0) {
370+
var hits = "";
371+
for (var i = 0; i < suggestions.length; i += 1) {
372+
var hit = suggestions[i];
373+
hits += "<option>" + hit + "</option>";
374+
}
375+
suggestionslist.size = suggestions.length;
376+
if (suggestions.length === 1) {
377+
// ensure we get the right styling when we have only one option
378+
suggestionslist.size = 2;
379+
}
380+
suggestionslist.innerHTML = hits;
381+
}
382+
}
383+
function changeFocus(e) {
384+
if (e.keyCode === 38 || e.keyCode === 40) {
385+
return;
386+
}
387+
if (e.keyCode === 13) {
388+
suggestionslist.innerHTML = "";
389+
}
390+
searchInput.focus();
391+
}
392+
})();

0 commit comments

Comments
 (0)