Skip to content

Commit 659a79f

Browse files
committed
introduce inline tab support
Attempt to support inlined tabs for both the sphinx-inline-tabs and sphinx-tabs extensions. Either requires the use of a third-party macro. Signed-off-by: James Knight <[email protected]>
1 parent 2218fc8 commit 659a79f

File tree

15 files changed

+334
-6
lines changed

15 files changed

+334
-6
lines changed

doc/configuration.rst

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2238,6 +2238,58 @@ Advanced processing configuration
22382238

22392239
.. versionadded:: 2.2
22402240

2241+
.. _confluence_tab_macro:
2242+
2243+
.. confval:: confluence_tab_macro
2244+
2245+
.. attention::
2246+
2247+
This feature is considered experimental. Inlined tabs are supported
2248+
through third-party Sphinx extensions as well as only supported on
2249+
Confluence instances using third-party marketplace extensions. The
2250+
combination is less than ideal for providing consistent results and
2251+
performing testing. This may be used by advanced users who can take
2252+
advantage of their instance's configurations to utilize inlined tabs.
2253+
2254+
This configuration is used when attempting to build macros for rendering
2255+
inlined tabs on a page. It is used to define what third-party macro can
2256+
be used to render inlined tabs. This configuration also defines what
2257+
parameter values dictate the name of a tab and well as which tab is
2258+
considered to be the first/primary tab for a set.
2259+
2260+
.. code-block:: python
2261+
2262+
confluence_tab_macro = {
2263+
'macro-name': 'macro-name',
2264+
'primary-id': 'id-for-primary-parameter',
2265+
'primary-value': 'value-to-primary-parameter',
2266+
'title-id': 'id-for-title-parameter',
2267+
}
2268+
2269+
The following configurations are known to function for the listed
2270+
marketplace extensions:
2271+
2272+
.. list-table::
2273+
:header-rows: 1
2274+
:widths: 40 60
2275+
2276+
* - Marketplace Application
2277+
2278+
- Configuration
2279+
2280+
* - Mosaic: Content Formatting Macros & Templates for Confluence
2281+
2282+
- .. code-block:: python
2283+
2284+
confluence_tab_macro = {
2285+
'macro-name': 'cfm-tabs-page',
2286+
'primary-id': 'primaryTab',
2287+
'primary-value': 'true',
2288+
'title-id': 'tabsPageTitle',
2289+
}
2290+
2291+
.. versionadded:: 2.14
2292+
22412293
.. _confluence_remove_title:
22422294

22432295
.. confval:: confluence_remove_title

doc/features.rst

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,12 +248,38 @@ Type Notes
248248
support for tabs, button and inline icons.
249249
`sphinx-diagrams`_ Supported
250250
`sphinx-gallery`_ Supported
251+
`sphinx-inline-tabs`_ Limited support.
252+
253+
Requires a Confluence instance that supports
254+
inlined tabs through the use of a third-party
255+
macro. :lref:`confluence_tab_macro` must be
256+
configured.
251257
`sphinx-needs`_ Limited support.
252258

253259
Formatting of content may not be as expected.
254260
The ``needs_default_layout`` option may need
255261
to be tailored specifically for a Confluence
256262
build.
263+
`sphinx-tabs`_ Limited support.
264+
265+
Requires a Confluence instance that supports
266+
inlined tabs through the use of a third-party
267+
macro. :lref:`confluence_tab_macro` must be
268+
configured.
269+
270+
Features such as group tabs are not
271+
supported.
272+
273+
May require an explicit registration for
274+
support with sphinx-tabs:
275+
276+
.. code-block:: python
277+
278+
sphinx_tabs_valid_builders = [
279+
'confluence',
280+
'singleconfluence',
281+
]
282+
257283
`sphinx-toolbox`_ Supported
258284
`sphinxcontrib-aafig`_ Supported.
259285

@@ -354,7 +380,9 @@ has another concern, feel free to bring up an issue:
354380
.. _sphinx-design: https://sphinx-design.readthedocs.io/
355381
.. _sphinx-diagrams: https://pypi.org/project/sphinx-diagrams/
356382
.. _sphinx-gallery: https://sphinx-gallery.github.io/
383+
.. _sphinx-inline-tabs: https://sphinx-inline-tabs.readthedocs.io/
357384
.. _sphinx-needs: https://sphinxcontrib-needs.readthedocs.io/
385+
.. _sphinx-tabs: https://sphinx-tabs.readthedocs.io/
358386
.. _sphinx-toolbox: https://sphinx-toolbox.readthedocs.io/
359387
.. _sphinx.ext.autodoc: https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html
360388
.. _sphinx.ext.autosectionlabel: https://www.sphinx-doc.org/en/master/usage/extensions/autosectionlabel.html

sphinxcontrib/confluencebuilder/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,8 @@ def setup(app):
279279
cm.add_conf('confluence_permit_raw_html', 'confluence')
280280
# Remove a detected title from generated documents.
281281
cm.add_conf_bool('confluence_remove_title', 'confluence')
282+
# Macro configuration for Confluence-managed inlined tab content.
283+
cm.add_conf('confluence_tab_macro', 'confluence')
282284

283285
# (configuration - third-party related)
284286
# Wrap Mermaid nodes into HTML macros.

sphinxcontrib/confluencebuilder/config/checks.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from sphinxcontrib.confluencebuilder.config.exceptions import ConfluenceHeaderFileConfigError
1616
from sphinxcontrib.confluencebuilder.config.exceptions import ConfluenceJiraServersConfigError
1717
from sphinxcontrib.confluencebuilder.config.exceptions import ConfluenceLatexMacroInvalidConfigError
18-
from sphinxcontrib.confluencebuilder.config.exceptions import ConfluenceLatexMacroMissingKeysConfigError
18+
from sphinxcontrib.confluencebuilder.config.exceptions import ConfluenceMacroMissingKeysConfigError
1919
from sphinxcontrib.confluencebuilder.config.exceptions import ConfluencePageGenerationNoticeConfigError
2020
from sphinxcontrib.confluencebuilder.config.exceptions import ConfluencePageSearchModeConfigError
2121
from sphinxcontrib.confluencebuilder.config.exceptions import ConfluenceParentPageConfigError
@@ -392,8 +392,9 @@ def validate_configuration(builder):
392392

393393
if not all(name in conf_keys for name in required_keys):
394394
keys_str = '\n - '.join(required_keys)
395-
raise ConfluenceLatexMacroMissingKeysConfigError(keys_str) \
396-
from ex
395+
cfg_name = 'confluence_latex_macro'
396+
raise ConfluenceMacroMissingKeysConfigError(
397+
cfg_name, keys_str) from ex
397398

398399
# ##################################################################
399400

@@ -709,6 +710,24 @@ def conf_translate(value):
709710

710711
# ##################################################################
711712

713+
# confluence_tab_macro
714+
validator.conf('confluence_tab_macro') \
715+
.dict_str_str()
716+
717+
if config.confluence_tab_macro:
718+
conf_keys = config.confluence_tab_macro.keys()
719+
720+
required_keys = [
721+
'macro-name',
722+
]
723+
724+
if not all(name in conf_keys for name in required_keys):
725+
keys_str = '\n - '.join(required_keys)
726+
cfg_name = 'confluence_tab_macro'
727+
raise ConfluenceMacroMissingKeysConfigError(cfg_name, keys_str)
728+
729+
# ##################################################################
730+
712731
# confluence_title_overrides
713732
validator.conf('confluence_title_overrides') \
714733
.dict_str_str()

sphinxcontrib/confluencebuilder/config/exceptions.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -143,10 +143,10 @@ def __init__(self):
143143
''')
144144

145145

146-
class ConfluenceLatexMacroMissingKeysConfigError(ConfluenceConfigError):
147-
def __init__(self, keys):
146+
class ConfluenceMacroMissingKeysConfigError(ConfluenceConfigError):
147+
def __init__(self, name, keys):
148148
super().__init__(f'''\
149-
missing keys in confluence_latex_macro
149+
missing keys in {name}
150150
151151
The following keys are required:
152152

sphinxcontrib/confluencebuilder/storage/translator.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3069,6 +3069,38 @@ def visit_DataViewerNode(self, node):
30693069

30703070
raise nodes.SkipNode
30713071

3072+
# -------------------------------------------------------
3073+
# sphinx -- extension (third party) -- sphinx-inline-tabs
3074+
# -------------------------------------------------------
3075+
3076+
def visit_TabContainer(self, node):
3077+
# check if this is an explicit hint to start a new tab container
3078+
primary_tab = node.get('new_set')
3079+
3080+
# if we are not explicitly a new tab container, check if our previous
3081+
# sibling is a tab container; if not, consider ourselves a new
3082+
# tab container
3083+
if not primary_tab:
3084+
prev_sibling = None
3085+
for child in node.parent.children:
3086+
if child is node:
3087+
if not isinstance(prev_sibling, type(node)):
3088+
primary_tab = True
3089+
break
3090+
prev_sibling = child
3091+
3092+
label_node = node.next_node()
3093+
if isinstance(label_node, nodes.label):
3094+
tabname = label_node.astext()
3095+
else:
3096+
tabname = ''
3097+
3098+
self._build_tab(node, tabname, primary_tab)
3099+
3100+
@depart_auto_context_decorator()
3101+
def depart_TabContainer(self, node):
3102+
pass
3103+
30723104
# -------------------------------------------------
30733105
# sphinx -- extension (third party) -- sphinx-needs
30743106
# -------------------------------------------------
@@ -3079,6 +3111,43 @@ def visit_PassthroughTextElement(self, node):
30793111
def depart_PassthroughTextElement(self, node):
30803112
pass
30813113

3114+
# ------------------------------------------------
3115+
# sphinx -- extension (third party) -- sphinx-tabs
3116+
# ------------------------------------------------
3117+
3118+
def visit_SphinxTabsTablist(self, node):
3119+
self._sphinxtabs_primary = True
3120+
self._sphinxtabs_tabnames = {}
3121+
3122+
for child in node.children:
3123+
tab_id = child.get('name')
3124+
if tab_id:
3125+
self._sphinxtabs_tabnames[tab_id] = child.astext()
3126+
3127+
raise nodes.SkipNode
3128+
3129+
def visit_SphinxTabsTab(self, node):
3130+
pass
3131+
3132+
def depart_SphinxTabsTab(self, node):
3133+
pass
3134+
3135+
def depart_SphinxTabsTablist(self, node):
3136+
pass
3137+
3138+
def visit_SphinxTabsPanel(self, node):
3139+
primary_tab = self._sphinxtabs_primary
3140+
self._sphinxtabs_primary = False
3141+
3142+
tab_id = node.get('name')
3143+
tab_name = self._sphinxtabs_tabnames.get(tab_id, '')
3144+
3145+
self._build_tab(node, tab_name, primary_tab)
3146+
3147+
@depart_auto_context_decorator()
3148+
def depart_SphinxTabsPanel(self, node):
3149+
pass
3150+
30823151
# ---------------------------------------------------
30833152
# sphinx -- extension (third party) -- sphinx-toolbox
30843153
# ---------------------------------------------------
@@ -3414,6 +3483,45 @@ def _build_anchor(self, node, anchor):
34143483
self.body.append(self.build_ac_param(node, '', compat_anchor))
34153484
self.body.append(self.end_ac_macro(node, suffix=''))
34163485

3486+
@visit_auto_context_decorator()
3487+
def _build_tab(self, node, tab_title, primary_tab):
3488+
"""
3489+
build an inlined tab entry
3490+
3491+
This is a helper call that is used by various Sphinx tab-related
3492+
extensions to help build an appropriate macro used to render tabbed
3493+
content.
3494+
3495+
Note: visit calls that use this hook need to ensure their respective
3496+
depart call is wrapped with `depart_auto_context_decorator`.
3497+
3498+
Args:
3499+
node: the node adding the anchor
3500+
tab_title: the title to use for the tab
3501+
primary_tab: whether this is the primary/first tab
3502+
"""
3503+
3504+
if not self.builder.config.confluence_tab_macro:
3505+
self._warnref(node, 'ignoring node since no tab macro configured')
3506+
raise nodes.SkipNode
3507+
3508+
conf = self.builder.config.confluence_tab_macro
3509+
macro = conf['macro-name']
3510+
pid = conf.get('primary-id')
3511+
pval = conf.get('primary-value')
3512+
tid = conf.get('title-id')
3513+
3514+
self.body.append(self.start_ac_macro(node, macro))
3515+
self.auto_append(self.end_tag(node))
3516+
3517+
if pid and primary_tab:
3518+
self.body.append(self.build_ac_param(node, pid, pval))
3519+
if tid:
3520+
self.body.append(self.build_ac_param(node, tid, tab_title))
3521+
3522+
self.body.append(self.start_ac_rich_text_body_macro(node))
3523+
self.auto_append(self.end_ac_rich_text_body_macro(node))
3524+
34173525
def start_tag(self, node, tag, suffix=None, empty=False, **kwargs):
34183526
"""
34193527
generates start tag content for a given node
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# sphinx-inline-tabs
2+
3+
The following checks the use of sphinx-inline-tabs directives.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
extensions = [
2+
'sphinx_inline_tabs',
3+
'sphinxcontrib.confluencebuilder',
4+
]
5+
6+
confluence_tab_macro = {
7+
'macro-name': 'cfm-tabs-page',
8+
'primary-id': 'primaryTab',
9+
'primary-value': 'true',
10+
'title-id': 'tabsPageTitle',
11+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
sphinx-inline-tabs
2+
==================
3+
4+
.. tab:: One
5+
6+
One is an interesting number.
7+
8+
.. tab:: Two
9+
10+
Two is the even prime.
11+
12+
This will break the tab set!
13+
14+
.. tab:: Three
15+
16+
Three is an odd prime.
17+
18+
.. tab:: Four
19+
20+
Four is not a perfect number.
21+
22+
.. tab:: Five
23+
:new-set:
24+
25+
Five is a nice number.
26+
27+
.. tab:: Six
28+
29+
Six is also nice.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[tox]
2+
package_root={toxinidir}{/}..{/}..{/}..
3+
4+
[testenv]
5+
deps =
6+
sphinx-inline-tabs
7+
commands =
8+
{envpython} -m tests.test_sample {posargs}
9+
setenv =
10+
PYTHONDONTWRITEBYTECODE=1
11+
TOX_INI_DIR={toxinidir}
12+
passenv = *
13+
use_develop = true
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# sphinx-tabs
2+
3+
The following checks the use of sphinx-tabs directives.

tests/sample-sets/sphinx-tabs/conf.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
extensions = [
2+
'sphinx_tabs.tabs',
3+
'sphinxcontrib.confluencebuilder',
4+
]
5+
6+
confluence_tab_macro = {
7+
'macro-name': 'cfm-tabs-page',
8+
'primary-id': 'primaryTab',
9+
'primary-value': 'true',
10+
'title-id': 'tabsPageTitle',
11+
}
12+
13+
sphinx_tabs_valid_builders = [
14+
'confluence',
15+
]

0 commit comments

Comments
 (0)