summaryrefslogtreecommitdiff
path: root/doc/script-files/search.js
diff options
context:
space:
mode:
Diffstat (limited to 'doc/script-files/search.js')
-rw-r--r--doc/script-files/search.js436
1 files changed, 436 insertions, 0 deletions
diff --git a/doc/script-files/search.js b/doc/script-files/search.js
new file mode 100644
index 0000000..0486cc6
--- /dev/null
+++ b/doc/script-files/search.js
@@ -0,0 +1,436 @@
1/*
2 * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
6 */
7"use strict";
8const messages = {
9 enterTerm: "Enter a search term",
10 noResult: "No results found",
11 oneResult: "Found one result",
12 manyResults: "Found {0} results",
13 loading: "Loading search index...",
14 searching: "Searching...",
15 redirecting: "Redirecting to first result...",
16}
17const categories = {
18 modules: "Modules",
19 packages: "Packages",
20 types: "Classes and Interfaces",
21 members: "Members",
22 searchTags: "Search Tags"
23};
24const highlight = "<span class='result-highlight'>$&</span>";
25const NO_MATCH = {};
26const MAX_RESULTS = 300;
27const UNICODE_LETTER = 0;
28const UNICODE_DIGIT = 1;
29const UNICODE_OTHER = 2;
30function checkUnnamed(name, separator) {
31 return name === "<Unnamed>" || !name ? "" : name + separator;
32}
33function escapeHtml(str) {
34 return str.replace(/</g, "&lt;").replace(/>/g, "&gt;");
35}
36function getHighlightedText(str, boundaries, from, to) {
37 var start = from;
38 var text = "";
39 for (var i = 0; i < boundaries.length; i += 2) {
40 var b0 = boundaries[i];
41 var b1 = boundaries[i + 1];
42 if (b0 >= to || b1 <= from) {
43 continue;
44 }
45 text += escapeHtml(str.slice(start, Math.max(start, b0)));
46 text += "<span class='result-highlight'>";
47 text += escapeHtml(str.slice(Math.max(start, b0), Math.min(to, b1)));
48 text += "</span>";
49 start = Math.min(to, b1);
50 }
51 text += escapeHtml(str.slice(start, to));
52 return text;
53}
54function getURLPrefix(item, category) {
55 var urlPrefix = "";
56 var slash = "/";
57 if (category === "modules") {
58 return item.l + slash;
59 } else if (category === "packages" && item.m) {
60 return item.m + slash;
61 } else if (category === "types" || category === "members") {
62 if (item.m) {
63 urlPrefix = item.m + slash;
64 } else {
65 $.each(packageSearchIndex, function(index, it) {
66 if (it.m && item.p === it.l) {
67 urlPrefix = it.m + slash;
68 }
69 });
70 }
71 }
72 return urlPrefix;
73}
74function getURL(item, category) {
75 if (item.url) {
76 return item.url;
77 }
78 var url = getURLPrefix(item, category);
79 if (category === "modules") {
80 url += "module-summary.html";
81 } else if (category === "packages") {
82 if (item.u) {
83 url = item.u;
84 } else {
85 url += item.l.replace(/\./g, '/') + "/package-summary.html";
86 }
87 } else if (category === "types") {
88 if (item.u) {
89 url = item.u;
90 } else {
91 url += checkUnnamed(item.p, "/").replace(/\./g, '/') + item.l + ".html";
92 }
93 } else if (category === "members") {
94 url += checkUnnamed(item.p, "/").replace(/\./g, '/') + item.c + ".html" + "#";
95 if (item.u) {
96 url += item.u;
97 } else {
98 url += item.l;
99 }
100 } else if (category === "searchTags") {
101 url += item.u;
102 }
103 item.url = url;
104 return url;
105}
106function createMatcher(term, camelCase) {
107 if (camelCase && !isUpperCase(term)) {
108 return null; // no need for camel-case matcher for lower case query
109 }
110 var pattern = "";
111 var upperCase = [];
112 term.trim().split(/\s+/).forEach(function(w, index, array) {
113 var tokens = w.split(/(?=[\p{Lu},.()<>?[\/])/u);
114 for (var i = 0; i < tokens.length; i++) {
115 var s = tokens[i];
116 // ',' and '?' are the only delimiters commonly followed by space in java signatures
117 pattern += "(" + escapeUnicodeRegex(s).replace(/[,?]/g, "$&\\s*?") + ")";
118 upperCase.push(false);
119 var isWordToken = /[\p{L}\p{Nd}_]$/u.test(s);
120 if (isWordToken) {
121 if (i === tokens.length - 1 && index < array.length - 1) {
122 // space in query string matches all delimiters
123 pattern += "(.*?)";
124 upperCase.push(isUpperCase(s[0]));
125 } else {
126 if (!camelCase && isUpperCase(s) && s.length === 1) {
127 pattern += "()";
128 } else {
129 pattern += "([\\p{L}\\p{Nd}\\p{Sc}<>?[\\]]*?)";
130 }
131 upperCase.push(isUpperCase(s[0]));
132 }
133 } else {
134 pattern += "()";
135 upperCase.push(false);
136 }
137 }
138 });
139 var re = new RegExp(pattern, "gui");
140 re.upperCase = upperCase;
141 return re;
142}
143// Unicode regular expressions do not allow certain characters to be escaped
144function escapeUnicodeRegex(pattern) {
145 return pattern.replace(/[\[\]{}()*+?.\\^$|\s]/g, '\\$&');
146}
147function findMatch(matcher, input, startOfName, endOfName) {
148 var from = startOfName;
149 matcher.lastIndex = from;
150 var match = matcher.exec(input);
151 // Expand search area until we get a valid result or reach the beginning of the string
152 while (!match || match.index + match[0].length < startOfName || endOfName < match.index) {
153 if (from === 0) {
154 return NO_MATCH;
155 }
156 from = input.lastIndexOf(".", from - 2) + 1;
157 matcher.lastIndex = from;
158 match = matcher.exec(input);
159 }
160 var boundaries = [];
161 var matchEnd = match.index + match[0].length;
162 var score = 5;
163 var start = match.index;
164 var prevEnd = -1;
165 for (var i = 1; i < match.length; i += 2) {
166 var charType = getCharType(input[start]);
167 var isMatcherUpper = matcher.upperCase[i];
168 // capturing groups come in pairs, match and non-match
169 boundaries.push(start, start + match[i].length);
170 // make sure groups are anchored on a left word boundary
171 var prevChar = input[start - 1] || "";
172 var nextChar = input[start + 1] || "";
173 if (start !== 0) {
174 if (charType === UNICODE_DIGIT && getCharType(prevChar) === UNICODE_DIGIT) {
175 return NO_MATCH;
176 } else if (charType === UNICODE_LETTER && getCharType(prevChar) === UNICODE_LETTER) {
177 var isUpper = isUpperCase(input[start]);
178 if (isUpper && (isLowerCase(prevChar) || isLowerCase(nextChar))) {
179 score -= 0.1;
180 } else if (isMatcherUpper && start === prevEnd) {
181 score -= isUpper ? 0.1 : 1.0;
182 } else {
183 return NO_MATCH;
184 }
185 }
186 }
187 prevEnd = start + match[i].length;
188 start += match[i].length + match[i + 1].length;
189
190 // lower score for parts of the name that are missing
191 if (match[i + 1] && prevEnd < endOfName) {
192 score -= rateNoise(match[i + 1]);
193 }
194 }
195 // lower score if a type name contains unmatched camel-case parts
196 if (input[matchEnd - 1] !== "." && endOfName > matchEnd)
197 score -= rateNoise(input.slice(matchEnd, endOfName));
198 score -= rateNoise(input.slice(0, Math.max(startOfName, match.index)));
199
200 if (score <= 0) {
201 return NO_MATCH;
202 }
203 return {
204 input: input,
205 score: score,
206 boundaries: boundaries
207 };
208}
209function isLetter(s) {
210 return /\p{L}/u.test(s);
211}
212function isUpperCase(s) {
213 return /\p{Lu}/u.test(s);
214}
215function isLowerCase(s) {
216 return /\p{Ll}/u.test(s);
217}
218function isDigit(s) {
219 return /\p{Nd}/u.test(s);
220}
221function getCharType(s) {
222 if (isLetter(s)) {
223 return UNICODE_LETTER;
224 } else if (isDigit(s)) {
225 return UNICODE_DIGIT;
226 } else {
227 return UNICODE_OTHER;
228 }
229}
230function rateNoise(str) {
231 return (str.match(/([.(])/g) || []).length / 5
232 + (str.match(/(\p{Lu}+)/gu) || []).length / 10
233 + str.length / 20;
234}
235function doSearch(request, response) {
236 var term = request.term.trim();
237 var maxResults = request.maxResults || MAX_RESULTS;
238 var matcher = {
239 plainMatcher: createMatcher(term, false),
240 camelCaseMatcher: createMatcher(term, true)
241 }
242 var indexLoaded = indexFilesLoaded();
243
244 function getPrefix(item, category) {
245 switch (category) {
246 case "packages":
247 return checkUnnamed(item.m, "/");
248 case "types":
249 return checkUnnamed(item.p, ".");
250 case "members":
251 return checkUnnamed(item.p, ".") + item.c + ".";
252 default:
253 return "";
254 }
255 }
256 function useQualifiedName(category) {
257 switch (category) {
258 case "packages":
259 return /[\s/]/.test(term);
260 case "types":
261 case "members":
262 return /[\s.]/.test(term);
263 default:
264 return false;
265 }
266 }
267 function searchIndex(indexArray, category) {
268 var matches = [];
269 if (!indexArray) {
270 if (!indexLoaded) {
271 matches.push({ l: messages.loading, category: category });
272 }
273 return matches;
274 }
275 $.each(indexArray, function (i, item) {
276 var prefix = getPrefix(item, category);
277 var simpleName = item.l;
278 var qualifiedName = prefix + simpleName;
279 var useQualified = useQualifiedName(category);
280 var input = useQualified ? qualifiedName : simpleName;
281 var startOfName = useQualified ? prefix.length : 0;
282 var endOfName = category === "members" && input.indexOf("(", startOfName) > -1
283 ? input.indexOf("(", startOfName) : input.length;
284 var m = findMatch(matcher.plainMatcher, input, startOfName, endOfName);
285 if (m === NO_MATCH && matcher.camelCaseMatcher) {
286 m = findMatch(matcher.camelCaseMatcher, input, startOfName, endOfName);
287 }
288 if (m !== NO_MATCH) {
289 m.indexItem = item;
290 m.prefix = prefix;
291 m.category = category;
292 if (!useQualified) {
293 m.input = qualifiedName;
294 m.boundaries = m.boundaries.map(function(b) {
295 return b + prefix.length;
296 });
297 }
298 matches.push(m);
299 }
300 return true;
301 });
302 return matches.sort(function(e1, e2) {
303 return e2.score - e1.score;
304 }).slice(0, maxResults);
305 }
306
307 var result = searchIndex(moduleSearchIndex, "modules")
308 .concat(searchIndex(packageSearchIndex, "packages"))
309 .concat(searchIndex(typeSearchIndex, "types"))
310 .concat(searchIndex(memberSearchIndex, "members"))
311 .concat(searchIndex(tagSearchIndex, "searchTags"));
312
313 if (!indexLoaded) {
314 updateSearchResults = function() {
315 doSearch(request, response);
316 }
317 } else {
318 updateSearchResults = function() {};
319 }
320 response(result);
321}
322// JQuery search menu implementation
323$.widget("custom.catcomplete", $.ui.autocomplete, {
324 _create: function() {
325 this._super();
326 this.widget().menu("option", "items", "> .result-item");
327 // workaround for search result scrolling
328 this.menu._scrollIntoView = function _scrollIntoView( item ) {
329 var borderTop, paddingTop, offset, scroll, elementHeight, itemHeight;
330 if ( this._hasScroll() ) {
331 borderTop = parseFloat( $.css( this.activeMenu[ 0 ], "borderTopWidth" ) ) || 0;
332 paddingTop = parseFloat( $.css( this.activeMenu[ 0 ], "paddingTop" ) ) || 0;
333 offset = item.offset().top - this.activeMenu.offset().top - borderTop - paddingTop;
334 scroll = this.activeMenu.scrollTop();
335 elementHeight = this.activeMenu.height() - 26;
336 itemHeight = item.outerHeight();
337
338 if ( offset < 0 ) {
339 this.activeMenu.scrollTop( scroll + offset );
340 } else if ( offset + itemHeight > elementHeight ) {
341 this.activeMenu.scrollTop( scroll + offset - elementHeight + itemHeight );
342 }
343 }
344 };
345 },
346 _renderMenu: function(ul, items) {
347 var currentCategory = "";
348 var widget = this;
349 widget.menu.bindings = $();
350 $.each(items, function(index, item) {
351 if (item.category && item.category !== currentCategory) {
352 ul.append("<li class='ui-autocomplete-category'>" + categories[item.category] + "</li>");
353 currentCategory = item.category;
354 }
355 var li = widget._renderItemData(ul, item);
356 if (item.category) {
357 li.attr("aria-label", categories[item.category] + " : " + item.l);
358 } else {
359 li.attr("aria-label", item.l);
360 }
361 li.attr("class", "result-item");
362 });
363 ul.append("<li class='ui-static-link'><a href='" + pathtoroot + "search.html?q="
364 + encodeURI(widget.term) + "'>Go to search page</a></li>");
365 },
366 _renderItem: function(ul, item) {
367 var li = $("<li/>").appendTo(ul);
368 var div = $("<div/>").appendTo(li);
369 var label = item.l
370 ? item.l
371 : getHighlightedText(item.input, item.boundaries, 0, item.input.length);
372 var idx = item.indexItem;
373 if (item.category === "searchTags" && idx && idx.h) {
374 if (idx.d) {
375 div.html(label + "<span class='search-tag-holder-result'> (" + idx.h + ")</span><br><span class='search-tag-desc-result'>"
376 + idx.d + "</span><br>");
377 } else {
378 div.html(label + "<span class='search-tag-holder-result'> (" + idx.h + ")</span>");
379 }
380 } else {
381 div.html(label);
382 }
383 return li;
384 }
385});
386$(function() {
387 var search = $("#search-input");
388 var reset = $("#reset-search");
389 search.catcomplete({
390 minLength: 1,
391 delay: 200,
392 source: function(request, response) {
393 reset.css("display", "inline");
394 if (request.term.trim() === "") {
395 return this.close();
396 }
397 return doSearch(request, response);
398 },
399 response: function(event, ui) {
400 if (!ui.content.length) {
401 ui.content.push({ l: messages.noResult });
402 } else {
403 $("#search-input").empty();
404 }
405 },
406 close: function(event, ui) {
407 reset.css("display", search.val() ? "inline" : "none");
408 },
409 change: function(event, ui) {
410 reset.css("display", search.val() ? "inline" : "none");
411 },
412 autoFocus: true,
413 focus: function(event, ui) {
414 return false;
415 },
416 position: {
417 collision: "flip"
418 },
419 select: function(event, ui) {
420 if (ui.item.indexItem) {
421 var url = getURL(ui.item.indexItem, ui.item.category);
422 window.location.href = pathtoroot + url;
423 $("#search-input").focus();
424 }
425 }
426 });
427 search.val('');
428 search.prop("disabled", false);
429 search.attr("autocapitalize", "off");
430 reset.prop("disabled", false);
431 reset.click(function() {
432 search.val('').focus();
433 reset.css("display", "none");
434 });
435 search.focus();
436});