Skip to content

Commit 9ce3cfa

Browse files
Merge pull request #269 from adamtheturtle/fix-bullet
Support nesting non-bullet items within bullets
2 parents 3093eef + 1737900 commit 9ce3cfa

File tree

3 files changed

+126
-118
lines changed

3 files changed

+126
-118
lines changed

sample/index.rst

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,53 @@ Some key features 👍:
206206
* Supports videos with URLs and local files
207207
* Supports PDFs with URLs and local files
208208

209+
Nested Content in Bullet Lists
210+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
211+
212+
This demonstrates the new support for nesting various content types within bullet lists:
213+
214+
* First bullet point with **bold text**
215+
216+
This is a paragraph nested within a bullet list item. It should work now!
217+
218+
.. image:: https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=400&h=300&fit=crop
219+
:alt: Nested image in bullet list
220+
221+
* Nested bullet point
222+
* Another nested bullet
223+
224+
* Deeply nested bullet
225+
226+
* Second bullet point with *italic text*
227+
228+
Here's some code nested within a bullet list:
229+
230+
.. code-block:: python
231+
232+
"""Python code."""
233+
234+
import sys
235+
236+
sys.stdout.write("Hello, world!")
237+
238+
And here's a note admonition nested within the bullet list:
239+
240+
.. note::
241+
242+
This is a note that's nested within a bullet list item. This should work now!
243+
244+
* Third bullet point
245+
246+
This bullet point contains a table:
247+
248+
+----------+----------+
249+
| Header 1 | Header 2 |
250+
+==========+==========+
251+
| Cell 1 | Cell 2 |
252+
+----------+----------+
253+
| Cell 3 | Cell 4 |
254+
+----------+----------+
255+
209256
Numbered Lists
210257
~~~~~~~~~~~~~~
211258

src/sphinx_notion/__init__.py

Lines changed: 24 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -398,77 +398,6 @@ def _map_pygments_to_notion_language(*, pygments_lang: str) -> CodeLang:
398398
return language_mapping[pygments_lang.lower()]
399399

400400

401-
@beartype
402-
def _process_list_item_recursively(
403-
*,
404-
node: nodes.list_item,
405-
) -> list[Block]:
406-
"""
407-
Recursively process a list item node and return a BulletedItem.
408-
"""
409-
paragraph = node.children[0]
410-
assert isinstance(paragraph, nodes.paragraph)
411-
rich_text = _create_rich_text_from_children(node=paragraph)
412-
block = UnoBulletedItem(text=rich_text)
413-
414-
assert isinstance(node, nodes.list_item)
415-
416-
for child in node.children[1:]:
417-
if not isinstance(child, nodes.bullet_list):
418-
bullet_only_msg = (
419-
"The only thing Notion supports within a bullet list is a "
420-
f"bullet list. Given {type(child).__name__} on line "
421-
f"{child.line} in {child.source}"
422-
)
423-
# Ignore error which is about a type error, but we want to
424-
# raise a value error because the user has not sent anything to
425-
# do with types.
426-
raise ValueError(bullet_only_msg) # noqa: TRY004
427-
for nested_list_item in child.children:
428-
assert isinstance(nested_list_item, nodes.list_item)
429-
block.append(
430-
blocks=_process_list_item_recursively(
431-
node=nested_list_item,
432-
)
433-
)
434-
return [block]
435-
436-
437-
@beartype
438-
def _process_numbered_list_item_recursively(
439-
*,
440-
node: nodes.list_item,
441-
) -> list[Block]:
442-
"""
443-
Recursively process a numbered list item node and return a NumberedItem.
444-
"""
445-
paragraph = node.children[0]
446-
assert isinstance(paragraph, nodes.paragraph)
447-
rich_text = _create_rich_text_from_children(node=paragraph)
448-
block = UnoNumberedItem(text=rich_text)
449-
450-
numbered_only_msg = (
451-
"The only thing Notion supports within a numbered list is a "
452-
f"numbered list. Given {type(node).__name__} on line {node.line} "
453-
f"in {node.source}"
454-
)
455-
assert isinstance(node, nodes.list_item)
456-
457-
for child in node.children[1:]:
458-
assert isinstance(child, nodes.enumerated_list), numbered_only_msg
459-
for nested_list_item in child.children:
460-
assert isinstance(nested_list_item, nodes.list_item), (
461-
numbered_only_msg
462-
)
463-
464-
block.append(
465-
blocks=_process_numbered_list_item_recursively(
466-
node=nested_list_item,
467-
)
468-
)
469-
return [block]
470-
471-
472401
@singledispatch
473402
@beartype
474403
def _process_node_to_blocks(
@@ -610,15 +539,21 @@ def _(
610539
"""
611540
Process bullet list nodes by creating Notion BulletedItem blocks.
612541
"""
613-
del section_level
614542
result: list[Block] = []
615543
for list_item in node.children:
616544
assert isinstance(list_item, nodes.list_item)
617-
result.extend(
618-
_process_list_item_recursively(
619-
node=list_item,
545+
paragraph = list_item.children[0]
546+
assert isinstance(paragraph, nodes.paragraph)
547+
rich_text = _create_rich_text_from_children(node=paragraph)
548+
block = UnoBulletedItem(text=rich_text)
549+
550+
for child in list_item.children[1:]:
551+
child_blocks = _process_node_to_blocks(
552+
child,
553+
section_level=section_level,
620554
)
621-
)
555+
block.append(blocks=child_blocks)
556+
result.append(block)
622557
return result
623558

624559

@@ -632,20 +567,22 @@ def _(
632567
"""
633568
Process enumerated list nodes by creating Notion NumberedItem blocks.
634569
"""
635-
del section_level
636570
result: list[Block] = []
637-
numbered_only_msg = (
638-
"The only thing Notion supports within a numbered list is a "
639-
f"numbered list. Given {type(node).__name__} on line {node.line} "
640-
f"in {node.source}"
641-
)
642571
for list_item in node.children:
643-
assert isinstance(list_item, nodes.list_item), numbered_only_msg
644-
result.extend(
645-
_process_numbered_list_item_recursively(
646-
node=list_item,
572+
assert isinstance(list_item, nodes.list_item)
573+
paragraph = list_item.children[0]
574+
assert isinstance(paragraph, nodes.paragraph)
575+
rich_text = _create_rich_text_from_children(node=paragraph)
576+
block = UnoNumberedItem(text=rich_text)
577+
578+
for child in list_item.children[1:]:
579+
child_blocks = _process_node_to_blocks(
580+
child,
581+
section_level=section_level,
647582
)
648-
)
583+
block.append(blocks=child_blocks)
584+
585+
result.append(block)
649586
return result
650587

651588

tests/test_integration.py

Lines changed: 55 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1782,37 +1782,6 @@ def test_strikethrough_text(
17821782
)
17831783

17841784

1785-
def test_bullet_list_item_invalid_nested_child_error(
1786-
*,
1787-
make_app: Callable[..., SphinxTestApp],
1788-
tmp_path: Path,
1789-
) -> None:
1790-
"""
1791-
Bullet list items with invalid nested children raise a clear error message.
1792-
"""
1793-
rst_content = """
1794-
* First bullet point
1795-
1796-
Some paragraph that should not be here
1797-
1798-
* Nested bullet
1799-
"""
1800-
1801-
index_rst = tmp_path / "src" / "index.rst"
1802-
expected_message = (
1803-
r"^The only thing Notion supports within a bullet list is a "
1804-
r"bullet list. Given paragraph on line 3 "
1805-
rf"in {re.escape(pattern=str(object=index_rst))}$"
1806-
)
1807-
with pytest.raises(expected_exception=ValueError, match=expected_message):
1808-
_assert_rst_converts_to_notion_objects(
1809-
rst_content=rst_content,
1810-
expected_objects=[],
1811-
make_app=make_app,
1812-
tmp_path=tmp_path,
1813-
)
1814-
1815-
18161785
def test_comment_ignored(
18171786
*,
18181787
make_app: Callable[..., SphinxTestApp],
@@ -2454,3 +2423,58 @@ def test_text_styles_and_strike(
24542423
"sphinxnotes.strike",
24552424
),
24562425
)
2426+
2427+
2428+
def test_bullet_list_with_nested_content(
2429+
*,
2430+
make_app: Callable[..., SphinxTestApp],
2431+
tmp_path: Path,
2432+
) -> None:
2433+
"""
2434+
Test that bullet lists can contain nested content like paragraphs and
2435+
nested bullets.
2436+
"""
2437+
rst_content = """
2438+
* First bullet point
2439+
2440+
This is a paragraph nested within a bullet list item.
2441+
2442+
* Nested bullet point
2443+
* Another nested bullet
2444+
2445+
* Second bullet point
2446+
2447+
Another nested paragraph.
2448+
"""
2449+
2450+
first_bullet = UnoBulletedItem(text=text(text="First bullet point"))
2451+
2452+
nested_paragraph = UnoParagraph(
2453+
text=text(text="This is a paragraph nested within a bullet list item.")
2454+
)
2455+
first_bullet.append(blocks=[nested_paragraph])
2456+
2457+
nested_bullet_1 = UnoBulletedItem(text=text(text="Nested bullet point"))
2458+
nested_bullet_2 = UnoBulletedItem(text=text(text="Another nested bullet"))
2459+
first_bullet.append(blocks=[nested_bullet_1])
2460+
first_bullet.append(blocks=[nested_bullet_2])
2461+
2462+
second_bullet = UnoBulletedItem(text=text(text="Second bullet point"))
2463+
2464+
nested_paragraph_2 = UnoParagraph(
2465+
text=text(text="Another nested paragraph.")
2466+
)
2467+
second_bullet.append(blocks=[nested_paragraph_2])
2468+
2469+
expected_objects: list[Block] = [
2470+
first_bullet,
2471+
second_bullet,
2472+
]
2473+
2474+
_assert_rst_converts_to_notion_objects(
2475+
rst_content=rst_content,
2476+
expected_objects=expected_objects,
2477+
make_app=make_app,
2478+
tmp_path=tmp_path,
2479+
extensions=("sphinx_notion",),
2480+
)

0 commit comments

Comments
 (0)