Skip to content

Commit b10c188

Browse files
Merge pull request #84 from adamtheturtle/toggle-support
Add toggle support
2 parents 7f42df4 + 53b9c2e commit b10c188

File tree

6 files changed

+146
-2
lines changed

6 files changed

+146
-2
lines changed

README.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,17 @@ Add the following to ``conf.py`` to enable the extension:
2424
2525
extensions = ["sphinx_notion"]
2626
27+
For collapsible sections (toggle blocks), also add the sphinx-toolbox collapse extension:
28+
29+
.. code-block:: python
30+
31+
"""Configuration for Sphinx."""
32+
33+
extensions = [
34+
"sphinx_notion",
35+
"sphinx_toolbox.collapse",
36+
]
37+
2738
Supported markup
2839
----------------
2940

@@ -35,6 +46,7 @@ The following syntax is supported:
3546
- Table of contents
3647
- Block quotes
3748
- Note, warning, and tip admonitions
49+
- Collapsible sections (using sphinx-toolbox collapse directive)
3850

3951
See a `sample document source <https://raw.githubusercontent.com/adamtheturtle/sphinx-notionbuilder/refs/heads/main/sample/index.rst>`_ and the `published Notion page <https://www.notion.so/Sphinx-Notionbuilder-Sample-2579ce7b60a48142a556d816c657eb55>`_.
4052

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ dependencies = [
3636
"docutils>=0.21",
3737
"notion-client>=2.0.0",
3838
"sphinx>=8.2.3",
39+
"sphinx-toolbox>=4.0.0",
3940
"ultimate-notion>=0.8",
4041
]
4142
optional-dependencies.dev = [

sample/conf.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,7 @@
22
Configuration for Sphinx.
33
"""
44

5-
extensions = ["sphinx_notion"]
5+
extensions = [
6+
"sphinx_notion",
7+
"sphinx_toolbox.collapse",
8+
]

sample/index.rst

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,38 @@ This is **bold** and *italic* and ``inline code``.
3535

3636
This is a helpful tip that demonstrates the tip admonition support.
3737

38+
.. collapse:: Click to expand this section
39+
40+
This content is hidden by default and can be expanded by clicking the toggle.
41+
42+
It supports **all the same formatting** as regular content:
43+
44+
* Bullet points
45+
* ``Code snippets``
46+
* *Emphasis* and **bold text**
47+
48+
.. note::
49+
50+
You can even nest admonitions inside collapsible sections!
51+
52+
.. code-block:: python
53+
54+
"""Run code within a collapse."""
55+
56+
57+
def example_function() -> str:
58+
"""Example code inside a collapsed section."""
59+
return "This is hidden by default"
60+
61+
62+
example_function()
63+
64+
.. collapse:: Another collapsible section
65+
66+
You can have multiple collapsible sections in your document.
67+
68+
Each one can contain different types of content.
69+
3870
.. code-block:: python
3971
4072
"""Python code."""

src/sphinx_notion/__init__.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from sphinx.application import Sphinx
1313
from sphinx.builders.text import TextBuilder
1414
from sphinx.util.typing import ExtensionMetadata
15+
from sphinx_toolbox.collapse import CollapseNode
1516
from ultimate_notion import Emoji
1617
from ultimate_notion.blocks import BulletedItem as UnoBulletedItem
1718
from ultimate_notion.blocks import Callout as UnoCallout
@@ -35,6 +36,9 @@
3536
from ultimate_notion.blocks import (
3637
TableOfContents as UnoTableOfContents,
3738
)
39+
from ultimate_notion.blocks import (
40+
ToggleItem as UnoToggleItem,
41+
)
3842
from ultimate_notion.core import NotionObject
3943
from ultimate_notion.obj_api.core import GenericObject
4044
from ultimate_notion.obj_api.enums import CodeLang, Color
@@ -304,6 +308,32 @@ def _(node: nodes.tip, *, section_level: int) -> list[NotionObject[Any]]:
304308
return _create_admonition_callout(node=node, emoji="💡", color=Color.GREEN)
305309

306310

311+
@_process_node_to_blocks.register
312+
def _(node: CollapseNode, *, section_level: int) -> list[NotionObject[Any]]:
313+
"""
314+
Process collapse nodes by creating Notion ToggleItem blocks.
315+
"""
316+
del section_level
317+
318+
children_to_process = node.children
319+
title_text = node.attributes["label"]
320+
toggle_block = UnoToggleItem(text=title_text)
321+
322+
for child in children_to_process:
323+
for child_block in list(
324+
_process_node_to_blocks(
325+
child,
326+
section_level=1,
327+
)
328+
):
329+
# Add nested blocks as children to the toggle
330+
# Remove pyright ignore once we have
331+
# https://github.com/ultimate-notion/ultimate-notion/issues/94.
332+
toggle_block.obj_ref.value.children.append(child_block.obj_ref) # pyright: ignore[reportUnknownMemberType]
333+
334+
return [toggle_block]
335+
336+
307337
def _map_pygments_to_notion_language(*, pygments_lang: str) -> CodeLang:
308338
"""
309339
Map ``Pygments`` language names to Notion CodeLang ``enum`` values.
@@ -540,6 +570,18 @@ def visit_tip(self, node: nodes.Element) -> None:
540570

541571
raise nodes.SkipNode
542572

573+
def visit_CollapseNode(self, node: nodes.Element) -> None: # pylint: disable=invalid-name # noqa: N802
574+
"""
575+
Handle collapse nodes by creating Notion ToggleItem blocks.
576+
"""
577+
blocks = _process_node_to_blocks(
578+
node,
579+
section_level=self._section_level,
580+
)
581+
self._blocks.extend(blocks)
582+
583+
raise nodes.SkipNode
584+
543585
def visit_document(self, node: nodes.Element) -> None:
544586
"""
545587
Initialize block collection at document start.
@@ -584,4 +626,5 @@ def setup(app: Sphinx) -> ExtensionMetadata:
584626
"""
585627
app.add_builder(builder=NotionBuilder)
586628
app.set_translator(name="notion", translator_class=NotionTranslator)
629+
587630
return {"parallel_read_safe": True}

tests/test_integration.py

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@
3232
from ultimate_notion.blocks import (
3333
TableOfContents as UnoTableOfContents,
3434
)
35+
from ultimate_notion.blocks import (
36+
ToggleItem as UnoToggleItem,
37+
)
3538
from ultimate_notion.core import NotionObject
3639
from ultimate_notion.obj_api.core import GenericObject
3740
from ultimate_notion.obj_api.enums import CodeLang, Color
@@ -62,12 +65,14 @@ def _assert_rst_converts_to_notion_objects(
6265
expected_objects: list[NotionObject[Any]],
6366
make_app: Callable[..., SphinxTestApp],
6467
tmp_path: Path,
68+
extensions: tuple[str, ...] = ("sphinx_notion",),
6569
) -> None:
6670
"""
6771
The given rST content is converted to the given expected objects.
6872
"""
6973
srcdir = tmp_path / "src"
7074
srcdir.mkdir()
75+
7176
(srcdir / "conf.py").write_text(data="")
7277

7378
cleaned_content = textwrap.dedent(text=rst_content).strip()
@@ -77,7 +82,7 @@ def _assert_rst_converts_to_notion_objects(
7782
srcdir=srcdir,
7883
builddir=tmp_path / "build",
7984
buildername="notion",
80-
confoverrides={"extensions": ["sphinx_notion"]},
85+
confoverrides={"extensions": list(extensions)},
8186
)
8287
app.build()
8388

@@ -1007,3 +1012,51 @@ def test_nested_bullet_list_error_on_excessive_depth(
10071012
make_app=make_app,
10081013
tmp_path=tmp_path,
10091014
)
1015+
1016+
1017+
def test_collapse_block(
1018+
*,
1019+
make_app: Callable[..., SphinxTestApp],
1020+
tmp_path: Path,
1021+
) -> None:
1022+
"""
1023+
Test that collapse directives convert to Notion ToggleItem blocks.
1024+
"""
1025+
rst_content = """
1026+
Regular paragraph.
1027+
1028+
.. collapse:: Click to expand
1029+
1030+
This content is hidden by default.
1031+
1032+
It supports **formatting**.
1033+
1034+
Another paragraph.
1035+
"""
1036+
1037+
toggle_block = UnoToggleItem(text="Click to expand")
1038+
1039+
nested_para1 = UnoParagraph(text="This content is hidden by default.")
1040+
nested_para2 = UnoParagraph(text="It supports formatting.")
1041+
nested_para2.rich_text = (
1042+
text(text="It supports ", bold=False)
1043+
+ text(text="formatting", bold=True)
1044+
+ text(text=".", bold=False)
1045+
)
1046+
1047+
toggle_block.obj_ref.value.children.append(nested_para1.obj_ref) # pyright: ignore[reportUnknownMemberType]
1048+
toggle_block.obj_ref.value.children.append(nested_para2.obj_ref) # pyright: ignore[reportUnknownMemberType]
1049+
1050+
expected_objects: list[NotionObject[Any]] = [
1051+
UnoParagraph(text="Regular paragraph."),
1052+
toggle_block,
1053+
UnoParagraph(text="Another paragraph."),
1054+
]
1055+
1056+
_assert_rst_converts_to_notion_objects(
1057+
rst_content=rst_content,
1058+
expected_objects=expected_objects,
1059+
make_app=make_app,
1060+
tmp_path=tmp_path,
1061+
extensions=("sphinx_notion", "sphinx_toolbox.collapse"),
1062+
)

0 commit comments

Comments
 (0)