From 12407be18b8c881e6afab41d68c69e588e30e3e6 Mon Sep 17 00:00:00 2001 From: Aaron Carlisle Date: Tue, 19 Mar 2024 23:18:55 -0400 Subject: [PATCH 1/2] Docs: Python API: Switch theme to Furo Since moving from the Wiki we have used the `sphinx_rtd_theme` which has worked well but has become a little dated and has had slow development. This changes to use "Furo" instead. [Furo](https://github.com/pradyunsg/furo) is a lightweight theme that is responsive and has native dark mode support. Dark mode is one of the major enhancements but this change also brings: - Content is slightly larger making text and images easier to view - Site navigation and page navigation is split into two navigation trees. The site navigation is on the left and page on the right if the page has multiple headings. - Return to top button - Content is centered on the screen which helps with wide monitors - No more breadcrumbs - Remove google analytics So far the caveat that I have noticed with this is slower compile times and larger pages, this is due to the better sidebar navigation. This change also brings a lot of simplifications to customizations that we made to the `sphinx_rtd_theme`. There is still likely room for improvement in the future. Pull Request: #119684 --- doc/python_api/requirements.txt | 2 +- doc/python_api/sphinx_doc_gen.py | 36 +- doc/python_api/static/css/theme_overrides.css | 332 +++++++++++++++++- doc/python_api/static/css/version_switch.css | 97 ++--- doc/python_api/templates/base.html | 6 + .../footer_contribute.html} | 10 +- doc/python_api/templates/page.html | 6 + .../variant-selector.html} | 10 +- 8 files changed, 403 insertions(+), 96 deletions(-) create mode 100644 doc/python_api/templates/base.html rename doc/python_api/templates/{footer.html => components/footer_contribute.html} (77%) create mode 100644 doc/python_api/templates/page.html rename doc/python_api/templates/{versions.html => sidebar/variant-selector.html} (71%) diff --git a/doc/python_api/requirements.txt b/doc/python_api/requirements.txt index 14d60036cb4..1c3b92f7498 100644 --- a/doc/python_api/requirements.txt +++ b/doc/python_api/requirements.txt @@ -9,4 +9,4 @@ requests==2.31.0 # Only needed to match the theme used for the official documentation. # Without this theme, the default theme will be used. -sphinx_rtd_theme==1.3.0rc1 +furo==2024.1.29 diff --git a/doc/python_api/sphinx_doc_gen.py b/doc/python_api/sphinx_doc_gen.py index 0a7702bf09c..02df13af1b9 100644 --- a/doc/python_api/sphinx_doc_gen.py +++ b/doc/python_api/sphinx_doc_gen.py @@ -1929,22 +1929,30 @@ def write_sphinx_conf_py(basepath): # The theme 'sphinx_rtd_theme' is no longer distributed with sphinx by default, only use when available. fw(r""" try: - __import__('sphinx_rtd_theme') - html_theme = 'sphinx_rtd_theme' + import furo + html_theme = "furo" + del furo except ModuleNotFoundError: pass -""") +if html_theme == "furo": + html_theme_options = { + "light_css_variables": { + "color-brand-primary": "#265787", + "color-brand-content": "#265787", + }, + } - fw("if html_theme == 'sphinx_rtd_theme':\n") - fw(" html_theme_options = {\n") - fw(" 'display_version': False,\n") - # fw(" 'analytics_id': '',\n") - # fw(" 'collapse_navigation': True,\n") - fw(" 'sticky_navigation': False,\n") - fw(" 'navigation_depth': 1,\n") - fw(" 'includehidden': False,\n") - # fw(" 'titles_only': False\n") - fw(" }\n\n") + html_sidebars = { + "**": [ + "sidebar/brand.html", + "sidebar/search.html", + "sidebar/scroll-start.html", + "sidebar/navigation.html", + "sidebar/scroll-end.html", + # "sidebar/variant-selector.html", + ] + } +""") # not helpful since the source is generated, adds to upload size. fw("html_copy_source = False\n") @@ -1961,7 +1969,7 @@ except ModuleNotFoundError: fw("html_logo = 'static/blender_logo.svg'\n") # Disable default `last_updated` value, since this is the date of doc generation, not the one of the source commit. fw("html_last_updated_fmt = None\n\n") - fw("if html_theme == 'sphinx_rtd_theme':\n") + fw("if html_theme == 'furo':\n") fw(" html_css_files = ['css/version_switch.css']\n") fw(" html_js_files = ['js/version_switch.js']\n") diff --git a/doc/python_api/static/css/theme_overrides.css b/doc/python_api/static/css/theme_overrides.css index 5ab449044db..c92ea04aacc 100644 --- a/doc/python_api/static/css/theme_overrides.css +++ b/doc/python_api/static/css/theme_overrides.css @@ -1,19 +1,323 @@ -/* Hide home icon in search area */ -.wy-side-nav-search > a:hover {background: none; opacity: 0.9} -.wy-side-nav-search > a.icon::before {content: none} +/* + * This stylesheet is applied after the theme's default one, + * and thus any overrides or additions can be added here. + * + * More info: + * https://www.sphinx-doc.org/en/master/extdev/appapi.html#sphinx.application.Sphinx.add_css_file + */ -.wy-nav-content { - max-width: 1000px !important; +body { + --sidebar-caption-font-size: var(--font-size--normal); + --toc-title-font-size: var(--font-size--normal); + --toc-font-size: var(--sidebar-item-font-size); + --admonition-font-size: var(--font-size--normal); + --admonition-title-font-size: var(--font-size--normal); + --color-api-name: #e87d0d; } -/* Fix long titles on mobile */ -h1, h2, h3, h4, h5, h6 {word-break: break-all} +h1, +h2, +h3 { + margin-top: 1.75rem; + margin-bottom: 1rem; +} -/* Temp fix for https://github.com/readthedocs/sphinx_rtd_theme/pull/1109 */ -.hlist tr { - display: -ms-flexbox; - display: flex; - flex-flow: row wrap; - } +h1 { + font-size: 2em; +} -.hlist td {margin-right: auto} +h2 { + font-size: 1.5em; +} + +h3 { + font-size: 1.25em; +} + +h4, +h5, +h6, +.rubric { + margin-top: 1.25rem; + margin-bottom: 0.75rem; + font-size: 1.125em; +} + +/* Reduce the margins on top/bottom of horizontal lines. */ +hr.docutils { + margin: 1rem 0; +} + + +/* Slightly decrease text to make the text fit on one line */ +.sidebar-brand-text { + font-size: 1.4rem; +} + +.toctree-checkbox~label .icon svg { + transition: transform 0.25s ease-out; +} + +/* Add more visual weight to definition terms */ +dl dt { + font-weight: bold !important +} + +/* Fixes to field list, see #104636 */ +dl.field-list { + display: grid; + grid-template-columns: auto minmax(80%, 95%); + + p { + margin-bottom: 0; + } +} + +/* TABLE & FIGURE */ + +/* Cell's vertical align. */ +/* use "valign" class for middle align */ +table.docutils:not(.valign) td { + vertical-align: baseline; +} + +/* Decrease whitespace above figure and add it below */ +figure { + padding-bottom: 0.5rem; +} + +figcaption { + margin-bottom: 0.5rem !important; + + p { + margin-top: 0; + } +} + +/* End TABLE & FIGURE. */ + +/* Force admonition to span the full width if close to a figure */ +.admonition { + clear: both; +} + +/* Use secondary font color for caption text */ +figcaption, +caption { + color: var(--color-foreground-secondary); + font-size: var(--font-size--small) +} + +/* A bit hacky, revert the themes styling of kbd */ +kbd:not(.compound) { + all: revert; +} + +/* Only style parent kbd elements instead of the individual children */ +:not(dl.option-list)> :not(kbd):not(kbd)>kbd, +.menuselection { + background-color: var(--color-background-secondary); + border: 1px solid var(--color-foreground-border); + border-radius: .2rem; + box-shadow: 0 .0625rem 0 rgba(0, 0, 0, .2), inset 0 0 0 .125rem var(--color-background-secondary); + color: var(--color-foreground-primary); + display: inline-block; + margin: 0; + padding: 0 .2rem; +} + +.highlight .nc, +.highlight .nn, +.highlight .gu { + text-decoration-line: none !important; +} + +.caption .menuselection { + background-color: transparent; + border: none; +} + +a { + text-decoration: none; +} + +/* Quotes for Fig. "link". */ +a[href^="#fig-"]::before { + content: "\201c"; +} + +a[href^="#fig-"]::after { + content: "\201d"; +} + +/* Mark external links. */ +a.external { + filter: brightness(150%); +} + +/* List blender.org as internal. */ +.external[href^="https://www.blender.org"], +.external[href^="https://docs.blender.org"], +.external[href^="https://projects.blender.org"], +.external[href^="https://builder.blender.org"], +.external[href^="https://code.blender.org"], +.external[href^="https://translate.blender.org"], +.external[href^="https://fund.blender.org"], +.external[href^="blender_manual_html.zip"], +.external[href^="blender_manual_epub.zip"], +.external[href^="https://archive.blender.org"] { + filter: revert; +} + +/* ".. container::" lead, block text float around image. */ +.lead { + clear: both; + width: 100%; +} + +/* Start reference admonition. */ +.admonition.refbox { + border-color: rgb(50, 50, 50); +} + +.admonition.refbox>.admonition-title { + background-color: rgba(50, 50, 50, 0.2); + border-color: rgb(50, 50, 50); +} + +.admonition.refbox>.admonition-title::before { + background-color: var(--color-content-foreground); +} + +/* 'refbox' field. */ +.refbox .field-list .field-name, +.refbox .field-list .field-body { + padding: 0px; +} + +.refbox dl dt { + font-weight: normal +} + +/* End reference admonition. */ + +/* Applied on main index:sections. */ + +.global-index-toc { + display: none; +} + +/* Start section cards. */ +.toc-cards { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); + grid-gap: 20px; + list-style-type: none; + margin-bottom: 24px; +} + +.card { + border-radius: .3em; + user-select: none; +} + +.card div.figure, +.card figure { + margin-bottom: 0px; + display: block; +} + +.card img { + border-top-left-radius: .3em; + border-top-right-radius: .3em; +} + +.card dl { + margin-bottom: 10px +} + +.card dl dt>a { + display: block; + width: 100%; + margin-bottom: 10px; +} + +.card dl dt a em, +.card dl dt a span { + font-weight: bold; + font-style: normal; + font-size: 1.3em; +} + +.card dl dt { + padding: 0px 15px 0px !important +} + +.card dl dd { + padding: 0px 15px 5px 15px; + font-style: normal; + margin: 0px; + color: var(--color-foreground-secondary); + font-size: 90%; +} + +.card { + box-shadow: 0 .2rem .5rem rgba(0, 0, 0, .05), 0 0 .0625rem rgba(0, 0, 0, .1); +} + +#getting-started .card { + box-shadow: none; +} + +/* End section cards. */ + +/* Start custom toctree. */ +/* Indent all lines following the first. */ +.toctree-wrapper * a { + display: block; + padding-top: 0.25em; +} + +.toctree-wrapper ul { + list-style: none; + padding-left: 0; +} + +/* Underline provided by nested ul (not li). */ +.toctree-wrapper * ul { + margin-bottom: 1rem !important; + border-top: solid var(--color-background-border) 1px; + padding-left: 2em; +} + +/* End custom toctree. */ + +/* Start footer contribute link */ +.footer-contribute { + display: block; + font-size: var(--font-size--small); +} + +.bottom-of-page { + padding-bottom: 0; +} + +.footer-contribute ul { + margin: 0; + padding: 0; + padding-bottom: 1rem +} + +.footer-contribute li { + display: inline; + list-style-type: none; + padding-right: 1.5rem; +} + +@media print { + .footer-contribute { + display: none; + } +} + +/* End footer contribute link */ \ No newline at end of file diff --git a/doc/python_api/static/css/version_switch.css b/doc/python_api/static/css/version_switch.css index adb80b01c0a..80d5c2a52b0 100644 --- a/doc/python_api/static/css/version_switch.css +++ b/doc/python_api/static/css/version_switch.css @@ -1,46 +1,35 @@ -/* Override RTD theme */ -.rst-versions { - display: none; - border-top: 0px; - overflow: visible; -} -.version-btn.vdeact { - cursor: default; - color: dimgray; -} - -.version-btn.vdeact::after { - content: ""; -} #versionwrap { + margin: 0; display: flex; padding-top: 2px; - font-size: 90%; + padding-left: 0; + font-size: var(--sidebar-item-font-size); justify-content: center; flex-wrap: wrap; } + +#versionwrap>ul { + list-style: none; +} + +#versionwrap>li { + display: flex; + width: 50%; +} + .version-btn { display: inline-block; - background-color: #272525; - width: 140px; + background-color: var(--color-sidebar-background); + width: 100%; text-align: center; padding: 3px 10px; margin: 0px 5px 4px; vertical-align: middle; - color: #27AE60; - border: solid 1px #444444; + color: var(--color-link); + border: solid 1px var(--color-sidebar-background-border); border-radius: 3px; cursor: pointer; z-index: 400; - transition: border-color 0.4s; -} -.version-btn::after { - content:"\f0d8"; - display: inline; - font: normal normal normal 16px/1 FontAwesome; - color: #8d8c8c; - vertical-align: top; - padding-left: 0.5em; } .version-btn-open::after { color: gray; @@ -50,7 +39,7 @@ } .version-btn-open { color: gray; - border: solid 1px gray; + border: solid 1px var(--color-sidebar-background-border); } .version-btn.wait { cursor: wait; @@ -63,30 +52,32 @@ display: none; position: absolute; bottom: 28px; - width: 140px; + width: 50%; margin: 0 5px; padding-bottom: 4px; - background-color: #0003; border-radius: 3px; box-shadow: 0 0 6px #000C; z-index: 999; max-height: calc(100vh - 30px); + overflow-x: clip; overflow-y: auto; cursor: default; } .version-title { padding: 5px; - color: black; + color: var(--color-content-foreground); text-align: center; font-size: 102%; - background-color: #27ae60; - border-bottom: solid 1.5px #444; + font-weight: 700; + background-color: var(--color-brand-primary); + border-bottom: solid 1.5px var(--color-sidebar-background-border); } .version-list { + padding-left: 0; + margin-top: 0; margin-bottom: 4px; text-align: center; - background-color: #000C; - border: solid 1px gray; + border: solid 1px var(--color-sidebar-background-border); border-radius: 0px 0px 3px 3px; } .version-list a, .version-list span, .version-list li { @@ -97,32 +88,20 @@ width: 100%; margin: 0; padding: 4px 0px; - color: #404040; + color: var(--color-sidebar-link-text); } .version-list li { - background-color: #ede9e9; - color: #404040; + background-color: var(--color-sidebar-background); + color: var(--color-sidebar-link-text); padding: 1px; } -.version-list li:hover, .version-list li a:focus { - background-color: #b9cfda; + +.version-list li:hover, +.version-list li a:focus { + background-color: var(--color-background-hover); } -.version-list li.selected, .version-list li.selected:hover { - background-color: #8d8c8c; -} -.version-list li.selected span { - cursor: default; - outline-color: red; -} -.version-arrow { - position: absolute; - width: 8px; - height: 8px; - left: 50%; - bottom: 4px; - margin-left: -4px; - transform: rotate(225deg); - background: #ede9e9; - border: 1px solid gray; - border-width: 1px 0 0 1px; + +.version-list li.selected { + background: var(--color-sidebar-item-background--current); + font-weight: 700; } diff --git a/doc/python_api/templates/base.html b/doc/python_api/templates/base.html new file mode 100644 index 00000000000..5fb72406236 --- /dev/null +++ b/doc/python_api/templates/base.html @@ -0,0 +1,6 @@ +{%- extends "!base.html" -%} + +{%- block theme_scripts -%} +{{ super() }} + +{%- endblock -%} diff --git a/doc/python_api/templates/footer.html b/doc/python_api/templates/components/footer_contribute.html similarity index 77% rename from doc/python_api/templates/footer.html rename to doc/python_api/templates/components/footer_contribute.html index 394aad996cf..3a34cf9ed0a 100644 --- a/doc/python_api/templates/footer.html +++ b/doc/python_api/templates/components/footer_contribute.html @@ -1,6 +1,3 @@ -{# For the "Report Issue" button on the bottom of pages. #} -{%- extends "!footer.html" %} -{%- block extrafooter %} {%- if not pagename in ("search", "404", "genindex") and hasdoc(pagename) %} -{%- endif %} -{% endblock %} +{%- endif %} \ No newline at end of file diff --git a/doc/python_api/templates/page.html b/doc/python_api/templates/page.html new file mode 100644 index 00000000000..f220bfeee4b --- /dev/null +++ b/doc/python_api/templates/page.html @@ -0,0 +1,6 @@ +{%- extends "!page.html" -%} + +{%- block footer -%} +{{ super() }} +{%- include "components/footer_contribute.html" -%} +{%- endblock footer -%} \ No newline at end of file diff --git a/doc/python_api/templates/versions.html b/doc/python_api/templates/sidebar/variant-selector.html similarity index 71% rename from doc/python_api/templates/versions.html rename to doc/python_api/templates/sidebar/variant-selector.html index 482d4361207..4eb56915f18 100644 --- a/doc/python_api/templates/versions.html +++ b/doc/python_api/templates/sidebar/variant-selector.html @@ -7,7 +7,6 @@ {{ release }} + From 429fea4abf360534676d708083312a06977938a6 Mon Sep 17 00:00:00 2001 From: Aaron Carlisle Date: Tue, 19 Mar 2024 20:51:30 -0400 Subject: [PATCH 2/2] Docs: Python API: Update the version switch to match the user manual Not quite a 1:1 match, some customizations have to be made for the API URL differences and the face that there are no translations. This also enables the version switch publicly now that this change fixes a few bugs. --- doc/python_api/static/css/version_switch.css | 17 +- doc/python_api/static/js/version_switch.js | 578 +++++++++--------- .../templates/sidebar/variant-selector.html | 12 +- 3 files changed, 317 insertions(+), 290 deletions(-) diff --git a/doc/python_api/static/css/version_switch.css b/doc/python_api/static/css/version_switch.css index 80d5c2a52b0..b001a1a2ce7 100644 --- a/doc/python_api/static/css/version_switch.css +++ b/doc/python_api/static/css/version_switch.css @@ -31,23 +31,30 @@ cursor: pointer; z-index: 400; } + .version-btn-open::after { color: gray; } -.version-btn:hover, .version-btn:focus { + +.version-btn:hover, +.version-btn:focus { border-color: #525252; } + .version-btn-open { color: gray; border: solid 1px var(--color-sidebar-background-border); } + .version-btn.wait { cursor: wait; } + .version-btn.disabled { cursor: not-allowed; color: dimgray; } + .version-dialog { display: none; position: absolute; @@ -63,6 +70,7 @@ overflow-y: auto; cursor: default; } + .version-title { padding: 5px; color: var(--color-content-foreground); @@ -72,6 +80,7 @@ background-color: var(--color-brand-primary); border-bottom: solid 1.5px var(--color-sidebar-background-border); } + .version-list { padding-left: 0; margin-top: 0; @@ -80,7 +89,10 @@ border: solid 1px var(--color-sidebar-background-border); border-radius: 0px 0px 3px 3px; } -.version-list a, .version-list span, .version-list li { + +.version-list a, +.version-list span, +.version-list li { position: relative; display: block; font-size: 98%; @@ -90,6 +102,7 @@ padding: 4px 0px; color: var(--color-sidebar-link-text); } + .version-list li { background-color: var(--color-sidebar-background); color: var(--color-sidebar-link-text); diff --git a/doc/python_api/static/js/version_switch.js b/doc/python_api/static/js/version_switch.js index b2d25069fbe..c00253bfe73 100644 --- a/doc/python_api/static/js/version_switch.js +++ b/doc/python_api/static/js/version_switch.js @@ -1,63 +1,60 @@ -(function() { // switch: v1.2 +(function() { // switch: v1.4 "use strict"; var versionsFileUrl = "https://docs.blender.org/PROD/versions.json" var all_versions; -var Popover = function() { - function Popover(id) +class Popover { + constructor(id) { this.isOpen = false; this.type = (id === "version-popover"); - this.$btn = $('#' + id); - this.$dialog = this.$btn.next(); - this.$list = this.$dialog.children("ul"); + this.btn = document.querySelector('#' + id); + this.dialog = this.btn.nextElementSibling; + this.list = this.dialog.querySelector("ul"); this.sel = null; - this.beforeInit(); - } - - Popover.prototype = { - beforeInit : function() { - var that = this; - this.$btn.on("click", function(e) { + const that = this; + this.btnClickHandler = function(e) { + that.init(); + e.preventDefault(); + e.stopPropagation(); + }; + this.btnKeyHandler = function(e) { + if (that.btnKeyFilter(e)) { that.init(); e.preventDefault(); e.stopPropagation(); - }); - this.$btn.on("keydown", function(e) { - if (that.btnKeyFilter(e)) { - that.init(); - e.preventDefault(); - e.stopPropagation(); - } - }); - }, - init : function() { - this.$btn.off("click"); - this.$btn.off("keydown"); + } + }; + this.btn.addEventListener("click", this.btnClickHandler); + this.btn.addEventListener("keydown", this.btnKeyHandler); + } + init() + { + this.btn.removeEventListener("click", this.btnClickHandler); + this.btn.removeEventListener("keydown", this.btnKeyHandler); + + new Promise((resolve, reject) => { if (all_versions === undefined) { - this.$btn.addClass("wait"); - this.loadVL(this); + this.btn.classList.add("wait"); + fetch(versionsFileUrl) + .then((response) => response.json()) + .then((data) => { + all_versions = data; + resolve(); + }) + .catch(() => { + console.error("Version Switch Error: versions.json could not be loaded."); + this.btn.classList.remove("disabled"); + }); } else { - this.afterLoad(); + resolve(); } - }, - loadVL : function(that) { - $.getJSON(versionsFileUrl, function(data) { - all_versions = data; - that.afterLoad(); - return true; - }).fail(function() { - console.log("Version Switch Error: versions.json could not be loaded."); - that.$btn.addClass("disabled"); - return false; - }); - }, - afterLoad : function() { - var release = DOCUMENTATION_OPTIONS.VERSION; + }).then(() => { + let release = DOCUMENTATION_OPTIONS.VERSION; const m = release.match(/\d\.\d+/g); if (m) { release = m[0]; @@ -65,259 +62,274 @@ var Popover = function() { this.warnOld(release, all_versions); - var version = this.getNamed(release); - var list = this.buildList(version); + const version = this.getNamed(release); + this.buildList(version); - this.$list.children(":first-child").remove(); - this.$list.append(list); - var that = this; - this.$list.on("keydown", function(e) { + this.list.firstElementChild.remove(); + const that = this; + this.list.addEventListener("keydown", function(e) { that.keyMove(e); }); - this.$btn.removeClass("wait"); + this.btn.classList.remove("wait"); this.btnOpenHandler(); - this.$btn.on("mousedown", function(e) { + this.btn.addEventListener("mousedown", function(e) { that.btnOpenHandler(); e.preventDefault() }); - this.$btn.on("keydown", function(e) { + this.btn.addEventListener("keydown", function(e) { if (that.btnKeyFilter(e)) { that.btnOpenHandler(); } }); - }, - warnOld : function(release, all_versions) { - // Note this is effectively disabled now, two issues must fixed: - // * versions.js does not contain a current entry, because that leads to - // duplicate version numbers in the menu. These need to be deduplicated. - // * It only shows the warning after opening the menu to switch version - // when versions.js is loaded. This is too late to be useful. - var current = all_versions.current - if (!current) - { - // console.log("Version Switch Error: no 'current' in version.json."); - return; - } - const m = current.match(/\d\.\d+/g); - if (m) { - current = parseFloat(m[0]); - } - if (release < current) { - var currentURL = window.location.pathname.replace(release, current); - var warning = $('
' + - '

Note

' + - '

' + - 'You are not using the most up to date version of the documentation. ' + - ' is the newest version.' + - '

' + - '
'); - - warning.find('a').attr('href', currentURL).text(current); - - var body = $("div.body"); - if (!body.length) { - body = $("div.document"); - } - body.prepend(warning); - } - }, - buildList : function(v) { - var url = new URL(window.location.href); - let pathSplit = [ "", "api", v ]; - if (url.pathname.startsWith("/api/")) { - pathSplit.push(url.pathname.split('/').slice(3).join('/')); - } - else { - pathSplit.push(url.pathname.substring(1)); - } - if (this.type) { - var dyn = all_versions; - var cur = v; - } - var buf = []; - var that = this; - $.each(dyn, function(ix, title) { - buf.push("' + - title + ''); - } - else { - pathSplit[2 + that.type] = ix; - var href = new URL(url); - href.pathname = pathSplit.join('/'); - buf.push(' tabindex="-1" role="presentation">' + - title + ''); - } - }); - return buf.join(''); - }, - getNamed : function(v) { - $.each(all_versions, function(ix, title) { - if (ix === "master" || ix === "main" || ix === "latest") { - var m = title.match(/\d\.\d[\w\d\.]*/)[0]; - if (parseFloat(m) == v) { - v = ix; - return false; - } - } - }); - return v; - }, - dialogToggle : function(speed) { - var wasClose = !this.isOpen; - var that = this; - if (!this.isOpen) { - this.$btn.addClass("version-btn-open"); - this.$btn.attr("aria-pressed", true); - this.$dialog.attr("aria-hidden", false); - this.$dialog.fadeIn(speed, function() { - that.$btn.parent().on("focusout", function(e) { - that.focusoutHandler(); - e.stopImmediatePropagation(); - }) - that.$btn.parent().on("mouseleave", function(e) { - that.mouseoutHandler(); - e.stopImmediatePropagation(); - }); - }); - this.isOpen = true; - } - else { - this.$btn.removeClass("version-btn-open"); - this.$btn.attr("aria-pressed", false); - this.$dialog.attr("aria-hidden", true); - this.$btn.parent().off("focusout"); - this.$btn.parent().off("mouseleave"); - this.$dialog.fadeOut(speed, function() { - if (this.$sel) { - this.$sel.attr("tabindex", -1); - } - that.$btn.attr("tabindex", 0); - if (document.activeElement !== null && document.activeElement !== document && - document.activeElement !== document.body) { - that.$btn.focus(); - } - }); - this.isOpen = false; - } - - if (wasClose) { - if (this.$sel) { - this.$sel.attr("tabindex", -1); - } - if (document.activeElement !== null && document.activeElement !== document && - document.activeElement !== document.body) { - var $nw = this.listEnter(); - $nw.attr("tabindex", 0); - $nw.focus(); - this.$sel = $nw; - } - } - }, - btnOpenHandler : function() { - this.dialogToggle(300); - }, - focusoutHandler : function() { - var list = this.$list; - var that = this; - setTimeout(function() { - if (list.find(":focus").length === 0) { - that.dialogToggle(200); - } - }, 200); - }, - mouseoutHandler : function() { - this.dialogToggle(200); - }, - btnKeyFilter : function(e) { - if (e.ctrlKey || e.shiftKey) { - return false; - } - if (e.key === " " || e.key === "Enter" || (e.key === "ArrowDown" && e.altKey) || - e.key === "ArrowDown" || e.key === "ArrowUp") { - return true; - } - return false; - }, - keyMove : function(e) { - if (e.ctrlKey || e.shiftKey) { - return true; - } - var p = true; - var $nw = $(e.target); - switch (e.key) { - case "ArrowUp": - $nw = this.listPrev($nw); - break; - case "ArrowDown": - $nw = this.listNext($nw); - break; - case "Home": - $nw = this.listFirst(); - break; - case "End": - $nw = this.listLast(); - break; - case "Escape": - $nw = this.listExit(); - break; - case "ArrowLeft": - $nw = this.listExit(); - break; - case "ArrowRight": - $nw = this.listExit(); - break; - default: - p = false; - } - if (p) { - $nw.attr("tabindex", 0); - $nw.focus(); - if (this.$sel) { - this.$sel.attr("tabindex", -1); - } - this.$sel = $nw; - e.preventDefault(); - e.stopPropagation(); - } - }, - listPrev : function($nw) { - if ($nw.parent().prev().length !== 0) { - return $nw.parent().prev().children(":first-child"); - } - else { - return this.listLast(); - } - }, - listNext : function($nw) { - if ($nw.parent().next().length !== 0) { - return $nw.parent().next().children(":first-child"); - } - else { - return this.listFirst(); - } - }, - listFirst : function() { - return this.$list.children(":first-child").children(":first-child"); - }, - listLast : function() { - return this.$list.children(":last-child").children(":first-child"); - }, - listExit : function() { - this.mouseoutHandler(); - return this.$btn; - }, - listEnter : function() { - return this.$list.children(":first-child").children(":first-child"); + }); + } + warnOld(release, all_versions) + { + // Note this is effectively disabled now, two issues must fixed: + // * versions.js does not contain a current entry, because that leads to + // duplicate version numbers in the menu. These need to be deduplicated. + // * It only shows the warning after opening the menu to switch version + // when versions.js is loaded. This is too late to be useful. + let current = all_versions.current + if (!current) { + // console.log("Version Switch Error: no 'current' in version.json."); + return; } - }; - return Popover -}(); + const m = current.match(/\d\.\d+/g); + if (m) { + current = parseFloat(m[0]); + } + if (release < current) { + const currentURL = window.location.pathname.replace(release, current); + const warning = + document.querySelector("template#version-warning").firstElementChild.cloneNode(true); + const link = warning.querySelector('a'); + link.setAttribute('href', currentURL); + link.textContent = current; -$(document).ready(function() { - var lng_popover = new Popover("version-popover"); -}); + let body = document.querySelector("div.body"); + if (!body.length) { + body = document.querySelector("div.document"); + } + body.prepend(warning); + } + } + buildList(v) + { + const url = new URL(window.location.href); + let pathSplit = [ "", "api", v ]; + if (url.pathname.startsWith("/api/")) { + pathSplit.push(url.pathname.split('/').slice(4).join('/')); + } + else { + pathSplit.push(url.pathname.substring(1)); + } + let dyn, cur; + if (this.type) { + dyn = all_versions; + cur = v; + } + const that = this; + const template = document.querySelector("template#version-entry").content; + for (let [ix, title] of Object.entries(dyn)) { + let clone; + if (ix === cur) { + clone = template.querySelector("li.selected").cloneNode(true); + clone.querySelector("span").innerHTML = title; + } + else { + pathSplit[1 + that.type] = ix; + let href = new URL(url); + href.pathname = pathSplit.join('/'); + clone = template.firstElementChild.cloneNode(true); + const link = clone.querySelector("a"); + link.href = href; + link.innerHTML = title; + } + that.list.append(clone); + }; + return this.list; + } + getNamed(v) + { + for (let [ix, title] of Object.entries(all_versions)) { + if (ix === "master" || ix === "main" || ix === "latest") { + const m = title.match(/\d\.\d[\w\d\.]*/)[0]; + if (parseFloat(m) == v) { + v = ix; + return false; + } + } + }; + return v; + } + dialogToggle(speed) + { + const wasClose = !this.isOpen; + const that = this; + if (!this.isOpen) { + this.btn.classList.add("version-btn-open"); + this.btn.setAttribute("aria-pressed", true); + this.dialog.setAttribute("aria-hidden", false); + this.dialog.style.display = "block"; + this.dialog.animate({opacity : [ 0, 1 ], easing : [ 'ease-in', 'ease-out' ]}, speed) + .finished.then(() => { + this.focusoutHandlerPrime = function(e) { + that.focusoutHandler(); + e.stopImmediatePropagation(); + }; + this.mouseoutHandlerPrime = function(e) { + that.mouseoutHandler(); + e.stopImmediatePropagation(); + }; + this.btn.parentNode.addEventListener("focusout", this.focusoutHandlerPrime); + this.btn.parentNode.addEventListener("mouseleave", this.mouseoutHandlerPrime); + }); + this.isOpen = true; + } + else { + this.btn.classList.remove("version-btn-open"); + this.btn.setAttribute("aria-pressed", false); + this.dialog.setAttribute("aria-hidden", true); + this.btn.parentNode.removeEventListener("focusout", this.focusoutHandlerPrime); + this.btn.parentNode.removeEventListener("mouseleave", this.mouseoutHandlerPrime); + this.dialog.animate({opacity : [ 1, 0 ], easing : [ 'ease-in', 'ease-out' ]}, speed) + .finished.then(() => { + this.dialog.style.display = "none"; + if (this.sel) { + this.sel.setAttribute("tabindex", -1); + } + this.btn.setAttribute("tabindex", 0); + if (document.activeElement !== null && document.activeElement !== document && + document.activeElement !== document.body) + { + this.btn.focus(); + } + }); + this.isOpen = false; + } + + if (wasClose) { + if (this.sel) { + this.sel.setAttribute("tabindex", -1); + } + if (document.activeElement !== null && document.activeElement !== document && + document.activeElement !== document.body) + { + const nw = this.listEnter(); + nw.setAttribute("tabindex", 0); + nw.focus(); + this.sel = nw; + } + } + } + btnOpenHandler() + { + this.dialogToggle(300); + } + focusoutHandler() + { + const list = this.list; + const that = this; + setTimeout(function() { + if (!list.querySelector(":focus")) { + that.dialogToggle(200); + } + }, 200); + } + mouseoutHandler() + { + this.dialogToggle(200); + } + btnKeyFilter(e) + { + if (e.ctrlKey || e.shiftKey) { + return false; + } + if (e.key === " " || e.key === "Enter" || (e.key === "ArrowDown" && e.altKey) || + e.key === "ArrowDown" || e.key === "ArrowUp") + { + return true; + } + return false; + } + keyMove(e) + { + if (e.ctrlKey || e.shiftKey) { + return true; + } + let nw = e.target; + switch (e.key) { + case "ArrowUp": + nw = this.listPrev(nw); + break; + case "ArrowDown": + nw = this.listNext(nw); + break; + case "Home": + nw = this.listFirst(); + break; + case "End": + nw = this.listLast(); + break; + case "Escape": + nw = this.listExit(); + break; + case "ArrowLeft": + nw = this.listExit(); + break; + case "ArrowRight": + nw = this.listExit(); + break; + default: + return false; + } + nw.setAttribute("tabindex", 0); + nw.focus(); + if (this.sel) { + this.sel.setAttribute("tabindex", -1); + } + this.sel = nw; + e.preventDefault(); + e.stopPropagation(); + } + listPrev(nw) + { + if (nw.parentNode.previousElementSibling.length !== 0) { + return nw.parentNode.previousElementSibling.firstElementChild; + } + else { + return this.listLast(); + } + } + listNext(nw) + { + if (nw.parentNode.nextElementSibling.length !== 0) { + return nw.parentNode.nextElementSibling.firstElementChild; + } + else { + return this.listFirst(); + } + } + listFirst() + { + return this.list.firstElementChild.firstElementChild; + } + listLast() + { + return this.list.lastElementChild.firstElementChild; + } + listExit() + { + this.mouseoutHandler(); + return this.btn; + } + listEnter() + { + return this.list.firstElementChild.firstElementChild; + } +} + +document.addEventListener('DOMContentLoaded', () => { new Popover("version-popover"); }); })(); diff --git a/doc/python_api/templates/sidebar/variant-selector.html b/doc/python_api/templates/sidebar/variant-selector.html index 4eb56915f18..3bac5f95306 100644 --- a/doc/python_api/templates/sidebar/variant-selector.html +++ b/doc/python_api/templates/sidebar/variant-selector.html @@ -1,19 +1,21 @@