From 3b6684568013f9607c68f3a3d331755f7f3f34f4 Mon Sep 17 00:00:00 2001 From: Jonas Ehrlich Date: Sat, 4 May 2024 21:35:01 +0200 Subject: [PATCH] Add support for lightbox configuration --- README.md | 22 +++++++++++ docs/conf.py | 6 +++ sphinxcontrib/lightbox2/__init__.py | 61 +++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+) diff --git a/README.md b/README.md index 4e970ee..d5da8a4 100644 --- a/README.md +++ b/README.md @@ -24,3 +24,25 @@ Add `sphinxcontrib.lightbox2` to the list of `extensions` in your *conf.py*: ``` python extensions = ["sphinxcontrib.lightbox2"] ``` + +## Configuration + +Lightbox2 offers different configuration [options](https://lokeshdhakar.com/projects/lightbox2/#options). +These options are exposed in `sphinxcontrib-lightbox2` through options in the *conf.py*. + +See the mapping of lightbox2 options to Sphinx options + +| Lightbox2 option name | Sphinx Option Name | Default Value | +| ----------------------|--------------------|---------------| +| `alwaysShowNavOnTouchDevices` | `lightbox2_always_show_nav_on_touch_devices` | `False` | +| `albumLabel` | `lightbox2_album_label` | `"Image %1 of %2"` | +| `disableScrolling` | `lightbox2_disable_scrolling`| `False` | +| `fadeDuration` | `lightbox2_fade_duration`| `600` | +| `fitImagesInViewport` | `lightbox2_fit_images_in_viewport`| `True` | +| `imageFadeDuration` | `lightbox2_image_fade_duration`| `600` | +| `maxWidth` | `lightbox2_max_width`| `None` | +| `maxHeight` | `lightbox2_max_height`| `None` | +| `positionFromTop` | `lightbox2_position_from_top`| `50` | +| `resizeDuration` | `lightbox2_resize_duration`| `700` | +| `showImageNumberLabel` | `lightbox2_show_image_number_label`| `True` | +| `wrapAround` | `lightbox2_wrap_around`| `True` | diff --git a/docs/conf.py b/docs/conf.py index 38bbe27..6e4d31f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -33,3 +33,9 @@ # https://myst-parser.readthedocs.io/en/latest/configuration.html myst_enable_extensions = ["colon_fence"] + +# -- Options for sphinxcontrib.lightbox2 ------------------------------------- + +# The time it takes for the Lightbox container and overlay to fade in and out, in milliseconds +lightbox2_fade_duration = 100 +lightbox2_image_fade_duration = 100 diff --git a/sphinxcontrib/lightbox2/__init__.py b/sphinxcontrib/lightbox2/__init__.py index 3514029..5bdb840 100644 --- a/sphinxcontrib/lightbox2/__init__.py +++ b/sphinxcontrib/lightbox2/__init__.py @@ -1,10 +1,14 @@ +import dataclasses import importlib.metadata +import json import pathlib import posixpath import urllib.parse +from typing import Generic, TypeVar from docutils import nodes from sphinx.application import Sphinx +from sphinx.config import Config from sphinx.environment import BuildEnvironment from sphinx.util import display, osutil from sphinx.util.typing import ExtensionMetadata @@ -26,6 +30,7 @@ # No metadata is available, this could be because the tool is running from source __version__ = "unknown" +T = TypeVar("T") STATIC_FILES = ( pathlib.Path("assets/images/close.png"), pathlib.Path("assets/images/next.png"), @@ -37,6 +42,53 @@ ) +@dataclasses.dataclass +class Lightbox2ConfigOption(Generic[T]): + name: str + default: T + + def sphinx_config_name(self) -> str: + return "lightbox2_" + self.name + + def js_config_name(self) -> str: + return snake_to_camel(self.name) + + +CONFIG_OPTIONS = ( + Lightbox2ConfigOption("always_show_nav_on_touch_devices", False), + Lightbox2ConfigOption("album_label", "Image %1 of %2"), + Lightbox2ConfigOption("disable_scrolling", False), + Lightbox2ConfigOption("fade_duration", 600), + Lightbox2ConfigOption("fit_images_in_viewport", True), + Lightbox2ConfigOption("image_fade_duration", 600), + Lightbox2ConfigOption("max_width", None), + Lightbox2ConfigOption("max_height", None), + Lightbox2ConfigOption("position_from_top", 50), + Lightbox2ConfigOption("resize_duration", 700), + Lightbox2ConfigOption("show_image_number_label", True), + Lightbox2ConfigOption("wrap_around", True), +) + + +def snake_to_camel(snake_str: str) -> str: + """Translate a snake_case string to camelCase""" + components = snake_str.split("_") + # Capitalize the first letter of each component except the first one + return components[0] + "".join(x.title() for x in components[1:]) + + +def render_lightbox2_option_method_call(config: Config) -> str: + """Render the lightbox.options method call with the configured options to a string""" + lightbox_options = {} + + for option in CONFIG_OPTIONS: + val = getattr(config, option.sphinx_config_name(), None) + if val is None: + continue + lightbox_options[option.js_config_name()] = val + return f"lightbox.option({json.dumps(lightbox_options)})" + + def start_lightbox_anchor(self: HTML5Translator, uri: str) -> None: """ Write the start of a lightbox anchor to the body @@ -69,6 +121,10 @@ def install_static_files(app: Sphinx, env: BuildEnvironment) -> None: elif dest_file_path.suffix == ".css": app.add_css_file(str(dest_file_path.relative_to(static_dir))) + lightbox_options_path = dest_path / "js" / "lightbox2-options.js" + lightbox_options_path.write_text(render_lightbox2_option_method_call(env.config)) + app.add_js_file(str(lightbox_options_path.relative_to(static_dir))) + def html_visit_plantuml(self: HTML5Translator, node: nodes.Element) -> None: @@ -109,6 +165,11 @@ def html_depart_image(self: HTML5Translator, node: nodes.Element) -> None: def setup(app: Sphinx) -> ExtensionMetadata: app.require_sphinx("7.0") + for option in CONFIG_OPTIONS: + app.add_config_value( + option.sphinx_config_name(), default=option.default, rebuild="env", types=type(option.default) + ) + if __PLANTUML_AVAILABLE__: # sphinxcontrib.plantuml is available, override require the extension to be setup before we continue app.setup_extension("sphinxcontrib.plantuml")