PKvMXdp((sphinx_book_theme/__init__.py"""A lightweight book theme based on the pydata sphinx theme.""" import hashlib import os from pathlib import Path from functools import lru_cache from docutils import nodes as docutil_nodes from sphinx.application import Sphinx from sphinx.locale import get_translation from sphinx.util import logging from pydata_sphinx_theme.utils import get_theme_options_dict from .directives import Margin from .nodes import SideNoteNode from .header_buttons import ( prep_header_buttons, add_header_buttons, update_sourcename, update_context_with_repository_info, ) from .header_buttons.launch import add_launch_buttons from .header_buttons.source import add_source_buttons from ._transforms import HandleFootnoteTransform __version__ = "1.1.2" """sphinx-book-theme version""" SPHINX_LOGGER = logging.getLogger(__name__) DEFAULT_LOG_TYPE = "sphinxbooktheme" MESSAGE_CATALOG_NAME = "booktheme" def get_html_theme_path(): """Return list of HTML theme paths.""" parent = Path(__file__).parent.resolve() theme_path = parent / "theme" / "sphinx_book_theme" return theme_path def add_metadata_to_page(app, pagename, templatename, context, doctree): """Adds some metadata about the page that we reuse later.""" # Add the site title to our context so it can be inserted into the navbar if not context.get("root_doc"): # TODO: Sphinx renamed master to root in 4.x, deprecate when we drop 3.x context["root_doc"] = context.get("master_doc") context["root_title"] = app.env.titles[context["root_doc"]].astext() # Update the page title because HTML makes it into the page title occasionally if pagename in app.env.titles: title = app.env.titles[pagename] context["pagetitle"] = title.astext() # Add a shortened page text to the context using the sections text if doctree: description = "" for section in doctree.traverse(docutil_nodes.section): description += section.astext().replace("\n", " ") description = description[:160] context["page_description"] = description # Add the author if it exists if app.config.author != "unknown": context["author"] = app.config.author # Translations translation = get_translation(MESSAGE_CATALOG_NAME) context["translate"] = translation # If search text hasn't been manually specified, use a shorter one here theme_options = get_theme_options_dict(app) if "search_bar_text" not in theme_options: context["theme_search_bar_text"] = translation("Search") + "..." @lru_cache(maxsize=None) def _gen_hash(path: str) -> str: return hashlib.sha1(path.read_bytes()).hexdigest() def hash_assets_for_files(assets: list, theme_static: Path, context, app): """Generate a hash for assets, and append to its entry in context. assets: a list of assets to hash, each path should be relative to the theme's static folder. theme_static: a path to the theme's static folder. context: the Sphinx context object where asset links are stored. These are: `css_files` and `script_files` keys. """ for asset_path in assets: # CSS assets are stored in css_files, JS assets in script_files asset_type = "css_files" if asset_path.endswith(".css") else "script_files" if asset_type in context: # Define paths to the original asset file, and its linked file in Sphinx asset_source_path = theme_static / asset_path if not asset_source_path.exists(): SPHINX_LOGGER.warning( f"Asset {asset_source_path} does not exist, not linking." ) # Find this asset in context, and update it to include the digest for ii, other_asset in enumerate(context[asset_type]): # TODO: eventually the contents of context['css_files'] etc should probably # only be _CascadingStyleSheet etc. For now, assume mixed with strings. if getattr(other_asset, "filename", str(other_asset)) != asset_path: continue # Take priority from existing asset or use default priority (500) priority = getattr(other_asset, "priority", 500) # Remove existing asset del context[asset_type][ii] # Add new asset app.add_css_file( asset_path, digest=_gen_hash(asset_source_path), priority=priority, ) def hash_html_assets(app, pagename, templatename, context, doctree): """Add ?digest={hash} to assets in order to bust cache when changes are made. The source files are in `static` while the built HTML is in `_static`. """ assets = ["scripts/sphinx-book-theme.js"] # Only append the book theme CSS if it's explicitly this theme. Sub-themes # will define their own CSS file, so if a sub-theme is used, this code is # run but the book theme CSS file won't be linked in Sphinx. if app.config.html_theme == "sphinx_book_theme": assets.append("styles/sphinx-book-theme.css") hash_assets_for_files(assets, get_html_theme_path() / "static", context, app) def update_mode_thebe_config(app): """Update thebe configuration with SBT-specific values""" theme_options = get_theme_options_dict(app) if theme_options.get("launch_buttons", {}).get("thebe") is True: # In case somebody specifies they want thebe in a launch button # but has not activated the sphinx_thebe extension. if not hasattr(app.env.config, "thebe_config"): SPHINX_LOGGER.warning( ( "Thebe is activated but not added to extensions list. " "Add `sphinx_thebe` to your site's extensions list." ) ) return # Will be empty if it doesn't exist thebe_config = app.env.config.thebe_config else: return if not theme_options.get("launch_buttons", {}).get("thebe"): return # Update the repository branch and URL # Assume that if there's already a thebe_config, then we don't want to over-ride if "repository_url" not in thebe_config: thebe_config["repository_url"] = theme_options.get("repository_url") if "repository_branch" not in thebe_config: branch = theme_options.get("repository_branch") if not branch: # Explicitly check in case branch is "" branch = "master" thebe_config["repository_branch"] = branch app.env.config.thebe_config = thebe_config def check_deprecation_keys(app): """Warns about the deprecated keys.""" deprecated_config_list = ["single_page"] for key in deprecated_config_list: if key in get_theme_options_dict(app): SPHINX_LOGGER.warning( f"'{key}' was deprecated from version 0.3.4 onwards. See the CHANGELOG for more information: https://github.com/executablebooks/sphinx-book-theme/blob/master/CHANGELOG.md" # noqa: E501 f"[{DEFAULT_LOG_TYPE}]", type=DEFAULT_LOG_TYPE, ) def update_general_config(app, config): theme_dir = get_html_theme_path() config.templates_path.append(os.path.join(theme_dir, "components")) def update_templates(app, pagename, templatename, context, doctree): """Update template names and assets for page build. This is a copy of what the pydata theme does here to include a new section - https://github.com/pydata/pydata-sphinx-theme/blob/0a4894fab49befc59eb497811949a1d0ede626eb/src/pydata_sphinx_theme/__init__.py#L173 # noqa: E501 """ # Allow for more flexibility in template names template_sections = ["theme_footer_content_items"] for section in template_sections: if context.get(section): # Break apart `,` separated strings so we can use , in the defaults if isinstance(context.get(section), str): context[section] = [ ii.strip() for ii in context.get(section).split(",") ] # Add `.html` to templates with no suffix for ii, template in enumerate(context.get(section)): if not os.path.splitext(template)[1]: context[section][ii] = template + ".html" def setup(app: Sphinx): # Register theme theme_dir = get_html_theme_path() app.add_html_theme("sphinx_book_theme", theme_dir) app.add_js_file("scripts/sphinx-book-theme.js") # Translations locale_dir = os.path.join(theme_dir, "static", "locales") app.add_message_catalog(MESSAGE_CATALOG_NAME, locale_dir) # Events app.connect("builder-inited", update_mode_thebe_config) app.connect("builder-inited", check_deprecation_keys) app.connect("builder-inited", update_sourcename) app.connect("builder-inited", update_context_with_repository_info) app.connect("html-page-context", add_metadata_to_page) app.connect("html-page-context", hash_html_assets) app.connect("html-page-context", update_templates) # This extension has both theme-like and extension-like features. # Themes are initialised immediately before use, thus we cannot # rely on an event to set the config - the theme config must be # set in setup(app): update_general_config(app, app.config) # Meanwhile, extensions are initialised _first_, and any config # values set during setup() will be overwritten. We must therefore # register the `config-inited` event to set these config options app.connect("config-inited", update_general_config) # Nodes SideNoteNode.add_node(app) # Header buttons app.connect("html-page-context", prep_header_buttons) # Bump priority so that it runs after the pydata theme sets up the edit URL func. app.connect("html-page-context", add_launch_buttons, priority=501) app.connect("html-page-context", add_source_buttons, priority=501) app.connect("html-page-context", add_header_buttons, priority=501) # Directives app.add_directive("margin", Margin) # Post-transforms app.add_post_transform(HandleFootnoteTransform) return { "parallel_read_safe": True, "parallel_write_safe": True, } PKvMX/Q *sphinx_book_theme/_compile_translations.py"""Generate compiled static translation assets for Sphinx.""" import json import os from pathlib import Path import subprocess # In case the smodin.io code is different from the Sphinx code RENAME_LANGUAGE_CODES = { "zh-cn": "zh_CN", "zh-tw": "zh_TW", } def convert_json(folder=None): """Convert JSON translations into .mo/.po files for Sphinx. folder: the source folder of the JSON translations. This function will put the compiled .mo/.po files in a specific folder relative to this source folder. This parameter is just provided to make testing easier. """ # Raw translation JSONs that are hand-edited folder = folder or Path(__file__).parent / "assets" / "translations" # Location of compiled static translation assets out_folder = folder / ".." / ".." / "theme" / "sphinx_book_theme" / "static" # compile po for path in (folder / "jsons").glob("*.json"): data = json.loads(path.read_text("utf8")) assert data[0]["symbol"] == "en" english = data[0]["text"] for item in data[1:]: language = item["symbol"] if language in RENAME_LANGUAGE_CODES: language = RENAME_LANGUAGE_CODES[language] out_path = ( out_folder / "locales" / language / "LC_MESSAGES" / "booktheme.po" # noqa: E501 ) if not out_path.parent.exists(): out_path.parent.mkdir(parents=True) if not out_path.exists(): header = f""" msgid "" msgstr "" "Project-Id-Version: Sphinx-Book-Theme\\n" "MIME-Version: 1.0\\n" "Content-Type: text/plain; charset=UTF-8\\n" "Content-Transfer-Encoding: 8bit\\n" "Language: {language}\\n" "Plural-Forms: nplurals=2; plural=(n != 1);\\n" """ out_path.write_text(header) with out_path.open("a") as f: f.write("\n") f.write(f'msgid "{english}"\n') text = item["text"].replace('"', '\\"') f.write(f'msgstr "{text}"\n') # compile mo for path in (out_folder / "locales").glob("**/booktheme.po"): print(path) subprocess.check_call( [ "msgfmt", os.path.abspath(path), "-o", os.path.abspath(path.parent / "booktheme.mo"), ] ) if __name__ == "__main__": print("[SBT]: Compiling translations") convert_json() PKvMX `>> sphinx_book_theme/_transforms.pyfrom sphinx.transforms.post_transforms import SphinxPostTransform from typing import Any from docutils import nodes as docutil_nodes from sphinx import addnodes as sphinx_nodes from pydata_sphinx_theme.utils import get_theme_options_dict from .nodes import SideNoteNode class HandleFootnoteTransform(SphinxPostTransform): """Transform footnotes into side/marginnotes.""" default_priority = 1 formats = ("html",) def run(self, **kwargs: Any) -> None: theme_options = get_theme_options_dict(self.app) if theme_options.get("use_sidenotes", False) is False: return None # Cycle through footnote references, and move their content next to the # reference. This lets us display the reference in the margin, # or just below on narrow screens. for ref_node in self.document.traverse(docutil_nodes.footnote_reference): parent = None # Each footnote reference should have a single node it points to via `ids` for foot_node in self.document.traverse(docutil_nodes.footnote): # matching the footnote reference with footnote if ( len(foot_node.attributes["backrefs"]) and foot_node.attributes["backrefs"][0] == ref_node.attributes["ids"][0] ): parent = foot_node.parent # second children of footnote node is the content text foot_node_content = foot_node.children[1].children sidenote = SideNoteNode() para = docutil_nodes.inline() # first children of footnote node is the label label = foot_node.children[0].astext() if foot_node_content[0].astext().startswith("{-}"): # marginnotes will have content starting with {-} # remove the number so it doesn't show para.attributes["classes"].append("marginnote") foot_node_content[0] = docutil_nodes.Text( foot_node_content[0].replace("{-}", "") ) para.children = foot_node_content sidenote.attributes["names"].append(f"marginnote-role-{label}") else: # sidenotes are the default behavior if no {-} # in this case we keep the number superscript = docutil_nodes.superscript("", label) para.attributes["classes"].append("sidenote") parachildren = [superscript] + foot_node_content para.children = parachildren sidenote.attributes["names"].append(f"sidenote-role-{label}") sidenote.append(superscript) # If the reference is nested (e.g. in an admonition), duplicate # the content node And place it just before the parent container, # so it works w/ margin. Only show one or another depending on # screen width. node_parent = ref_node.parent para_dup = para.deepcopy() # looping to check parent node while not isinstance( node_parent, (docutil_nodes.section, sphinx_nodes.document) ): # if parent node is another container if not isinstance( node_parent, (docutil_nodes.paragraph, docutil_nodes.footnote), ): node_parent.replace_self([para, node_parent]) para_dup.attributes["classes"].append("d-n") break node_parent = node_parent.parent ref_node.replace_self([sidenote, para_dup]) break if parent: parent.remove(foot_node) PKvMXuC)sphinx_book_theme/assets/scripts/index.js// Import CSS variables // ref: https://css-tricks.com/getting-javascript-to-talk-to-css-and-sass/ import "../styles/index.scss"; /** * A helper function to load scripts when the DOM is loaded. * This waits for everything to be on the page first before running, since * some functionality doesn't behave properly until everything is ready. */ var sbRunWhenDOMLoaded = (cb) => { if (document.readyState != "loading") { cb(); } else if (document.addEventListener) { document.addEventListener("DOMContentLoaded", cb); } else { document.attachEvent("onreadystatechange", function () { if (document.readyState == "complete") cb(); }); } }; /** * Toggle full-screen with button * * There are some browser-specific hacks in here: * - Safari requires a `webkit` prefix, so this uses conditionals to check for that * ref: https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API */ var toggleFullScreen = () => { var isInFullScreen = (document.fullscreenElement && document.fullscreenElement !== null) || (document.webkitFullscreenElement && document.webkitFullscreenElement !== null); let docElm = document.documentElement; if (!isInFullScreen) { console.log("[SBT]: Entering full screen"); if (docElm.requestFullscreen) { docElm.requestFullscreen(); } else if (docElm.webkitRequestFullscreen) { docElm.webkitRequestFullscreen(); } } else { console.log("[SBT]: Exiting full screen"); if (document.exitFullscreen) { document.exitFullscreen(); } else if (document.webkitExitFullscreen) { document.webkitExitFullscreen(); } } }; /** * Manage scrolling behavior. This is primarily two things: * * 1. Hide the Table of Contents any time sidebar content is on the screen. * * This will be triggered any time a sidebar item enters or exits the screen. * It adds/removes items from an array if they have entered the screen, and * removes them when they exit the screen. It hides the TOC if anything is * on-screen. * * ref: https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API * * 2. Add a `scrolled` class to to trigger CSS changes. */ var initTocHide = () => { var onScreenItems = []; let hideTocCallback = (entries, observer) => { // Check whether any sidebar item is displayed entries.forEach((entry) => { if (entry.isIntersecting) { // If an element just came on screen, add it our list onScreenItems.push(entry.target); } else { // Otherwise, if it's in our list then remove it for (let ii = 0; ii < onScreenItems.length; ii++) { if (onScreenItems[ii] === entry.target) { onScreenItems.splice(ii, 1); break; } } } }); // Hide the TOC if any margin content is displayed on the screen if (onScreenItems.length > 0) { document.querySelector("div.bd-sidebar-secondary").classList.add("hide"); } else { document .querySelector("div.bd-sidebar-secondary") .classList.remove("hide"); } }; let manageScrolledClassOnBody = (entries, observer) => { // The pixel is at the top, so if we're < 0 that it means we've scrolled if (entries[0].boundingClientRect.y < 0) { document.body.classList.add("scrolled"); } else { document.body.classList.remove("scrolled"); } }; // Set up the intersection observer to watch all margin content let options = { // Trigger callback when the top of a margin item is 1/3 up the screen rootMargin: "0px 0px -33% 0px", }; let tocObserver = new IntersectionObserver(hideTocCallback, options); // TODO: deprecate popout after v0.5.0 const selectorClasses = [ "marginnote", "sidenote", "margin", "margin-caption", "full-width", "sidebar", "popout", ]; let marginSelector = []; selectorClasses.forEach((ii) => { // Use three permutations of each class name because `tag_` and `_` used to be supported marginSelector.push( ...[ `.${ii}`, `.tag_${ii}`, `.${ii.replace("-", "_")}`, `.tag_${ii.replace("-", "_")}`, ], ); }); document.querySelectorAll(marginSelector.join(", ")).forEach((ii) => { tocObserver.observe(ii); }); // Set up the observer to check if we've scrolled from top of page let scrollObserver = new IntersectionObserver(manageScrolledClassOnBody); scrollObserver.observe(document.querySelector(".sbt-scroll-pixel-helper")); }; /** * Activate Thebe with a custom button click. */ var initThebeSBT = () => { var title = document.querySelector("section h1"); var sibling = title.nextElementSibling; // If the next element after the title isn't a thebe button, add one now. // That way it is initiatlized when thebe is first-clicked and isn't re-added after. if (!sibling.classList.contains("thebe-launch-button")) { title.insertAdjacentHTML( "afterend", "", ); } // This function is provided by sphinx-thebe initThebe(); }; /** * Add no print class to certain DOM elements */ function addNoPrint() { var noPrintSelector = [ ".bd-header-announcement", ".bd-header", ".bd-header-article", ".bd-sidebar-primary", ".bd-sidebar-secondary", ".bd-footer-article", ".bd-footer-content", ".bd-footer", ].join(","); document.querySelectorAll(noPrintSelector).forEach((ii) => { ii.classList.add("noprint"); }); } /** * Set up callback functions for UI click actions */ window.initThebeSBT = initThebeSBT; window.toggleFullScreen = toggleFullScreen; /** * Set up functions to load when the DOM is ready */ sbRunWhenDOMLoaded(initTocHide); sbRunWhenDOMLoaded(addNoPrint); PKvMX.|XX6sphinx_book_theme/assets/styles/abstracts/_mixins.scss/********************************************* * SASS Mixins *********************************************/ /** * Hide the scrollbar until the element is hovered, so keep the page clean * Use this sparingly because it's not a great UX pattern. */ @mixin scrollbar-on-hover() { &:not(:hover) { &::-webkit-scrollbar-thumb { visibility: hidden; } } } /** * The PyData Sphinx Theme box shadow rule * Copied here in csae we need to re-use. */ @mixin pst-box-shadow() { box-shadow: 0 0.2rem 0.5rem var(--pst-color-shadow), 0 0 0.0625rem var(--pst-color-shadow) !important; } PKvMX^9sphinx_book_theme/assets/styles/abstracts/_variables.scss/********************************************* * Variables * *********************************************/ // Breakpoints from Bootstrap: https://getbootstrap.com/docs/5.0/layout/breakpoints/ $breakpoint-xxl: 1200px; $breakpoint-xl: 1200px; $breakpoint-lg: 992px; $breakpoint-md: 768px; $breakpoint-sm: 576px; // A few semantic z-indices $zindex-bottom: 1; $zindex-middle: 2; $zindex-top: 3; // Semantic Z-index from bootstrap. Copied here so we can re-use if needed. // ref: https://getbootstrap.com/docs/5.0/layout/z-index/ $zindex-dropdown: 1000; $zindex-sticky: 1020; $zindex-fixed: 1030; $zindex-modal-backdrop: 1040; $zindex-offcanvas: 1050; $zindex-modal: 1060; $zindex-popover: 1070; $zindex-tooltip: 1080; // Spacing $header-article-height: 3rem; $sidebar-primary-width-widescreen: 20%; $toc-width-mobile: 75%; // Consistent styling for page elements $box-border-radius: 0.4em; $animation-time: 0.25s; // Font sizes $sbt-font-size-small-1: 87.5%; /** * Variables that aren't impacted by light/dark */ html[data-theme="light"], html[data-theme="dark"] { // Over-ride the pydata theme so that readers can use their system base --pst-font-size-base: none; // Default secondary color (has enough contrast on both light/dark so // no need to special case. --pst-color-secondary: #e89217; } // Overrides for pydata sphinx theme. // See https://github.com/pydata/pydata-sphinx-theme/blob/master/pydata_sphinx_theme/static/css/theme.css html[data-theme="light"] { // Announcement --sbt-color-announcement: rgb(97, 97, 97); // Default primary color (need to adjust on dark theme due to bad contrast) --pst-color-primary: #176de8; } html[data-theme="dark"] { // Slightly lighten these colors to make them stand out more on dark --pst-color-primary: #528fe4; // Over-ride the purple announcement color --sbt-color-announcement: rgb(97, 97, 97); // Desaturate the background --pst-color-background: #121212; } PKvMX?t/sphinx_book_theme/assets/styles/base/_base.scss/** * General structural things */ html { // The PyData theme value for this is based on `header-height` variable. // We set the variable to 0 and have our own $article-header-height SCSS variable. // So here we follow the same pattern but now using our variable. scroll-padding-top: $header-article-height + 1rem; } /** * Utility classes used in a few places */ // For the helper pixel that we can watch to decide whether we've scrolled .sbt-scroll-pixel-helper { position: absolute; width: 0px; height: 0px; top: 0; left: 0; } // We define our own display-none class since bootstrap uses !important and we want to be able to over-ride .d-n { display: none; } /** * Printing behavior */ // Only display upon printing .onlyprint { display: none; @media print { display: block !important; } } // Prevent an item from being printed .noprint { @media print { display: none !important; } } PKvMX>6XX0sphinx_book_theme/assets/styles/base/_print.scss/********************************************* * Print-specific CSS * *********************************************/ @media print { .bd-main { .bd-content { margin-left: 2rem; height: auto; // Structural elements #jb-print-docs-body { margin-left: 0rem; h1 { font-size: 3em; text-align: center; margin-bottom: 0; } } // Main content adjustments .bd-article { padding-top: 0; // The first H1 is the title, we've already displayed above h1:first-of-type { display: none; } } // HACK: Without this, some code cells take the whole width .container { min-width: 0% !important; } // Content h1 { margin-top: 1em; margin-bottom: 1em; } h1, h2, h3, h4 { break-after: avoid; color: black; } table { break-inside: avoid; } pre { word-wrap: break-word; } a.headerlink { display: none; } // Remove borders to save some ink blockquote.epigraph, aside.margin, aside.sidebar { border: none; } .footer { margin-top: 1em; } // Print-only table of contents #jb-print-toc { margin-bottom: 1.5rem; margin-left: 0rem; .section-nav { border-left: 0px !important; list-style-type: disc !important; margin-left: 3em !important; a { text-decoration: none !important; } li { display: list-item !important; } .nav { display: none; } } } } // Hide the footer on printing .bd-footer-content { display: none !important; } } } PKvMX0 5sphinx_book_theme/assets/styles/base/_typography.scss/********************************************* * Basic text formatting and content structure * *********************************************/ .bd-article-container { h1, h2, h3, h4, h5, p.caption { color: var(--pst-color-muted); } // Top two headers are slightly bolder h1, h2 { font-weight: 500; } } // counteracting pydata style on a::before, for citation style a.brackets::before { color: inherit; font-family: inherit; margin-right: 0rem; } table { position: relative; } PKvMXiEE<sphinx_book_theme/assets/styles/components/_back-to-top.scss#pst-back-to-top { // We should only display the back-to-top button when the TOC is not available. // AKA, on narrow-ish screens. // This over-rides a PST default @media (min-width: $breakpoint-xl) { display: none !important; } // Reduce size to be slightly less intrusive on the page font-size: 0.8rem; } PKvMXiE;sphinx_book_theme/assets/styles/components/_icon-links.scss/** * Icon links design for the primary sidebar, where it defaults in this theme. */ .bd-sidebar-primary { .navbar-icon-links { column-gap: 0.5rem; .nav-link { // There are few kinds of elements that can be icon links and each is different i, span { font-size: 1.2rem; } // Images usually fill more vertical space so we make them a bit smaller img { font-size: 0.8rem; } } } } PKvMXnX5sphinx_book_theme/assets/styles/components/_logo.scss.navbar-brand { height: unset; max-height: unset; // Auto-center the image and title and make items vertical flex-direction: column; justify-content: center; gap: 0.25rem; // To prevent site text from having underlines &:hover { text-decoration: none; } .logo__title { font-size: 1.25rem; white-space: normal; text-align: center; } // Prevent images from stretching vertically .logo__image { height: unset; } } PKvMX}##7sphinx_book_theme/assets/styles/components/_search.scss// Clean up the search page so that it has less unnecessary text .bd-search-container { margin: 2em; #search-results { h2:first-child { display: none; } } } // Sidebar search button field .search-button-field { width: 100%; font-size: 0.9rem; // Hide by default and we'll add a rule to show it on wide sreen below display: none; .search-button__kbd-shortcut { margin-left: auto; } } // Rules to switch off visibility of the field button and the header button @media (min-width: $breakpoint-lg) { .search-button { display: none !important; } .search-button-field { display: flex; } } // The "clear search results" button // Make it line up with our content better div#searchbox { padding-right: 2rem; padding-left: 2rem; @media (max-width: $breakpoint-md) { padding-right: 1rem; padding-left: 1rem; } p.highlight-link { // Remove the extra margin that is added @media (min-width: $breakpoint-md) { margin-left: 0; a { font-size: 1rem; } } } } PKvMXʠ#2sphinx_book_theme/assets/styles/content/_code.scss// Over-ride Sphinx default so that content fills whitespace w/ margin content pre, div[class*="highlight-"] { clear: none; } PKvMX1S9884sphinx_book_theme/assets/styles/content/_images.scss// Images img { max-width: 100%; &.align-center { margin-left: auto; margin-right: auto; display: block; } &.align-left { clear: left; float: left; margin-right: 1em; } &.align-right { clear: right; float: right; margin-left: 1em; } } // Figures div.figure { width: 100%; margin-bottom: 1em; text-align: center; &.align-left { text-align: left; p.caption { margin-left: 0; } } &.align-right { text-align: right; p.caption { margin-right: 0; } } p.caption { margin: 0.5em 10%; } &.margin, &.margin-caption { p.caption { margin: 0.5em 0; } } &.margin-caption p.caption { text-align: left; } span.caption-number { font-weight: bold; } span { font-size: 0.9rem; } } PKvMX4sphinx_book_theme/assets/styles/content/_margin.scss/** * Margin content * This is the area to the right of the main content * normally covered by the in-page table of contents * but if `.margin` classes are present, it shows up to the right in the margin * and the in-page TOC is hidden. * On narrow screens, margin content behaves like a sidebar */ /** * Variables for calculating margin widths */ // We want our margin content to be 1/3 the width of the main page content // after factoring in a small margin $margin-width-relative-to-content: 36%; $margin-gutter: 3%; $margin-width-width-gutter: $margin-width-relative-to-content - $margin-gutter; // re-subtract the gutter width because we want it to leave a white-space // This is how much to offset the margin content. $margin-offset: -$margin-width-relative-to-content; /** * This mixin will cause something to "pop out" to the margin on wide screens */ @mixin margin-content() { // On narrow screens this is the width of margin content width: 40%; float: right; background-color: unset; font-size: 0.9em; margin-left: 0.5rem; // Wide screens and printing should force margin behavior @media (min-width: $breakpoint-lg), print { width: $margin-width-width-gutter; margin: 0 $margin-offset 0 0; // Prevent sequential margin content from overlapping clear: right; p.sidebar-title { margin-bottom: -1rem; border-bottom: none; padding-left: 0px; // Content of admonitions has fewer horizontal white space to save space ~ * { padding-left: 0px; padding-right: 0px; } } } } /** * Sidenotes and marginnotes * over-rides the "footnote" behavior to pop out to the margin instead. */ label.margin-toggle { margin-bottom: 0em; &.marginnote-label { display: none; } sup { user-select: none; } @media (max-width: $breakpoint-lg) { cursor: pointer; color: rgb(0, 113, 188); &.marginnote-label { display: inline; &:after { content: "\2295"; } } } } input.margin-toggle { display: none; @media (max-width: $breakpoint-lg) { &:checked + .sidenote, &:checked + .marginnote { display: block; float: left; left: 1rem; clear: both; width: 95%; margin: 1rem 2.5%; position: relative; } } } span.sidenote, span.marginnote { z-index: $zindex-middle; position: relative; sup { user-select: none; } @include margin-content(); border-left: none; @media (max-width: $breakpoint-lg) { display: none; } } aside.sidebar { .note { // styling for notes inside sidebars margin: 1rem; padding: 0rem 0rem 1rem 0rem; } // The titles sticking .admonition-title { margin: 0rem -1rem 0rem 0rem; } } /** * Sidebar content in the margin. * This will be added by the `margin` directive or the `sidebar` directive */ aside.sidebar.margin { // Margin content can have an empty title so this prevents an empty title block // from showing up on mobile .sidebar-title:empty { display: none; } // On narrow screens padding is added to all sidebar content // This replaces padding w/ margin for admonitions because padding is used // for the title background color .admonition { margin: 0.5rem; padding-left: 0; padding-right: 0; // Remove the title margin so it .admonition-title { margin-left: 0; margin-right: 0; } } @media (min-width: $breakpoint-lg) { // No border for margin sidebars on wide screen border: none; // Admonitions don't need the extra whitespace in the margin .admonition { margin: 1rem 0rem; padding: 0rem 0rem 1rem 0rem; } } } div.margin, figure.margin, aside.margin, .margin.docutils.container, .cell.tag_popout, .cell.tag_margin { z-index: $zindex-middle; position: relative; @include margin-content(); // Make cell outputs take up more space if they're in the margin div.cell.tag_margin .cell_output { padding-left: 0; } } // A few permutations because docutils 0.18 switched to semantic tags div.figure.margin-caption p.caption, div.figure.margin-caption figcaption, figure.margin-caption figcaption { z-index: $zindex-middle; position: relative; @include margin-content(); } // Margin captions .margin-caption figcaption { text-align: left; } /** * Full width content */ // Grow 100% by the ratio of the margin to the content width. $content-fullwidth-width: 100% + $margin-width-relative-to-content; div.cell.tag_full-width, div.cell.tag_full_width, div.full_width, div.full-width { z-index: $zindex-middle; position: relative; @media (min-width: $breakpoint-lg) { max-width: $content-fullwidth-width; width: $content-fullwidth-width; } } PKvMXZ:7sphinx_book_theme/assets/styles/content/_notebooks.scss// MyST-NB and Jupyter Notebooks div.cell.tag_output_scroll div.cell_output, div.cell.tag_scroll-output div.cell_output { max-height: 24em; overflow-y: auto; } div.cell.tag_scroll-input div.cell_input { max-height: 24em; overflow-y: auto; } PKvMX |ll4sphinx_book_theme/assets/styles/content/_quotes.scss/** * Epigraphs and other "special" quote blocks */ blockquote { &.pull-quote, &.epigraph, &.highlights { font-size: 1.25em; border-left: none; background-color: var(--pst-color-background); } div > p + p.attribution { font-style: normal; font-size: 0.9em; text-align: right; color: #6c757d; padding-right: 2em; } } PKvMXX449sphinx_book_theme/assets/styles/extensions/_comments.scss/** * Comment JavaScript libraries * Libraries supported here are: * - Hypothes.is : https://web.hypothes.is/ * - Utteranc.es : https://utteranc.es/ */ // In general we want to hide any comment libraries on print @media only print { hypothesis-sidebar, div.utterances { display: none; } } PKvMXpz6sphinx_book_theme/assets/styles/extensions/_thebe.scss/********************************************* * Thebe * * ref: https://github.com/executablebooks/thebe *********************************************/ .thebelab-cell { border: none !important; margin-right: 0.5em !important; } .thebelab-cell .thebelab-input { padding-left: 10px !important; } .cell.docutils.container { padding-right: 0px !important; } button.thebe-launch-button { height: 2.5em; font-size: 1em; } PKvMX*sphinx_book_theme/assets/styles/index.scss/*! sphinx-book-theme CSS * BSD 3-Clause License * Copyright (c) 2020, EBP * All rights reserved. * * This follows the 7-1 pattern described here: * https://sass-guidelin.es/#architecture */ // Variables and re-usable SCSS functions @import "abstracts/mixins"; @import "abstracts/variables"; // Basic styling applied throughout site @import "base/base"; @import "base/typography"; @import "base/print"; // Major theme layout, skeleton, and whitespace @import "sections/announcement"; @import "sections/article"; @import "sections/article-container"; @import "sections/header-article"; @import "sections/header-primary"; @import "sections/sidebar-primary"; @import "sections/sidebar-secondary"; @import "sections/footer-content"; @import "sections/footer-article"; // Re-usable components across the theme @import "components/back-to-top"; @import "components/icon-links"; @import "components/logo"; @import "components/search"; // Content blocks in standard Sphinx @import "content/images"; @import "content/margin"; @import "content/quotes"; @import "content/code"; @import "content/notebooks"; // Content blocks from Sphinx extensions @import "extensions/comments"; @import "extensions/thebe"; PKvMXSI^^;sphinx_book_theme/assets/styles/sections/_announcement.scss.bd-header-announcement { background-color: var(--sbt-color-announcement); color: #fff; } PKvMX@sphinx_book_theme/assets/styles/sections/_article-container.scss.bd-main .bd-content .bd-article-container { // Re-adjust padding defaults to be flush on the top and right padding: 0rem; // Unset overflow x so that sticky: top works for the article header overflow-x: unset; // prevent from overflowing the flex container min-width: 0; @media (min-width: $breakpoint-xl) { // keep article at reasonable width in absence of sidebar max-width: calc(100% - var(--pst-sidebar-secondary)); } .bd-article { padding-right: 2rem; padding-left: 2rem; @media (max-width: $breakpoint-md) { padding-right: 1rem; padding-left: 1rem; } } details.above-input, details.below-input { summary { border-left: 3px solid var(--pst-color-primary); } } } PKvMXAqq6sphinx_book_theme/assets/styles/sections/_article.scss.bd-main .bd-content { // make article container left aligned in absence of sidebar justify-content: left; } PKvMXP=sphinx_book_theme/assets/styles/sections/_footer-article.scss.bd-footer-article { padding: 0rem 1rem; @media (max-width: $breakpoint-md) { // More room for text on mobile padding: 0rem 0.5rem; } } PKvMX*=sphinx_book_theme/assets/styles/sections/_footer-content.scss/********************************************* * Footer - content * *********************************************/ footer { font-size: var(--sbt-font-size-small-1); &.bd-footer-content { display: flex; flex-wrap: wrap; padding: 15px; border-top: 1px solid #ccc; font-size: $sbt-font-size-small-1; .bd-footer-content__inner { padding-left: 0px; p { margin-bottom: 0px; } } } } PKvMXA=sphinx_book_theme/assets/styles/sections/_header-article.scss/********************************************* * Top Bar * *********************************************/ /** * Mixin: * Header behavior on mobile */ @mixin header-height-mobile { @media (max-width: $breakpoint-md) { height: $header-article-height + 0.5rem; } } /** * Sidebar toggle over-rides for PST */ // Primary toggle is always visible label.sidebar-toggle.primary-toggle { @media (min-width: $breakpoint-md) { display: inline-block; } @media (max-width: $breakpoint-md) { margin-bottom: 0px; } } // Secondary toggle mimics behavior of "persistent header" div of PST label.sidebar-toggle.secondary-toggle { @media (min-width: $breakpoint-lg) { display: none; } @media (max-width: $breakpoint-md) { margin-bottom: 0px; } } // Wrapper container .bd-header-article { display: flex; align-items: center; position: sticky; top: 0; background-color: var(--pst-color-background); transition: left 0.2s; font-size: 0.9em; padding: 0 1rem; @media (max-width: $breakpoint-md) { // Give mobile view a bit more space for text padding: 0 0.5rem; } z-index: $zindex-sticky; @include header-height-mobile; // The box shadow that shows up when you've scrolled past the top .scrolled & { box-shadow: 0 6px 6px -6px var(--pst-color-shadow); } // Remove inner padding so that spacing of buttons is a bit tighter .header-article__inner { padding: 0; } // Inner container with items in it .header-article-items { display: flex; align-items: center; height: $header-article-height; width: 100%; } // Contains the individual item components so we want it centered .header-article-item { display: flex; align-items: center; } /** * Buttons in the header */ // A series of buttons we special-case in the theme .article-header-buttons { display: flex; } // Generic button styles .btn { // Basic button size font-size: 1.3rem; color: var(--pst-color-text-muted); border: none; padding: 0 0.5rem; // Make sure anything inside is aligned vertically display: flex; align-items: center; // Fix width of buttons so they're all the same svg { width: 1.3rem; } // Hover / active behavior &.show, &:hover { color: var(--pst-color-text-base); border: none; & + .dropdown-menu { display: block; } } &:focus { box-shadow: none; } &.dropdown-toggle { // Hide the bootstrap caret &:after { display: none; } } } // Vertically align dropdown buttons div.dropdown { display: flex; align-items: center; } .theme-switch-button { // Removing some special-casing that was needed in PST margin: 0; // Remove extra padding since it is already there between sidebar items span, i, button { padding: 0; } // No background on hover. Need important to over-ride the PST which uses it too. span, i { transition: color $animation-time ease-out; } &:active, &:hover { background-color: unset !important; span, i { color: var(--pst-color-text-base); } } } // The menu that is normally hidden until you hover .dropdown-menu { // Hidden unless we are hovering &:hover { display: block; } // Positioning and layout of dropdown items to be standardized top: 2rem; transform: translateX(-75%); @include pst-box-shadow; // Color and shadowing border-color: var(--pst-color-border); background-color: var(--pst-color-background); color: var(--pst-color-text-muted); .dropdown-item { display: inline-flex; align-items: center; padding-left: 0.5em; // To prevent link underline from showing up &:hover { text-decoration: none; background-color: initial; color: var(--pst-color-text-base); } // Slightly smaller font for everything font-size: 1em; // Image icons should be the same height as icons span img { height: 1em; } span.btn__icon-container { width: 1.7em; align-items: center; display: inline-flex; justify-content: center; } } } } PKvMX=sphinx_book_theme/assets/styles/sections/_header-primary.scss.bd-header { // Turn off sticky positioning so that it scrolls position: inherit; // Turn these off because we'll show it in the article header // The primary sidebar toggle label.sidebar-toggle { display: none; } } PKvMX )>sphinx_book_theme/assets/styles/sections/_sidebar-primary.scss/********************************************* * Left Nav Bar * *********************************************/ .bd-sidebar-primary { // So that it sticks to the top of the page instead of the header height top: 0; max-height: 100vh; // Tighter padding so the spacing is more equal padding: 1rem; // Draw open/close animation transition: margin-left $animation-time ease 0s, opacity $animation-time ease 0s, visibility $animation-time ease 0s; @media (max-width: $breakpoint-md) { // So that tooltips don't overlap z-index: $zindex-tooltip + 1; } // Remove the top border since there's often no header items above .sidebar-primary-items__start { border-top: none; } // On wide screens we have a smaller sidebar width than PST @media (min-width: $breakpoint-lg) { flex-basis: $sidebar-primary-width-widescreen; } } // Default visibility for left sidebar is the reverse of what it is on mobile. // It is shown by default, and hidden with a click. // Mobile behavior is defined in the pydata sphinx theme @media (min-width: $breakpoint-lg) { input#__primary:checked ~ .bd-container .bd-sidebar-primary { margin-left: -$sidebar-primary-width-widescreen; visibility: hidden; opacity: 0; } } PKvMXz+A  @sphinx_book_theme/assets/styles/sections/_sidebar-secondary.scss/********************************************* * Table of Contents (right nav bar) * *********************************************/ .bd-sidebar-secondary { // So that it sticks to the top of the page instead of the header height top: 0; @media (max-width: $breakpoint-lg) { // So that tooltips don't overlap z-index: $zindex-tooltip + 1; } // So that we can center the TOC button with the article header buttons .sidebar-secondary-items { padding: 0; display: flex; gap: 0.5rem; // Remove the manual padding since we'll use `gap` .sidebar-secondary-item { padding-top: 0; padding-bottom: 0; } } // The 'on this page' title div should take up the full header .onthispage { height: $header-article-height; min-height: $header-article-height; display: flex; gap: 0.5rem; align-items: center; margin: 0; color: var(--pst-color-muted); } // Wide screen behavior @media (min-width: $breakpoint-lg) { background: var(--pst-color-background); height: fit-content; transition: max-height 0.4s ease; // To make sure it shows above the page content z-index: $zindex-middle; // Remove padding so that it's flush on the top and left padding: 0; // This is the container div for the secondary sidebar nav .toc-item { // Border is a bit less prominent border-left-color: var(--pst-color-surface); // Flush with the top so that we can fix the height of the toc header div padding-top: 0; // The table of contents