diff options
Diffstat (limited to 'doc/script-files/search.js')
-rw-r--r-- | doc/script-files/search.js | 436 |
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"; | ||
8 | const 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 | } | ||
17 | const categories = { | ||
18 | modules: "Modules", | ||
19 | packages: "Packages", | ||
20 | types: "Classes and Interfaces", | ||
21 | members: "Members", | ||
22 | searchTags: "Search Tags" | ||
23 | }; | ||
24 | const highlight = "<span class='result-highlight'>$&</span>"; | ||
25 | const NO_MATCH = {}; | ||
26 | const MAX_RESULTS = 300; | ||
27 | const UNICODE_LETTER = 0; | ||
28 | const UNICODE_DIGIT = 1; | ||
29 | const UNICODE_OTHER = 2; | ||
30 | function checkUnnamed(name, separator) { | ||
31 | return name === "<Unnamed>" || !name ? "" : name + separator; | ||
32 | } | ||
33 | function escapeHtml(str) { | ||
34 | return str.replace(/</g, "<").replace(/>/g, ">"); | ||
35 | } | ||
36 | function 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 | } | ||
54 | function 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 | } | ||
74 | function 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 | } | ||
106 | function 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 | ||
144 | function escapeUnicodeRegex(pattern) { | ||
145 | return pattern.replace(/[\[\]{}()*+?.\\^$|\s]/g, '\\$&'); | ||
146 | } | ||
147 | function 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 | } | ||
209 | function isLetter(s) { | ||
210 | return /\p{L}/u.test(s); | ||
211 | } | ||
212 | function isUpperCase(s) { | ||
213 | return /\p{Lu}/u.test(s); | ||
214 | } | ||
215 | function isLowerCase(s) { | ||
216 | return /\p{Ll}/u.test(s); | ||
217 | } | ||
218 | function isDigit(s) { | ||
219 | return /\p{Nd}/u.test(s); | ||
220 | } | ||
221 | function 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 | } | ||
230 | function rateNoise(str) { | ||
231 | return (str.match(/([.(])/g) || []).length / 5 | ||
232 | + (str.match(/(\p{Lu}+)/gu) || []).length / 10 | ||
233 | + str.length / 20; | ||
234 | } | ||
235 | function 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 | }); | ||