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.

465 lines
16 KiB

  1. /*
  2. * Copyright (c) 2015, 2022, 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. "use strict";
  26. const messages = {
  27. enterTerm: "Enter a search term",
  28. noResult: "Keine Ergebnisse gefunden",
  29. oneResult: "Found one result",
  30. manyResults: "Found {0} results",
  31. loading: "Suchindex wird geladen...",
  32. searching: "Searching...",
  33. redirecting: "Redirecting to first result...",
  34. copyUrl: "Copy URL",
  35. urlCopied: "Copied!"
  36. }
  37. const categories = {
  38. modules: "Module",
  39. packages: "Packages",
  40. types: "Klassen und Schnittstellen",
  41. members: "Mitglieder",
  42. searchTags: "Tags suchen"
  43. };
  44. const highlight = "<span class='result-highlight'>$&</span>";
  45. const NO_MATCH = {};
  46. const MAX_RESULTS = 500;
  47. function checkUnnamed(name, separator) {
  48. return name === "<Unnamed>" || !name ? "" : name + separator;
  49. }
  50. function escapeHtml(str) {
  51. return str.replace(/</g, "&lt;").replace(/>/g, "&gt;");
  52. }
  53. function getHighlightedText(str, boundaries, from, to) {
  54. var start = from;
  55. var text = "";
  56. for (var i = 0; i < boundaries.length; i += 2) {
  57. var b0 = boundaries[i];
  58. var b1 = boundaries[i + 1];
  59. if (b0 >= to || b1 <= from) {
  60. continue;
  61. }
  62. text += escapeHtml(str.slice(start, Math.max(start, b0)));
  63. text += "<span class='result-highlight'>";
  64. text += escapeHtml(str.slice(Math.max(start, b0), Math.min(to, b1)));
  65. text += "</span>";
  66. start = Math.min(to, b1);
  67. }
  68. text += escapeHtml(str.slice(start, to));
  69. return text;
  70. }
  71. function getURLPrefix(item, category) {
  72. var urlPrefix = "";
  73. var slash = "/";
  74. if (category === "modules") {
  75. return item.l + slash;
  76. } else if (category === "packages" && item.m) {
  77. return item.m + slash;
  78. } else if (category === "types" || category === "members") {
  79. if (item.m) {
  80. urlPrefix = item.m + slash;
  81. } else {
  82. $.each(packageSearchIndex, function(index, it) {
  83. if (it.m && item.p === it.l) {
  84. urlPrefix = it.m + slash;
  85. }
  86. });
  87. }
  88. }
  89. return urlPrefix;
  90. }
  91. function getURL(item, category) {
  92. if (item.url) {
  93. return item.url;
  94. }
  95. var url = getURLPrefix(item, category);
  96. if (category === "modules") {
  97. url += "module-summary.html";
  98. } else if (category === "packages") {
  99. if (item.u) {
  100. url = item.u;
  101. } else {
  102. url += item.l.replace(/\./g, '/') + "/package-summary.html";
  103. }
  104. } else if (category === "types") {
  105. if (item.u) {
  106. url = item.u;
  107. } else {
  108. url += checkUnnamed(item.p, "/").replace(/\./g, '/') + item.l + ".html";
  109. }
  110. } else if (category === "members") {
  111. url += checkUnnamed(item.p, "/").replace(/\./g, '/') + item.c + ".html" + "#";
  112. if (item.u) {
  113. url += item.u;
  114. } else {
  115. url += item.l;
  116. }
  117. } else if (category === "searchTags") {
  118. url += item.u;
  119. }
  120. item.url = url;
  121. return url;
  122. }
  123. function createMatcher(term, camelCase) {
  124. if (camelCase && !isUpperCase(term)) {
  125. return null; // no need for camel-case matcher for lower case query
  126. }
  127. var pattern = "";
  128. var upperCase = [];
  129. term.trim().split(/\s+/).forEach(function(w, index, array) {
  130. var tokens = w.split(/(?=[A-Z,.()<>?[\/])/);
  131. for (var i = 0; i < tokens.length; i++) {
  132. var s = tokens[i];
  133. // ',' and '?' are the only delimiters commonly followed by space in java signatures
  134. pattern += "(" + $.ui.autocomplete.escapeRegex(s).replace(/[,?]/g, "$&\\s*?") + ")";
  135. upperCase.push(false);
  136. var isWordToken = /\w$/.test(s);
  137. if (isWordToken) {
  138. if (i === tokens.length - 1 && index < array.length - 1) {
  139. // space in query string matches all delimiters
  140. pattern += "(.*?)";
  141. upperCase.push(isUpperCase(s[0]));
  142. } else {
  143. if (!camelCase && isUpperCase(s) && s.length === 1) {
  144. pattern += "()";
  145. } else {
  146. pattern += "([a-z0-9$<>?[\\]]*?)";
  147. }
  148. upperCase.push(isUpperCase(s[0]));
  149. }
  150. } else {
  151. pattern += "()";
  152. upperCase.push(false);
  153. }
  154. }
  155. });
  156. var re = new RegExp(pattern, "gi");
  157. re.upperCase = upperCase;
  158. return re;
  159. }
  160. function analyzeMatch(matcher, input, startOfName, category) {
  161. var from = startOfName;
  162. matcher.lastIndex = from;
  163. var match = matcher.exec(input);
  164. while (!match && from > 1) {
  165. from = input.lastIndexOf(".", from - 2) + 1;
  166. matcher.lastIndex = from;
  167. match = matcher.exec(input);
  168. }
  169. if (!match) {
  170. return NO_MATCH;
  171. }
  172. var boundaries = [];
  173. var matchEnd = match.index + match[0].length;
  174. var leftParen = input.indexOf("(");
  175. // exclude peripheral matches
  176. if (category !== "modules" && category !== "searchTags") {
  177. if (leftParen > -1 && leftParen < match.index) {
  178. return NO_MATCH;
  179. } else if (startOfName - 1 >= matchEnd) {
  180. return NO_MATCH;
  181. }
  182. }
  183. var endOfName = leftParen > -1 ? leftParen : input.length;
  184. var score = 5;
  185. var start = match.index;
  186. var prevEnd = -1;
  187. for (var i = 1; i < match.length; i += 2) {
  188. var isUpper = isUpperCase(input[start]);
  189. var isMatcherUpper = matcher.upperCase[i];
  190. // capturing groups come in pairs, match and non-match
  191. boundaries.push(start, start + match[i].length);
  192. // make sure groups are anchored on a left word boundary
  193. var prevChar = input[start - 1] || "";
  194. var nextChar = input[start + 1] || "";
  195. if (start !== 0 && !/[\W_]/.test(prevChar) && !/[\W_]/.test(input[start])) {
  196. if (isUpper && (isLowerCase(prevChar) || isLowerCase(nextChar))) {
  197. score -= 0.1;
  198. } else if (isMatcherUpper && start === prevEnd) {
  199. score -= isUpper ? 0.1 : 1.0;
  200. } else {
  201. return NO_MATCH;
  202. }
  203. }
  204. prevEnd = start + match[i].length;
  205. start += match[i].length + match[i + 1].length;
  206. // lower score for parts of the name that are missing
  207. if (match[i + 1] && prevEnd < endOfName) {
  208. score -= rateNoise(match[i + 1]);
  209. }
  210. }
  211. // lower score if a type name contains unmatched camel-case parts
  212. if (input[matchEnd - 1] !== "." && endOfName > matchEnd)
  213. score -= rateNoise(input.slice(matchEnd, endOfName));
  214. score -= rateNoise(input.slice(0, Math.max(startOfName, match.index)));
  215. if (score <= 0) {
  216. return NO_MATCH;
  217. }
  218. return {
  219. input: input,
  220. score: score,
  221. category: category,
  222. boundaries: boundaries
  223. };
  224. }
  225. function isUpperCase(s) {
  226. return s !== s.toLowerCase();
  227. }
  228. function isLowerCase(s) {
  229. return s !== s.toUpperCase();
  230. }
  231. function rateNoise(str) {
  232. return (str.match(/([.(])/g) || []).length / 5
  233. + (str.match(/([A-Z]+)/g) || []).length / 10
  234. + str.length / 20;
  235. }
  236. function doSearch(request, response) {
  237. var term = request.term.trim();
  238. var maxResults = request.maxResults || MAX_RESULTS;
  239. if (term.length === 0) {
  240. return this.close();
  241. }
  242. var matcher = {
  243. plainMatcher: createMatcher(term, false),
  244. camelCaseMatcher: createMatcher(term, true)
  245. }
  246. var indexLoaded = indexFilesLoaded();
  247. function getPrefix(item, category) {
  248. switch (category) {
  249. case "packages":
  250. return checkUnnamed(item.m, "/");
  251. case "types":
  252. return checkUnnamed(item.p, ".");
  253. case "members":
  254. return checkUnnamed(item.p, ".") + item.c + ".";
  255. default:
  256. return "";
  257. }
  258. }
  259. function useQualifiedName(category) {
  260. switch (category) {
  261. case "packages":
  262. return /[\s/]/.test(term);
  263. case "types":
  264. case "members":
  265. return /[\s.]/.test(term);
  266. default:
  267. return false;
  268. }
  269. }
  270. function searchIndex(indexArray, category) {
  271. var matches = [];
  272. if (!indexArray) {
  273. if (!indexLoaded) {
  274. matches.push({ l: messages.loading, category: category });
  275. }
  276. return matches;
  277. }
  278. $.each(indexArray, function (i, item) {
  279. var prefix = getPrefix(item, category);
  280. var simpleName = item.l;
  281. var qualifiedName = prefix + simpleName;
  282. var useQualified = useQualifiedName(category);
  283. var input = useQualified ? qualifiedName : simpleName;
  284. var startOfName = useQualified ? prefix.length : 0;
  285. var m = analyzeMatch(matcher.plainMatcher, input, startOfName, category);
  286. if (m === NO_MATCH && matcher.camelCaseMatcher) {
  287. m = analyzeMatch(matcher.camelCaseMatcher, input, startOfName, category);
  288. }
  289. if (m !== NO_MATCH) {
  290. m.indexItem = item;
  291. m.prefix = prefix;
  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 matches.length < maxResults;
  301. });
  302. return matches.sort(function(e1, e2) {
  303. return e2.score - e1.score;
  304. });
  305. }
  306. var result = searchIndex(moduleSearchIndex, "modules")
  307. .concat(searchIndex(packageSearchIndex, "packages"))
  308. .concat(searchIndex(typeSearchIndex, "types"))
  309. .concat(searchIndex(memberSearchIndex, "members"))
  310. .concat(searchIndex(tagSearchIndex, "searchTags"));
  311. if (!indexLoaded) {
  312. updateSearchResults = function() {
  313. doSearch(request, response);
  314. }
  315. } else {
  316. updateSearchResults = function() {};
  317. }
  318. response(result);
  319. }
  320. // JQuery search menu implementation
  321. $.widget("custom.catcomplete", $.ui.autocomplete, {
  322. _create: function() {
  323. this._super();
  324. this.widget().menu("option", "items", "> .result-item");
  325. // workaround for search result scrolling
  326. this.menu._scrollIntoView = function _scrollIntoView( item ) {
  327. var borderTop, paddingTop, offset, scroll, elementHeight, itemHeight;
  328. if ( this._hasScroll() ) {
  329. borderTop = parseFloat( $.css( this.activeMenu[ 0 ], "borderTopWidth" ) ) || 0;
  330. paddingTop = parseFloat( $.css( this.activeMenu[ 0 ], "paddingTop" ) ) || 0;
  331. offset = item.offset().top - this.activeMenu.offset().top - borderTop - paddingTop;
  332. scroll = this.activeMenu.scrollTop();
  333. elementHeight = this.activeMenu.height() - 26;
  334. itemHeight = item.outerHeight();
  335. if ( offset < 0 ) {
  336. this.activeMenu.scrollTop( scroll + offset );
  337. } else if ( offset + itemHeight > elementHeight ) {
  338. this.activeMenu.scrollTop( scroll + offset - elementHeight + itemHeight );
  339. }
  340. }
  341. };
  342. },
  343. _renderMenu: function(ul, items) {
  344. var currentCategory = "";
  345. var widget = this;
  346. widget.menu.bindings = $();
  347. $.each(items, function(index, item) {
  348. if (item.category && item.category !== currentCategory) {
  349. ul.append("<li class='ui-autocomplete-category'>" + categories[item.category] + "</li>");
  350. currentCategory = item.category;
  351. }
  352. var li = widget._renderItemData(ul, item);
  353. if (item.category) {
  354. li.attr("aria-label", categories[item.category] + " : " + item.l);
  355. } else {
  356. li.attr("aria-label", item.l);
  357. }
  358. li.attr("class", "result-item");
  359. });
  360. ul.append("<li class='ui-static-link'><a href='" + pathtoroot + "search.html?q="
  361. + encodeURI(widget.term) + "'>Go to search page</a></li>");
  362. },
  363. _renderItem: function(ul, item) {
  364. var li = $("<li/>").appendTo(ul);
  365. var div = $("<div/>").appendTo(li);
  366. var label = item.l
  367. ? item.l
  368. : getHighlightedText(item.input, item.boundaries, 0, item.input.length);
  369. var idx = item.indexItem;
  370. if (item.category === "searchTags" && idx.h) {
  371. if (idx.d) {
  372. div.html(label + "<span class='search-tag-holder-result'> (" + idx.h + ")</span><br><span class='search-tag-desc-result'>"
  373. + idx.d + "</span><br>");
  374. } else {
  375. div.html(label + "<span class='search-tag-holder-result'> (" + idx.h + ")</span>");
  376. }
  377. } else {
  378. div.html(label);
  379. }
  380. return li;
  381. }
  382. });
  383. $(function() {
  384. var expanded = false;
  385. var windowWidth;
  386. function collapse() {
  387. if (expanded) {
  388. $("div#navbar-top").removeAttr("style");
  389. $("button#navbar-toggle-button")
  390. .removeClass("expanded")
  391. .attr("aria-expanded", "false");
  392. expanded = false;
  393. }
  394. }
  395. $("button#navbar-toggle-button").click(function (e) {
  396. if (expanded) {
  397. collapse();
  398. } else {
  399. var navbar = $("div#navbar-top");
  400. navbar.height(navbar.prop("scrollHeight"));
  401. $("button#navbar-toggle-button")
  402. .addClass("expanded")
  403. .attr("aria-expanded", "true");
  404. expanded = true;
  405. windowWidth = window.innerWidth;
  406. }
  407. });
  408. $("ul.sub-nav-list-small li a").click(collapse);
  409. $("input#search-input").focus(collapse);
  410. $("main").click(collapse);
  411. $("section[id] > :header, :header[id], :header:has(a[id])").hover(
  412. function () {
  413. $(this).append($("<button class='copy copy-header' onclick='copyUrl(this)'> " +
  414. "<img src='" + pathtoroot + "copy.svg' alt='" + messages.copyUrl + "'> " +
  415. "<span data-copied='" + messages.urlCopied + "'></span></button>"));
  416. },
  417. function () {
  418. $(this).find("button:last").remove();
  419. }
  420. );
  421. $(window).on("orientationchange", collapse).on("resize", function(e) {
  422. if (expanded && windowWidth !== window.innerWidth) collapse();
  423. });
  424. var search = $("#search-input");
  425. var reset = $("#reset-button");
  426. search.catcomplete({
  427. minLength: 1,
  428. delay: 200,
  429. source: doSearch,
  430. response: function(event, ui) {
  431. if (!ui.content.length) {
  432. ui.content.push({ l: messages.noResult });
  433. } else {
  434. $("#search-input").empty();
  435. }
  436. },
  437. autoFocus: true,
  438. focus: function(event, ui) {
  439. return false;
  440. },
  441. position: {
  442. collision: "flip"
  443. },
  444. select: function(event, ui) {
  445. if (ui.item.indexItem) {
  446. var url = getURL(ui.item.indexItem, ui.item.category);
  447. window.location.href = pathtoroot + url;
  448. $("#search-input").focus();
  449. }
  450. }
  451. });
  452. search.val('');
  453. search.prop("disabled", false);
  454. reset.prop("disabled", false);
  455. reset.click(function() {
  456. search.val('').focus();
  457. });
  458. search.focus();
  459. });