From 82c6fee3e50c6451a859e532d7f0066477ff91cd Mon Sep 17 00:00:00 2001 From: Nizar Benalla Date: Tue, 15 Jul 2025 15:44:19 +0100 Subject: [PATCH 1/2] add toggle to sort TOC in lexical order --- .../doclets/formats/html/HtmlDoclet.java | 2 + .../doclets/formats/html/HtmlIds.java | 2 + .../doclets/formats/html/TableOfContents.java | 9 +++ .../doclets/formats/html/markup/Head.java | 1 + .../formats/html/markup/HtmlStyles.java | 5 ++ .../formats/html/resources/stylesheet.css | 34 ++++++++++ .../doclets/formats/html/resources/toc.js | 63 +++++++++++++++++++ .../doclets/formats/html/resources/toggle.svg | 13 ++++ .../toolkit/resources/doclets.properties | 1 + .../doclets/toolkit/util/DocPaths.java | 6 ++ .../testPassthruFiles/TestPassThruFiles.java | 3 +- .../jdk/javadoc/tool/api/basic/APITest.java | 3 +- 12 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/toc.js create mode 100644 src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/toggle.svg diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDoclet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDoclet.java index 47b2322226ee8..97b20f1890bdd 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDoclet.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDoclet.java @@ -323,10 +323,12 @@ protected void generateOtherFiles(ClassTree classTree) copyResource(DocPaths.STYLESHEET, DocPaths.RESOURCE_FILES.resolve(DocPaths.STYLESHEET), true); } copyResource(DocPaths.SCRIPT_JS_TEMPLATE, DocPaths.SCRIPT_FILES.resolve(DocPaths.SCRIPT_JS), true); + copyResource(DocPaths.TOC_JS, DocPaths.SCRIPT_FILES.resolve(DocPaths.TOC_JS), true); copyResource(DocPaths.LEFT_SVG, DocPaths.RESOURCE_FILES.resolve(DocPaths.LEFT_SVG), true); copyResource(DocPaths.RIGHT_SVG, DocPaths.RESOURCE_FILES.resolve(DocPaths.RIGHT_SVG), true); copyResource(DocPaths.CLIPBOARD_SVG, DocPaths.RESOURCE_FILES.resolve(DocPaths.CLIPBOARD_SVG), true); copyResource(DocPaths.LINK_SVG, DocPaths.RESOURCE_FILES.resolve(DocPaths.LINK_SVG), true); + copyResource(DocPaths.TOGGLE_SVG, DocPaths.RESOURCE_FILES.resolve(DocPaths.TOGGLE_SVG), true); if (options.createIndex()) { copyResource(DocPaths.SEARCH_JS_TEMPLATE, DocPaths.SCRIPT_FILES.resolve(DocPaths.SEARCH_JS), true); diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlIds.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlIds.java index 42d93c41e116a..669f96deb4dce 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlIds.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlIds.java @@ -115,6 +115,8 @@ public class HtmlIds { static final HtmlId SERVICES = HtmlId.of("services-summary"); static final HtmlId SKIP_NAVBAR_TOP = HtmlId.of("skip-navbar-top"); static final HtmlId UNNAMED_PACKAGE_ANCHOR = HtmlId.of("unnamed-package"); + static final String TOC_ORDER_TOGGLE = "toc-lexical-order-toggle"; + static final HtmlId TOC_LIST = HtmlId.of("toc-list"); private static final String FIELDS_INHERITANCE = "fields-inherited-from-class-"; private static final String METHODS_INHERITANCE = "methods-inherited-from-class-"; private static final String NESTED_CLASSES_INHERITANCE = "nested-classes-inherited-from-class-"; diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/TableOfContents.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/TableOfContents.java index 6a3dff8a5d61a..2c338f86f5311 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/TableOfContents.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/TableOfContents.java @@ -112,6 +112,15 @@ protected Content toContent(boolean hasFilterInput) { .add(HtmlTree.INPUT(HtmlAttr.InputType.RESET, HtmlStyles.resetFilter) .put(HtmlAttr.TABINDEX, "-1") .put(HtmlAttr.VALUE, writer.resources.getText("doclet.filter_reset"))); + + header.add(Entity.NO_BREAK_SPACE) + .add(HtmlTree.BUTTON(HtmlStyles.tocSortToggle) + .put(HtmlAttr.ID, HtmlIds.TOC_ORDER_TOGGLE) + .add(HtmlTree.IMG(writer.pathToRoot.resolve(DocPaths.RESOURCE_FILES).resolve(DocPaths.TOGGLE_SVG), + writer.resources.getText("doclet.sort_table_of_contents") + )) + ); + } content.add(header); content.add(listBuilder); diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/Head.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/Head.java index 2b6bfa77951c2..95cf231075261 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/Head.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/Head.java @@ -372,6 +372,7 @@ private void addScripts(HtmlTree head) { if (addDefaultScript) { addScriptElement(head, DocPaths.SCRIPT_JS); } + addScriptElement(head, DocPaths.TOC_JS); if (index) { if (pathToRoot != null && mainBodyScript != null) { String ptrPath = pathToRoot.isEmpty() ? "." : pathToRoot.getPath(); diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlStyles.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlStyles.java index 358f490334b7e..4e606c3e994cb 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlStyles.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlStyles.java @@ -164,6 +164,11 @@ public enum HtmlStyles implements HtmlStyle { */ tocList, + /** + * The class used for lexical order toggle in the table of contents. + */ + tocSortToggle, + // // diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/stylesheet.css b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/stylesheet.css index f7994c7f7ed3d..c34912357edb1 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/stylesheet.css +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/stylesheet.css @@ -1665,3 +1665,37 @@ pre.snippet .highlighted { display:none; } } + +nav.toc div.toc-header .toc-sort-toggle { + display: flex; + flex-wrap: nowrap; + align-items: center; + gap: 0.5em; + overflow-x: auto; +} + +nav.toc div.toc-header input.filter-input { + flex: 1 1 0; + min-width: 0; + height: 2em; + width: auto; + font-size: 1em; + background-size: 18px; +} + +nav.toc div.toc-header button#toc-lexical-order-toggle { + flex: 0 0 auto; + position: static; + display: inline-flex; + align-items: center; + padding: 0.5em; + background: none; + border: none; + cursor: pointer; +} + +.main-grid nav.toc button > img { + vertical-align: middle; + width: 20px; + height: 20px; +} diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/toc.js b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/toc.js new file mode 100644 index 0000000000000..6b6f0253bfcbd --- /dev/null +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/toc.js @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ + */ + +const LABEL_ALPHA = "${doclet.lexical}"; +const LABEL_SOURCE = "${doclet.sort_to_source}"; + +document.addEventListener("DOMContentLoaded", () => { + const btn = document.getElementById("toc-lexical-order-toggle"); + const toc = document.querySelector("nav.toc ol.toc-list"); + if (!btn || !toc) return; + const img = btn.querySelector("img"); + + const sections = Array.from(toc.children); + + const nestedMap = new Map(); + sections.forEach(section => { + const subList = section.querySelector(":scope > ol.toc-list"); + if (subList) { + nestedMap.set(subList, Array.from(subList.children)); + } + }); + + function reorder(mode) { + toc.textContent = ""; + sections.forEach(li => toc.appendChild(li)); + + nestedMap.forEach((originalChildren, subList) => { + subList.textContent = ""; + if (mode === "alpha") { + originalChildren + .slice() + .sort((a, b) => + a.textContent.trim() + .localeCompare(b.textContent.trim(), undefined, { + numeric: true, + sensitivity: "base" + }) + ) + .forEach(child => subList.appendChild(child)); + } else { + originalChildren.forEach(child => subList.appendChild(child)); + } + }); + + const nextLabel = (mode === "alpha") ? LABEL_SOURCE : LABEL_ALPHA; + btn.setAttribute("aria-label", nextLabel); + if (img) img.alt = nextLabel; + localStorage.setItem("toc-order-mode", mode); + } + + reorder(localStorage.getItem("toc-order-mode") || "source"); + + btn.addEventListener("click", () => { + const newMode = (btn.getAttribute("aria-label") === LABEL_ALPHA) + ? "alpha" + : "source"; + reorder(newMode); + }); +}); diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/toggle.svg b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/toggle.svg new file mode 100644 index 0000000000000..25857e4d0fe48 --- /dev/null +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/toggle.svg @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets.properties b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets.properties index 9836c58843b62..5919d7c71ed3f 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets.properties +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets.properties @@ -255,6 +255,7 @@ doclet.show_sidebar=Show sidebar doclet.filter_label=Filter contents (type .) doclet.filter_table_of_contents=Filter table of contents doclet.filter_reset=Reset +doclet.sort_table_of_contents=Sort table of contents members in lexicographical order doclet.linkMismatch_PackagedLinkedtoModule=The code being documented uses packages in the unnamed module, \ but the packages defined in {0} are in named modules. doclet.linkMismatch_ModuleLinkedtoPackage=The code being documented uses modules but the packages defined \ diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/DocPaths.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/DocPaths.java index 21f1267361954..568e9fde61ee5 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/DocPaths.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/DocPaths.java @@ -103,6 +103,9 @@ public static DocPath indexN(int n) { /** The name of the template of the default javascript file. */ public static final DocPath SCRIPT_JS_TEMPLATE = DocPath.create("script.js.template"); + /** The name of the table of contents toggle javascript file. */ + public static final DocPath TOC_JS = DocPath.create("toc.js"); + /** The name of the copy-to-clipboard icon file. */ public static final DocPath CLIPBOARD_SVG = DocPath.create("copy.svg"); @@ -112,6 +115,9 @@ public static DocPath indexN(int n) { /** The name of the link icon file. */ public static final DocPath LINK_SVG = DocPath.create("link.svg"); + /** The name of the table of contents toggle icon file. */ + public static final DocPath TOGGLE_SVG = DocPath.create("toggle.svg"); + /** The name of the right pointing angle icon. */ public static final DocPath RIGHT_SVG = DocPath.create("right.svg"); diff --git a/test/langtools/jdk/javadoc/doclet/testPassthruFiles/TestPassThruFiles.java b/test/langtools/jdk/javadoc/doclet/testPassthruFiles/TestPassThruFiles.java index 71d683b2d5483..7dfdd94f639d8 100644 --- a/test/langtools/jdk/javadoc/doclet/testPassthruFiles/TestPassThruFiles.java +++ b/test/langtools/jdk/javadoc/doclet/testPassthruFiles/TestPassThruFiles.java @@ -63,7 +63,8 @@ public void testPassThroughFiles(Path base) throws Exception { "resource-files/stylesheet.css", "script-files/script.js", "script-files/search.js", - "script-files/search-page.js" + "script-files/search-page.js", + "script-files/toc.js" ); for (var f : files) { diff --git a/test/langtools/jdk/javadoc/tool/api/basic/APITest.java b/test/langtools/jdk/javadoc/tool/api/basic/APITest.java index 2d45d6c7a8f0f..338ee70c1fee8 100644 --- a/test/langtools/jdk/javadoc/tool/api/basic/APITest.java +++ b/test/langtools/jdk/javadoc/tool/api/basic/APITest.java @@ -212,6 +212,7 @@ protected void error(String msg) { "resource-files/right.svg", "resource-files/stylesheet.css", "resource-files/x.svg", + "resource-files/toggle.svg", "resource-files/fonts/dejavu.css", "resource-files/fonts/DejaVuLGCSans-Bold.woff", "resource-files/fonts/DejaVuLGCSans-Bold.woff2", @@ -242,6 +243,7 @@ protected void error(String msg) { "script-files/script.js", "script-files/search.js", "script-files/search-page.js", + "script-files/toc.js", "tag-search-index.js", "type-search-index.js" )); @@ -262,4 +264,3 @@ protected void error(String msg) { && !s.equals("system-properties.html")) .collect(Collectors.toSet()); } - From 3d315fcd68b6a1d92476165f8b09f871467c368f Mon Sep 17 00:00:00 2001 From: Nizar Benalla Date: Wed, 16 Jul 2025 12:39:32 +0100 Subject: [PATCH 2/2] make toc.js resilient to being loaded after script.js --- .../doclets/formats/html/resources/toc.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/toc.js b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/toc.js index 6b6f0253bfcbd..e423680c34c45 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/toc.js +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/toc.js @@ -5,12 +5,17 @@ * Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ */ -const LABEL_ALPHA = "${doclet.lexical}"; -const LABEL_SOURCE = "${doclet.sort_to_source}"; +const LABEL_ALPHA = "Sort lexicographically"; +const LABEL_SOURCE = "Sort by source order"; document.addEventListener("DOMContentLoaded", () => { - const btn = document.getElementById("toc-lexical-order-toggle"); - const toc = document.querySelector("nav.toc ol.toc-list"); + const sidebarNav = document.querySelector(".main-grid nav.toc") + || Array.from(document.querySelectorAll("nav.toc")) + .find(n => !n.closest("#navbar-top")); + if (!sidebarNav) return; + + const btn = sidebarNav.querySelector("#toc-lexical-order-toggle"); + const toc = sidebarNav.querySelector("ol.toc-list"); if (!btn || !toc) return; const img = btn.querySelector("img"); @@ -60,4 +65,6 @@ document.addEventListener("DOMContentLoaded", () => { : "source"; reorder(newMode); }); + + btn.blur(); });