diff --git a/docs/_static/logo-chevron.svg b/docs/_static/logo-chevron.svg new file mode 100644 index 0000000..cfc047e --- /dev/null +++ b/docs/_static/logo-chevron.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index 106229f..1cdb144 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -38,7 +38,7 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = ["myst_parser", "sphinx_design", "sphinx_togglebutton"] +extensions = ["myst_nb", "sphinx_copybutton", "sphinx_design", "sphinx_togglebutton"] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] @@ -89,6 +89,14 @@ myst_enable_extensions = ["colon_fence"] # To test behavior in JS +togglebutton_groups = { + "cell-inputs": ".cell.tag_hide-input .cell_input", +"cell-outputs": ".cell.tag_hide-output .cell_output", +"directive": ".toggle", +"admonitions": ".admonition.dropdown", +"group1": ".group-one", +"group2": ".group-two", +} # togglebutton_hint = "test show" # togglebutton_hint_hide = "test hide" # togglebutton_open_on_print = False @@ -97,6 +105,8 @@ # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". # html_static_path = ["_static"] +html_logo = "_static/logo-chevron.svg" +html_title = "Sphinx Togglebutton" # Custom sidebar templates, must be a dictionary that maps document names # to template names. @@ -176,3 +186,23 @@ "Miscellaneous", ) ] + +# -- Create an example directive for the docs --------------------------------- +from docutils.parsers.rst.directives.admonitions import Admonition + + +class Example(Admonition): + def run(self): + # Manually add a "tip" class to style it + if "class" not in self.options: + self.options["class"] = ["tip"] + else: + self.options["class"].append("tip") + # Add `Example` to the title so we don't have to type it + self.arguments[0] = f"Example: {self.arguments[0]}" + # Now run the Admonition logic so it behaves the same way + nodes = super().run() + return nodes + +def setup(app): + app.add_directive("example", Example) \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index 759109c..ab5f59d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -21,7 +21,7 @@ Some content ```` ::: -See {ref}`dropdown-admonitions` for more information. +See {ref}`use:admonition-toggles` for more information. ## Hide any content behind a toggle button @@ -81,5 +81,6 @@ See {ref}`usage` for information about how to use `sphinx-togglebutton`. :maxdepth: 2 use reference/index +reference/notebooks changelog ``` \ No newline at end of file diff --git a/docs/reference/index.md b/docs/reference/index.md index 87a993f..d9ce2c4 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -3,6 +3,18 @@ This page shows the most common ways that `sphinx-togglebutton` is used as a reference. This is a benchmark for styling, and also helps demonstrate the behavior of this extension. +## Toggle all button + +Here's a button that will toggle all items of a particular type + +```{toggle-all-button} +``` + +With a custom title: + +```{toggle-all-button} A test! +``` + ## Use amongst text Here's a paragraph, it's just here to provide some text context for the togglebuttons in this section. diff --git a/docs/reference/notebooks.md b/docs/reference/notebooks.md new file mode 100644 index 0000000..d08c694 --- /dev/null +++ b/docs/reference/notebooks.md @@ -0,0 +1,69 @@ +--- +jupytext: + cell_metadata_filter: -all + formats: md:myst + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.11.5 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +# MyST Notebooks + +`sphinx-togglebutton` is particularly useful with `MyST-NB` notebooks. +This is used to show and hide code cell inputs and outputs. + +Here is a demonstration of the functionality. + +## `{toggle-all-button}` usage + +The code below generated the buttons that follow: + +```` +```{toggle-all-button} +``` + +```{toggle-all-button} cell-inputs +``` + +```{toggle-all-button} cell-outputs +``` +```` + +```{toggle-all-button} +``` + +```{toggle-all-button} cell-inputs +``` + +```{toggle-all-button} cell-outputs +``` + +## Cell inputs + +```{code-cell} +:tags: [hide-input] +for ii in range(20): + print(f"Number: {ii}") +``` + +## Cell outputs + +```{code-cell} +:tags: [hide-output] +for ii in range(20): + print(f"Number: {ii}") +``` + +## Hide the whole cell + +```{code-cell} +:tags: [hide-cell] +for ii in range(20): + print(f"Number: {ii}") +``` diff --git a/docs/use.md b/docs/use.md index 4612b3d..9b95b5f 100644 --- a/docs/use.md +++ b/docs/use.md @@ -3,19 +3,110 @@ This page covers how to use and configure / customize `sphinx-togglebutton`. -There are three main ways to use `sphinx-togglebutton`: +**To toggle content**, there are two options: -- Wrap arbitrary objects in a toggle button via a CSS selector -- Collapse admonitions with the `dropdown` class -- Make arbitrary chunks of content "toggle-able" with the `toggle` directive +- Use [a CSS selector](use:css-selector) to identify and toggle content +- Use [a `{toggle}` directive](use:toggle-directive) to toggle chunks of content. -Each is described below +**To toggle multiple items at once** you may **group** elements to toggle, and [insert a button to toggle all items of a group](use:toggle-groups). + +Each is described below. (use:css-selector)= -## Collapse a block of content with a CSS selector +## Collapse content with a toggle button + +You can hide any content and display a toggle button to show it. +There are two ways to do this. + +### With CSS classes + +`sphinx-togglebutton` will use a CSS selector to match elements on a page, and will convert each of them into toggle-able content. +By default, `sphinx-togglebutton` uses this selector: + +``` +.toggle, .admonition.dropdown +``` + +This will match any items with `toggle` class, and Sphinx admonitions with a `dropdown` class. + +:::{example} A div with a `toggle` class +Create a `div` with a `toggle` class, so that `sphinx-togglebutton` will detect it with the default selector: + +```` +```{container} toggle +Some toggled content with a `toggle` class. +``` +```` + +results in: + +```{container} toggle +Some toggled content with a `toggle` class. +``` +::: + +:::{tip} +You can change the selector that is used by providing your own selector in `togglebutton_selector`. +However, a more flexible way to control which elements are toggled is [to use toggle button groups](use:toggle-groups). +::: + +(use:toggle-directive)= +### With the `{toggle}` directive + +As a short-hand for the above approach, there is a `{toggle}` directive to quickly wrap content in a Toggle button. +The content of the `{toggle}` directive will be wrapped in a `
` element (see [](use:block-level)). + +:::{example} Wrap an image with a `{toggle}` directive +To wrap an image use the toggle directive like so: + +```` +```{toggle} +Here is my toggle-able content! +``` +```` +results in + +```{toggle} +Here is my toggle-able content! +``` +::: + +#### Show the content by default + +To show the toggle-able content by default, use the `:show:` flag. + +:::{example} Show content by default + +```` +```{toggle} +:show: +Here is my toggle-able content! +``` +```` + +results in + +```{toggle} +:show: +Here is my toggle-able content! +``` +::: + +## Types of toggled content + +There are two types of toggle-able content: [block-level toggles](use:block-level) and [admonition toggles](use:admonition-toggle). -You can hide any content and display a toggle button to show it by using certain CSS classes. -`sphinx-togglebutton` will wrap elements with these classes in a `
` block like so: +(use:block-level)= +### Block-level toggles + +For most content, `sphinx-togglebutton` will wrap elements that match the selector in a `
` block. +The block looks like this: + +```{toggle} +Some toggle-able content! +``` + +These blocks have the following `
` structure: ```html
@@ -24,124 +115,271 @@ You can hide any content and display a toggle button to show it by using certain
``` -:::{admonition} example -:class: tip -This MyST Markdown: +:::{example} Block-level toggles +Add a `toggle` class to an image directive so that it becomes toggled. ````md ```{image} https://media.giphy.com/media/FaKV1cVKlVRxC/giphy.gif :class: toggle ``` ```` + results in: + ```{image} https://media.giphy.com/media/FaKV1cVKlVRxC/giphy.gif :class: toggle ``` + +::: + + +(use:admonition-toggles)= +### Admonition toggles + +If the matched element **also has an `admonition` class** then `sphinx-togglebutton` will treat it as a Sphinx admonition, and will only toggle the _content_ of the admonition. +For example: + +:::{warning} +:class: dropdown +A toggled warning! ::: -### Configure the CSS selector used to insert toggle buttons +The default selector will match admonitions with a `dropdown` class (the selector is `.admonitions.dropdown`). + +:::{example} Make an `{admonition}` toggled -By default, `sphinx-togglebutton` will use this selector: +Create a toggled admonition by adding the `dropdown` class to it. +`````md +````{admonition} Here's my admonition +:class: dropdown + +```{image} https://media.giphy.com/media/FaKV1cVKlVRxC/giphy.gif ``` -.toggle, .admonition.dropdown +```` +````` + +results in: + +````{admonition} Here's my admonition +:class: dropdown + +```{image} https://media.giphy.com/media/FaKV1cVKlVRxC/giphy.gif ``` +```` +::: + + +(use:toggle-groups)= +## Create groups of toggled content -However, you can customize this behavior with the `togglebutton_selector` configuration value. -To specify the selector to use, pass a valid CSS selector as a string: +You can create groups of toggle content that can each be opened and closed as a group. +To do so, use the `togglebutton_groups` configuration. +This is a dictionary of `group-name: css-selector` pairs. +Each group name can be referenced by the [group toggle button](use:group-toggle-all). -:::{admonition} example -:class: tip -Configure `sphinx-togglebutton` to look for a `.toggle-this-element` class and an element with ID `#my-special-id` **instead of** `.toggle` and `.admonition.dropdown`. +For example, the following configuration creates two toggle groups ```python -sphinx_togglebutton_selector = ".toggle-this-element, #my-special-id" +togglebutton_groups = { + "group1": ".group-one", + "group2": ".group-two" +} ``` + +Any elements that match the selector `.group-one` will be assigned to `group1`, and any elements that match the selector `.group-two` will be assigned to `group2`. + +:::{warning} +Using `togglebutton_groups` will **override** the value of `togglebutton_selector`. +If you want to manually keep the default selector for toggle-buttons, add a group with the `.toggle, .admonition.dropdown` selector to it. ::: -(dropdown-admonitions)= -## Collapse admonitions with the `dropdown` class +(use:group-toggle-all)= +### Toggle groups at once with the `{toggle-group}` directive -`sphinx-togglebutton` treats admonitions as a special case if they are selected. -If a Sphinx admonition matches the toggle button selector, then its title will be displayed with a button to reveal its content. +To toggle all items that belong to the same group at once, use the `{toggle-group}` directive. +It will add a button that, when clicked, will toggle all of the content in a group. +Each item that is closed will be opened, and each item that is open will be closed. -:::{admonition} example -:class: tip -````md -```{admonition} This will be shown -:class: dropdown -And this will be hidden! +:::{example} Toggle only buttons from one group + +Below we define a `{toggle-group}` button that only toggles elements from toggled items that are part of `group1`. + +First, define `group1` and `group2` in `conf.py`: + +```python +togglebutton_groups = { + "group1": ".group-one", + "group2": ".group-two" +} +``` + +Next, create two blocks of toggled content, one for each group: + +```` +```{container} group-one +This first content is for **Group One**. +``` +```{container} group-one +This second content is for **Group One**. +``` +```{container} group-two +This first content is for **Group Two**. +``` +```{container} group-two +This second content is for **Group Two**. ``` ```` -results in -```{admonition} This will be shown -:class: dropdown -And this will be hidden! +```{container} group-one +This first content is for **Group One**. +``` +```{container} group-one +This second content is for **Group One**. +``` +```{container} group-two +This first content is for **Group Two**. +``` +```{container} group-two +This second content is for **Group Two**. ``` -::: -This works for any kind of Sphinx admoniton: +Finally, add a button to toggle **only** group one: -:::{note} -:class: dropdown -A note! -::: +```` +```{toggle-group} group1 +``` +```` -:::{warning} -:class: dropdown -A warning! +results in: + +```{toggle-group} group1 +``` ::: +#### Toggle all groups at once -(toggle-directive)= -## Use the `{toggle}` directive to toggle blocks of content +To toggle all groups with the button, simply do not provide a group name and it will selector all groups. -To add toggle-able content, use the **toggle directive**. This directive -will wrap its content in a toggle-able container. You can call it like so: +:::{example} Toggle all buttons regardless of group -:::{tab-set-code} +The following code will toggle *all* buttons on the page: -````markdown -```{toggle} -Here is my toggle-able content! +```` +```{toggle-group} ``` ```` -```rst -.. toggle:: +```{toggle-group} +``` +::: + +#### Customize the title of `{toggle-group}` + +You may optionally provide a title as well. - Here is my toggle-able content! +:::{example} Custom title with `{toggle-group}` + +```` +```{toggle-group} tips +:text: Toggle all of the buttons on the page! ``` +```` + +results in: +```{toggle-group} tips +:text: Toggle all of the buttons on the page! +``` ::: +### Use the JavaScript function to toggle all buttons -The code above results in: +There is also a JavaScript function you can call to trigger the same behavior as the `{toggle-group}` directive described above. -:::{toggle} +This function is called `toggleAllByGroup` and takes a single argument, which is a group name that you wish to toggle. -Here is my toggle-able content! +You can call it like: + +```javascript +toggleAllByGroup("groupname"); +``` + +It is meant for designing your own buttons and UI elements that trigger toggle buttons on the page. + +:::{example} Create your own `toggle-groups` button + +Here's a toggle-able admonition matching **group1** from above: + +```` +```{note} +:class: group-one +Here's a toggle-admonition in `group-one`! +``` +```` +results in: + +```{note} +:class: group-one +Here's a toggle-admonition in `group-one`! +``` + +And the following code embeds a `{raw}` block with custom HTML to trigger this function: + +```` +```{raw} html + +``` +```` + +results in: + +```{raw} html + +``` ::: -To show the toggle-able content by default, use the `:show:` flag. +To **toggle all buttons at once** with JavaScript, regardless of group, pass `" "` to the function (i.e., `toggleAllByGroup("**")`). -````markdown -```{toggle} -:show: +:::{example} Toggle all buttons with JavaScript +Below are two toggle-buttons from two different groups: -Here is my toggle-able content! +```` +```{container} group-one +Group one text! +``` +```{container} group-two +Group two text! ``` ```` -It results in the following: +results in: -:::{toggle} -:show: +```{container} group-one +Group one text! +``` +```{container} group-two +Group two text! +``` -Here is my toggle-able content! +And the following JavaScript button opens both at once: + +```` +```{raw} html + +``` +```` +results in + +```{raw} html + +``` ::: -## Change the button hint text +## Customize the toggle button style + +There are a few ways you can customize the toggle button style. + +### Change toggle button hint text You can control the "hint" text that is displayed next to togglebuttons. To do so, use the following configuration variable in your `conf.py` file: @@ -151,7 +389,7 @@ togglebutton_hint = "Displayed when the toggle is closed." togglebutton_hint_hide = "Displayed when the toggle is open." ``` -## Change the toggle icon color +### Change the toggle icon color You can apply some extra styles to the toggle button to achieve the look you want. This is particularly useful if the color of the toggle button does not contrast with the background of an admonition. @@ -171,7 +409,7 @@ button.toggle-button { } ``` -## Printing behavior with toggle buttons +## Print behavior with toggle buttons By default `sphinx-togglebutton` will **open all toggle-able content when you print**. It will close them again when the printing operation is complete. diff --git a/setup.py b/setup.py index 07e35d9..566f187 100644 --- a/setup.py +++ b/setup.py @@ -27,6 +27,6 @@ "sphinx_togglebutton": ["_static/togglebutton.css", "_static/togglebutton.js", "_static/togglebutton-chevron.svg"] }, install_requires=["setuptools", "wheel", "sphinx", "docutils"], - extras_require={"sphinx": ["myst_parser", "sphinx_book_theme", "sphinx_design"]}, + extras_require={"sphinx": ["myst_nb", "sphinx_copybutton", "sphinx_book_theme", "sphinx_design"]}, classifiers=["License :: OSI Approved :: MIT License"], ) diff --git a/sphinx_togglebutton/__init__.py b/sphinx_togglebutton/__init__.py index aeac7b5..d6f6302 100644 --- a/sphinx_togglebutton/__init__.py +++ b/sphinx_togglebutton/__init__.py @@ -1,10 +1,15 @@ """A small sphinx extension to add "toggle" buttons to items.""" import os -from docutils.parsers.rst import Directive, directives +from typing import Dict, AnyStr from docutils import nodes +from sphinx.util import logging + +from .directive import Toggle, ToggleAllInGroupButton, ToggleAllInGroupNode __version__ = "0.3.0" +SPHINX_LOGGER = logging.getLogger(__name__) + def st_static_path(app): static_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "_static")) @@ -22,30 +27,26 @@ def initialize_js_assets(app, config): # This function reads in a variable and inserts it into JavaScript def insert_custom_selection_config(app): - # This is a configuration that you've specified for users in `conf.py` - selector = app.config["togglebutton_selector"] - js_text = "var togglebuttonSelector = '%s';" % selector + if isinstance(app.config.togglebutton_selector, str): + selectors = {"default_selector": app.config.togglebutton_selector} + if app.config.togglebutton_groups: + if isinstance(app.config.togglebutton_selector, str): + SPHINX_LOGGER.info("toggle-button]: Selector groups over-riding selector string.") + selectors = app.config.togglebutton_groups + js_text = "var togglebuttonSelectors = %s;" % selectors app.add_js_file(None, body=js_text) -class Toggle(Directive): - """Hide a block of markup text by wrapping it in a container.""" - - optional_arguments = 1 - final_argument_whitespace = True - has_content = True +# Helper functions for controlling our toggle node's behavior +def visit_element_html(self, node): + """Render an element node as HTML.""" + self.body.append(node.html()) + raise nodes.SkipNode - option_spec = {"id": directives.unchanged, "show": directives.flag} - def run(self): - self.assert_has_content() - classes = ["toggle"] - if "show" in self.options: - classes.append("toggle-shown") - - parent = nodes.container(classes=classes) - self.state.nested_parse(self.content, self.content_offset, parent) - return [parent] +def skip(self, node): + """Skip the node on a particular builder type.""" + raise nodes.SkipNode # We connect this function to the step after the builder is initialized @@ -59,6 +60,7 @@ def setup(app): # Add the string we'll use to select items in the JS # Tell Sphinx about this configuration variable app.add_config_value("togglebutton_selector", ".toggle, .admonition.dropdown", "html") + app.add_config_value("togglebutton_groups", {}, "html") app.add_config_value("togglebutton_hint", "Click to show", "html") app.add_config_value("togglebutton_hint_hide", "Click to hide", "html") app.add_config_value("togglebutton_open_on_print", True, "html") @@ -66,7 +68,19 @@ def setup(app): # Run the function after the builder is initialized app.connect("builder-inited", insert_custom_selection_config) app.connect("config-inited", initialize_js_assets) + + # Register nodes and directives app.add_directive("toggle", Toggle) + app.add_directive("toggle-group", ToggleAllInGroupButton) + app.add_node( + ToggleAllInGroupNode, + html=(visit_element_html, None), + latex=(skip, None), + textinfo=(skip, None), + text=(skip, None), + man=(skip, None), + override=True, + ) return { "version": __version__, "parallel_read_safe": True, diff --git a/sphinx_togglebutton/_static/togglebutton.css b/sphinx_togglebutton/_static/togglebutton.css index 3560ceb..c3eb44f 100644 --- a/sphinx_togglebutton/_static/togglebutton.css +++ b/sphinx_togglebutton/_static/togglebutton.css @@ -1,3 +1,32 @@ +/** + * General button styles + */ +.toggle-button-style { + display: flex; + align-items: center; + width: fit-content; + + border-radius: .4em; + border: 1px solid #ccc; + background: #f8f8f8; + padding: 0.5em 1em; + font-size: .9em; + margin-bottom: 1em; +} + +.toggle-button-style:hover { + background: #f6f6f6; +} + +.toggle-button-style:active { + background: #eee; +} + +.toggle-button-style:focus, .toggle-button-style:focus-visible { + outline: none; +} + + /** * Admonition-based toggles */ @@ -84,24 +113,9 @@ details.toggle-details { } details.toggle-details summary { - display: flex; - align-items: center; - width: fit-content; cursor: pointer; list-style: none; - border-radius: .4em; - border: 1px solid #ccc; - background: #f8f8f8; padding: 0.4em 1em 0.4em 0.5em; /* Less padding on left because the SVG has left margin */ - font-size: .9em; -} - -details.toggle-details summary:hover { - background: #f6f6f6; -} - -details.toggle-details summary:active { - background: #eee; } details.toggle-details[open] summary { diff --git a/sphinx_togglebutton/_static/togglebutton.js b/sphinx_togglebutton/_static/togglebutton.js index 0d15d0c..5d64193 100644 --- a/sphinx_togglebutton/_static/togglebutton.js +++ b/sphinx_togglebutton/_static/togglebutton.js @@ -9,83 +9,89 @@ let toggleChevron = ` `; var initToggleItems = () => { - var itemsToToggle = document.querySelectorAll(togglebuttonSelector); - console.log(`[togglebutton]: Adding toggle buttons to ${itemsToToggle.length} items`) - // Add the button to each admonition and hook up a callback to toggle visibility - itemsToToggle.forEach((item, index) => { - if (item.classList.contains("admonition")) { - // If it's an admonition block, then we'll add a button inside - // Generate unique IDs for this item - var toggleID = `toggle-${index}`; - var buttonID = `button-${toggleID}`; - - item.setAttribute('id', toggleID); - if (!item.classList.contains("toggle")){ - item.classList.add("toggle"); - } - // This is the button that will be added to each item to trigger the toggle - var collapseButton = ` - `; - - item.insertAdjacentHTML("afterbegin", collapseButton); - thisButton = document.getElementById(buttonID); - - // Add click handlers for the button + admonition title (if admonition) - thisButton.addEventListener('click', toggleClickHandler); - admonitionTitle = document.querySelector(`#${toggleID} > .admonition-title`) - if (admonitionTitle) { - admonitionTitle.addEventListener('click', toggleClickHandler); - admonitionTitle.dataset.target = toggleID - admonitionTitle.dataset.button = buttonID - } + for (const [group, selector] of Object.entries(togglebuttonSelectors)) { + var itemsToToggle = document.querySelectorAll(selector); + + console.log(`[togglebutton]: Adding toggle button group: ${group} with ${itemsToToggle.length} items`) + // Add the button to each admonition and hook up a callback to toggle visibility + itemsToToggle.forEach((item, index) => { + // Attach togglebutton data entries + item.dataset.togglebuttonGroup = group; + item.dataset.togglebutton = "true"; + if (item.classList.contains("admonition")) { + // If it's an admonition block, then we'll add a button inside + // Generate unique IDs for this item + var toggleID = `toggle-${index}`; + var buttonID = `button-${toggleID}`; - // Now hide the item for this toggle button unless explicitly noted to show - if (!item.classList.contains("toggle-shown")) { - toggleHidden(thisButton); - } - } else { - // If not an admonition, wrap the block in a
block - // Define the structure of the details block and insert it as a sibling - var detailsBlock = ` -
- + item.setAttribute('id', toggleID); + if (!item.classList.contains("toggle")){ + item.classList.add("toggle"); + } + // This is the button that will be added to each item to trigger the toggle + var collapseButton = ` + -
`; - item.insertAdjacentHTML("beforebegin", detailsBlock); - - // Now move the toggle-able content inside of the details block - details = item.previousElementSibling - details.appendChild(item) - - // Set up a click trigger to change the text as needed - details.addEventListener('click', (click) => { - let parent = click.target.parentElement; - if (parent.tagName.toLowerCase() == "details") { - summary = parent.querySelector("summary"); - details = parent; - } else { - summary = parent; - details = parent.parentElement; + `; + + item.insertAdjacentHTML("afterbegin", collapseButton); + thisButton = document.getElementById(buttonID); + + // Add click handlers for the button + admonition title (if admonition) + thisButton.addEventListener('click', toggleClickHandler); + admonitionTitle = document.querySelector(`#${toggleID} > .admonition-title`) + if (admonitionTitle) { + admonitionTitle.addEventListener('click', toggleClickHandler); + admonitionTitle.dataset.target = toggleID + admonitionTitle.dataset.button = buttonID } - // Update the inner text for the proper hint - if (details.open) { - summary.querySelector("span").innerText = toggleHintShow; - } else { - summary.querySelector("span").innerText = toggleHintHide; + + // Now hide the item for this toggle button unless explicitly noted to show + if (!item.classList.contains("toggle-shown")) { + toggleHidden(thisButton); } - - }); + } else { + // If not an admonition, wrap the block in a
block + // Define the structure of the details block and insert it as a sibling + var detailsBlock = ` +
+ + ${toggleChevron} + ${toggleHintShow} + +
`; + item.insertAdjacentHTML("beforebegin", detailsBlock); - // If we have a toggle-shown class, open details block should be open - if (item.classList.contains("toggle-shown")) { - details.click(); + // Now move the toggle-able content inside of the details block + details = item.previousElementSibling + details.appendChild(item) + + // Set up a click trigger to change the text as needed + details.addEventListener('click', (click) => { + let parent = click.target.parentElement; + if (parent.tagName.toLowerCase() == "details") { + summary = parent.querySelector("summary"); + details = parent; + } else { + summary = parent; + details = parent.parentElement; + } + // Update the inner text for the proper hint + if (details.open) { + summary.querySelector("span").innerText = toggleHintShow; + } else { + summary.querySelector("span").innerText = toggleHintHide; + } + + }); + + // If we have a toggle-shown class, open details block should be open + if (item.classList.contains("toggle-shown")) { + details.click(); + } } - } - }) + }); + } }; // This should simply add / remove the collapsed class and change the button text @@ -149,7 +155,6 @@ if (toggleOpenOnPrint == "true") { // Open the admonitions document.querySelectorAll(".admonition.toggle.toggle-hidden").forEach((el) => { - console.log(el); el.querySelector("button.toggle-button").click(); el.dataset["toggle_after_print"] = "true"; }); @@ -170,3 +175,23 @@ if (toggleOpenOnPrint == "true") { }); }); } + + +const toggleAllByGroup = (group) => { + // Are we toggling all buttons or just a group? + if (group === "**") { + var selector = "*[data-togglebutton='true']"; + } else { + var selector = `*[data-togglebutton-group="${group}"]`; + } + + // Trigger a toggle for each button that has matched the selector + document.querySelectorAll(selector).forEach((el) => { + if (el.classList.contains("admonition")) { + el.querySelector("button.toggle-button").click(); + } else { + // We have a details tag, the parent is the `
` block + el.parentElement.open = !(el.parentElement.open === true) + } + }); +} \ No newline at end of file diff --git a/sphinx_togglebutton/directive.py b/sphinx_togglebutton/directive.py new file mode 100644 index 0000000..ac092a2 --- /dev/null +++ b/sphinx_togglebutton/directive.py @@ -0,0 +1,74 @@ +import email +from docutils import nodes +from docutils.parsers.rst import directives +from sphinx.util.docutils import SphinxDirective + + +class ToggleAllInGroupNode(nodes.Element): + """Appended to the doctree by the ToggleAll directive + + Renders as a button to enable thebe on the page. + + If no ToggleAll directive is found in the document but thebe + is enabled, the node is added at the bottom of the document. + """ + + def __init__(self, rawsource="", *children, group=None, text="Toggle All", **attributes): + super().__init__("", text=text, group=group) + + def html(self): + text = self["text"] + group = self["group"] + return (f"""\ + """) + + +class ToggleAllInGroupButton(SphinxDirective): + """Trigger toggle on all elements that match a group.""" + + optional_arguments = 1 + final_argument_whitespace = True + option_spec = { + "text": directives.unchanged + } + has_content = False + + def run(self): + kwargs = {} + if self.arguments: + kwargs["group"] = self.arguments[0] + else: + kwargs["group"] = "**" + + if self.options.get("text"): + kwargs["text"] = self.options["text"] + else: + if kwargs["group"] == "**": + msg = "Toggle all content" + else: + msg = f"Toggle all {kwargs['group']}" + kwargs["text"] = msg + return [ToggleAllInGroupNode(**kwargs)] + + +class Toggle(SphinxDirective): + """Hide a block of markup text by wrapping it in a container.""" + + optional_arguments = 1 + final_argument_whitespace = True + has_content = True + + option_spec = {"id": directives.unchanged, "show": directives.flag} + + def run(self): + self.assert_has_content() + classes = ["toggle"] + if "show" in self.options: + classes.append("toggle-shown") + + parent = nodes.container(classes=classes) + self.state.nested_parse(self.content, self.content_offset, parent) + return [parent]