Skip to content

Commit 3e7d1f7

Browse files
Merge pull request #288 from adamtheturtle/rich-text-styles
Support more rich text styles
2 parents 89bc593 + c92ce16 commit 3e7d1f7

File tree

6 files changed

+150
-61
lines changed

6 files changed

+150
-61
lines changed

README.rst

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ The following syntax is supported:
100100
- PDFs (with URLs or local paths)
101101
- Tables
102102
- Strikethrough text
103-
- Colored text
103+
- Colored text and text styles (bold, italic, monospace)
104104

105105
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>`_.
106106

@@ -130,10 +130,10 @@ PDF files can be embedded using the ``pdf-include`` directive. Both remote URLs
130130
131131
The PDF will be rendered as an embedded PDF viewer in the generated Notion page.
132132

133-
Using Colored Text
134-
------------------
133+
Using Text Styles
134+
-----------------
135135

136-
Colored text can be added using the `sphinxcontrib-text-styles <https://sphinxcontrib-text-styles.readthedocs.io/>`_ extension. First, install the extension:
136+
Text styles can be added using the `sphinxcontrib-text-styles <https://sphinxcontrib-text-styles.readthedocs.io/>`_ extension. First, install the extension:
137137

138138
.. code-block:: console
139139
@@ -150,13 +150,39 @@ Then add it to your ``conf.py``:
150150
"sphinx_notion",
151151
]
152152
153-
You can then use colored text in your reStructuredText documents:
153+
You can then use various text styles in your reStructuredText documents:
154+
155+
Text Colors
156+
~~~~~~~~~~~
154157

155158
.. code-block:: rst
156159
157160
This is :text-red:`red text`, :text-blue:`blue text`, and :text-green:`green text`.
158161
159-
The following colors are supported: red, blue, green, yellow, orange, purple, pink, brown, and gray.
162+
The following text colors are supported: red, blue, green, yellow, orange, purple, pink, brown, and gray.
163+
164+
Background Colors
165+
~~~~~~~~~~~~~~~~~
166+
167+
.. code-block:: rst
168+
169+
This is :bg-red:`red background text`, :bg-blue:`blue background text`, and :bg-green:`green background text`.
170+
171+
The following background colors are supported: red, blue, green, yellow, orange, purple, pink, brown, and gray.
172+
173+
Additional Text Styles
174+
~~~~~~~~~~~~~~~~~~~~~~
175+
176+
.. code-block:: rst
177+
178+
This is :text-bold:`bold text`, :text-italic:`italic text`, :text-mono:`monospace text`, :text-strike:`strikethrough text`, and :text-underline:`underlined text`.
179+
180+
The following additional text styles are supported:
181+
- ``:text-bold:`text`` - Makes text bold
182+
- ``:text-italic:`text`` - Makes text italic
183+
- ``:text-mono:`text`` - Makes text monospace
184+
- ``:text-strike:`text`` - Makes text strikethrough
185+
- ``:text-underline:`text`` - Makes text underlined
160186

161187
Using TODO Lists
162188
----------------

sample/index.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ The builder also supports background colors using `sphinxcontrib-text-styles <ht
3030

3131
This is :bg-red:`red background text`, :bg-blue:`blue background text`, :bg-green:`green background text`, :bg-yellow:`yellow background text`, :bg-orange:`orange background text`, :bg-purple:`purple background text`, :bg-pink:`pink background text`, :bg-brown:`brown background text`, and :bg-gray:`gray background text`.
3232

33+
Other Text Styles
34+
~~~~~~~~~~~~~~~~~~
35+
36+
The builder supports additional text styles: :text-bold:`bold text`, :text-italic:`italic text`, :text-mono:`monospace text`, :text-strike:`strikethrough text`, and :text-underline:`underlined text`.
37+
3338
.. note::
3439

3540
This is an important note that demonstrates the note admonition support.

sample/notion-sha-mapping.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"10be79a09f2b92602ce383a0455f9fe6268f6792decba9383ddce53fded08137": "2829ce7b-60a4-814e-892c-ec3e68051237",
3-
"822e7aef5ac028d8cf5565420b59a1f3286a0af6fa17d6f0c617151a1f386507": "2829ce7b-60a4-816a-9324-e6c531c8ea88",
4-
"cd765602a4632f5abb325a66ce59aef506e84fdb6e4fe5590c634f4db567ef2c": "2829ce7b-60a4-81ac-9cc6-c7b7c6db359f",
5-
"eeeca6a7b26d982c4de616bc3bf36b9f40fb090938081cb828709920613dae72": "2829ce7b-60a4-81ec-bf6c-d003b291168e"
2+
"10be79a09f2b92602ce383a0455f9fe6268f6792decba9383ddce53fded08137": "2829ce7b-60a4-8121-9bed-c1e6a78ac799",
3+
"822e7aef5ac028d8cf5565420b59a1f3286a0af6fa17d6f0c617151a1f386507": "2829ce7b-60a4-81ef-87a3-cab67ceaf773",
4+
"cd765602a4632f5abb325a66ce59aef506e84fdb6e4fe5590c634f4db567ef2c": "2829ce7b-60a4-8140-bf23-d049a1ca8e76",
5+
"eeeca6a7b26d982c4de616bc3bf36b9f40fb090938081cb828709920613dae72": "2829ce7b-60a4-813b-a1c9-f5a595dcadec"
66
}

src/_notion_scripts/upload.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def _clean_deleted_blocks_from_mapping(
5151
sha_to_block_id: dict[str, str],
5252
session: Session,
5353
) -> dict[str, str]:
54-
"""Remove deleted blocks from SHA mapping.
54+
"""Remove deleted blocks from ``SHA`` mapping.
5555
5656
Returns a new dictionary with only existing blocks.
5757
"""

src/sphinx_notion/__init__.py

Lines changed: 58 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -110,12 +110,11 @@ def _get_background_color_classes() -> set[str]:
110110

111111

112112
@beartype
113-
def _color_from_node(*, node: nodes.inline) -> Color | None:
113+
def _color_from_css_classes(*, classes: list[str]) -> Color | None:
114114
"""Extract Notion color from CSS classes.
115115
116116
Classes created by ``sphinxcontrib-text-styles``.
117117
"""
118-
classes = node.attributes.get("classes", [])
119118
color_mapping = _get_text_color_mapping()
120119

121120
for css_class in classes:
@@ -126,12 +125,13 @@ def _color_from_node(*, node: nodes.inline) -> Color | None:
126125

127126

128127
@beartype
129-
def _background_color_from_node(*, node: nodes.inline) -> BGColor | None:
128+
def _background_color_from_css_classes(
129+
*, classes: list[str]
130+
) -> BGColor | None:
130131
"""Extract Notion background color from CSS classes.
131132
132133
Classes created by ``sphinxcontrib-text-styles``.
133134
"""
134-
classes = node.attributes.get("classes", [])
135135
bg_color_mapping: dict[str, BGColor] = {
136136
"bg-red": BGColor.RED,
137137
"bg-blue": BGColor.BLUE,
@@ -228,61 +228,76 @@ def _create_rich_text_from_children(*, node: nodes.Element) -> Text:
228228
)
229229
elif isinstance(child, nodes.target):
230230
continue
231-
elif isinstance(child, nodes.inline):
232-
bg_color = _background_color_from_node(node=child)
233-
text_color = _color_from_node(node=child)
234-
235-
classes = child.attributes.get("classes", [])
236-
color_mapping = _get_text_color_mapping()
237-
bg_color_classes = _get_background_color_classes()
238-
239-
for css_class in classes:
240-
if (
241-
css_class not in color_mapping
242-
and css_class not in bg_color_classes
243-
):
244-
_LOGGER.warning(
245-
"Unsupported text style classes: %s. "
246-
"Text will be rendered without styling.",
247-
css_class,
248-
)
249-
250-
color: BGColor | Color | None = bg_color or text_color
251-
new_text = text(
252-
text=child.astext(),
253-
bold=isinstance(child, nodes.strong),
254-
italic=isinstance(child, nodes.emphasis),
255-
code=isinstance(child, nodes.literal),
256-
strikethrough=isinstance(child, strike_node),
257-
# Ignore the type check here because Ultimate Notion has
258-
# a bad type hint: https://github.com/ultimate-notion/ultimate-notion/issues/140
259-
color=color, # type: ignore[arg-type] # pyright: ignore[reportArgumentType]
260-
)
261231
elif isinstance(child, nodes.title_reference):
262232
# We match the behavior of the HTML builder here.
263233
# If you render ``A `B``` in HTML, it will render as
264234
# ``A <i>B</i>``.
265-
new_text = text(
266-
text=child.astext(),
267-
italic=True,
268-
)
235+
new_text = text(text=child.astext(), italic=True)
236+
elif isinstance(child, nodes.Text):
237+
new_text = text(text=child.astext())
269238
elif isinstance(
270239
child,
271240
(
272-
nodes.Text,
241+
nodes.inline,
273242
nodes.strong,
274243
nodes.emphasis,
275244
nodes.literal,
276245
strike_node,
277246
nodes.paragraph,
278247
),
279248
):
249+
classes = child.attributes.get("classes", [])
250+
bg_color = _background_color_from_css_classes(classes=classes)
251+
text_color = _color_from_css_classes(classes=classes)
252+
253+
color_mapping = _get_text_color_mapping()
254+
bg_color_classes = _get_background_color_classes()
255+
256+
is_bold = isinstance(child, nodes.strong) or "text-bold" in classes
257+
is_italic = (
258+
isinstance(child, nodes.emphasis) or "text-italic" in classes
259+
)
260+
is_code = (
261+
isinstance(child, nodes.literal) or "text-mono" in classes
262+
)
263+
is_strikethrough = (
264+
isinstance(child, strike_node) or "text-strike" in classes
265+
)
266+
is_underline = "text-underline" in classes
267+
268+
supported_style_classes = {
269+
"text-bold",
270+
"text-italic",
271+
"text-mono",
272+
"text-strike",
273+
"text-underline",
274+
*color_mapping.keys(),
275+
*bg_color_classes,
276+
}
277+
unsupported_styles = [
278+
css_class
279+
for css_class in classes
280+
if css_class not in supported_style_classes
281+
]
282+
283+
if unsupported_styles:
284+
_LOGGER.warning(
285+
"Unsupported text style classes: %s. "
286+
"Text will be rendered without styling.",
287+
", ".join(unsupported_styles),
288+
)
289+
290+
color: BGColor | Color | None = bg_color or text_color
280291
new_text = text(
281292
text=child.astext(),
282-
bold=isinstance(child, nodes.strong),
283-
italic=isinstance(child, nodes.emphasis),
284-
code=isinstance(child, nodes.literal),
285-
strikethrough=isinstance(child, strike_node),
293+
bold=is_bold,
294+
italic=is_italic,
295+
code=is_code,
296+
strikethrough=is_strikethrough,
297+
underline=is_underline,
298+
# Ignore the type check here because Ultimate Notion has
299+
# a bad type hint: https://github.com/ultimate-notion/ultimate-notion/issues/140
300+
color=color, # type: ignore[arg-type] # pyright: ignore[reportArgumentType]
286301
)
287302
else:
288303
unsupported_child_type_msg = (

tests/test_integration.py

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
from ultimate_notion.blocks import Video as UnoVideo
5050
from ultimate_notion.file import ExternalFile
5151
from ultimate_notion.obj_api.enums import BGColor, CodeLang, Color
52-
from ultimate_notion.rich_text import text
52+
from ultimate_notion.rich_text import Text, text
5353

5454

5555
@beartype
@@ -2315,28 +2315,28 @@ def test_individual_colors(
23152315
)
23162316

23172317

2318-
def test_text_styles_non_color(
2318+
def test_text_styles_unsupported_color(
23192319
*,
23202320
make_app: Callable[..., SphinxTestApp],
23212321
tmp_path: Path,
23222322
) -> None:
23232323
"""
2324-
Non-color text styles (like underline) emit a warning.
2324+
Unsupported colors from ``sphinxcontrib-text-styles`` emit warnings.
23252325
"""
23262326
rst_content = """
2327-
This is :text-underline:`underlined text`.
2327+
This is :text-cyan:`cyan text`.
23282328
"""
23292329

23302330
expected_warning = (
2331-
"Unsupported text style classes: text-underline. "
2331+
"Unsupported text style classes: text-cyan. "
23322332
"Text will be rendered without styling."
23332333
)
23342334

23352335
normal_text = text(text="This is ")
2336-
underline_text = text(text="underlined text")
2336+
cyan_text = text(text="cyan text")
23372337
normal_text2 = text(text=".")
23382338

2339-
combined_text = normal_text + underline_text + normal_text2
2339+
combined_text = normal_text + cyan_text + normal_text2
23402340

23412341
expected_paragraph = UnoParagraph(text=combined_text)
23422342

@@ -2440,6 +2440,49 @@ def test_text_styles_and_strike(
24402440
)
24412441

24422442

2443+
@pytest.mark.parametrize(
2444+
argnames=("role", "expected_text"),
2445+
argvalues=[
2446+
("text-bold", text(text="text-bold text", bold=True)),
2447+
("text-italic", text(text="text-italic text", italic=True)),
2448+
("text-mono", text(text="text-mono text", code=True)),
2449+
("text-strike", text(text="text-strike text", strikethrough=True)),
2450+
("text-underline", text(text="text-underline text", underline=True)),
2451+
],
2452+
)
2453+
def test_additional_text_styles(
2454+
*,
2455+
make_app: Callable[..., SphinxTestApp],
2456+
tmp_path: Path,
2457+
role: str,
2458+
expected_text: Text,
2459+
) -> None:
2460+
"""
2461+
Additional text styles from the ``sphinxcontrib_text_styles`` extension are
2462+
supported.
2463+
"""
2464+
rst_content = f"""
2465+
This is :{role}:`{role} text`.
2466+
"""
2467+
2468+
normal_text1 = text(text="This is ")
2469+
normal_text2 = text(text=".")
2470+
2471+
combined_text = normal_text1 + expected_text + normal_text2
2472+
2473+
expected_paragraph = UnoParagraph(text=combined_text)
2474+
2475+
expected_objects: list[Block] = [expected_paragraph]
2476+
2477+
_assert_rst_converts_to_notion_objects(
2478+
rst_content=rst_content,
2479+
expected_objects=expected_objects,
2480+
make_app=make_app,
2481+
tmp_path=tmp_path,
2482+
extensions=("sphinx_notion", "sphinxcontrib_text_styles"),
2483+
)
2484+
2485+
24432486
def test_flat_task_list(
24442487
*,
24452488
make_app: Callable[..., SphinxTestApp],

0 commit comments

Comments
 (0)