8248863: Add search landing page to API documentation

Reviewed-by: jjg
This commit is contained in:
Hannes Wallnöfer 2022-05-06 08:56:42 +00:00
parent fa1ca98fff
commit 3cdedf1ddb
20 changed files with 735 additions and 64 deletions
src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets
test/langtools/jdk/javadoc
doclet
checkStylesheetClasses
testHelpOption
testMetadata
testSearch
testStylesheet
tool/api/basic

@ -184,7 +184,7 @@ public class HelpWriter extends HtmlDocletWriter {
// Search
if (options.createIndex()) {
section = newHelpSection(getContent("doclet.help.search.head"), subTOC, HtmlIds.HELP_SEARCH);
section = newHelpSection(getContent("doclet.help.search.head"), PageMode.SEARCH, subTOC);
var searchIntro = HtmlTree.P(getContent("doclet.help.search.intro"));
var searchExamples = HtmlTree.UL(HtmlStyle.helpSectionList);
for (String[] example : SEARCH_EXAMPLES) {

@ -266,6 +266,7 @@ public class HtmlDoclet extends AbstractDoclet {
}
configuration.mainIndex.createSearchIndexFiles();
IndexWriter.generate(configuration);
SearchWriter.generate(configuration);
}
if (options.createOverview()) {
@ -292,6 +293,9 @@ public class HtmlDoclet extends AbstractDoclet {
f = DocFile.createFileForOutput(configuration, DocPaths.SEARCH_JS);
f.copyResource(DOCLET_RESOURCES.resolve(DocPaths.SEARCH_JS_TEMPLATE), configuration.docResources);
f = DocFile.createFileForOutput(configuration, DocPaths.SEARCH_PAGE_JS);
f.copyResource(DOCLET_RESOURCES.resolve(DocPaths.SEARCH_PAGE_JS), configuration.docResources);
f = DocFile.createFileForOutput(configuration, DocPaths.RESOURCES.resolve(DocPaths.GLASS_IMG));
f.copyResource(DOCLET_RESOURCES.resolve(DocPaths.GLASS_IMG), true, false);
@ -308,10 +312,10 @@ public class HtmlDoclet extends AbstractDoclet {
private void copyJqueryFiles() throws DocletException {
List<String> files = Arrays.asList(
"jquery-3.6.0.min.js",
"jquery-ui.min.js",
"jquery-ui.min.css",
"jquery-ui.structure.min.css",
DocPaths.JQUERY_JS.getPath(),
DocPaths.JQUERY_UI_JS.getPath(),
DocPaths.JQUERY_UI_CSS.getPath(),
DocPaths.JQUERY_UI_STRUCTURE_CSS.getPath(),
"images/ui-bg_glass_65_dadada_1x400.png",
"images/ui-icons_454545_256x240.png",
"images/ui-bg_glass_95_fef1ec_1x400.png",

@ -83,7 +83,6 @@ public class HtmlIds {
static final HtmlId FOR_REMOVAL = HtmlId.of("for-removal");
static final HtmlId HELP_NAVIGATION = HtmlId.of("help-navigation");
static final HtmlId HELP_PAGES = HtmlId.of("help-pages");
static final HtmlId HELP_SEARCH = HtmlId.of("help-search");
static final HtmlId METHOD_DETAIL = HtmlId.of("method-detail");
static final HtmlId METHOD_SUMMARY = HtmlId.of("method-summary");
static final HtmlId METHOD_SUMMARY_TABLE = HtmlId.of("method-summary-table");

@ -93,6 +93,7 @@ public class Navigation {
PACKAGE,
PREVIEW,
SERIALIZED_FORM,
SEARCH,
SYSTEM_PROPERTIES,
TREE,
USE;
@ -316,6 +317,7 @@ public class Navigation {
case ALL_PACKAGES:
case CONSTANT_VALUES:
case SERIALIZED_FORM:
case SEARCH:
case SYSTEM_PROPERTIES:
addOverviewLink(target);
addModuleLink(target);
@ -590,7 +592,8 @@ public class Navigation {
var inputReset = HtmlTree.INPUT(reset, HtmlIds.RESET_BUTTON)
.put(HtmlAttr.VALUE, reset);
var searchDiv = HtmlTree.DIV(HtmlStyle.navListSearch,
HtmlTree.LABEL(HtmlIds.SEARCH_INPUT.name(), searchLabel));
links.createLink(pathToRoot.resolve(DocPaths.SEARCH_PAGE),
searchLabel, ""));
searchDiv.add(inputText);
searchDiv.add(inputReset);
target.add(searchDiv);
@ -624,7 +627,7 @@ public class Navigation {
links.createLink(HtmlIds.SKIP_NAVBAR_TOP, skipNavLinks,
skipNavLinks.toString())));
Content aboutContent = userHeader;
boolean addSearch = options.createIndex();
boolean addSearch = options.createIndex() && documentedPage != PageMode.SEARCH;
var aboutDiv = HtmlTree.DIV(HtmlStyle.aboutLanguage, aboutContent);
navDiv.add(aboutDiv);

@ -0,0 +1,138 @@
/*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.javadoc.internal.doclets.formats.html;
import jdk.javadoc.internal.doclets.formats.html.markup.BodyContents;
import jdk.javadoc.internal.doclets.formats.html.markup.ContentBuilder;
import jdk.javadoc.internal.doclets.formats.html.markup.HtmlAttr;
import jdk.javadoc.internal.doclets.formats.html.markup.HtmlId;
import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle;
import jdk.javadoc.internal.doclets.formats.html.markup.TagName;
import jdk.javadoc.internal.doclets.formats.html.markup.HtmlTree;
import jdk.javadoc.internal.doclets.formats.html.Navigation.PageMode;
import jdk.javadoc.internal.doclets.formats.html.markup.Text;
import jdk.javadoc.internal.doclets.toolkit.Content;
import jdk.javadoc.internal.doclets.toolkit.util.DocFileIOException;
import jdk.javadoc.internal.doclets.toolkit.util.DocPath;
import jdk.javadoc.internal.doclets.toolkit.util.DocPaths;
/**
* Generates the search landing page for the generated API documentation.
*/
public class SearchWriter extends HtmlDocletWriter {
/**
* Constructor to construct SearchWriter object.
* @param configuration the configuration
* @param filename file to be generated
*/
public SearchWriter(HtmlConfiguration configuration, DocPath filename) {
super(configuration, filename);
}
/**
* Constructs the SearchWriter object and then use it to generate the search
* file. The name of the generated file is "search.html". The search file
* will get generated if and only if "-noindex" is not used on the command line.
*
* @param configuration the configuration
* @throws DocFileIOException if there is a problem while generating the documentation
*/
public static void generate(HtmlConfiguration configuration) throws DocFileIOException {
DocPath filename = DocPaths.SEARCH_PAGE;
SearchWriter searchWriter = new SearchWriter(configuration, filename);
searchWriter.generateSearchFile();
}
/**
* Generates the search file contents.
*
* @throws DocFileIOException if there is a problem while generating the documentation
*/
protected void generateSearchFile() throws DocFileIOException {
String title = resources.getText("doclet.Window_Search_title");
HtmlTree body = getBody(getWindowTitle(title));
ContentBuilder searchFileContent = new ContentBuilder();
addSearchFileContents(searchFileContent);
body.add(new BodyContents()
.setHeader(getHeader(PageMode.SEARCH))
.addMainContent(searchFileContent)
.setFooter(getFooter()));
printHtmlDocument(null, "search", body);
}
/**
* Adds the search file contents to the content tree.
*/
protected void addSearchFileContents(Content contentTree) {
String copyText = resources.getText("doclet.Copy_url_to_clipboard");
String copiedText = resources.getText("doclet.Copied_url_to_clipboard");
Content helpSection = Text.EMPTY;
// Suppress link to help page if no help page is generated or a custom help page is used.
HtmlOptions options = configuration.getOptions();
if (!options.noHelp() && options.helpFile().isEmpty()) {
Content helpLink = HtmlTree.A("help-doc.html#search", contents.getContent("doclet.search.help_page_link"));
helpSection = HtmlTree.P(contents.getContent("doclet.search.help_page_info", helpLink));
}
contentTree.add(HtmlTree.HEADING(Headings.PAGE_TITLE_HEADING, HtmlStyle.title,
contents.getContent("doclet.search.main_heading")))
.add(HtmlTree.DIV(HtmlTree.INPUT("text", HtmlId.of("page-search-input"))
.put(HtmlAttr.PLACEHOLDER, resources.getText("doclet.search_placeholder")))
.add(HtmlTree.INPUT("reset", HtmlId.of("page-search-reset"))
.put(HtmlAttr.VALUE, resources.getText("doclet.search_reset"))
.put(HtmlAttr.STYLE, "margin: 6px;"))
.add(HtmlTree.DETAILS(HtmlStyle.pageSearchDetails)
.add(HtmlTree.SUMMARY(contents.getContent("doclet.search.show_more"))
.setId(HtmlId.of("page-search-expand")))))
.add(HtmlTree.DIV(HtmlStyle.pageSearchInfo, helpSection)
.add(HtmlTree.P(contents.getContent("doclet.search.keyboard_info")))
.add(HtmlTree.P(contents.getContent("doclet.search.browser_info")))
.add(HtmlTree.SPAN(Text.of("link"))
.setId(HtmlId.of("page-search-link")))
.add(new HtmlTree(TagName.BUTTON)
.add(new HtmlTree(TagName.IMG)
.put(HtmlAttr.SRC, pathToRoot.resolve(DocPaths.CLIPBOARD_SVG).getPath())
.put(HtmlAttr.ALT, copyText))
.add(HtmlTree.SPAN(Text.of(copyText))
.put(HtmlAttr.DATA_COPIED, copiedText))
.addStyle(HtmlStyle.copyUrl)
.setId(HtmlId.of("page-search-copy")))
.add(HtmlTree.P(HtmlTree.INPUT("checkbox", HtmlId.of("search-redirect")))
.add(HtmlTree.LABEL("search-redirect",
contents.getContent("doclet.search.redirect")))))
.add(new HtmlTree(TagName.P)
.setId(HtmlId.of("page-search-notify"))
.add(contents.getContent("doclet.search.loading")))
.add(HtmlTree.DIV(new HtmlTree(TagName.DIV)
.setId(HtmlId.of("result-container"))
.add(HtmlTree.EMPTY))
.setId(HtmlId.of("result-section"))
.put(HtmlAttr.STYLE, "display: none;")
.add(HtmlTree.SCRIPT(pathToRoot.resolve(DocPaths.SEARCH_PAGE_JS).getPath())));
}
}

@ -60,6 +60,7 @@ public enum HtmlAttr {
SCOPE,
SCROLLING,
SRC,
STYLE,
SUMMARY,
TABINDEX,
TARGET,

@ -655,6 +655,21 @@ public enum HtmlStyle {
//
// The following constants are used for items in the static and interactive search indexes.
/**
* The class for a {@code button} in the search page to copy the search URL to the clipboard.
*/
copyUrl,
/**
* The class for a {@code details} element in the search page to show additional information.
*/
pageSearchDetails,
/**
* The class for a {@code div} element in the search page which contains additional information.
*/
pageSearchInfo,
/**
* The class for a link in the static "Index" pages to a custom searchable item,
* such as defined with an {@code @index} tag.
@ -775,6 +790,11 @@ public enum HtmlStyle {
*/
previewListPage,
/**
* The class of the {@code body} element for the search page.
*/
searchPage,
/**
* The class of the {@code body} element for the serialized-forms page.
*/

@ -48,6 +48,7 @@ public enum TagName {
DT,
EM,
FOOTER,
FORM,
H1,
H2,
H3,

@ -0,0 +1,299 @@
/*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
"use strict";
$(function() {
var copy = $("#page-search-copy");
var expand = $("#page-search-expand");
var searchLink = $("span#page-search-link");
var redirect = $("input#search-redirect");
function setSearchUrlTemplate() {
var href = document.location.href.split(/[#?]/)[0];
href += "?q=" + "%s";
if (redirect.is(":checked")) {
href += "&r=1";
}
searchLink.html(href);
copy[0].onmouseenter();
}
function copyLink(e) {
var textarea = document.createElement("textarea");
textarea.style.height = 0;
document.body.appendChild(textarea);
textarea.value = this.previousSibling.innerText;
textarea.select();
document.execCommand("copy");
document.body.removeChild(textarea);
var span = this.lastElementChild;
var copied = span.getAttribute("data-copied");
if (span.innerHTML !== copied) {
var initialLabel = span.innerHTML;
span.innerHTML = copied;
var parent = this.parentElement.parentElement;
parent.onmouseleave = parent.ontouchend = copy[0].onmouseenter = function() {
span.innerHTML = initialLabel;
};
}
}
copy.click(copyLink);
copy[0].onmouseenter = function() {};
redirect.click(setSearchUrlTemplate);
setSearchUrlTemplate();
copy.prop("disabled", false);
redirect.prop("disabled", false);
expand.click(function (e) {
var searchInfo = $("div.page-search-info");
if(this.parentElement.hasAttribute("open")) {
searchInfo.attr("style", "border-width: 0;");
} else {
searchInfo.attr("style", "border-width: 1px;").height(searchInfo.prop("scrollHeight"));
}
});
});
$(window).on("load", function() {
var input = $("#page-search-input");
var reset = $("#page-search-reset");
var notify = $("#page-search-notify");
var resultSection = $("div#result-section");
var resultContainer = $("div#result-container");
var searchTerm = "";
var activeTab = "";
var fixedTab = false;
var visibleTabs = [];
var feelingLucky = false;
function renderResults(result) {
if (!result.length) {
notify.html(messages.noResult);
} else if (result.length === 1) {
notify.html(messages.oneResult);
} else {
notify.html(messages.manyResults.replace("{0}", result.length));
}
resultContainer.empty();
var r = {
"types": [],
"members": [],
"packages": [],
"modules": [],
"searchTags": []
};
for (var i in result) {
var item = result[i];
var arr = r[item.category];
arr.push(item);
}
if (!activeTab || r[activeTab].length === 0 || !fixedTab) {
Object.keys(r).reduce(function(prev, curr) {
if (r[curr].length > 0 && r[curr][0].score > prev) {
activeTab = curr;
return r[curr][0].score;
}
return prev;
}, 0);
}
if (feelingLucky && activeTab) {
notify.html(messages.redirecting)
var firstItem = r[activeTab][0];
window.location = getURL(firstItem.indexItem, firstItem.category);
return;
}
if (result.length > 20) {
if (searchTerm[searchTerm.length - 1] === ".") {
if (activeTab === "types" && r["members"].length > r["types"].length) {
activeTab = "members";
} else if (activeTab === "packages" && r["types"].length > r["packages"].length) {
activeTab = "types";
}
}
}
var categoryCount = Object.keys(r).reduce(function(prev, curr) {
return prev + (r[curr].length > 0 ? 1 : 0);
}, 0);
visibleTabs = [];
var tabContainer = $("<div class='table-tabs'></div>").appendTo(resultContainer);
for (var key in r) {
var id = "#result-tab-" + key.replace("searchTags", "search_tags");
if (r[key].length) {
var count = r[key].length >= 1000 ? "999+" : r[key].length;
if (result.length > 20 && categoryCount > 1) {
var button = $("<button id='result-tab-" + key
+ "' class='page-search-header'><span>" + categories[key] + "</span>"
+ "<span style='font-weight: normal'> (" + count + ")</span></button>").appendTo(tabContainer);
button.click(key, function(e) {
fixedTab = true;
renderResult(e.data, $(this));
});
visibleTabs.push(key);
} else {
$("<span class='page-search-header active-table-tab'>" + categories[key]
+ "<span style='font-weight: normal'> (" + count + ")</span></span>").appendTo(tabContainer);
renderTable(key, r[key]).appendTo(resultContainer);
tabContainer = $("<div class='table-tabs'></div>").appendTo(resultContainer);
}
}
}
if (activeTab && result.length > 20 && categoryCount > 1) {
$("button#result-tab-" + activeTab).addClass("active-table-tab");
renderTable(activeTab, r[activeTab]).appendTo(resultContainer);
}
resultSection.show();
function renderResult(category, button) {
activeTab = category;
setSearchUrl();
resultContainer.find("div.summary-table").remove();
renderTable(activeTab, r[activeTab]).appendTo(resultContainer);
button.siblings().removeClass("active-table-tab");
button.addClass("active-table-tab");
}
}
function selectTab(category) {
$("button#result-tab-" + category).click();
}
function renderTable(category, items) {
var table = $("<div class='summary-table'>")
.addClass(category === "modules"
? "one-column-search-results"
: "two-column-search-results");
var col1, col2;
if (category === "modules") {
col1 = "Module";
} else if (category === "packages") {
col1 = "Module";
col2 = "Package";
} else if (category === "types") {
col1 = "Package";
col2 = "Class"
} else if (category === "members") {
col1 = "Class";
col2 = "Member";
} else if (category === "searchTags") {
col1 = "Location";
col2 = "Name";
}
$("<div class='table-header col-plain'>" + col1 + "</div>").appendTo(table);
if (category !== "modules") {
$("<div class='table-header col-plain'>" + col2 + "</div>").appendTo(table);
}
$.each(items, function(index, item) {
var rowColor = index % 2 ? "odd-row-color" : "even-row-color";
renderItem(item, table, rowColor);
});
return table;
}
function renderItem(item, table, rowColor) {
var label = getHighlightedText(item.input, item.boundaries, item.prefix.length, item.input.length);
var link = $("<a/>")
.attr("href", getURL(item.indexItem, item.category))
.attr("tabindex", "0")
.addClass("search-result-link")
.html(label);
var container = getHighlightedText(item.input, item.boundaries, 0, item.prefix.length - 1);
if (item.category === "searchTags") {
container = item.indexItem.h || "";
}
if (item.category !== "modules") {
$("<div/>").html(container).addClass("col-plain").addClass(rowColor).appendTo(table);
}
$("<div/>").html(link).addClass("col-last").addClass(rowColor).appendTo(table);
}
var timeout;
function schedulePageSearch() {
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(function () {
doPageSearch()
}, 100);
}
function doPageSearch() {
setSearchUrl();
var term = searchTerm = input.val().trim();
if (term === "") {
notify.html(messages.enterTerm);
activeTab = "";
fixedTab = false;
resultContainer.empty();
resultSection.hide();
} else {
notify.html(messages.searching);
doSearch({ term: term, maxResults: 1200 }, renderResults);
}
}
function setSearchUrl() {
var query = input.val().trim();
var url = document.location.pathname;
if (query) {
url += "?q=" + encodeURI(query);
if (activeTab && fixedTab) {
url += "&c=" + activeTab;
}
}
history.replaceState({query: query}, "", url);
}
input.on("input", function(e) {
feelingLucky = false;
schedulePageSearch();
});
$(document).keydown(function(e) {
if ((e.ctrlKey || e.metaKey) && (e.key === "ArrowLeft" || e.key === "ArrowRight")) {
if (activeTab && visibleTabs.length > 1) {
var idx = visibleTabs.indexOf(activeTab);
idx += e.key === "ArrowLeft" ? visibleTabs.length - 1 : 1;
selectTab(visibleTabs[idx % visibleTabs.length]);
return false;
}
}
});
reset.click(function() {
notify.html(messages.enterTerm);
resultSection.hide();
activeTab = "";
fixedTab = false;
resultContainer.empty();
input.val('').focus();
setSearchUrl();
});
input.prop("disabled", false);
reset.prop("disabled", false);
var urlParams = new URLSearchParams(window.location.search);
if (urlParams.has("q")) {
input.val(urlParams.get("q"))
}
if (urlParams.has("c")) {
activeTab = urlParams.get("c");
fixedTab = true;
}
if (urlParams.get("r")) {
feelingLucky = true;
}
if (input.val()) {
doPageSearch();
} else {
notify.html(messages.enterTerm);
}
input.select().focus();
});

@ -24,8 +24,13 @@
*/
"use strict";
const messages = {
enterTerm: "##REPLACE:doclet.search.enter_search_term##",
noResult: "##REPLACE:doclet.search.no_results##",
loading: "##REPLACE:doclet.search.loading##"
oneResult: "##REPLACE:doclet.search.one_result##",
manyResults: "##REPLACE:doclet.search.many_results##",
loading: "##REPLACE:doclet.search.loading##",
searching: "##REPLACE:doclet.search.searching##",
redirecting: "##REPLACE:doclet.search.redirecting##",
}
const categories = {
modules: "##REPLACE:doclet.search.modules##",
@ -36,7 +41,7 @@ const categories = {
};
const highlight = "<span class='result-highlight'>$&</span>";
const NO_MATCH = {};
const MAX_RESULTS = 1200;
const MAX_RESULTS = 500;
function checkUnnamed(name, separator) {
return name === "<Unnamed>" || !name ? "" : name + separator;
}
@ -230,6 +235,7 @@ function rateNoise(str) {
}
function doSearch(request, response) {
var term = request.term.trim();
var maxResults = request.maxResults || MAX_RESULTS;
if (term.length === 0) {
return this.close();
}
@ -292,7 +298,7 @@ function doSearch(request, response) {
}
matches.push(m);
}
return matches.length < MAX_RESULTS;
return matches.length < maxResults;
});
return matches.sort(function(e1, e2) {
return e2.score - e1.score;
@ -319,6 +325,25 @@ $.widget("custom.catcomplete", $.ui.autocomplete, {
_create: function() {
this._super();
this.widget().menu("option", "items", "> .result-item");
// workaround for search result scrolling
this.menu._scrollIntoView = function _scrollIntoView( item ) {
var borderTop, paddingTop, offset, scroll, elementHeight, itemHeight;
if ( this._hasScroll() ) {
borderTop = parseFloat( $.css( this.activeMenu[ 0 ], "borderTopWidth" ) ) || 0;
paddingTop = parseFloat( $.css( this.activeMenu[ 0 ], "paddingTop" ) ) || 0;
offset = item.offset().top - this.activeMenu.offset().top - borderTop - paddingTop;
scroll = this.activeMenu.scrollTop();
elementHeight = this.activeMenu.height() - 26;
console.log(item)
itemHeight = item.outerHeight();
if ( offset < 0 ) {
this.activeMenu.scrollTop( scroll + offset );
} else if ( offset + itemHeight > elementHeight ) {
this.activeMenu.scrollTop( scroll + offset - elementHeight + itemHeight );
}
}
};
},
_renderMenu: function(ul, items) {
var currentCategory = "";
@ -337,6 +362,8 @@ $.widget("custom.catcomplete", $.ui.autocomplete, {
}
li.attr("class", "result-item");
});
ul.append("<li class='ui-static-link'><a href='" + pathtoroot + "search.html?q="
+ encodeURI(widget.term) + "'>Go to search page</a></li>");
},
_renderItem: function(ul, item) {
var li = $("<li/>").appendTo(ul);
@ -374,7 +401,8 @@ $(function() {
if (expanded) {
collapse();
} else {
$("div#navbar-top").height($("#navbar-top").prop("scrollHeight"));
var navbar = $("div#navbar-top");
navbar.height(navbar.prop("scrollHeight"));
$("button#navbar-toggle-button")
.addClass("expanded")
.attr("aria-expanded", "true");

@ -168,6 +168,24 @@ doclet.systemProperties=System Properties
doclet.systemPropertiesSummary=System Properties Summary
doclet.Window_Source_title=Source code
doclet.Window_Help_title=API Help
doclet.Window_Search_title=Search
doclet.search.main_heading=Search
# label for link/button element to show the information below
doclet.search.show_more=Additional resources
doclet.search.help_page_link=help page
# 0: a link to the help page with text above
doclet.search.help_page_info= \
The {0} provides an introduction to the scope and syntax of JavaDoc search.
doclet.search.keyboard_info= \
You can use the <ctrl> or <cmd> keys in combination with the left and right arrow \
keys to switch between result tabs in this page.
doclet.search.browser_info= \
The URL template below may be used to configure this page as a search engine \
in browsers that support this feature. It has been tested to work in Google \
Chrome and Mozilla Firefox. Note that other browsers may not support this \
feature or require a different URL format.
doclet.search.redirect=Redirect to first result
# 0: a date
doclet.Option_date_out_of_range=value for ''--date'' out of range: {0}

@ -55,6 +55,8 @@ doclet.File_not_found=File not found: {0}
doclet.snippet_file_not_found=file not found on source path or snippet path: {0}
doclet.Copy_Overwrite_warning=File {0} not copied to {1} due to existing file with same name...
doclet.Copy_Ignored_warning=File {0} not copied: invalid name
doclet.Copy_url_to_clipboard=Copy URL
doclet.Copied_url_to_clipboard=Copied!
doclet.Copy_snippet_to_clipboard=Copy
doclet.Copied_snippet_to_clipboard=Copied!
doclet.Copying_File_0_To_Dir_1=Copying file {0} to directory {1}...
@ -223,8 +225,9 @@ doclet.Modifier=Modifier
doclet.Type=Type
doclet.Modifier_and_Type=Modifier and Type
doclet.Implementation=Implementation(s):
doclet.search=SEARCH:
doclet.search=SEARCH
doclet.search_placeholder=Search
doclet.search_reset=Reset
doclet.Field=Field
doclet.Property=Property
doclet.Constructor=Constructor
@ -342,8 +345,13 @@ doclet.platform.docs.old=https://docs.oracle.com/javase/{0}/docs/api/
doclet.platform.docs.new=https://docs.oracle.com/en/java/javase/{0}/docs/api/
doclet.platform.docs.ea=https://download.java.net/java/early_access/jdk{0}/docs/api/
doclet.search.enter_search_term=Enter a search term
doclet.search.no_results=No results found
doclet.search.one_result=Found one result
doclet.search.many_results=Found {0} results
doclet.search.loading=Loading search index...
doclet.search.searching=Searching...
doclet.search.redirecting=Redirecting to first result...
doclet.search.modules=Modules
doclet.search.packages=Packages
doclet.search.classes_and_interfaces=Classes and Interfaces

@ -370,10 +370,7 @@ ul.see-list-long li:not(:last-child):after {
}
.caption span {
white-space:nowrap;
padding-top:5px;
padding-left:12px;
padding-right:12px;
padding-bottom:7px;
padding:5px 12px 7px 12px;
display:inline-block;
float:left;
background-color:#F8981D;
@ -382,7 +379,7 @@ ul.see-list-long li:not(:last-child):after {
}
div.table-tabs {
padding:10px 0 0 1px;
margin:0;
margin:10px 0 0 0;
}
div.table-tabs > button {
border: none;
@ -399,25 +396,33 @@ div.table-tabs > button.table-tab {
background: #4D7A97;
color: #FFFFFF;
}
.two-column-search-results {
display: grid;
grid-template-columns: minmax(400px, max-content) minmax(400px, auto);
}
.two-column-summary {
display: grid;
grid-template-columns: minmax(15%, max-content) minmax(15%, auto);
grid-template-columns: minmax(25%, max-content) minmax(25%, auto);
}
.three-column-summary {
display: grid;
grid-template-columns: minmax(10%, max-content) minmax(15%, max-content) minmax(15%, auto);
grid-template-columns: minmax(15%, max-content) minmax(20%, max-content) minmax(20%, auto);
}
.four-column-summary {
display: grid;
grid-template-columns: minmax(10%, max-content) minmax(10%, max-content) minmax(10%, max-content) minmax(10%, auto);
grid-template-columns: minmax(10%, max-content) minmax(15%, max-content) minmax(15%, max-content) minmax(15%, auto);
}
@media screen and (max-width: 600px) {
.two-column-summary {
@media screen and (max-width: 1000px) {
.four-column-summary {
display: grid;
grid-template-columns: 1fr;
grid-template-columns: minmax(15%, max-content) minmax(15%, auto);
}
}
@media screen and (max-width: 800px) {
.two-column-search-results {
display: grid;
grid-template-columns: minmax(40%, max-content) minmax(40%, auto);
}
.three-column-summary {
display: grid;
grid-template-columns: minmax(10%, max-content) minmax(25%, auto);
@ -426,10 +431,10 @@ div.table-tabs > button.table-tab {
grid-column-end: span 2;
}
}
@media screen and (max-width: 1000px) {
.four-column-summary {
@media screen and (max-width: 600px) {
.two-column-summary {
display: grid;
grid-template-columns: minmax(15%, max-content) minmax(15%, auto);
grid-template-columns: 1fr;
}
}
.summary-table > div, .details-table > div {
@ -564,7 +569,7 @@ details.invalid-tag, span.invalid-tag {
padding: 2px 4px;
display:inline-block;
}
details.invalid-tag summary {
details summary {
cursor: pointer;
}
/*
@ -583,30 +588,48 @@ main, nav, header, footer, section {
background-color:#4D7A97;
color:#FFFFFF;
}
.result-item {
li.result-item {
font-size:13px;
}
.ui-autocomplete {
max-height:85%;
max-width:65%;
overflow-y:scroll;
overflow-x:scroll;
overflow-y:auto;
overflow-x:auto;
scrollbar-width: thin;
white-space:nowrap;
box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
}
ul.ui-autocomplete {
position:fixed;
z-index:999999;
z-index:1;
}
ul.ui-autocomplete li {
float:left;
clear:both;
min-width:100%;
}
.result-highlight {
ul.ui-autocomplete li.ui-static-link {
position:sticky;
bottom:0;
left:0;
background: #dee3e9;
padding: 5px 0;
font-family: 'DejaVu Sans', Arial, Helvetica, sans-serif;
font-size: 13px;
font-weight: bolder;
z-index: 2;
}
li.ui-static-link a, li.ui-static-link a:visited {
text-decoration:none;
color:#4A6782;
float:right;
margin-right:20px;
}
.ui-autocomplete .result-highlight {
font-weight:bold;
}
#search-input {
#search-input, #page-search-input {
background-image:url('resources/glass.png');
background-size:13px;
background-repeat:no-repeat;
@ -615,6 +638,9 @@ ul.ui-autocomplete li {
width: 250px;
margin: 0;
}
#search-input {
margin-left: 4px;
}
#reset-button {
background-color: transparent;
background-image:url('resources/x.png');
@ -644,6 +670,98 @@ ul.ui-autocomplete li {
.search-tag-result:target {
background-color:yellow;
}
details.page-search-details {
display: inline-block;
}
div#result-container {
font-size: 14px;
}
div#result-container a.search-result-link {
padding: 0;
margin: 4px 0;
width: 100%;
}
#result-container .result-highlight {
font-weight:bolder;
}
.page-search-info {
background-color: #f5f8ff;
border-radius: 3px;
border: 0 solid #b9c8d3;
padding: 0 8px;
overflow: hidden;
height: 0;
transition: all 0.2s ease;
}
div.table-tabs > button.table-tab {
background: #4D7A97;
color: #FFFFFF;
}
.page-search-header {
padding: 5px 12px 7px 12px;
font-weight: bold;
margin-right: 3px;
background-color:#4D7A97;
color:#ffffff;
display: inline-block;
}
button.page-search-header {
border: none;
cursor: pointer;
}
span#page-search-link {
text-decoration: underline;
}
.active-table-tab {
background: #F8981D;
color: #253441;
}
button.copy-url {
opacity: 80%;
transition: opacity 0.2s;
margin-left: 0.4em;
border: none;
border-radius: 3px;
cursor: pointer;
background: none;
padding:0.3em;
position: relative;
top:0.13em
}
button.copy-url img {
width: 1.2em;
height: 1.2em;
padding: 0.01em 0;
background: none;
position:relative;
top: 0.15em;
}
div.page-search-info:hover button.copy-url {
opacity: 90%;
}
div.page-search-info button.copy-url:hover {
background-color: #dfe6f1;
opacity: 100%;
}
button.copy-url span {
color: #000000;
content: attr(aria-label);
font-family:'DejaVu Sans', Arial, Helvetica, sans-serif;
font-size: 85%;
line-height: 1.2em;
padding: 0.2em;
position: relative;
top: -0.18em;
transition: opacity 0.1s;
opacity: 0;
}
div.page-search-info:hover button.copy-url span {
opacity: 90%;
}
div.page-search-info button.copy-url:active {
background-color: #cfdbee;
opacity: 100%;
}
.module-graph span {
display:none;
position:absolute;
@ -685,7 +803,7 @@ ul.help-subtoc > li::before {
content: "\2022" ;
padding-right:2px;
}
span.help-note {
.help-note {
font-style: italic;
}
/*
@ -922,7 +1040,7 @@ table.striped > tbody > tr > th {
.nav-list-search {
width: 94%;
}
#search-input {
#search-input, #page-search-input {
width: 70%;
}
}
@ -933,7 +1051,7 @@ table.striped > tbody > tr > th {
.nav-list-search {
width: 90%;
}
#search-input {
#search-input, #page-search-input {
width: 80%;
}
}

@ -106,6 +106,9 @@ public class DocPaths {
/** The name of the default jQuery UI javascript file. */
public static final DocPath JQUERY_UI_JS = DocPath.create("jquery-ui.min.js");
/** The name of the jQuery UI stylesheet file containing structural declarations. */
public static final DocPath JQUERY_UI_STRUCTURE_CSS = DocPath.create("jquery-ui.structure.min.css");
/** The name of the directory for legal files. */
public static final DocPath LEGAL = DocPath.create("legal");
@ -145,6 +148,9 @@ public class DocPaths {
/** The name of the directory for the script files. */
public static final DocPath SCRIPT_DIR = DocPath.create("script-dir");
/** The name of the file for search page. */
public static final DocPath SEARCH_PAGE = DocPath.create("search.html");
/** The name of the file for all system properties. */
public static final DocPath SYSTEM_PROPERTIES = DocPath.create("system-properties.html");
@ -284,6 +290,9 @@ public class DocPaths {
/** The name of the template for the search javascript file. */
public static final DocPath SEARCH_JS_TEMPLATE = DocPath.create("search.js.template");
/** The name of the search javascript file. */
public static final DocPath SEARCH_PAGE_JS = DocPath.create("search-page.js");
/** The name of the file for the serialized form info. */
public static final DocPath SERIALIZED_FORM = DocPath.create("serialized-form.html");

@ -134,10 +134,11 @@ public class CheckStylesheetClasses {
// for doc-comment authors; maybe worthy of inclusion in HtmlStyle, just to be documented
removeAll(styleSheetNames, "borderless", "plain", "striped");
// used in search.js; may be worth documenting in HtmlStyle
// used in search.js and search-page.js; may be worth documenting in HtmlStyle
removeAll(styleSheetNames, "result-highlight", "result-item",
"search-tag-desc-result", "search-tag-holder-result",
"ui-autocomplete", "ui-autocomplete-category", "expanded");
"search-tag-desc-result", "search-tag-holder-result", "page-search-header",
"ui-autocomplete", "ui-autocomplete-category", "expanded",
"search-result-link", "two-column-search-results", "ui-static-link");
// very JDK specific
styleSheetNames.remove("module-graph");

@ -11,6 +11,7 @@ Help, help.
<p id="index">Index</p>
<p id="package">Package</p>
<p id="tree">Tree</p>
<p id="search">Search</p>
</body>
</html>

@ -158,6 +158,7 @@ public class TestMetadata extends JavadocTester {
"package-index-page",
"package-tree-page",
"package-use-page",
"search-page",
"serialized-form-page",
"source-page",
"system-properties-page",
@ -223,6 +224,7 @@ public class TestMetadata extends JavadocTester {
"PackageTreeWriter",
"PackageUseWriter",
"PackageWriterImpl",
"SearchWriter",
"SerializedFormWriterImpl",
"SourceToHTMLConverter",
"SystemPropertiesWriter",
@ -358,6 +360,10 @@ public class TestMetadata extends JavadocTester {
check(generator, content, content.contains("tree"));
break;
case "SearchWriter":
check(generator, content, content.contains("search"));
break;
case "SerializedFormWriterImpl":
check(generator, content, content.contains("serialized"));
break;

@ -26,7 +26,7 @@
* @bug 8141492 8071982 8141636 8147890 8166175 8168965 8176794 8175218 8147881
* 8181622 8182263 8074407 8187521 8198522 8182765 8199278 8196201 8196202
* 8184205 8214468 8222548 8223378 8234746 8241219 8254627 8247994 8263528
* 8266808
* 8266808 8248863
* @summary Test the search feature of javadoc.
* @library ../../lib
* @modules jdk.javadoc/jdk.javadoc.internal.tool
@ -64,7 +64,8 @@ public class TestSearch extends JavadocTester {
"module-search-index.js",
"package-search-index.js",
"tag-search-index.js",
"type-search-index.js");
"type-search-index.js",
"search.html");
}
@Test
@ -87,7 +88,8 @@ public class TestSearch extends JavadocTester {
"module-search-index.js",
"package-search-index.js",
"tag-search-index.js",
"type-search-index.js");
"type-search-index.js",
"search.html");
}
@Test
@ -108,7 +110,8 @@ public class TestSearch extends JavadocTester {
"member-search-index.js",
"package-search-index.js",
"tag-search-index.js",
"type-search-index.js");
"type-search-index.js",
"search.html");
}
@Test
@ -129,7 +132,9 @@ public class TestSearch extends JavadocTester {
"type-search-index.js",
"index-all.html",
"allpackages-index.html",
"allclasses-index.html");
"allclasses-index.html",
"search-page.js",
"search.html");
}
@Test
@ -150,7 +155,8 @@ public class TestSearch extends JavadocTester {
"member-search-index.js",
"package-search-index.js",
"tag-search-index.js",
"type-search-index.js");
"type-search-index.js",
"search.html");
}
@Test
@ -170,7 +176,9 @@ public class TestSearch extends JavadocTester {
"package-search-index.js",
"tag-search-index.js",
"type-search-index.js",
"index-all.html");
"index-all.html",
"search-page.js",
"search.html");
}
@Test
@ -190,7 +198,8 @@ public class TestSearch extends JavadocTester {
"member-search-index.js",
"package-search-index.js",
"tag-search-index.js",
"type-search-index.js");
"type-search-index.js",
"search.html");
}
@Test
@ -211,7 +220,8 @@ public class TestSearch extends JavadocTester {
"member-search-index.js",
"package-search-index.js",
"tag-search-index.js",
"type-search-index.js");
"type-search-index.js",
"search.html");
}
@Test
@ -233,7 +243,8 @@ public class TestSearch extends JavadocTester {
"member-search-index.js",
"package-search-index.js",
"tag-search-index.js",
"type-search-index.js");
"type-search-index.js",
"search.html");
}
@Test
@ -255,7 +266,8 @@ public class TestSearch extends JavadocTester {
"module-search-index.js",
"package-search-index.js",
"tag-search-index.js",
"type-search-index.js");
"type-search-index.js",
"search.html");
}
@Test
@ -420,7 +432,7 @@ public class TestSearch extends JavadocTester {
loadScripts(document, 'script');""",
"<div class=\"nav-list-search\">",
"""
<label for="search-input">SEARCH:</label>
<div class="nav-list-search"><a href="search.html">SEARCH</a>
<input type="text" id="search-input" disabled placeholder="Search">
<input type="reset" id="reset-button" disabled value="reset">
""");
@ -713,6 +725,10 @@ public class TestSearch extends JavadocTester {
"function getURLPrefix(item, category) {",
"url += item.l;");
checkOutput("search-page.js", true,
"function renderResults(result) {",
"function selectTab(category) {");
checkCssClasses("search.js", "stylesheet.css");
}

@ -105,18 +105,15 @@ public class TestStylesheet extends JavadocTester {
white-space:pre;
}""",
"""
.caption span {
white-space:nowrap;
padding-top:5px;
padding-left:12px;
padding-right:12px;
padding-bottom:7px;
display:inline-block;
float:left;
background-color:#F8981D;
border: none;
height:16px;
}""",
.caption span {
white-space:nowrap;
padding:5px 12px 7px 12px;
display:inline-block;
float:left;
background-color:#F8981D;
border: none;
height:16px;
}""",
"""
div.table-tabs > button {
border: none;

@ -216,6 +216,8 @@ class APITest {
"script-dir/images/ui-bg_glass_55_fbf9ee_1x400.png",
"script-dir/images/ui-icons_222222_256x240.png",
"script-dir/images/ui-bg_glass_75_e6e6e6_1x400.png",
"search.html",
"search-page.js",
"member-search-index.js",
"module-search-index.js",
"overview-tree.html",
@ -240,6 +242,8 @@ class APITest {
&& !s.endsWith("-search-index.js")
&& !s.equals("index-all.html")
&& !s.equals("search.js")
&& !s.equals("search.html")
&& !s.equals("search-page.js")
&& !s.equals("jquery-ui.overrides.css")
&& !s.equals("allclasses-index.html")
&& !s.equals("allpackages-index.html")