You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

354 lines
13 KiB

  1. /*
  2. * Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
  3. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4. *
  5. * This code is free software; you can redistribute it and/or modify it
  6. * under the terms of the GNU General Public License version 2 only, as
  7. * published by the Free Software Foundation. Oracle designates this
  8. * particular file as subject to the "Classpath" exception as provided
  9. * by Oracle in the LICENSE file that accompanied this code.
  10. *
  11. * This code is distributed in the hope that it will be useful, but WITHOUT
  12. * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13. * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
  14. * version 2 for more details (a copy is included in the LICENSE file that
  15. * accompanied this code).
  16. *
  17. * You should have received a copy of the GNU General Public License version
  18. * 2 along with this work; if not, write to the Free Software Foundation,
  19. * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20. *
  21. * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22. * or visit www.oracle.com if you need additional information or have any
  23. * questions.
  24. */
  25. var noResult = {l: "No results found"};
  26. var loading = {l: "Loading search index..."};
  27. var catModules = "Modules";
  28. var catPackages = "Packages";
  29. var catTypes = "Types";
  30. var catMembers = "Members";
  31. var catSearchTags = "Search Tags";
  32. var highlight = "<span class=\"result-highlight\">$&</span>";
  33. var searchPattern = "";
  34. var fallbackPattern = "";
  35. var RANKING_THRESHOLD = 2;
  36. var NO_MATCH = 0xffff;
  37. var MIN_RESULTS = 3;
  38. var MAX_RESULTS = 500;
  39. var UNNAMED = "<Unnamed>";
  40. function escapeHtml(str) {
  41. return str.replace(/</g, "&lt;").replace(/>/g, "&gt;");
  42. }
  43. function getHighlightedText(item, matcher, fallbackMatcher) {
  44. var escapedItem = escapeHtml(item);
  45. var highlighted = escapedItem.replace(matcher, highlight);
  46. if (highlighted === escapedItem) {
  47. highlighted = escapedItem.replace(fallbackMatcher, highlight)
  48. }
  49. return highlighted;
  50. }
  51. function getURLPrefix(ui) {
  52. var urlPrefix="";
  53. var slash = "/";
  54. if (ui.item.category === catModules) {
  55. return ui.item.l + slash;
  56. } else if (ui.item.category === catPackages && ui.item.m) {
  57. return ui.item.m + slash;
  58. } else if (ui.item.category === catTypes || ui.item.category === catMembers) {
  59. if (ui.item.m) {
  60. urlPrefix = ui.item.m + slash;
  61. } else {
  62. $.each(packageSearchIndex, function(index, item) {
  63. if (item.m && ui.item.p === item.l) {
  64. urlPrefix = item.m + slash;
  65. }
  66. });
  67. }
  68. }
  69. return urlPrefix;
  70. }
  71. function createSearchPattern(term) {
  72. var pattern = "";
  73. var isWordToken = false;
  74. term.replace(/,\s*/g, ", ").trim().split(/\s+/).forEach(function(w, index) {
  75. if (index > 0) {
  76. // whitespace between identifiers is significant
  77. pattern += (isWordToken && /^\w/.test(w)) ? "\\s+" : "\\s*";
  78. }
  79. var tokens = w.split(/(?=[A-Z,.()<>[\/])/);
  80. for (var i = 0; i < tokens.length; i++) {
  81. var s = tokens[i];
  82. if (s === "") {
  83. continue;
  84. }
  85. pattern += $.ui.autocomplete.escapeRegex(s);
  86. isWordToken = /\w$/.test(s);
  87. if (isWordToken) {
  88. pattern += "([a-z0-9_$<>\\[\\]]*?)";
  89. }
  90. }
  91. });
  92. return pattern;
  93. }
  94. function createMatcher(pattern, flags) {
  95. var isCamelCase = /[A-Z]/.test(pattern);
  96. return new RegExp(pattern, flags + (isCamelCase ? "" : "i"));
  97. }
  98. var watermark = 'Search';
  99. $(function() {
  100. var search = $("#search-input");
  101. var reset = $("#reset-button");
  102. search.val('');
  103. search.prop("disabled", false);
  104. reset.prop("disabled", false);
  105. search.val(watermark).addClass('watermark');
  106. search.blur(function() {
  107. if ($(this).val().length === 0) {
  108. $(this).val(watermark).addClass('watermark');
  109. }
  110. });
  111. search.on('click keydown paste', function() {
  112. if ($(this).val() === watermark) {
  113. $(this).val('').removeClass('watermark');
  114. }
  115. });
  116. reset.click(function() {
  117. search.val('').focus();
  118. });
  119. search.focus()[0].setSelectionRange(0, 0);
  120. });
  121. $.widget("custom.catcomplete", $.ui.autocomplete, {
  122. _create: function() {
  123. this._super();
  124. this.widget().menu("option", "items", "> :not(.ui-autocomplete-category)");
  125. },
  126. _renderMenu: function(ul, items) {
  127. var rMenu = this;
  128. var currentCategory = "";
  129. rMenu.menu.bindings = $();
  130. $.each(items, function(index, item) {
  131. var li;
  132. if (item.category && item.category !== currentCategory) {
  133. ul.append("<li class=\"ui-autocomplete-category\">" + item.category + "</li>");
  134. currentCategory = item.category;
  135. }
  136. li = rMenu._renderItemData(ul, item);
  137. if (item.category) {
  138. li.attr("aria-label", item.category + " : " + item.l);
  139. li.attr("class", "result-item");
  140. } else {
  141. li.attr("aria-label", item.l);
  142. li.attr("class", "result-item");
  143. }
  144. });
  145. },
  146. _renderItem: function(ul, item) {
  147. var label = "";
  148. var matcher = createMatcher(escapeHtml(searchPattern), "g");
  149. var fallbackMatcher = new RegExp(fallbackPattern, "gi")
  150. if (item.category === catModules) {
  151. label = getHighlightedText(item.l, matcher, fallbackMatcher);
  152. } else if (item.category === catPackages) {
  153. label = getHighlightedText(item.l, matcher, fallbackMatcher);
  154. } else if (item.category === catTypes) {
  155. label = (item.p && item.p !== UNNAMED)
  156. ? getHighlightedText(item.p + "." + item.l, matcher, fallbackMatcher)
  157. : getHighlightedText(item.l, matcher, fallbackMatcher);
  158. } else if (item.category === catMembers) {
  159. label = (item.p && item.p !== UNNAMED)
  160. ? getHighlightedText(item.p + "." + item.c + "." + item.l, matcher, fallbackMatcher)
  161. : getHighlightedText(item.c + "." + item.l, matcher, fallbackMatcher);
  162. } else if (item.category === catSearchTags) {
  163. label = getHighlightedText(item.l, matcher, fallbackMatcher);
  164. } else {
  165. label = item.l;
  166. }
  167. var li = $("<li/>").appendTo(ul);
  168. var div = $("<div/>").appendTo(li);
  169. if (item.category === catSearchTags && item.h) {
  170. if (item.d) {
  171. div.html(label + "<span class=\"search-tag-holder-result\"> (" + item.h + ")</span><br><span class=\"search-tag-desc-result\">"
  172. + item.d + "</span><br>");
  173. } else {
  174. div.html(label + "<span class=\"search-tag-holder-result\"> (" + item.h + ")</span>");
  175. }
  176. } else {
  177. if (item.m) {
  178. div.html(item.m + "/" + label);
  179. } else {
  180. div.html(label);
  181. }
  182. }
  183. return li;
  184. }
  185. });
  186. function rankMatch(match, category) {
  187. if (!match) {
  188. return NO_MATCH;
  189. }
  190. var index = match.index;
  191. var input = match.input;
  192. var leftBoundaryMatch = 2;
  193. var periferalMatch = 0;
  194. // make sure match is anchored on a left word boundary
  195. if (index === 0 || /\W/.test(input[index - 1]) || "_" === input[index]) {
  196. leftBoundaryMatch = 0;
  197. } else if ("_" === input[index - 1] || (input[index] === input[index].toUpperCase() && !/^[A-Z0-9_$]+$/.test(input))) {
  198. leftBoundaryMatch = 1;
  199. }
  200. var matchEnd = index + match[0].length;
  201. var leftParen = input.indexOf("(");
  202. var endOfName = leftParen > -1 ? leftParen : input.length;
  203. // exclude peripheral matches
  204. if (category !== catModules && category !== catSearchTags) {
  205. var delim = category === catPackages ? "/" : ".";
  206. if (leftParen > -1 && leftParen < index) {
  207. periferalMatch += 2;
  208. } else if (input.lastIndexOf(delim, endOfName) >= matchEnd) {
  209. periferalMatch += 2;
  210. }
  211. }
  212. var delta = match[0].length === endOfName ? 0 : 1; // rank full match higher than partial match
  213. for (var i = 1; i < match.length; i++) {
  214. // lower ranking if parts of the name are missing
  215. if (match[i])
  216. delta += match[i].length;
  217. }
  218. if (category === catTypes) {
  219. // lower ranking if a type name contains unmatched camel-case parts
  220. if (/[A-Z]/.test(input.substring(matchEnd)))
  221. delta += 5;
  222. if (/[A-Z]/.test(input.substring(0, index)))
  223. delta += 5;
  224. }
  225. return leftBoundaryMatch + periferalMatch + (delta / 200);
  226. }
  227. function doSearch(request, response) {
  228. var result = [];
  229. searchPattern = createSearchPattern(request.term);
  230. fallbackPattern = createSearchPattern(request.term.toLowerCase());
  231. if (searchPattern === "") {
  232. return this.close();
  233. }
  234. var camelCaseMatcher = createMatcher(searchPattern, "");
  235. var fallbackMatcher = new RegExp(fallbackPattern, "i");
  236. function searchIndexWithMatcher(indexArray, matcher, category, nameFunc) {
  237. if (indexArray) {
  238. var newResults = [];
  239. $.each(indexArray, function (i, item) {
  240. item.category = category;
  241. var ranking = rankMatch(matcher.exec(nameFunc(item)), category);
  242. if (ranking < RANKING_THRESHOLD) {
  243. newResults.push({ranking: ranking, item: item});
  244. }
  245. return newResults.length <= MAX_RESULTS;
  246. });
  247. return newResults.sort(function(e1, e2) {
  248. return e1.ranking - e2.ranking;
  249. }).map(function(e) {
  250. return e.item;
  251. });
  252. }
  253. return [];
  254. }
  255. function searchIndex(indexArray, category, nameFunc) {
  256. var primaryResults = searchIndexWithMatcher(indexArray, camelCaseMatcher, category, nameFunc);
  257. result = result.concat(primaryResults);
  258. if (primaryResults.length <= MIN_RESULTS && !camelCaseMatcher.ignoreCase) {
  259. var secondaryResults = searchIndexWithMatcher(indexArray, fallbackMatcher, category, nameFunc);
  260. result = result.concat(secondaryResults.filter(function (item) {
  261. return primaryResults.indexOf(item) === -1;
  262. }));
  263. }
  264. }
  265. searchIndex(moduleSearchIndex, catModules, function(item) { return item.l; });
  266. searchIndex(packageSearchIndex, catPackages, function(item) {
  267. return (item.m && request.term.indexOf("/") > -1)
  268. ? (item.m + "/" + item.l) : item.l;
  269. });
  270. searchIndex(typeSearchIndex, catTypes, function(item) {
  271. return request.term.indexOf(".") > -1 ? item.p + "." + item.l : item.l;
  272. });
  273. searchIndex(memberSearchIndex, catMembers, function(item) {
  274. return request.term.indexOf(".") > -1
  275. ? item.p + "." + item.c + "." + item.l : item.l;
  276. });
  277. searchIndex(tagSearchIndex, catSearchTags, function(item) { return item.l; });
  278. if (!indexFilesLoaded()) {
  279. updateSearchResults = function() {
  280. doSearch(request, response);
  281. }
  282. result.unshift(loading);
  283. } else {
  284. updateSearchResults = function() {};
  285. }
  286. response(result);
  287. }
  288. $(function() {
  289. $("#search-input").catcomplete({
  290. minLength: 1,
  291. delay: 300,
  292. source: doSearch,
  293. response: function(event, ui) {
  294. if (!ui.content.length) {
  295. ui.content.push(noResult);
  296. } else {
  297. $("#search-input").empty();
  298. }
  299. },
  300. autoFocus: true,
  301. focus: function(event, ui) {
  302. return false;
  303. },
  304. position: {
  305. collision: "flip"
  306. },
  307. select: function(event, ui) {
  308. if (ui.item.category) {
  309. var url = getURLPrefix(ui);
  310. if (ui.item.category === catModules) {
  311. url += "module-summary.html";
  312. } else if (ui.item.category === catPackages) {
  313. if (ui.item.u) {
  314. url = ui.item.u;
  315. } else {
  316. url += ui.item.l.replace(/\./g, '/') + "/package-summary.html";
  317. }
  318. } else if (ui.item.category === catTypes) {
  319. if (ui.item.u) {
  320. url = ui.item.u;
  321. } else if (ui.item.p === UNNAMED) {
  322. url += ui.item.l + ".html";
  323. } else {
  324. url += ui.item.p.replace(/\./g, '/') + "/" + ui.item.l + ".html";
  325. }
  326. } else if (ui.item.category === catMembers) {
  327. if (ui.item.p === UNNAMED) {
  328. url += ui.item.c + ".html" + "#";
  329. } else {
  330. url += ui.item.p.replace(/\./g, '/') + "/" + ui.item.c + ".html" + "#";
  331. }
  332. if (ui.item.u) {
  333. url += ui.item.u;
  334. } else {
  335. url += ui.item.l;
  336. }
  337. } else if (ui.item.category === catSearchTags) {
  338. url += ui.item.u;
  339. }
  340. if (top !== window) {
  341. parent.classFrame.location = pathtoroot + url;
  342. } else {
  343. window.location.href = pathtoroot + url;
  344. }
  345. $("#search-input").focus();
  346. }
  347. }
  348. });
  349. });