From 2c78cb04544a030e40924f02d143ee863cd54080 Mon Sep 17 00:00:00 2001 From: Daniela Rus Morales Date: Sun, 10 Mar 2024 08:27:05 +0100 Subject: [PATCH 1/3] Prepare rel 0.3.0 --- ChangeLog.md | 5 + js/src/index.js | 5 +- js/src/tocresize.js | 16 +- js/src/versions.js | 36 ++ js/tests/unit/tocresize.spec.js | 8 +- scss/_layout.scss | 80 +-- scss/components/_toc.scss | 2 +- setup.py | 2 +- site/apidoc.html | 847 +++++++++++++++++++++++++ site/index.html | 129 ++-- site/index_wo_toc.html | 629 ++++++++++++++++++ sphinx_nefertiti/__init__.py | 10 +- sphinx_nefertiti/layout.html | 63 +- sphinx_nefertiti/version-dropdown.html | 10 +- sphinx_nefertiti/versions.py | 21 + 15 files changed, 1699 insertions(+), 164 deletions(-) create mode 100644 site/apidoc.html create mode 100644 site/index_wo_toc.html create mode 100644 sphinx_nefertiti/versions.py diff --git a/ChangeLog.md b/ChangeLog.md index 5ed7d25..dec89d5 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,5 +1,10 @@ # Change Log +## [0.3.0] - 2024-03-10 + +- When producing the HTML static site, create a file called `doc_versions.js`, containing the list of versions (name and url) specified in the `version` entry of the `html_theme_options`. +- Change the CSS layout so that the grid centers itself, regardless of whether the toc at the right side is visible or not. + ## [0.2.3] - 2024-02-20 - Use a fluid layout (Bootstrap's `container-fluid`) to allow expanding the TOC diff --git a/js/src/index.js b/js/src/index.js index 594cac3..feb9df8 100644 --- a/js/src/index.js +++ b/js/src/index.js @@ -4,7 +4,7 @@ import { MenuHandler } from "./menu.js"; import { updateRepoMetrics } from "./repometrics.js"; import { TocObserver } from "./pagetoc.js"; import { resizeAsides, updateScrollPaddingTop } from "./tocresize.js"; -import { updateVersion } from "./versions.js"; +import { feedVersionsMenu, updateVersion } from "./versions.js"; function agentHas(keyword) { @@ -56,6 +56,9 @@ window.addEventListener('DOMContentLoaded', (_) => { const luz_handler = new LuzHandler(); luz_handler.registerClickEvents(); + // Feed the versions dropdown element. + feedVersionsMenu(); + // The updateVersion function controls the display of the version // in the header, adding the CSS class "current" to display the // tick symbol near the version selected. diff --git a/js/src/tocresize.js b/js/src/tocresize.js index da4f5d3..e28e3cb 100644 --- a/js/src/tocresize.js +++ b/js/src/tocresize.js @@ -30,21 +30,7 @@ export function resizeAsides() { nftt_toc?.setAttribute("style", height); } - // Compute new 'grid-template-columns' for nftt-layout. - const max = (window.innerWidth - nftt_content.offsetWidth); - const newmax = nftt_toc - ? Math.floor(max / 2) - : ( - nftt_sidebar_content - ? Math.floor((max + nftt_sidebar_content.offsetWidth) / 2) - : undefined - ); - if (newmax) { - const newgtcols = `minmax(300px, ${newmax}px) 5fr`; - nftt_layout?.setAttribute("style", `grid-template-columns: ${newgtcols}`); - } - - return [height, newmax]; + return height; } diff --git a/js/src/versions.js b/js/src/versions.js index 6c08248..6f4abc4 100644 --- a/js/src/versions.js +++ b/js/src/versions.js @@ -32,3 +32,39 @@ export function updateVersion() { } } } + +export function feedVersionsMenu() { + const vermenu = document.getElementById("versions-dropdown-menu"); + if (!vermenu) { + console.log("Did not find the versions dropdown menu."); + return; + } + // Use the variable 'doc_versions', loaded as a script in layout.html. + // The file doc_versions.js is produced by versions.py when building + // the site (make html). + console.log("Found the versions dropdown menu!"); + console.log("doc_versions are:"); + console.dir(doc_versions); + + for (const item of doc_versions) { + const li = document.createElement("li"); + const anchor = document.createElement("a"); + anchor.classList.add( + "dropdown-item", "d-flex", "align-items-center", + "justify-content-between" + ); + anchor.setAttribute("aria-pressed", "false"); + anchor.setAttribute("href", item.url); + anchor.dataset.snfttVersionUrl = item.url; + anchor.dataset.snfttVersion = item.name; + const span = document.createElement("span"); + span.classList.add("small", "ms-2"); + span.textContent = item.name; + const i = document.createElement("i"); + i.classList.add("bi", "bi-check", "ms-auto"); + anchor.append(span); + anchor.append(i); + li.append(anchor); + vermenu.append(li); + } +} \ No newline at end of file diff --git a/js/tests/unit/tocresize.spec.js b/js/tests/unit/tocresize.spec.js index c0539f0..e3749ea 100644 --- a/js/tests/unit/tocresize.spec.js +++ b/js/tests/unit/tocresize.spec.js @@ -70,8 +70,8 @@ describe('resize', () => { } ); - const results = resizeAsides(); - expect(results[0]).toEqual("height: calc(100vh - 7rem)"); + const result = resizeAsides(); + expect(result).toEqual("height: calc(100vh - 7rem)"); }); it('checks resizeAsides when nftt_content shorter than body', () => { @@ -90,7 +90,7 @@ describe('resize', () => { } ); - const results = resizeAsides(); - expect(results[0]).toEqual(expected_height); + const result = resizeAsides(); + expect(result).toEqual(expected_height); }); }); \ No newline at end of file diff --git a/scss/_layout.scss b/scss/_layout.scss index 1218dc1..91bddb0 100644 --- a/scss/_layout.scss +++ b/scss/_layout.scss @@ -23,17 +23,52 @@ body { padding-top: 63px; } -.nftt-layout { +.nftt-page { flex: 1 0 auto; margin-top: 2.5rem; margin-bottom: 1.5rem; @include media-breakpoint-up(xl) { display: grid; - grid-template-areas: "sidebar main"; - grid-template-columns: minmax(300px, 1.5fr) 5fr; + grid-template-areas: "sidebar content toc"; + grid-template-columns: minmax(300px, 1.5fr) minmax(570px, 760px) minmax(190px, 1.3fr); gap: 25px; } + + @include media-breakpoint-down(xl) { + display: grid; + grid-template-areas: "content toc"; + grid-template-columns: minmax(570px, 5fr) minmax(190px, 1fr); + gap: 25px; + } + + @include media-breakpoint-down(lg) { + max-width: 892px; + margin-inline: auto; + grid-template-areas: + "toc" + "content"; + grid-template-rows: 1fr auto; + grid-template-columns: none; + } +} + +.nftt-page-wo-toc { + flex: 1 0 auto; + margin-top: 2.5rem; + margin-bottom: 1.5rem; + + @include media-breakpoint-up(xl) { + display: grid; + grid-template-areas: "sidebar content toc"; + grid-template-columns: minmax(300px, 1.5fr) minmax(570px, 760px) minmax(190px, 1.3fr); + gap: 25px; + } + + @include media-breakpoint-down(xl) { + max-width: 92%; + margin-inline: auto; + } } .nftt-sidebar { @@ -59,39 +94,6 @@ body { } } -.nftt-main-wo-toc { - display: grid; - grid-area: main; - grid-template-areas: "content"; - grid-template-columns: minmax(570px, 860px); - padding-right: 48px; - - @include media-breakpoint-down(xl) { - grid-template-columns: none; - padding-right: 0; - } -} - -.nftt-main { - display: grid; - grid-area: main; - grid-template-areas: - "toc" - "content"; - grid-template-rows: 1fr auto; - gap: inherit; - - @include media-breakpoint-down(xl) { - max-width: 1100px; - margin-inline: auto; - } - - @include media-breakpoint-up(lg) { - grid-template-areas: "content toc"; - grid-template-columns: minmax(570px, 760px) minmax(190px, 1.3fr); - } -} - .nftt-toc { grid-area: toc; @@ -100,6 +102,12 @@ body { font-family: var(--#{$prefix}font-monospace); font-size: .7rem; } + + @include media-breakpoint-down(lg) { + padding-right: 2rem; + padding-left: 2rem; + } + } diff --git a/scss/components/_toc.scss b/scss/components/_toc.scss index b873189..8dec4d3 100644 --- a/scss/components/_toc.scss +++ b/scss/components/_toc.scss @@ -203,7 +203,7 @@ display: flex; align-items: center; - @include media-breakpoint-down(sm) { + @include media-breakpoint-down(md) { justify-content: space-between; width: 100%; } diff --git a/setup.py b/setup.py index a4b95ab..1aa1662 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ setup( name="sphinx-nefertiti", - version="0.2.3", + version="0.3.0", packages=find_packages(), include_package_data=True, license="MIT", diff --git a/site/apidoc.html b/site/apidoc.html new file mode 100644 index 0000000..a4b9717 --- /dev/null +++ b/site/apidoc.html @@ -0,0 +1,847 @@ + + + + + + + + + + + + Nefertiti Theme documentation — Nefertiti for Sphinx 0.1.0 documentation + + + + + + + +
+
+ + +
+
+

API documentation

+

Using Sphinx’s sphinx.ext.autodoc plugin, it is possible to auto-generate documentation of a Python module.

+
+

Tip

+

Avoid having in-function-signature type annotations with autodoc, + by setting the following options:

+
+
+
# -- Options for autodoc ----------------------------------------------------
+    # https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#configuration
+
+    # Automatically extract typehints when specified and place them in
+    # descriptions of the relevant function/method.
+    autodoc_typehints = "description"
+
+    # Don't show class signature with the class' name.
+    autodoc_class_signature = "separated"
+    
+
+
+
+

Parse (absolute and relative) URLs.

+

urlparse module is based upon the following RFC specifications.

+

RFC 3986 (STD66): “Uniform Resource Identifiers” by T. Berners-Lee, R. Fielding + and L. Masinter, January 2005.

+

RFC 2732 : “Format for Literal IPv6 Addresses in URL’s by R.Hinden, B.Carpenter + and L.Masinter, December 1999.

+

RFC 2396: “Uniform Resource Identifiers (URI)”: Generic Syntax by T. + Berners-Lee, R. Fielding, and L. Masinter, August 1998.

+

RFC 2368: “The mailto URL scheme”, by P.Hoffman , L Masinter, J. Zawinski, July 1998.

+

RFC 1808: “Relative Uniform Resource Locators”, by R. Fielding, UC Irvine, June + 1995.

+

RFC 1738: “Uniform Resource Locators (URL)” by T. Berners-Lee, L. Masinter, M. + McCahill, December 1994

+

RFC 3986 is considered the current standard and any future changes to + urlparse module should conform with it. The urlparse module is + currently not entirely compliant with this RFC due to defacto + scenarios for parsing, and for backward compatibility purposes, some + parsing quirks from older RFCs are retained. The testcases in + test_urlparse.py provides a good indicator of parsing behavior.

+
+
+ class urllib.parse.DefragResult(url, fragment)[source] +
+
+
+ +
+
+ class urllib.parse.DefragResultBytes(url, fragment)[source] +
+
+
+ +
+
+ class urllib.parse.ParseResult(scheme, netloc, path, params, query, fragment)[source] +
+
+
+ +
+
+ class urllib.parse.ParseResultBytes(scheme, netloc, path, params, query, fragment)[source] +
+
+
+ +
+
+ class urllib.parse.SplitResult(scheme, netloc, path, query, fragment)[source] +
+
+
+ +
+
+ class urllib.parse.SplitResultBytes(scheme, netloc, path, query, fragment)[source] +
+
+
+ +
+
+ urllib.parse.parse_qs(qs, keep_blank_values=False, strict_parsing=False, encoding='utf-8', errors='replace', max_num_fields=None, separator='&')[source] +
+
+

Parse a query given as a string argument.

+

Arguments:

+

qs: percent-encoded query string to be parsed

+
+
keep_blank_values: flag indicating whether blank values in
+
+

percent-encoded queries should be treated as blank strings. + A true value indicates that blanks should be retained as + blank strings. The default false value indicates that + blank values are to be ignored and treated as if they were + not included.

+
+
strict_parsing: flag indicating what to do with parsing errors.
+
+

If false (the default), errors are silently ignored. + If true, errors raise a ValueError exception.

+
+
encoding and errors: specify how to decode percent-encoded sequences
+
+

into Unicode characters, as accepted by the bytes.decode() method.

+
+
max_num_fields: int. If set, then throws a ValueError if there
+
+

are more than n fields read by parse_qsl().

+
+
separator: str. The symbol to use for separating the query arguments.
+
+

Defaults to &.

+
+
+

Returns a dictionary.

+
+
+ +
+
+ urllib.parse.parse_qsl(qs, keep_blank_values=False, strict_parsing=False, encoding='utf-8', errors='replace', max_num_fields=None, separator='&')[source] +
+
+

Parse a query given as a string argument.

+

Arguments:

+

qs: percent-encoded query string to be parsed

+
+
keep_blank_values: flag indicating whether blank values in
+
+

percent-encoded queries should be treated as blank strings. + A true value indicates that blanks should be retained as blank + strings. The default false value indicates that blank values + are to be ignored and treated as if they were not included.

+
+
strict_parsing: flag indicating what to do with parsing errors. If
+
+

false (the default), errors are silently ignored. If true, + errors raise a ValueError exception.

+
+
encoding and errors: specify how to decode percent-encoded sequences
+
+

into Unicode characters, as accepted by the bytes.decode() method.

+
+
max_num_fields: int. If set, then throws a ValueError
+
+

if there are more than n fields read by parse_qsl().

+
+
separator: str. The symbol to use for separating the query arguments.
+
+

Defaults to &.

+
+
+

Returns a list, as G-d intended.

+
+
+ +
+
+ urllib.parse.quote('abc def') 'abc%20def'[source] +
+
+

Each part of a URL, e.g. the path info, the query, etc., has a + different set of reserved characters that must be quoted. The + quote function offers a cautious (not minimal) way to quote a + string for most of these parts.

+

RFC 3986 Uniform Resource Identifier (URI): Generic Syntax lists + the following (un)reserved characters.

+

unreserved = ALPHA / DIGIT / “-” / “.” / “_” / “~” + reserved = gen-delims / sub-delims + gen-delims = “:” / “/” / “?” / “#” / “[” / “]” / “@” + sub-delims = “!” / “$” / “&” / “’” / “(” / “)”

+
+
+

/ “*” / “+” / “,” / “;” / “=”

+
+
+

Each of the reserved characters is reserved in some component of a URL, + but not necessarily in all of them.

+

The quote function %-escapes all characters that are neither in the + unreserved chars (“always safe”) nor the additional chars set via the + safe arg.

+

The default for the safe arg is ‘/’. The character is reserved, but in + typical usage the quote function is being called on a path where the + existing slash characters are to be preserved.

+

Python 3.7 updates from using RFC 2396 to RFC 3986 to quote URL strings. + Now, “~” is included in the set of unreserved characters.

+

string and safe may be either str or bytes objects. encoding and errors + must not be specified if string is a bytes object.

+

The optional encoding and errors parameters specify how to deal with + non-ASCII characters, as accepted by the str.encode method. + By default, encoding=’utf-8’ (characters are encoded with UTF-8), and + errors=’strict’ (unsupported characters raise a UnicodeEncodeError).

+
+
+ +
+
+ urllib.parse.quote_from_bytes(bs, safe='/')[source] +
+
+

Like quote(), but accepts a bytes object rather than a str, and does + not perform string-to-bytes encoding. It always returns an ASCII string. + quote_from_bytes(b’abc def?’) -> ‘abc%20def%3f’

+
+
+ +
+
+ urllib.parse.quote_plus(string, safe='', encoding=None, errors=None)[source] +
+
+

Like quote(), but also replace ‘ ‘ with ‘+’, as required for quoting + HTML form values. Plus signs in the original string are escaped unless + they are included in safe. It also does not have safe default to ‘/’.

+
+
+ +
+
+ urllib.parse.unquote(string, encoding='utf-8', errors='replace')[source] +
+
+

Replace %xx escapes by their single-character equivalent. The optional + encoding and errors parameters specify how to decode percent-encoded + sequences into Unicode characters, as accepted by the bytes.decode() + method. + By default, percent-encoded sequences are decoded with UTF-8, and invalid + sequences are replaced by a placeholder character.

+

unquote(‘abc%20def’) -> ‘abc def’.

+
+
+ +
+
+ urllib.parse.unquote_plus(string, encoding='utf-8', errors='replace')[source] +
+
+

Like unquote(), but also replace plus signs by spaces, as required for + unquoting HTML form values.

+

unquote_plus(‘%7e/abc+def’) -> ‘~/abc def’

+
+
+ +
+
+ urllib.parse.unquote_to_bytes('abc%20def') b'abc def'.[source] +
+
+
+ +
+
+ urllib.parse.urldefrag(url)[source] +
+
+

Removes any existing fragment from URL.

+

Returns a tuple of the defragmented URL and the fragment. If + the URL contained no fragments, the second element is the + empty string.

+
+
+ +
+
+ urllib.parse.urlencode(query, doseq=False, safe='', encoding=None, errors=None, quote_via=<function quote_plus>)[source] +
+
+

Encode a dict or sequence of two-element tuples into a URL query string.

+

If any values in the query arg are sequences and doseq is true, each + sequence element is converted to a separate parameter.

+

If the query arg is a sequence of two-element tuples, the order of the + parameters in the output will match the order of parameters in the + input.

+

The components of a query arg may each be either a string or a bytes type.

+

The safe, encoding, and errors parameters are passed down to the function + specified by quote_via (encoding and errors only if a component is a str).

+
+
+ +
+
+ urllib.parse.urljoin(base, url, allow_fragments=True)[source] +
+
+

Join a base URL and a possibly relative URL to form an absolute + interpretation of the latter.

+
+
+ +
+
+ urllib.parse.urlparse(url, scheme='', allow_fragments=True)[source] +
+
+

Parse a URL into 6 components: + <scheme>://<netloc>/<path>;<params>?<query>#<fragment>

+

The result is a named 6-tuple with fields corresponding to the + above. It is either a ParseResult or ParseResultBytes object, + depending on the type of the url parameter.

+

The username, password, hostname, and port sub-components of netloc + can also be accessed as attributes of the returned object.

+

The scheme argument provides the default value of the scheme + component when no scheme is found in url.

+

If allow_fragments is False, no attempt is made to separate the + fragment component from the previous component, which can be either + path or query.

+

Note that % escapes are not expanded.

+
+
+ +
+
+ urllib.parse.urlsplit(url, scheme='', allow_fragments=True)[source] +
+
+

Parse a URL into 5 components: + <scheme>://<netloc>/<path>?<query>#<fragment>

+

The result is a named 5-tuple with fields corresponding to the + above. It is either a SplitResult or SplitResultBytes object, + depending on the type of the url parameter.

+

The username, password, hostname, and port sub-components of netloc + can also be accessed as attributes of the returned object.

+

The scheme argument provides the default value of the scheme + component when no scheme is found in url.

+

If allow_fragments is False, no attempt is made to separate the + fragment component from the previous component, which can be either + path or query.

+

Note that % escapes are not expanded.

+
+
+ +
+
+ urllib.parse.urlunparse(components)[source] +
+
+

Put a parsed URL back together again. This may result in a + slightly different, but equivalent URL, if the URL that was parsed + originally had redundant delimiters, e.g. a ? with an empty query + (the draft states that these are equivalent).

+
+
+ +
+
+ urllib.parse.urlunsplit(components)[source] +
+
+

Combine the elements of a tuple as returned by urlsplit() into a + complete URL as a string. The data argument can be any five-item iterable. + This may result in a slightly different, but equivalent URL, if the URL that + was parsed originally had unnecessary delimiters (for example, a ? with an + empty query; the RFC states that these are equivalent).

+
+
+ +
+ +
+ + +
+
+ + + + + + + + \ No newline at end of file diff --git a/site/index.html b/site/index.html index 9f9d3ce..cd520a5 100644 --- a/site/index.html +++ b/site/index.html @@ -33,7 +33,7 @@ -
-

Quick start

@@ -547,8 +547,7 @@

6. Footer links + + Nefertiti for SphinxNefertiti for Sphinx + +
+ + +
+ +
+
+
Search the documentation
+ +
+
+
+ +
+
+ + + + + +
+
+ +
+
+

Quick start

+

Nefertiti for Sphinx can be setup and running in a blink of an eye. Follow this quick start guide to go through the minimal steps to use it with your project.

+
+

Note

+

This page assumes that you want to try Nefertiti on an already existing Sphinx project.

+
+ +
+

Customize the theme

+

The following details of Nefertiti for Sphinx can be customized:

+
+
    +
  1. Fonts.

  2. +
  3. Color set: blue, indigo, purple, pink, red, …

  4. +
  5. Pygments styles for light and dark color schemes.

  6. +
  7. Repository name and URL to display it in the header.

  8. +
  9. Project version dropdown selector.

  10. +
  11. Footer links.

  12. +
+
+

They all are entries of the html_theme_options setting of your conf.py file.

+
+

1. Fonts

+

There are 5 options related to font face customization and 2 options related to font size.

+

The font face options are:

+
+
    +
  • sans_serif_font: Default site font, defaults to Nunito.

  • +
  • monospace_font: Default monospace font, for code blocks, defaults to Red Hat Mono.

  • +
  • project_name_font: The font for the Project’s name. sans_serif_font otherwise.

  • +
  • documentation_font: reStructuredText and Markdown content. sans_serif_font otherwise.

  • +
  • doc_headers_font: To render documentation headers. documentation_font otherwise.

  • +
+
+

And the font size options:

+
+
    +
  • monospace_font_size: The CSS font-size for the monospace_font. ie: "1rem".

  • +
  • documentation_font_size: The CSS font-size for the documentation_font. ie: "1.1rem".

  • +
+
+

Edit your conf.py file and add or modify your html_theme_options setting with the following content. By the way, the fonts referred to in this example are part of the Nefertiti for Sphinx distribution. If you want to use other Open Source licensed fonts read the section “How to add more fonts” to include them with your project.

+
html_theme_options = {
+    "sans_serif_font": "Mulish",
+    "monospace_font": "Ubuntu Mono",
+    "monospace_font_size": "1.1rem",
+    "project_name_font": "Montserrat",
+    "documentation_font": "Assistant",
+    "documentation_font_size": "1.1rem",
+    "doc_headers_font": "Georgia",
+}
+
+
+

Save the content, clean up the build directory, build it and serve it again:

+
$ make clean
+    $ make html
+    $ python -m http.server -d build/html
+
+
+

Visit http://localhost:8000 to take a look at the changes.

+
+
+

2. Color sets

+

Another customizable feature of the theme is the color set. In the header of this documentation you can see a dropdown with a palette icon. The colors listed in the dropdown represent the available color sets. Try them to apply the selected color set to this documentation.

+

To customize the color set in your project add an entry style to the html_theme_options setting in your conf.py module:

+
html_theme_options = {
+    # ... other options ...
+    "style": "pink",
+}
+
+
+

When style is not given the theme defaults to cyan.

+
+
+

3. Pygments styles

+

Pygments is the package in charge of rendering code blocks. Sphinx supports two settings related with pygments:

+
+
    +
  • pygments_style, applied when browser’s prefers-color-scheme returns light.

  • +
  • pygments_dark_style, applied when browser’s prefers-color-scheme returns dark.

  • +
+
+

Nefertiti for Sphinx extends the use of these settings in a way that their styling is applied when the user selects the scheme in the light/dark dropdown, at the right side of the header.

+

If your Sphinx project has code-blocks, try changing the pygments style settings and see how they are applied when switching between light and dark schemes in the header. Update your conf.py module:

+
html_theme_options = {
+    # ... other options ...
+    "pygments_style": "solarized-light",
+    "pygments_dark_style": "solarized-dark",
+}
+
+
+

Then clean up and rebuild the project, as usual.

+

See more code blocks rendered with Pygments in the Code blocks document in Nefertiti User’s guide.

+
+
+

4. Repository data

+

If your Sphinx project is about a source code product, and it resides in a Git repository, in GitHub or GitLab, Nefertiti can display information relative to your repository in the header.

+

Just add the repository_name and repository_url keys to your html_theme_options setting:

+
html_theme_options = {
+    # ... other options ...
+    "repository_name": "danirus/sphinx-nefertiti",
+    "repository_url": "https://github.com/danirus/sphinx-nefertiti",
+}
+
+
+

Clean up and rebuild the project, as usual.

+
+
+

5. Version dropdown

+

If your project is available in different versions Nefertiti for Sphinx can display a dropdown in the header to switch between them. If you use readthedocs it is enabled by default so long as you have enabled different versions in the admin interface of readthedocs.

+

If you host different versions in different URLs, like:

+ + + + + + + + + + + + + + + + + +

Version

URL

v2.9.9

https://django-comments-xtd.readthedocs.io/en/latest/

v2.8.5

https://django-comments-xtd.readthedocs.io/en/2.8.5/

v2.7.2

https://django-comments-xtd.readthedocs.io/en/2.7.2/

+

Enable the version dropdown by adding the versions key to your html_theme_options setting:

+
html_theme_options = {
+    # ... other options ...
+    "versions": [
+        ("v2.9.9", "https://django-comments-xtd.readthedocs.io/en/latest/"),
+        ("v2.8.5", "https://django-comments-xtd.readthedocs.io/en/2.8.5/"),
+        ("v2.7.2", "https://django-comments-xtd.readthedocs.io/en/2.7.2/"),
+    ]
+}
+
+
+

Clean up and rebuild the project, as usual.

+
+ +
+
+
+ +
+
+ + + + + + diff --git a/sphinx_nefertiti/__init__.py b/sphinx_nefertiti/__init__.py index 3763356..cfc68be 100644 --- a/sphinx_nefertiti/__init__.py +++ b/sphinx_nefertiti/__init__.py @@ -1,10 +1,11 @@ """sphinx-nefertiti theme""" +import json from pathlib import Path import pkg_resources import sys -from sphinx_nefertiti import colorsets, fonts, pygments +from sphinx_nefertiti import colorsets, fonts, pygments, versions __version__ = pkg_resources.require("sphinx_nefertiti")[0].version @@ -29,6 +30,9 @@ def initialize_theme(app): pygments_provider = pygments.PygmentsProvider(app) app.pygments_assets = [asset for asset in pygments_provider] + version_provider = versions.VersionProvider(app) + app.all_versions = [version for version in version_provider] + except (fonts.FontNotSupportedException, Exception) as exc: print(exc) sys.exit(1) @@ -48,6 +52,10 @@ def copy_nefertiti_files(app, exc): for asset in app.pygments_assets: asset.create_pygments_style_file(app.builder.outdir) + versions_json = Path(app.builder.outdir) / "_static" / "doc_versions.js" + with versions_json.open("w") as f: + f.write("const doc_versions = " + json.dumps(app.all_versions)) + def update_context(app, pagename, templatename, context, doctree): context["nefertiti_version"] = __version__ diff --git a/sphinx_nefertiti/layout.html b/sphinx_nefertiti/layout.html index e4e5bb5..ffa192e 100644 --- a/sphinx_nefertiti/layout.html +++ b/sphinx_nefertiti/layout.html @@ -96,7 +96,7 @@ -
- -
+
{%- block body %} {% endblock -%}
- {% if display_toc and not hidetoc %} -
+
+ +
+
+ {% endif %} + +