celshast 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. celshast/__init__.py +377 -0
  2. celshast/_demo_module.py +54 -0
  3. celshast/assets/scripts/celshast.js +175 -0
  4. celshast/assets/scripts/gumshoe-patched.js +466 -0
  5. celshast/assets/styles/_scaffold.sass +460 -0
  6. celshast/assets/styles/_shame.sass +12 -0
  7. celshast/assets/styles/base/_index.sass +4 -0
  8. celshast/assets/styles/base/_print.sass +43 -0
  9. celshast/assets/styles/base/_screen-readers.sass +13 -0
  10. celshast/assets/styles/base/_theme.sass +55 -0
  11. celshast/assets/styles/base/_typography.sass +93 -0
  12. celshast/assets/styles/celshast-extensions.sass +1 -0
  13. celshast/assets/styles/celshast.sass +9 -0
  14. celshast/assets/styles/components/_footer.sass +109 -0
  15. celshast/assets/styles/components/_index.sass +4 -0
  16. celshast/assets/styles/components/_search.sass +17 -0
  17. celshast/assets/styles/components/_sidebar.sass +231 -0
  18. celshast/assets/styles/components/_table_of_contents.sass +65 -0
  19. celshast/assets/styles/content/_admonitions.sass +77 -0
  20. celshast/assets/styles/content/_api.sass +96 -0
  21. celshast/assets/styles/content/_blocks.sass +7 -0
  22. celshast/assets/styles/content/_captions.sass +19 -0
  23. celshast/assets/styles/content/_code.sass +137 -0
  24. celshast/assets/styles/content/_footnotes.sass +45 -0
  25. celshast/assets/styles/content/_gui-labels.sass +8 -0
  26. celshast/assets/styles/content/_images.sass +37 -0
  27. celshast/assets/styles/content/_index.sass +16 -0
  28. celshast/assets/styles/content/_indexes.sass +12 -0
  29. celshast/assets/styles/content/_lists.sass +67 -0
  30. celshast/assets/styles/content/_math.sass +21 -0
  31. celshast/assets/styles/content/_misc.sass +53 -0
  32. celshast/assets/styles/content/_rubrics.sass +13 -0
  33. celshast/assets/styles/content/_sidebar.sass +28 -0
  34. celshast/assets/styles/content/_tables.sass +42 -0
  35. celshast/assets/styles/content/_target.sass +81 -0
  36. celshast/assets/styles/extensions/_copybutton.sass +45 -0
  37. celshast/assets/styles/extensions/_farama.sass +714 -0
  38. celshast/assets/styles/extensions/_index.sass +6 -0
  39. celshast/assets/styles/extensions/_readthedocs.sass +76 -0
  40. celshast/assets/styles/extensions/_sphinx-design.sass +51 -0
  41. celshast/assets/styles/extensions/_sphinx-inline-tabs.sass +18 -0
  42. celshast/assets/styles/extensions/_sphinx-panels.sass +18 -0
  43. celshast/assets/styles/variables/_admonitions.scss +48 -0
  44. celshast/assets/styles/variables/_colors.scss +192 -0
  45. celshast/assets/styles/variables/_fonts.scss +41 -0
  46. celshast/assets/styles/variables/_icons.scss +32 -0
  47. celshast/assets/styles/variables/_index.sass +8 -0
  48. celshast/assets/styles/variables/_layout.scss +9 -0
  49. celshast/assets/styles/variables/_spacing.scss +43 -0
  50. celshast/farama_directives.py +42 -0
  51. celshast/gen_tutorials.py +64 -0
  52. celshast/navigation.py +87 -0
  53. celshast/sphinxext.py +95 -0
  54. celshast/theme/celshast/base.html +483 -0
  55. celshast/theme/celshast/components/edit-this-page.html +26 -0
  56. celshast/theme/celshast/domainindex.html +51 -0
  57. celshast/theme/celshast/genindex.html +58 -0
  58. celshast/theme/celshast/globaltoc.html +4 -0
  59. celshast/theme/celshast/layout.html +16 -0
  60. celshast/theme/celshast/localtoc.html +4 -0
  61. celshast/theme/celshast/page.html +262 -0
  62. celshast/theme/celshast/partials/_head_css_variables.html +28 -0
  63. celshast/theme/celshast/partials/icons.html +61 -0
  64. celshast/theme/celshast/search.html +29 -0
  65. celshast/theme/celshast/sidebar/brand.html +31 -0
  66. celshast/theme/celshast/sidebar/donate.html +7 -0
  67. celshast/theme/celshast/sidebar/ethical-ads.html +10 -0
  68. celshast/theme/celshast/sidebar/navigation.html +3 -0
  69. celshast/theme/celshast/sidebar/rtd-versions.html +34 -0
  70. celshast/theme/celshast/sidebar/scroll-end.html +1 -0
  71. celshast/theme/celshast/sidebar/scroll-start.html +1 -0
  72. celshast/theme/celshast/sidebar/search.html +6 -0
  73. celshast/theme/celshast/sidebar/title.html +7 -0
  74. celshast/theme/celshast/sidebar/variant-selector.html +33 -0
  75. celshast/theme/celshast/static/.gitignore +7 -0
  76. celshast/theme/celshast/static/img/MAgent2-white.svg +131 -0
  77. celshast/theme/celshast/static/img/autorom-white.svg +112 -0
  78. celshast/theme/celshast/static/img/d4rl-white.svg +107 -0
  79. celshast/theme/celshast/static/img/farama-logo-header.svg +115 -0
  80. celshast/theme/celshast/static/img/farama_solid_white.svg +95 -0
  81. celshast/theme/celshast/static/img/gymnasium-white.svg +89 -0
  82. celshast/theme/celshast/static/img/gymrobotics-white.svg +165 -0
  83. celshast/theme/celshast/static/img/jumpy-white.svg +156 -0
  84. celshast/theme/celshast/static/img/kabuki-white.svg +160 -0
  85. celshast/theme/celshast/static/img/microrts-white.svg +179 -0
  86. celshast/theme/celshast/static/img/minigrid-white.svg +136 -0
  87. celshast/theme/celshast/static/img/miniworld-white.svg +184 -0
  88. celshast/theme/celshast/static/img/pettingzoo-white.svg +169 -0
  89. celshast/theme/celshast/static/img/procgen2-white.svg +151 -0
  90. celshast/theme/celshast/static/img/supersuit-white.svg +80 -0
  91. celshast/theme/celshast/static/img/tinyscaler-white.svg +123 -0
  92. celshast/theme/celshast/static/scripts/celshast-extensions.js +0 -0
  93. celshast/theme/celshast/static/scripts/celshast.js +3 -0
  94. celshast/theme/celshast/static/scripts/celshast.js.LICENSE.txt +7 -0
  95. celshast/theme/celshast/static/scripts/celshast.js.map +1 -0
  96. celshast/theme/celshast/static/styles/celshast-extensions.css +2 -0
  97. celshast/theme/celshast/static/styles/celshast-extensions.css.map +1 -0
  98. celshast/theme/celshast/static/styles/celshast.css +2 -0
  99. celshast/theme/celshast/static/styles/celshast.css.map +1 -0
  100. celshast/theme/celshast/static/versioning/versioning_menu.html +222 -0
  101. celshast/theme/celshast/theme.conf +35 -0
  102. celshast-0.0.1.dist-info/METADATA +518 -0
  103. celshast-0.0.1.dist-info/RECORD +106 -0
  104. celshast-0.0.1.dist-info/WHEEL +4 -0
  105. celshast-0.0.1.dist-info/entry_points.txt +2 -0
  106. celshast-0.0.1.dist-info/licenses/LICENSE +19 -0
celshast/__init__.py ADDED
@@ -0,0 +1,377 @@
1
+ """A Farama Foundation Sphinx documentation theme based on Furo."""
2
+
3
+ __version__ = "0.0.1"
4
+
5
+ import hashlib
6
+ import logging
7
+ import os
8
+ from functools import lru_cache
9
+ from pathlib import Path
10
+ from typing import Any, Dict, Iterator, List, Optional
11
+
12
+ import sphinx.application
13
+ from docutils import nodes
14
+ from pygments.formatters import HtmlFormatter
15
+ from pygments.style import Style
16
+ from pygments.token import Text
17
+ from sphinx.builders.html import StandaloneHTMLBuilder
18
+ from sphinx.environment.adapters.toctree import TocTree
19
+ from sphinx.errors import ConfigError
20
+ from sphinx.highlighting import PygmentsBridge
21
+ from sphinx.transforms.post_transforms import SphinxPostTransform
22
+
23
+ from .farama_directives import FaramaProjectHeadingDirective, FaramaProjectLogoDirective
24
+ from .navigation import get_navigation_tree
25
+
26
+ THEME_PATH = (Path(__file__).parent / "theme" / "celshast").resolve()
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+ # GLOBAL STATE
31
+ _KNOWN_STYLES_IN_USE: Dict[str, Optional[Style]] = {
32
+ "light": None,
33
+ "dark": None,
34
+ }
35
+
36
+
37
+ class WrapTableAndMathInAContainerTransform(SphinxPostTransform):
38
+ """A Sphinx post-transform that wraps `table` and `div.math` in a container `div`.
39
+
40
+ This makes it possible to handle these overflowing the content-width, which is
41
+ necessary in a responsive theme.
42
+ """
43
+
44
+ formats = ("html",)
45
+ default_priority = 500
46
+
47
+ def run(self, **kwargs: Any) -> None:
48
+ """Perform the post-transform on `self.document`."""
49
+ get_nodes = (
50
+ self.document.findall # docutils 0.18+
51
+ if hasattr(self.document, "findall")
52
+ else self.document.traverse # docutils <= 0.17.x
53
+ )
54
+ for node in list(get_nodes(nodes.table)):
55
+ new_node = nodes.container(classes=["table-wrapper"])
56
+ new_node.update_all_atts(node)
57
+ node.parent.replace(node, new_node)
58
+ new_node.append(node)
59
+
60
+ for node in list(get_nodes(nodes.math_block)):
61
+ new_node = nodes.container(classes=["math-wrapper"])
62
+ new_node.update_all_atts(node)
63
+ node.parent.replace(node, new_node)
64
+ new_node.append(node)
65
+
66
+
67
+ def has_not_enough_items_to_show_toc(
68
+ builder: StandaloneHTMLBuilder, docname: str
69
+ ) -> bool:
70
+ """Check if the toc has one or fewer items."""
71
+ assert builder.env
72
+
73
+ toctree = TocTree(builder.env).get_toc_for(docname, builder)
74
+ try:
75
+ self_toctree = toctree[0][1]
76
+ except IndexError:
77
+ val = True
78
+ else:
79
+ # There's only the page's own toctree in there.
80
+ val = len(self_toctree) == 1 and self_toctree[0].tagname == "toctree"
81
+ return val
82
+
83
+
84
+ def get_pygments_style_colors(
85
+ style: Style, *, fallbacks: Dict[str, str]
86
+ ) -> Dict[str, str]:
87
+ """Get background/foreground colors for given pygments style."""
88
+ background = style.background_color
89
+ text_colors = style.style_for_token(Text)
90
+ foreground = text_colors["color"]
91
+
92
+ if not background:
93
+ background = fallbacks["background"]
94
+
95
+ if not foreground:
96
+ foreground = fallbacks["foreground"]
97
+ else:
98
+ foreground = f"#{foreground}"
99
+
100
+ return {"background": background, "foreground": foreground}
101
+
102
+
103
+ @lru_cache(maxsize=2)
104
+ def get_colors_for_codeblocks(
105
+ highlighter: PygmentsBridge, *, fg: str, bg: str
106
+ ) -> Dict[str, str]:
107
+ """Get background/foreground colors for given pygments style."""
108
+ return get_pygments_style_colors(
109
+ highlighter.formatter_args["style"],
110
+ fallbacks={
111
+ "foreground": fg,
112
+ "background": bg,
113
+ },
114
+ )
115
+
116
+
117
+ def _compute_navigation_tree(context: Dict[str, Any]) -> str:
118
+ # The navigation tree, generated from the sphinx-provided ToC tree.
119
+ if "toctree" in context:
120
+ toctree = context["toctree"]
121
+ toctree_html = toctree(
122
+ collapse=False,
123
+ titles_only=True,
124
+ maxdepth=-1,
125
+ includehidden=True,
126
+ )
127
+ else:
128
+ toctree_html = ""
129
+
130
+ return get_navigation_tree(toctree_html)
131
+
132
+
133
+ def _compute_hide_toc(
134
+ context: Dict[str, Any],
135
+ *,
136
+ builder: StandaloneHTMLBuilder,
137
+ docname: str,
138
+ ) -> bool:
139
+ # Should the table of contents be hidden?
140
+ file_meta = context.get("meta", None) or {}
141
+ if "hide-toc" in file_meta:
142
+ return True
143
+ elif "toc" not in context:
144
+ return True
145
+ elif not context["toc"]:
146
+ return True
147
+
148
+ return has_not_enough_items_to_show_toc(builder, docname)
149
+
150
+
151
+ @lru_cache(maxsize=None)
152
+ def _asset_hash(path: str) -> str:
153
+ """Append a `?digest=` to an url based on the file content."""
154
+ full_path = THEME_PATH / "static" / path
155
+ digest = hashlib.sha1(full_path.read_bytes()).hexdigest()
156
+
157
+ return f"_static/{path}?digest={digest}"
158
+
159
+
160
+ def _add_asset_hashes(static: List[str], add_digest_to: List[str]) -> None:
161
+ if sphinx.version_info >= (7, 1):
162
+ # https://github.com/sphinx-doc/sphinx/pull/11415 added the relevant
163
+ # functionality to Sphinx, so we don't need to do anything.
164
+ return
165
+
166
+ for asset in add_digest_to:
167
+ index = static.index("_static/" + asset)
168
+ static[index].filename = _asset_hash(asset) # type: ignore
169
+
170
+
171
+ def _html_page_context(
172
+ app: sphinx.application.Sphinx,
173
+ pagename: str,
174
+ templatename: str,
175
+ context: Dict[str, Any],
176
+ doctree: Any,
177
+ ) -> None:
178
+ if "css_files" in context:
179
+ if "_static/styles/celshast.css" not in [
180
+ c.filename.split("?")[0] for c in context["css_files"]
181
+ ]:
182
+ raise ConfigError(
183
+ "This documentation is not using `celshast.css` as the stylesheet. "
184
+ "If you have set `html_style` in your conf.py file, remove it."
185
+ )
186
+
187
+ _add_asset_hashes(
188
+ context["css_files"],
189
+ ["styles/celshast.css", "styles/celshast-extensions.css"],
190
+ )
191
+ if "scripts" in context:
192
+ _add_asset_hashes(
193
+ context["scripts"],
194
+ ["scripts/celshast.js"],
195
+ )
196
+
197
+ # Basic constants
198
+ context["celshast_version"] = __version__
199
+
200
+ # Values computed from page-level context.
201
+ context["celshast_navigation_tree"] = _compute_navigation_tree(context)
202
+ context["celshast_hide_toc"] = _compute_hide_toc(
203
+ context, builder=app.builder, docname=pagename
204
+ )
205
+
206
+ # Inject information about styles
207
+ context["celshast_pygments"] = {
208
+ "light": get_pygments_style_colors(
209
+ _KNOWN_STYLES_IN_USE["light"],
210
+ fallbacks=dict(
211
+ foreground="black",
212
+ background="white",
213
+ ),
214
+ ),
215
+ "dark": get_pygments_style_colors(
216
+ _KNOWN_STYLES_IN_USE["dark"],
217
+ fallbacks=dict(
218
+ foreground="white",
219
+ background="black",
220
+ ),
221
+ ),
222
+ }
223
+
224
+ # Base url
225
+ context["html_baseurl"] = app.config.html_baseurl
226
+
227
+ # Projects Menu
228
+ context["farama_projects_api"] = "https://farama.org/api/projects.json"
229
+
230
+
231
+ def _builder_inited(app: sphinx.application.Sphinx) -> None:
232
+ if "celshast" in app.config.extensions:
233
+ raise ConfigError(
234
+ "Did you list 'celshast' in the `extensions` in conf.py? "
235
+ "If so, please remove it. Celshast does not work with non-HTML builders "
236
+ "and specifying it as an `html_theme` is sufficient."
237
+ )
238
+
239
+ if not isinstance(app.builder, StandaloneHTMLBuilder) or app.builder.name not in {
240
+ "html",
241
+ "dirhtml",
242
+ }:
243
+ raise ConfigError(
244
+ "Celshast is being used as an extension in a non-HTML build. "
245
+ "This should not happen."
246
+ )
247
+
248
+ # Our JS file needs to be loaded as soon as possible.
249
+ app.add_js_file("scripts/celshast.js", priority=200)
250
+
251
+ # 500 is the default priority for extensions, we want this after this.
252
+ app.add_css_file("styles/celshast-extensions.css", priority=600)
253
+
254
+ # Add custom directives
255
+ app.add_directive("project-logo", FaramaProjectLogoDirective)
256
+ app.add_directive("project-heading", FaramaProjectHeadingDirective)
257
+
258
+ builder = app.builder
259
+ assert (
260
+ builder.highlighter is not None
261
+ ), "there should be a default style known to Sphinx"
262
+ assert (
263
+ builder.dark_highlighter is None
264
+ ), "this shouldn't be a dark style known to Sphinx"
265
+ update_known_styles_state(app)
266
+
267
+ def _update_default(key: str, *, new_default: Any) -> None:
268
+ app.config.values[key] = (new_default, *app.config.values[key][1:])
269
+
270
+ # Change the default permalinks icon
271
+ _update_default("html_permalinks_icon", new_default="#")
272
+
273
+
274
+ def update_known_styles_state(app: sphinx.application.Sphinx) -> None:
275
+ """Update a global store of known styles of this application."""
276
+ global _KNOWN_STYLES_IN_USE
277
+
278
+ _KNOWN_STYLES_IN_USE = {
279
+ "light": _get_light_style(app),
280
+ "dark": _get_dark_style(app),
281
+ }
282
+
283
+
284
+ def _get_light_style(app: sphinx.application.Sphinx) -> Style:
285
+ return app.builder.highlighter.formatter_args["style"]
286
+
287
+
288
+ def _get_dark_style(app: sphinx.application.Sphinx) -> Style:
289
+ dark_style = app.config.pygments_dark_style
290
+ return PygmentsBridge("html", dark_style).formatter_args["style"]
291
+
292
+
293
+ def _get_styles(formatter: HtmlFormatter, *, prefix: str) -> Iterator[str]:
294
+ """Get styles out of a formatter, where everything has the correct prefix."""
295
+ for line in formatter.get_linenos_style_defs():
296
+ yield f"{prefix} {line}"
297
+ yield from formatter.get_background_style_defs(prefix)
298
+ yield from formatter.get_token_style_defs(prefix)
299
+
300
+
301
+ def get_pygments_stylesheet() -> str:
302
+ """Generate the theme-specific pygments.css.
303
+
304
+ There is no way to tell Sphinx how the theme handles dark mode; at this time.
305
+ """
306
+ light_formatter = PygmentsBridge.html_formatter(style=_KNOWN_STYLES_IN_USE["light"])
307
+ dark_formatter = PygmentsBridge.html_formatter(style=_KNOWN_STYLES_IN_USE["dark"])
308
+
309
+ lines: List[str] = []
310
+
311
+ lines.extend(_get_styles(light_formatter, prefix=".highlight"))
312
+
313
+ lines.append("@media not print {")
314
+
315
+ dark_prefix = 'body[data-theme="dark"] .highlight'
316
+ lines.extend(_get_styles(dark_formatter, prefix=dark_prefix))
317
+
318
+ not_light_prefix = 'body:not([data-theme="light"]) .highlight'
319
+ lines.append("@media (prefers-color-scheme: dark) {")
320
+ lines.extend(_get_styles(dark_formatter, prefix=not_light_prefix))
321
+ lines.append("}")
322
+
323
+ lines.append("}")
324
+
325
+ return "\n".join(lines)
326
+
327
+
328
+ # Yup, we overwrite the default pygments.css file, because it can't possibly respect
329
+ # the needs of this theme.
330
+ def _overwrite_pygments_css(
331
+ app: sphinx.application.Sphinx,
332
+ exception: Optional[Exception],
333
+ ) -> None:
334
+ if exception is not None:
335
+ return
336
+
337
+ assert app.builder
338
+ with open(
339
+ os.path.join(app.builder.outdir, "_static", "pygments.css"),
340
+ "w",
341
+ encoding="utf-8",
342
+ ) as f:
343
+ f.write(get_pygments_stylesheet())
344
+
345
+
346
+ def setup(app: sphinx.application.Sphinx) -> Dict[str, Any]:
347
+ """Entry point for sphinx theming."""
348
+ app.require_sphinx("6.0")
349
+
350
+ app.add_config_value(
351
+ "pygments_dark_style", default="native", rebuild="env", types=[str]
352
+ )
353
+
354
+ # Add default MyST extensions
355
+ if "myst_enable_extensions" in app.config:
356
+ if isinstance(app.config.myst_enable_extensions, list):
357
+ app.config.myst_enable_extensions.append("dollarmath")
358
+ elif isinstance(app.config.myst_enable_extensions, set):
359
+ app.config.myst_enable_extensions.add("dollarmath")
360
+ else:
361
+ raise ConfigError(
362
+ "myst_enable_extensions should be a list or set of strings."
363
+ )
364
+
365
+ app.add_html_theme("celshast", str(THEME_PATH))
366
+
367
+ app.add_post_transform(WrapTableAndMathInAContainerTransform)
368
+
369
+ app.connect("html-page-context", _html_page_context)
370
+ app.connect("builder-inited", _builder_inited)
371
+ app.connect("build-finished", _overwrite_pygments_css)
372
+
373
+ return {
374
+ "parallel_read_safe": True,
375
+ "parallel_write_safe": True,
376
+ "version": __version__,
377
+ }
@@ -0,0 +1,54 @@
1
+ # Copyright (c) 2021 Pradyun Gedam
2
+ # Licensed under the Open Software License version 3.0
3
+ # SPDX-License-Identifier: OSL-3.0
4
+
5
+ """This is a demo module included in the docs in order to exercise autodoc."""
6
+
7
+ from typing import Optional, TextIO, Type, Union
8
+
9
+
10
+ def show_warning(
11
+ message: Union[Warning, str],
12
+ category: Type[Warning],
13
+ filename: str,
14
+ lineno: int,
15
+ file: Optional[TextIO] = None,
16
+ line: Optional[str] = None,
17
+ ) -> None:
18
+ """Show a warning to the end user.
19
+
20
+ .. code:: python
21
+
22
+ warnings.showwarning = show_warning
23
+
24
+ :param message: What you want to tell the user.
25
+ :param category: The type of warning.
26
+ :param filename: The file that the warning is for.
27
+ :param lineno: The line that the warning is for.
28
+ :param file: Where to write the warning.
29
+ :param line: line of source code to be included in the warning message
30
+ """
31
+
32
+
33
+ class RandomNumberGenerator:
34
+ """A random number generator."""
35
+
36
+ def __init__(self, *, seed: int = 4) -> None:
37
+ """Initialize."""
38
+ self._seed = 4
39
+
40
+ @property
41
+ def seed(self) -> int:
42
+ """Get seed for random number generation.
43
+
44
+ .. seealso:: https://xkcd.com/221/
45
+ """
46
+ return self._seed
47
+
48
+ def get_random_integer(self) -> int:
49
+ """Return a random integer."""
50
+ return self.seed
51
+
52
+ def get_random_float(self) -> float:
53
+ """Return a random float."""
54
+ return float(self.seed)
@@ -0,0 +1,175 @@
1
+ import Gumshoe from "./gumshoe-patched.js";
2
+
3
+ ////////////////////////////////////////////////////////////////////////////////
4
+ // Scroll Handling
5
+ ////////////////////////////////////////////////////////////////////////////////
6
+ var tocScroll = null;
7
+ var header = null;
8
+ var lastScrollTop = window.pageYOffset || document.documentElement.scrollTop;
9
+ const GO_TO_TOP_OFFSET = 64;
10
+
11
+ // function scrollHandlerForHeader() {
12
+ // if (Math.floor(header.getBoundingClientRect().top) == 0) {
13
+ // header.classList.add("scrolled");
14
+ // } else {
15
+ // header.classList.remove("scrolled");
16
+ // }
17
+ // }
18
+
19
+ function scrollHandlerForBackToTop(positionY) {
20
+ if (positionY < GO_TO_TOP_OFFSET) {
21
+ document.documentElement.classList.remove("show-back-to-top");
22
+ } else {
23
+ if (positionY < lastScrollTop) {
24
+ document.documentElement.classList.add("show-back-to-top");
25
+ } else if (positionY > lastScrollTop) {
26
+ document.documentElement.classList.remove("show-back-to-top");
27
+ }
28
+ }
29
+ lastScrollTop = positionY;
30
+ }
31
+
32
+ function scrollHandlerForTOC(positionY) {
33
+ if (tocScroll === null) {
34
+ return;
35
+ }
36
+
37
+ // top of page.
38
+ if (positionY == 0) {
39
+ tocScroll.scrollTo(0, 0);
40
+ } else if (
41
+ // bottom of page.
42
+ Math.ceil(positionY) >=
43
+ Math.floor(document.documentElement.scrollHeight - window.innerHeight)
44
+ ) {
45
+ tocScroll.scrollTo(0, tocScroll.scrollHeight);
46
+ } else {
47
+ // somewhere in the middle.
48
+ const current = document.querySelector(".scroll-current");
49
+ if (current == null) {
50
+ return;
51
+ }
52
+
53
+ // https://github.com/pypa/pip/issues/9159 This breaks scroll behaviours.
54
+ // // scroll the currently "active" heading in toc, into view.
55
+ // const rect = current.getBoundingClientRect();
56
+ // if (0 > rect.top) {
57
+ // current.scrollIntoView(true); // the argument is "alignTop"
58
+ // } else if (rect.bottom > window.innerHeight) {
59
+ // current.scrollIntoView(false);
60
+ // }
61
+ }
62
+ }
63
+
64
+ function scrollHandler(positionY) {
65
+ // scrollHandlerForHeader();
66
+ scrollHandlerForBackToTop(positionY);
67
+ scrollHandlerForTOC(positionY);
68
+ }
69
+
70
+ ////////////////////////////////////////////////////////////////////////////////
71
+ // Theme Toggle
72
+ ////////////////////////////////////////////////////////////////////////////////
73
+ function setTheme(mode) {
74
+ if (mode !== "light" && mode !== "dark" && mode !== "auto") {
75
+ console.error(`Got invalid theme mode: ${mode}. Resetting to auto.`);
76
+ mode = "auto";
77
+ }
78
+
79
+ document.body.dataset.theme = mode;
80
+ localStorage.setItem("theme", mode);
81
+ console.log(`Changed to ${mode} mode.`);
82
+ }
83
+
84
+ function cycleThemeOnce() {
85
+ const currentTheme = localStorage.getItem("theme") || "auto";
86
+ const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
87
+
88
+ if (prefersDark) {
89
+ // Auto (dark) -> Light -> Dark
90
+ if (currentTheme === "auto") {
91
+ setTheme("light");
92
+ } else if (currentTheme == "light") {
93
+ setTheme("dark");
94
+ } else {
95
+ setTheme("auto");
96
+ }
97
+ } else {
98
+ // Auto (light) -> Dark -> Light
99
+ if (currentTheme === "auto") {
100
+ setTheme("dark");
101
+ } else if (currentTheme == "dark") {
102
+ setTheme("light");
103
+ } else {
104
+ setTheme("auto");
105
+ }
106
+ }
107
+ }
108
+
109
+ ////////////////////////////////////////////////////////////////////////////////
110
+ // Setup
111
+ ////////////////////////////////////////////////////////////////////////////////
112
+ function setupScrollHandler() {
113
+ // Taken from https://developer.mozilla.org/en-US/docs/Web/API/Document/scroll_event
114
+ let last_known_scroll_position = 0;
115
+ let ticking = false;
116
+
117
+ window.addEventListener("scroll", function (e) {
118
+ last_known_scroll_position = window.scrollY;
119
+
120
+ if (!ticking) {
121
+ window.requestAnimationFrame(function () {
122
+ scrollHandler(last_known_scroll_position);
123
+ ticking = false;
124
+ });
125
+
126
+ ticking = true;
127
+ }
128
+ });
129
+ window.scroll();
130
+ }
131
+
132
+ function setupScrollSpy() {
133
+ if (tocScroll === null) {
134
+ return;
135
+ }
136
+
137
+ // Scrollspy -- highlight table on contents, based on scroll
138
+ // new Gumshoe(".toc-tree a", {
139
+ // reflow: true,
140
+ // recursive: true,
141
+ // navClass: "scroll-current",
142
+ // offset: () => {
143
+ // let rem = parseFloat(getComputedStyle(document.documentElement).fontSize);
144
+ // return header.getBoundingClientRect().height + 0.5 * rem + 1;
145
+ // },
146
+ // });
147
+ }
148
+
149
+ function setupTheme() {
150
+ // Attach event handlers for toggling themes
151
+ const buttons = document.getElementsByClassName("theme-toggle");
152
+ Array.from(buttons).forEach((btn) => {
153
+ btn.addEventListener("click", cycleThemeOnce);
154
+ });
155
+ }
156
+
157
+ function setup() {
158
+ setupTheme();
159
+ setupScrollHandler();
160
+ setupScrollSpy();
161
+ }
162
+
163
+ ////////////////////////////////////////////////////////////////////////////////
164
+ // Main entrypoint
165
+ ////////////////////////////////////////////////////////////////////////////////
166
+ function main() {
167
+ document.body.parentNode.classList.remove("no-js");
168
+
169
+ // header = document.querySelectorAll("header")[1];
170
+ tocScroll = document.querySelector(".toc-scroll");
171
+
172
+ setup();
173
+ }
174
+
175
+ document.addEventListener("DOMContentLoaded", main);