From 0dc0a52b87acc7e8c361bd81c74a7d8fb8c28a10 Mon Sep 17 00:00:00 2001 From: Christoph Mewes Date: Sat, 16 Sep 2023 20:26:05 +0200 Subject: [PATCH] improve scrolling in API ref docs --- static/css/style_apiref.css | 29 +++- static/js/scroll-apiref.js | 278 ++++++++++++------------------------ 2 files changed, 122 insertions(+), 185 deletions(-) diff --git a/static/css/style_apiref.css b/static/css/style_apiref.css index cfba8c3be1b04..072a8b8979019 100644 --- a/static/css/style_apiref.css +++ b/static/css/style_apiref.css @@ -82,7 +82,7 @@ body > #wrapper { background-color: whitesmoke; border-right: 2px solid slategrey; overflow-x: auto; - padding-top: 60px; + padding-top: 30px; } #sidebar-wrapper a { @@ -227,3 +227,30 @@ body > #wrapper { .side-nav a { color: black; } + +#navigation .nav-level-1, +#navigation .nav-level-2 { + margin-bottom: 1rem; +} + +#navigation .nav-level-1 > ul { + margin-top: 1rem; +} + +/* hide operations by default, reveal them via JS */ +#navigation li.nav-level-2 ul { + display: none; +} + +/* do not indent resources */ +#navigation .nav-level-1 > ul, +#navigation .nav-level-1 > ul > li { + margin-left: 0; +} + +/* make section links / operation categories bold */ +#navigation .nav-level-1 > a, +#navigation .nav-level-3 > a { + font-weight: bold; + font-family: monospace; +} diff --git a/static/js/scroll-apiref.js b/static/js/scroll-apiref.js index 5b8cbefed6885..ac79207f9b45c 100644 --- a/static/js/scroll-apiref.js +++ b/static/js/scroll-apiref.js @@ -1,196 +1,106 @@ -$(document).ready(function() { - - /** - * TODO: Refactor with intent toward pure functions. Mutation of state can lead to bugs and difficult debugging. - */ - - var toc = navData.toc; - var flatToc = navData.flatToc.reverse(); - - function collectNodes(tocMap) { - var tocNodes = {}; - tocMap.map(function(node, index) { - var sectionNode = $('#' + node.section); - var tocSubsections = {}; - tocItem = {section: sectionNode}; - var subsectionNodes; - if (node.subsections) { - subsectionNodes = (collectNodes(node.subsections)); - tocItem.subsections = subsectionNodes; - } - tocNodes[node.section] = tocItem; - }); - return tocNodes; - } - var tocItems = collectNodes(toc); - - function collectNodesFlat(tocMap, obj) { - var collect = obj || {}; - tocMap.map(function(node, index) { - var sectionNode = $('#' + node.section); - tocItem = {section: sectionNode}; - if (node.subsections) { - subsectionNodes = (collectNodesFlat(node.subsections, collect)); - } - collect[node.section] = sectionNode; - }); - return collect; - } - var tocFlat = collectNodesFlat(toc); - - var prevSectionToken; - var prevSubsectionToken; - var activeTokensObj = {}; - - function scrollActions(scrollPosition) { - var activeSection = checkNodePositions(toc, tocFlat, scrollPosition); - var activeSubSection, - prevL1Nav, - currL1Nav, - prevL2Nav, - currL2Nav; - - // No active section - return existing activeTokensObj (may be empty) - if (!activeSection) { - return activeTokensObj; - } - - /** - * This block deals with L1Nav sections - */ - - // If no previous token, set previous to current active and show L1Nav - if (!prevSectionToken) { - prevSectionToken = activeSection.token; - currL1Nav = getNavNode(activeSection.token); - currL1Nav.show('fast'); - } - // If active is not the same as previous, hide previous L1Nav and show current L1Nav; set previous to current - else if (activeSection.token !== prevSectionToken) { - prevL1Nav = getNavNode(prevSectionToken); - currL1Nav = getNavNode(activeSection.token); - prevL1Nav.hide('fast'); - currL1Nav.show('fast'); - prevSectionToken = activeSection.token; - } - - /** - * This block deals with L2Nav subsections - */ - - // If there is a subsections array and it has a non-zero length, set active subsection - if (activeSection.subsections && activeSection.subsections.length !== 0) { - activeSubSection = checkNodePositions(activeSection.subsections, tocFlat, scrollPosition); - if (activeSubSection) { - if (!prevSubsectionToken) { - prevSubsectionToken = activeSubSection.token; - currL2Nav = getNavNode(activeSubSection.token); - currL2Nav.show('fast'); - } else if (activeSubSection.token !== prevSubsectionToken) { - prevL2Nav = getNavNode(prevSubsectionToken); - currL2Nav = getNavNode(activeSubSection.token); - prevL2Nav.hide('fast'); - currL2Nav.show('fast'); - prevSubsectionToken = activeSubSection.token; - } - } else { - prevL2Nav = getNavNode(prevSubsectionToken); - prevL2Nav.hide('fast'); - prevSubsectionToken = null; - } - } - activeTokensObj.L1 = prevSectionToken; - activeTokensObj.L2 = prevSubsectionToken; - return activeTokensObj; +jQuery(function($) { + // tocItems are headings in the main content area that have a representation in the TOC + // (not all headings are present in the TOC). + let tocItems = $('#page-content-wrapper .toc-item'); + + function getCurrentlyVisibleSection(scrollPosition) { + // Walk the list from the bottom up and check; + // each TOC item is the immediate child of a
that + // carries the TOC item's ID. + for (let i = tocItems.length - 1; i >= 0; --i) { + let item = $(tocItems.get(i)); + let offsetTop = item.offset().top - 50; + + if (scrollPosition >= offsetTop) { + return item.parent().attr('id'); + } } - /** - * Checks for active elements by scroll position - */ - - var prevElemToken; - var activeElemToken; - - function checkActiveElement(items, scrollPosition) { - var offset = 50; - var offsetScroll = scrollPosition + offset; - var visibleNode; - for (var i = 0; i < items.length; i++) { - var token = items[i]; - var node = getHeadingNode(token); - if (offsetScroll >= node.offset().top) { - activeElemToken = token; - } - } - if (!prevElemToken) { - getNavElemNode(activeElemToken).addClass('selected'); - prevElemToken = activeElemToken; - return; - } - if (activeElemToken !== prevElemToken) { - getNavElemNode(prevElemToken).removeClass('selected'); - getNavElemNode(activeElemToken).addClass('selected'); - prevElemToken = activeElemToken; - } - return activeElemToken; - } + return null; + } - function getHeadingNode(token) { - return $('#' + token); - } + function updateNavigationState(visibleSection) { + let selectedLink = $('#navigation a.selected'); + let selectedSection = selectedLink.length === 0 ? null : selectedLink.attr('href').replace(/#/, ''); - function getNavNode(token) { - return $('#' + token + '-nav'); + // nothing to do :) + if (visibleSection === selectedSection) { + return; } - function getNavElemNode(token) { - return $('#sidebar-wrapper > ul a[href="#' + token + '"]'); + // un-select whatever was previously selected + if (selectedLink.length > 0) { + selectedLink.removeClass('selected'); } - function checkNodePositions(nodes, flatNodeMap, scrollPosition) { - var activeNode; - for (var i = 0; i < nodes.length; i++) { - var item = nodes[i]; - var node = flatNodeMap[item.section]; - var nodeTop = node.offset().top - 50; - if (scrollPosition >= nodeTop) { - activeNode = {token: item.section, node: node}; - - if (item.subsections) { - activeNode.subsections = item.subsections; - } - break; - } - } - return activeNode; + if (visibleSection !== null) { + // show the leaf node in the navigation for the activeSection, plus + // all nodes that lead to it (in a->b->c->d, if c is active, show + // b and c because a is always shown already). + let activeSectionLink = '#' + visibleSection; + let link = $('#navigation a[href="' + activeSectionLink + '"]'); + let listItem = link.parent(); + + link.addClass('selected'); + + while (listItem.data('level') > 1) { + let ul = listItem.parent(); + ul.show('fast'); + listItem = ul.parent(); + } + + // expand the currently selected item, i.e. do not just show the + // parent path, but also the immediate child