Skip to content

Commit

Permalink
Add options to customize synchronization behavior
Browse files Browse the repository at this point in the history
  • Loading branch information
cdce8p committed Dec 7, 2024
1 parent 12a2ee2 commit 9294f71
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 7 deletions.
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 one 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

0 comments on commit 9294f71

Please sign in to comment.