Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add options to customize synchronization behavior #50

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 69 additions & 2 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,10 @@ Why not.

## Synchronisation

Tabs across multiple sets are synchronised according to the label, unconditionally. This requires JavaScript to be enabled on the end user's browser and, thus, should be considered a progressive enhancement.
Tabs across multiple sets are synchronised according to the label. This requires JavaScript to be enabled on the end user's browser and, thus, should be considered a progressive enhancement.

```{hint}
Nearly all usage of tabbed content in documentation is based on something about the user which stays consistent throughout the reading (like their OS, or preferred language etc). That's why this behaviour is unconditional.
Nearly all usage of tabbed content in documentation is based on something about the user which stays consistent throughout the reading (like their OS, or preferred language etc). That's why this behaviour is the default.
```

````{tab} Windows
Expand Down Expand Up @@ -195,6 +195,73 @@ $ make html
```
````

### Customisation

```{versionadded} 2024.12.08

```

Synchronisation can be disable by adding the `:no-sync:` option to a tab. It can
also be disabled from the config file.

```py
# disable sync globally
tabs_default_sync_behavior = "none" # default "tab-title"

# disable sync globally for labels "One" and "Two"
tabs_no_sync_labels = {"One", "Two"} # default set()
```

The sync label can be overwritten with the `:sync: <label-name>` option.

```rst
.. tab:: One
:no-sync:

Sync is disabled for this one.

.. tab:: Two
:sync: Five

Sync with label `Five`.

.. tab:: Three

Three is an odd prime.

.. tab:: Four

Four is not a perfect number.
```

```{tab} One
:no-sync:
Sync is disabled for this one.
```

```{tab} Two
:sync: Five
Sync with label `Five`.
```

```{tab} Three
Three is an odd prime.
```

```{tab} Four
Four is not a perfect number.
```

<!-- -->

```{tab} One
This tab is synchronized.
```

```{tab} Five
Five is a nice number.
```

## Nesting

```{versionadded} 2020.04.11.beta8
Expand Down
2 changes: 2 additions & 0 deletions src/sphinx_inline_tabs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ def setup(app):
# We do imports from Sphinx, after validating the Sphinx version
from ._impl import TabContainer, TabDirective, TabHtmlTransform, TabInput, TabLabel

app.add_config_value("tabs_default_sync_behavior", "tab-title", "html", types=[str])
app.add_config_value("tabs_no_sync_labels", set(), "html", types=[set])
app.add_directive("tab", TabDirective)
app.add_post_transform(TabHtmlTransform)
app.add_node(TabInput, html=(TabInput.visit, TabInput.depart))
Expand Down
56 changes: 53 additions & 3 deletions src/sphinx_inline_tabs/_impl.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
"""The actual implementation."""

import itertools
from typing import List
from typing import List, Set

from docutils import nodes
from docutils.parsers.rst import directives
from sphinx.transforms.post_transforms import SphinxPostTransform
from sphinx.util import logging
from sphinx.util.docutils import SphinxDirective
from sphinx.util.nodes import NodeMatcher

logger = logging.getLogger(__name__)


class TabContainer(nodes.container):
"""The initial tree-node for holding tab content."""
Expand Down Expand Up @@ -60,13 +63,42 @@ class TabDirective(SphinxDirective):
has_content = True
option_spec = {
"new-set": directives.flag,
"sync": directives.unchanged_required,
"no-sync": directives.flag,
}

def run(self):
"""Parse a tabs directive."""
self.assert_has_content()

container = TabContainer("", type="tab", new_set="new-set" in self.options)
default_sync_behavior = self.env.app.config.tabs_default_sync_behavior
no_sync_labels = self.env.app.config.tabs_no_sync_labels

sync_label = self.options.get("sync") or self.arguments[0]

no_sync = (
default_sync_behavior == "none"
or sync_label in no_sync_labels
or "no-sync" in self.options
)

if no_sync and self.options.get("sync"):
# no_sync with explicit sync label
logger.warning(
"Sychronisation is disabled for tab group '%s' in %s:%s [sphinx_inline_tabs]",
self.arguments[0],
self.reporter.source,
self.lineno,
)

container = TabContainer(
"",
type="tab",
new_set="new-set" in self.options,
label_text=self.arguments[0],
sync_label=sync_label,
no_sync=no_sync,
)
self.set_source_info(container)

# Handle the label (non-plain-text variants allowed)
Expand Down Expand Up @@ -164,19 +196,37 @@ def finalize_set(self, tab_set: List[TabContainer], set_counter: int):

tab_set_name = f"tab-set--{set_counter}"
node_counter = 0
labels: Set[str] = set()
for node in tab_set:
node_counter += 1
tab_id = tab_set_name + f"-input--{node_counter}"
title, content = node.children

if node.attributes["no_sync"] is False:
sync_label = node.attributes["sync_label"]
if sync_label in labels:
logger.warning(
"Duplicate sync label in tab group '%s' in %s:%s [sphinx_inline_tabs]",
node.attributes["label_text"],
node.source,
node.line,
)
labels.add(sync_label)
sync_attr = {"sync": sync_label}
else:
sync_attr = {"no_sync": True}

# <input>, for storing state in radio boxes.
input_node = TabInput(
type="radio", ids=[tab_id], name=tab_set_name, classes=["tab-input"]
)

# <label>
label_node = TabLabel(
"", *title.children, **{"for": tab_id}, classes=["tab-label"]
"",
*title.children,
**{"for": tab_id, **sync_attr},
classes=["tab-label"],
)

# For error messages
Expand Down
11 changes: 9 additions & 2 deletions src/sphinx_inline_tabs/static/tabs.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ function ready() {

for (const label of li) {
label.onclick = onLabelClick;
const text = label.textContent;
const text = label.getAttribute("sync");
if (text == null) {
continue;
}
if (!labels_by_text[text]) {
labels_by_text[text] = [];
}
Expand All @@ -23,7 +26,11 @@ function ready() {

function onLabelClick() {
// Activate other labels with the same text.
for (label of labels_by_text[this.textContent]) {
text = this.getAttribute("sync");
if (text == null) {
return;
}
for (label of labels_by_text[text]) {
label.previousSibling.checked = true;
}
}
Expand Down