notionary 0.2.19__py3-none-any.whl → 0.2.22__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- notionary/__init__.py +8 -4
- notionary/base_notion_client.py +3 -1
- notionary/blocks/__init__.py +2 -91
- notionary/blocks/_bootstrap.py +271 -0
- notionary/blocks/audio/__init__.py +8 -2
- notionary/blocks/audio/audio_element.py +69 -106
- notionary/blocks/audio/audio_markdown_node.py +13 -5
- notionary/blocks/audio/audio_models.py +6 -55
- notionary/blocks/base_block_element.py +42 -0
- notionary/blocks/bookmark/__init__.py +9 -2
- notionary/blocks/bookmark/bookmark_element.py +49 -139
- notionary/blocks/bookmark/bookmark_markdown_node.py +19 -18
- notionary/blocks/bookmark/bookmark_models.py +15 -0
- notionary/blocks/breadcrumbs/__init__.py +17 -0
- notionary/blocks/breadcrumbs/breadcrumb_element.py +39 -0
- notionary/blocks/breadcrumbs/breadcrumb_markdown_node.py +32 -0
- notionary/blocks/breadcrumbs/breadcrumb_models.py +12 -0
- notionary/blocks/bulleted_list/__init__.py +12 -2
- notionary/blocks/bulleted_list/bulleted_list_element.py +55 -53
- notionary/blocks/bulleted_list/bulleted_list_markdown_node.py +2 -1
- notionary/blocks/bulleted_list/bulleted_list_models.py +18 -0
- notionary/blocks/callout/__init__.py +9 -2
- notionary/blocks/callout/callout_element.py +53 -86
- notionary/blocks/callout/callout_markdown_node.py +3 -1
- notionary/blocks/callout/callout_models.py +33 -0
- notionary/blocks/child_database/__init__.py +14 -0
- notionary/blocks/child_database/child_database_element.py +61 -0
- notionary/blocks/child_database/child_database_models.py +12 -0
- notionary/blocks/child_page/__init__.py +9 -0
- notionary/blocks/child_page/child_page_element.py +94 -0
- notionary/blocks/child_page/child_page_models.py +12 -0
- notionary/blocks/{shared/block_client.py → client.py} +54 -54
- notionary/blocks/code/__init__.py +6 -2
- notionary/blocks/code/code_element.py +96 -181
- notionary/blocks/code/code_markdown_node.py +64 -13
- notionary/blocks/code/code_models.py +94 -0
- notionary/blocks/column/__init__.py +25 -1
- notionary/blocks/column/column_element.py +44 -312
- notionary/blocks/column/column_list_element.py +52 -0
- notionary/blocks/column/column_list_markdown_node.py +50 -0
- notionary/blocks/column/column_markdown_node.py +59 -0
- notionary/blocks/column/column_models.py +26 -0
- notionary/blocks/divider/__init__.py +9 -2
- notionary/blocks/divider/divider_element.py +18 -49
- notionary/blocks/divider/divider_markdown_node.py +2 -1
- notionary/blocks/divider/divider_models.py +12 -0
- notionary/blocks/embed/__init__.py +9 -2
- notionary/blocks/embed/embed_element.py +65 -111
- notionary/blocks/embed/embed_markdown_node.py +3 -1
- notionary/blocks/embed/embed_models.py +14 -0
- notionary/blocks/equation/__init__.py +14 -0
- notionary/blocks/equation/equation_element.py +133 -0
- notionary/blocks/equation/equation_element_markdown_node.py +35 -0
- notionary/blocks/equation/equation_models.py +11 -0
- notionary/blocks/file/__init__.py +25 -0
- notionary/blocks/file/file_element.py +112 -0
- notionary/blocks/file/file_element_markdown_node.py +37 -0
- notionary/blocks/file/file_element_models.py +39 -0
- notionary/blocks/guards.py +22 -0
- notionary/blocks/heading/__init__.py +16 -2
- notionary/blocks/heading/heading_element.py +83 -69
- notionary/blocks/heading/heading_markdown_node.py +2 -1
- notionary/blocks/heading/heading_models.py +29 -0
- notionary/blocks/image_block/__init__.py +13 -0
- notionary/blocks/image_block/image_element.py +89 -0
- notionary/blocks/{image → image_block}/image_markdown_node.py +13 -6
- notionary/blocks/image_block/image_models.py +10 -0
- notionary/blocks/mixins/captions/__init__.py +4 -0
- notionary/blocks/mixins/captions/caption_markdown_node_mixin.py +31 -0
- notionary/blocks/mixins/captions/caption_mixin.py +92 -0
- notionary/blocks/models.py +174 -0
- notionary/blocks/numbered_list/__init__.py +12 -2
- notionary/blocks/numbered_list/numbered_list_element.py +48 -56
- notionary/blocks/numbered_list/numbered_list_markdown_node.py +3 -1
- notionary/blocks/numbered_list/numbered_list_models.py +17 -0
- notionary/blocks/paragraph/__init__.py +12 -2
- notionary/blocks/paragraph/paragraph_element.py +40 -66
- notionary/blocks/paragraph/paragraph_markdown_node.py +2 -1
- notionary/blocks/paragraph/paragraph_models.py +16 -0
- notionary/blocks/pdf/__init__.py +13 -0
- notionary/blocks/pdf/pdf_element.py +97 -0
- notionary/blocks/pdf/pdf_markdown_node.py +37 -0
- notionary/blocks/pdf/pdf_models.py +11 -0
- notionary/blocks/quote/__init__.py +11 -2
- notionary/blocks/quote/quote_element.py +45 -62
- notionary/blocks/quote/quote_markdown_node.py +6 -3
- notionary/blocks/quote/quote_models.py +18 -0
- notionary/blocks/registry/__init__.py +4 -0
- notionary/blocks/registry/block_registry.py +60 -121
- notionary/blocks/registry/block_registry_builder.py +115 -59
- notionary/blocks/rich_text/__init__.py +33 -0
- notionary/blocks/rich_text/name_to_id_resolver.py +205 -0
- notionary/blocks/rich_text/rich_text_models.py +221 -0
- notionary/blocks/rich_text/text_inline_formatter.py +456 -0
- notionary/blocks/syntax_prompt_builder.py +137 -0
- notionary/blocks/table/__init__.py +16 -2
- notionary/blocks/table/table_element.py +136 -228
- notionary/blocks/table/table_markdown_node.py +2 -1
- notionary/blocks/table/table_models.py +28 -0
- notionary/blocks/table_of_contents/__init__.py +19 -0
- notionary/blocks/table_of_contents/table_of_contents_element.py +68 -0
- notionary/blocks/table_of_contents/table_of_contents_markdown_node.py +35 -0
- notionary/blocks/table_of_contents/table_of_contents_models.py +18 -0
- notionary/blocks/todo/__init__.py +9 -2
- notionary/blocks/todo/todo_element.py +52 -92
- notionary/blocks/todo/todo_markdown_node.py +2 -1
- notionary/blocks/todo/todo_models.py +19 -0
- notionary/blocks/toggle/__init__.py +13 -3
- notionary/blocks/toggle/toggle_element.py +69 -260
- notionary/blocks/toggle/toggle_markdown_node.py +25 -15
- notionary/blocks/toggle/toggle_models.py +17 -0
- notionary/blocks/toggleable_heading/__init__.py +6 -2
- notionary/blocks/toggleable_heading/toggleable_heading_element.py +86 -241
- notionary/blocks/toggleable_heading/toggleable_heading_markdown_node.py +26 -18
- notionary/blocks/types.py +130 -0
- notionary/blocks/video/__init__.py +8 -2
- notionary/blocks/video/video_element.py +70 -141
- notionary/blocks/video/video_element_models.py +10 -0
- notionary/blocks/video/video_markdown_node.py +13 -6
- notionary/database/client.py +26 -8
- notionary/database/database.py +13 -14
- notionary/database/database_filter_builder.py +2 -2
- notionary/database/database_provider.py +5 -4
- notionary/database/models.py +337 -0
- notionary/database/notion_database.py +6 -7
- notionary/file_upload/client.py +5 -7
- notionary/file_upload/models.py +3 -2
- notionary/file_upload/notion_file_upload.py +2 -3
- notionary/markdown/markdown_builder.py +729 -0
- notionary/markdown/markdown_document_model.py +228 -0
- notionary/{blocks → markdown}/markdown_node.py +1 -0
- notionary/models/notion_database_response.py +0 -338
- notionary/page/client.py +34 -15
- notionary/page/models.py +327 -0
- notionary/page/notion_page.py +136 -58
- notionary/page/{content/page_content_writer.py → page_content_deleting_service.py} +25 -59
- notionary/page/page_content_writer.py +177 -0
- notionary/page/page_context.py +65 -0
- notionary/page/reader/handler/__init__.py +19 -0
- notionary/page/reader/handler/base_block_renderer.py +44 -0
- notionary/page/reader/handler/block_processing_context.py +35 -0
- notionary/page/reader/handler/block_rendering_context.py +48 -0
- notionary/page/reader/handler/column_list_renderer.py +51 -0
- notionary/page/reader/handler/column_renderer.py +60 -0
- notionary/page/reader/handler/line_renderer.py +73 -0
- notionary/page/reader/handler/numbered_list_renderer.py +85 -0
- notionary/page/reader/handler/toggle_renderer.py +69 -0
- notionary/page/reader/handler/toggleable_heading_renderer.py +89 -0
- notionary/page/reader/page_content_retriever.py +81 -0
- notionary/page/search_filter_builder.py +2 -1
- notionary/page/writer/handler/__init__.py +24 -0
- notionary/page/writer/handler/code_handler.py +72 -0
- notionary/page/writer/handler/column_handler.py +141 -0
- notionary/page/writer/handler/column_list_handler.py +139 -0
- notionary/page/writer/handler/equation_handler.py +74 -0
- notionary/page/writer/handler/line_handler.py +35 -0
- notionary/page/writer/handler/line_processing_context.py +54 -0
- notionary/page/writer/handler/regular_line_handler.py +86 -0
- notionary/page/writer/handler/table_handler.py +66 -0
- notionary/page/writer/handler/toggle_handler.py +155 -0
- notionary/page/writer/handler/toggleable_heading_handler.py +173 -0
- notionary/page/writer/markdown_to_notion_converter.py +95 -0
- notionary/page/writer/markdown_to_notion_converter_context.py +30 -0
- notionary/page/writer/markdown_to_notion_formatting_post_processor.py +73 -0
- notionary/page/writer/notion_text_length_processor.py +150 -0
- notionary/telemetry/__init__.py +2 -2
- notionary/telemetry/service.py +3 -3
- notionary/user/__init__.py +2 -2
- notionary/user/base_notion_user.py +2 -1
- notionary/user/client.py +2 -3
- notionary/user/models.py +1 -0
- notionary/user/notion_bot_user.py +4 -5
- notionary/user/notion_user.py +3 -4
- notionary/user/notion_user_manager.py +23 -95
- notionary/util/__init__.py +3 -2
- notionary/util/fuzzy.py +2 -1
- notionary/util/logging_mixin.py +2 -2
- notionary/util/singleton_metaclass.py +1 -1
- notionary/workspace.py +6 -5
- notionary-0.2.22.dist-info/METADATA +237 -0
- notionary-0.2.22.dist-info/RECORD +200 -0
- notionary/blocks/document/__init__.py +0 -7
- notionary/blocks/document/document_element.py +0 -102
- notionary/blocks/document/document_markdown_node.py +0 -31
- notionary/blocks/image/__init__.py +0 -7
- notionary/blocks/image/image_element.py +0 -151
- notionary/blocks/markdown_builder.py +0 -356
- notionary/blocks/mention/__init__.py +0 -7
- notionary/blocks/mention/mention_element.py +0 -229
- notionary/blocks/mention/mention_markdown_node.py +0 -38
- notionary/blocks/prompts/element_prompt_builder.py +0 -83
- notionary/blocks/prompts/element_prompt_content.py +0 -41
- notionary/blocks/shared/models.py +0 -713
- notionary/blocks/shared/notion_block_element.py +0 -37
- notionary/blocks/shared/text_inline_formatter.py +0 -262
- notionary/blocks/shared/text_inline_formatter_new.py +0 -139
- notionary/database/models/page_result.py +0 -10
- notionary/models/notion_block_response.py +0 -264
- notionary/models/notion_page_response.py +0 -78
- notionary/models/search_response.py +0 -0
- notionary/page/__init__.py +0 -0
- notionary/page/content/markdown_whitespace_processor.py +0 -80
- notionary/page/content/notion_text_length_utils.py +0 -87
- notionary/page/content/page_content_retriever.py +0 -60
- notionary/page/formatting/line_processor.py +0 -153
- notionary/page/formatting/markdown_to_notion_converter.py +0 -153
- notionary/page/markdown_syntax_prompt_generator.py +0 -114
- notionary/page/notion_to_markdown_converter.py +0 -179
- notionary/page/properites/property_value_extractor.py +0 -0
- notionary/user/notion_user_provider.py +0 -1
- notionary-0.2.19.dist-info/METADATA +0 -225
- notionary-0.2.19.dist-info/RECORD +0 -150
- /notionary/{blocks/document/document_models.py → markdown/___init__.py} +0 -0
- /notionary/{blocks/image/image_models.py → markdown/makdown_document_model.py} +0 -0
- /notionary/{blocks/mention/mention_models.py → page/reader/handler/equation_renderer.py} +0 -0
- /notionary/{blocks/shared/__init__.py → page/writer/markdown_to_notion_post_processor.py} +0 -0
- /notionary/{blocks/toggleable_heading/toggleable_heading_models.py → page/writer/markdown_to_notion_text_length_post_processor.py} +0 -0
- /notionary/{elements/__init__.py → util/concurrency_limiter.py} +0 -0
- {notionary-0.2.19.dist-info → notionary-0.2.22.dist-info}/LICENSE +0 -0
- {notionary-0.2.19.dist-info → notionary-0.2.22.dist-info}/WHEEL +0 -0
@@ -0,0 +1,729 @@
|
|
1
|
+
"""
|
2
|
+
Clean Fluent Markdown Builder
|
3
|
+
============================
|
4
|
+
|
5
|
+
A direct, chainable builder for all MarkdownNode types without overengineering.
|
6
|
+
Maps 1:1 to the available blocks with clear, expressive method names.
|
7
|
+
"""
|
8
|
+
|
9
|
+
from __future__ import annotations
|
10
|
+
|
11
|
+
from typing import Any, Callable, Optional, Self
|
12
|
+
|
13
|
+
from notionary.blocks.audio import AudioMarkdownBlockParams, AudioMarkdownNode
|
14
|
+
from notionary.blocks.bookmark import BookmarkMarkdownBlockParams, BookmarkMarkdownNode
|
15
|
+
from notionary.blocks.breadcrumbs import BreadcrumbMarkdownNode
|
16
|
+
from notionary.blocks.bulleted_list import (
|
17
|
+
BulletedListMarkdownBlockParams,
|
18
|
+
BulletedListMarkdownNode,
|
19
|
+
)
|
20
|
+
from notionary.blocks.callout import CalloutMarkdownBlockParams, CalloutMarkdownNode
|
21
|
+
from notionary.blocks.code import CodeBlock, CodeLanguage, CodeMarkdownNode
|
22
|
+
from notionary.blocks.column import (
|
23
|
+
ColumnListMarkdownBlockParams,
|
24
|
+
ColumnListMarkdownNode,
|
25
|
+
ColumnMarkdownNode,
|
26
|
+
)
|
27
|
+
from notionary.blocks.divider import DividerMarkdownBlockParams, DividerMarkdownNode
|
28
|
+
from notionary.blocks.embed import EmbedMarkdownBlockParams, EmbedMarkdownNode
|
29
|
+
from notionary.blocks.equation import EquationMarkdownBlockParams, EquationMarkdownNode
|
30
|
+
from notionary.blocks.file import FileMarkdownNode, FileMarkdownNodeParams
|
31
|
+
from notionary.blocks.heading import HeadingMarkdownBlockParams, HeadingMarkdownNode
|
32
|
+
from notionary.blocks.image_block import ImageMarkdownBlockParams, ImageMarkdownNode
|
33
|
+
from notionary.blocks.numbered_list import (
|
34
|
+
NumberedListMarkdownBlockParams,
|
35
|
+
NumberedListMarkdownNode,
|
36
|
+
)
|
37
|
+
from notionary.blocks.paragraph import (
|
38
|
+
ParagraphMarkdownBlockParams,
|
39
|
+
ParagraphMarkdownNode,
|
40
|
+
)
|
41
|
+
from notionary.blocks.pdf import PdfMarkdownNode, PdfMarkdownNodeParams
|
42
|
+
from notionary.blocks.quote import QuoteMarkdownBlockParams, QuoteMarkdownNode
|
43
|
+
from notionary.blocks.table import TableMarkdownBlockParams, TableMarkdownNode
|
44
|
+
from notionary.blocks.table_of_contents import (
|
45
|
+
TableOfContentsMarkdownBlockParams,
|
46
|
+
TableOfContentsMarkdownNode,
|
47
|
+
)
|
48
|
+
from notionary.blocks.todo import TodoMarkdownBlockParams, TodoMarkdownNode
|
49
|
+
from notionary.blocks.toggle import ToggleMarkdownBlockParams, ToggleMarkdownNode
|
50
|
+
from notionary.blocks.toggleable_heading import (
|
51
|
+
ToggleableHeadingMarkdownBlockParams,
|
52
|
+
ToggleableHeadingMarkdownNode,
|
53
|
+
)
|
54
|
+
from notionary.blocks.types import BlockType, MarkdownBlockType
|
55
|
+
from notionary.blocks.video import VideoMarkdownBlockParams, VideoMarkdownNode
|
56
|
+
from notionary.markdown.markdown_document_model import (
|
57
|
+
MarkdownBlock,
|
58
|
+
MarkdownDocumentModel,
|
59
|
+
)
|
60
|
+
from notionary.markdown.markdown_node import MarkdownNode
|
61
|
+
|
62
|
+
|
63
|
+
class MarkdownBuilder:
|
64
|
+
"""
|
65
|
+
Fluent interface builder for creating Notion content with clean, direct methods.
|
66
|
+
"""
|
67
|
+
|
68
|
+
def __init__(self) -> None:
|
69
|
+
self.children: list[MarkdownNode] = []
|
70
|
+
|
71
|
+
self._block_processors: dict[str, Callable[[Any], None]] = {
|
72
|
+
MarkdownBlockType.HEADING_1: self._add_heading,
|
73
|
+
MarkdownBlockType.HEADING_2: self._add_heading,
|
74
|
+
MarkdownBlockType.HEADING_3: self._add_heading,
|
75
|
+
MarkdownBlockType.PARAGRAPH: self._add_paragraph,
|
76
|
+
MarkdownBlockType.QUOTE: self._add_quote,
|
77
|
+
MarkdownBlockType.BULLETED_LIST_ITEM: self._add_bulleted_list,
|
78
|
+
MarkdownBlockType.NUMBERED_LIST_ITEM: self._add_numbered_list,
|
79
|
+
MarkdownBlockType.TO_DO: self._add_todo,
|
80
|
+
MarkdownBlockType.CALLOUT: self._add_callout,
|
81
|
+
MarkdownBlockType.CODE: self._add_code,
|
82
|
+
MarkdownBlockType.IMAGE: self._add_image,
|
83
|
+
MarkdownBlockType.VIDEO: self._add_video,
|
84
|
+
MarkdownBlockType.AUDIO: self._add_audio,
|
85
|
+
MarkdownBlockType.FILE: self._add_file,
|
86
|
+
MarkdownBlockType.PDF: self._add_pdf,
|
87
|
+
MarkdownBlockType.BOOKMARK: self._add_bookmark,
|
88
|
+
MarkdownBlockType.EMBED: self._add_embed,
|
89
|
+
MarkdownBlockType.TABLE: self._add_table,
|
90
|
+
MarkdownBlockType.DIVIDER: self._add_divider,
|
91
|
+
MarkdownBlockType.EQUATION: self._add_equation,
|
92
|
+
MarkdownBlockType.TABLE_OF_CONTENTS: self._add_table_of_contents,
|
93
|
+
MarkdownBlockType.TOGGLE: self._add_toggle,
|
94
|
+
MarkdownBlockType.COLUMN_LIST: self._add_columns,
|
95
|
+
MarkdownBlockType.BREADCRUMB: self._add_breadcrumb,
|
96
|
+
MarkdownBlockType.HEADING: self._add_heading,
|
97
|
+
MarkdownBlockType.BULLETED_LIST: self._add_bulleted_list,
|
98
|
+
MarkdownBlockType.NUMBERED_LIST: self._add_numbered_list,
|
99
|
+
MarkdownBlockType.TODO: self._add_todo,
|
100
|
+
MarkdownBlockType.TOGGLEABLE_HEADING: self._add_toggleable_heading,
|
101
|
+
MarkdownBlockType.COLUMNS: self._add_columns,
|
102
|
+
MarkdownBlockType.SPACE: self._add_space,
|
103
|
+
}
|
104
|
+
|
105
|
+
@classmethod
|
106
|
+
def from_model(cls, model: MarkdownDocumentModel) -> Self:
|
107
|
+
"""Create MarkdownBuilder from a Pydantic model."""
|
108
|
+
builder = cls()
|
109
|
+
builder._process_blocks(model.blocks)
|
110
|
+
return builder
|
111
|
+
|
112
|
+
def h1(self, text: str) -> Self:
|
113
|
+
"""
|
114
|
+
Add an H1 heading.
|
115
|
+
|
116
|
+
Args:
|
117
|
+
text: The heading text content
|
118
|
+
"""
|
119
|
+
self.children.append(HeadingMarkdownNode(text=text, level=1))
|
120
|
+
return self
|
121
|
+
|
122
|
+
def h2(self, text: str) -> Self:
|
123
|
+
"""
|
124
|
+
Add an H2 heading.
|
125
|
+
|
126
|
+
Args:
|
127
|
+
text: The heading text content
|
128
|
+
"""
|
129
|
+
self.children.append(HeadingMarkdownNode(text=text, level=2))
|
130
|
+
return self
|
131
|
+
|
132
|
+
def h3(self, text: str) -> Self:
|
133
|
+
"""
|
134
|
+
Add an H3 heading.
|
135
|
+
|
136
|
+
Args:
|
137
|
+
text: The heading text content
|
138
|
+
"""
|
139
|
+
self.children.append(HeadingMarkdownNode(text=text, level=3))
|
140
|
+
return self
|
141
|
+
|
142
|
+
def heading(self, text: str, level: int = 2) -> Self:
|
143
|
+
"""
|
144
|
+
Add a heading with specified level.
|
145
|
+
|
146
|
+
Args:
|
147
|
+
text: The heading text content
|
148
|
+
level: Heading level (1-3), defaults to 2
|
149
|
+
"""
|
150
|
+
self.children.append(HeadingMarkdownNode(text=text, level=level))
|
151
|
+
return self
|
152
|
+
|
153
|
+
def paragraph(self, text: str) -> Self:
|
154
|
+
"""
|
155
|
+
Add a paragraph block.
|
156
|
+
|
157
|
+
Args:
|
158
|
+
text: The paragraph text content
|
159
|
+
"""
|
160
|
+
self.children.append(ParagraphMarkdownNode(text=text))
|
161
|
+
return self
|
162
|
+
|
163
|
+
def text(self, content: str) -> Self:
|
164
|
+
"""
|
165
|
+
Add a text paragraph (alias for paragraph).
|
166
|
+
|
167
|
+
Args:
|
168
|
+
content: The text content
|
169
|
+
"""
|
170
|
+
return self.paragraph(content)
|
171
|
+
|
172
|
+
def quote(self, text: str) -> Self:
|
173
|
+
"""
|
174
|
+
Add a blockquote.
|
175
|
+
|
176
|
+
Args:
|
177
|
+
text: Quote text content
|
178
|
+
author: Optional quote author/attribution
|
179
|
+
"""
|
180
|
+
self.children.append(QuoteMarkdownNode(text=text))
|
181
|
+
return self
|
182
|
+
|
183
|
+
def divider(self) -> Self:
|
184
|
+
"""Add a horizontal divider."""
|
185
|
+
self.children.append(DividerMarkdownNode())
|
186
|
+
return self
|
187
|
+
|
188
|
+
def numbered_list(self, items: list[str]) -> Self:
|
189
|
+
"""
|
190
|
+
Add a numbered list.
|
191
|
+
|
192
|
+
Args:
|
193
|
+
items: List of text items for the numbered list
|
194
|
+
"""
|
195
|
+
self.children.append(NumberedListMarkdownNode(texts=items))
|
196
|
+
return self
|
197
|
+
|
198
|
+
def bulleted_list(self, items: list[str]) -> Self:
|
199
|
+
"""
|
200
|
+
Add a bulleted list.
|
201
|
+
|
202
|
+
Args:
|
203
|
+
items: List of text items for the bulleted list
|
204
|
+
"""
|
205
|
+
self.children.append(BulletedListMarkdownNode(texts=items))
|
206
|
+
return self
|
207
|
+
|
208
|
+
def todo(self, text: str, checked: bool = False) -> Self:
|
209
|
+
"""
|
210
|
+
Add a single todo item.
|
211
|
+
|
212
|
+
Args:
|
213
|
+
text: The todo item text
|
214
|
+
checked: Whether the todo item is completed, defaults to False
|
215
|
+
"""
|
216
|
+
self.children.append(TodoMarkdownNode(text=text, checked=checked))
|
217
|
+
return self
|
218
|
+
|
219
|
+
def todo_list(
|
220
|
+
self, items: list[str], completed: Optional[list[bool]] = None
|
221
|
+
) -> Self:
|
222
|
+
"""
|
223
|
+
Add multiple todo items.
|
224
|
+
|
225
|
+
Args:
|
226
|
+
items: List of todo item texts
|
227
|
+
completed: List of completion states for each item, defaults to all False
|
228
|
+
"""
|
229
|
+
if completed is None:
|
230
|
+
completed = [False] * len(items)
|
231
|
+
|
232
|
+
for i, item in enumerate(items):
|
233
|
+
is_done = completed[i] if i < len(completed) else False
|
234
|
+
self.children.append(TodoMarkdownNode(text=item, checked=is_done))
|
235
|
+
return self
|
236
|
+
|
237
|
+
def callout(self, text: str, emoji: Optional[str] = None) -> Self:
|
238
|
+
"""
|
239
|
+
Add a callout block.
|
240
|
+
|
241
|
+
Args:
|
242
|
+
text: The callout text content
|
243
|
+
emoji: Optional emoji for the callout icon
|
244
|
+
"""
|
245
|
+
self.children.append(CalloutMarkdownNode(text=text, emoji=emoji))
|
246
|
+
return self
|
247
|
+
|
248
|
+
def toggle(
|
249
|
+
self, title: str, builder_func: Callable[["MarkdownBuilder"], "MarkdownBuilder"]
|
250
|
+
) -> Self:
|
251
|
+
"""
|
252
|
+
Add a toggle block with content built using the builder API.
|
253
|
+
|
254
|
+
Args:
|
255
|
+
title: The toggle title/header text
|
256
|
+
builder_func: Function that receives a MarkdownBuilder and returns it configured
|
257
|
+
|
258
|
+
Example:
|
259
|
+
builder.toggle("Advanced Settings", lambda t:
|
260
|
+
t.h3("Configuration")
|
261
|
+
.paragraph("Settings description")
|
262
|
+
.table(["Setting", "Value"], [["Debug", "True"]])
|
263
|
+
.callout("Important note", "⚠️")
|
264
|
+
)
|
265
|
+
"""
|
266
|
+
toggle_builder = MarkdownBuilder()
|
267
|
+
builder_func(toggle_builder)
|
268
|
+
self.children.append(
|
269
|
+
ToggleMarkdownNode(title=title, children=toggle_builder.children)
|
270
|
+
)
|
271
|
+
return self
|
272
|
+
|
273
|
+
def toggleable_heading(
|
274
|
+
self,
|
275
|
+
text: str,
|
276
|
+
level: int,
|
277
|
+
builder_func: Callable[["MarkdownBuilder"], "MarkdownBuilder"],
|
278
|
+
) -> Self:
|
279
|
+
"""
|
280
|
+
Add a toggleable heading with content built using the builder API.
|
281
|
+
|
282
|
+
Args:
|
283
|
+
text: The heading text content
|
284
|
+
level: Heading level (1-3)
|
285
|
+
builder_func: Function that receives a MarkdownBuilder and returns it configured
|
286
|
+
|
287
|
+
Example:
|
288
|
+
builder.toggleable_heading("Advanced Section", 2, lambda t:
|
289
|
+
t.paragraph("Introduction to this section")
|
290
|
+
.numbered_list(["Step 1", "Step 2", "Step 3"])
|
291
|
+
.code("example_code()", "python")
|
292
|
+
.table(["Feature", "Status"], [["API", "Ready"]])
|
293
|
+
)
|
294
|
+
"""
|
295
|
+
toggle_builder = MarkdownBuilder()
|
296
|
+
builder_func(toggle_builder)
|
297
|
+
self.children.append(
|
298
|
+
ToggleableHeadingMarkdownNode(
|
299
|
+
text=text, level=level, children=toggle_builder.children
|
300
|
+
)
|
301
|
+
)
|
302
|
+
return self
|
303
|
+
|
304
|
+
def image(
|
305
|
+
self, url: str, caption: Optional[str] = None, alt: Optional[str] = None
|
306
|
+
) -> Self:
|
307
|
+
"""
|
308
|
+
Add an image.
|
309
|
+
|
310
|
+
Args:
|
311
|
+
url: Image URL or file path
|
312
|
+
caption: Optional image caption text
|
313
|
+
alt: Optional alternative text for accessibility
|
314
|
+
"""
|
315
|
+
self.children.append(ImageMarkdownNode(url=url, caption=caption, alt=alt))
|
316
|
+
return self
|
317
|
+
|
318
|
+
def video(self, url: str, caption: Optional[str] = None) -> Self:
|
319
|
+
"""
|
320
|
+
Add a video.
|
321
|
+
|
322
|
+
Args:
|
323
|
+
url: Video URL or file path
|
324
|
+
caption: Optional video caption text
|
325
|
+
"""
|
326
|
+
self.children.append(VideoMarkdownNode(url=url, caption=caption))
|
327
|
+
return self
|
328
|
+
|
329
|
+
def audio(self, url: str, caption: Optional[str] = None) -> Self:
|
330
|
+
"""
|
331
|
+
Add audio content.
|
332
|
+
|
333
|
+
Args:
|
334
|
+
url: Audio file URL or path
|
335
|
+
caption: Optional audio caption text
|
336
|
+
"""
|
337
|
+
self.children.append(AudioMarkdownNode(url=url, caption=caption))
|
338
|
+
return self
|
339
|
+
|
340
|
+
def file(self, url: str, caption: Optional[str] = None) -> Self:
|
341
|
+
"""
|
342
|
+
Add a file.
|
343
|
+
|
344
|
+
Args:
|
345
|
+
url: File URL or path
|
346
|
+
caption: Optional file caption text
|
347
|
+
"""
|
348
|
+
self.children.append(FileMarkdownNode(url=url, caption=caption))
|
349
|
+
return self
|
350
|
+
|
351
|
+
def pdf(self, url: str, caption: Optional[str] = None) -> Self:
|
352
|
+
"""
|
353
|
+
Add a PDF document.
|
354
|
+
|
355
|
+
Args:
|
356
|
+
url: PDF URL or file path
|
357
|
+
caption: Optional PDF caption text
|
358
|
+
"""
|
359
|
+
self.children.append(PdfMarkdownNode(url=url, caption=caption))
|
360
|
+
return self
|
361
|
+
|
362
|
+
def bookmark(
|
363
|
+
self, url: str, title: Optional[str] = None, caption: Optional[str] = None
|
364
|
+
) -> Self:
|
365
|
+
"""
|
366
|
+
Add a bookmark.
|
367
|
+
|
368
|
+
Args:
|
369
|
+
url: Bookmark URL
|
370
|
+
title: Optional bookmark title
|
371
|
+
description: Optional bookmark description text
|
372
|
+
"""
|
373
|
+
self.children.append(
|
374
|
+
BookmarkMarkdownNode(url=url, title=title, caption=caption)
|
375
|
+
)
|
376
|
+
return self
|
377
|
+
|
378
|
+
def embed(self, url: str, caption: Optional[str] = None) -> Self:
|
379
|
+
"""
|
380
|
+
Add an embed.
|
381
|
+
|
382
|
+
Args:
|
383
|
+
url: URL to embed (e.g., YouTube, Twitter, etc.)
|
384
|
+
caption: Optional embed caption text
|
385
|
+
"""
|
386
|
+
self.children.append(EmbedMarkdownNode(url=url, caption=caption))
|
387
|
+
return self
|
388
|
+
|
389
|
+
def code(
|
390
|
+
self, code: str, language: Optional[str] = None, caption: Optional[str] = None
|
391
|
+
) -> Self:
|
392
|
+
"""
|
393
|
+
Add a code block.
|
394
|
+
|
395
|
+
Args:
|
396
|
+
code: The source code content
|
397
|
+
language: Optional programming language for syntax highlighting
|
398
|
+
caption: Optional code block caption text
|
399
|
+
"""
|
400
|
+
self.children.append(
|
401
|
+
CodeMarkdownNode(code=code, language=language, caption=caption)
|
402
|
+
)
|
403
|
+
return self
|
404
|
+
|
405
|
+
def mermaid(self, diagram: str, caption: Optional[str] = None) -> Self:
|
406
|
+
"""
|
407
|
+
Add a Mermaid diagram block.
|
408
|
+
|
409
|
+
Args:
|
410
|
+
diagram: The Mermaid diagram source code
|
411
|
+
caption: Optional diagram caption text
|
412
|
+
"""
|
413
|
+
self.children.append(
|
414
|
+
CodeMarkdownNode(
|
415
|
+
code=diagram, language=CodeLanguage.MERMAID.value, caption=caption
|
416
|
+
)
|
417
|
+
)
|
418
|
+
return self
|
419
|
+
|
420
|
+
def table(self, headers: list[str], rows: list[list[str]]) -> Self:
|
421
|
+
"""
|
422
|
+
Add a table.
|
423
|
+
|
424
|
+
Args:
|
425
|
+
headers: List of column header texts
|
426
|
+
rows: List of rows, where each row is a list of cell texts
|
427
|
+
"""
|
428
|
+
self.children.append(TableMarkdownNode(headers=headers, rows=rows))
|
429
|
+
return self
|
430
|
+
|
431
|
+
def add_custom(self, node: MarkdownNode) -> Self:
|
432
|
+
"""
|
433
|
+
Add a custom MarkdownNode.
|
434
|
+
|
435
|
+
Args:
|
436
|
+
node: A custom MarkdownNode instance
|
437
|
+
"""
|
438
|
+
self.children.append(node)
|
439
|
+
return self
|
440
|
+
|
441
|
+
def breadcrumb(self) -> Self:
|
442
|
+
"""Add a breadcrumb navigation block."""
|
443
|
+
self.children.append(BreadcrumbMarkdownNode())
|
444
|
+
return self
|
445
|
+
|
446
|
+
def equation(self, expression: str) -> Self:
|
447
|
+
"""
|
448
|
+
Add a LaTeX equation block.
|
449
|
+
|
450
|
+
Args:
|
451
|
+
expression: LaTeX mathematical expression
|
452
|
+
|
453
|
+
Example:
|
454
|
+
builder.equation("E = mc^2")
|
455
|
+
builder.equation("f(x) = \\sin(x) + \\cos(x)")
|
456
|
+
builder.equation("x = \\frac{-b \\pm \\sqrt{b^2-4ac}}{2a}")
|
457
|
+
"""
|
458
|
+
self.children.append(EquationMarkdownNode(expression=expression))
|
459
|
+
return self
|
460
|
+
|
461
|
+
def table_of_contents(self, color: Optional[str] = None) -> Self:
|
462
|
+
"""
|
463
|
+
Add a table of contents.
|
464
|
+
|
465
|
+
Args:
|
466
|
+
color: Optional color for the table of contents (e.g., "blue", "blue_background")
|
467
|
+
"""
|
468
|
+
self.children.append(TableOfContentsMarkdownNode(color=color))
|
469
|
+
return self
|
470
|
+
|
471
|
+
def columns(
|
472
|
+
self,
|
473
|
+
*builder_funcs: Callable[["MarkdownBuilder"], "MarkdownBuilder"],
|
474
|
+
width_ratios: Optional[list[float]] = None,
|
475
|
+
) -> Self:
|
476
|
+
"""
|
477
|
+
Add multiple columns in a layout.
|
478
|
+
|
479
|
+
Args:
|
480
|
+
*builder_funcs: Multiple functions, each building one column
|
481
|
+
width_ratios: Optional list of width ratios (0.0 to 1.0).
|
482
|
+
If None, columns have equal width.
|
483
|
+
Length must match number of builder_funcs.
|
484
|
+
|
485
|
+
Examples:
|
486
|
+
# Equal width (original API unchanged):
|
487
|
+
builder.columns(
|
488
|
+
lambda col: col.h2("Left").paragraph("Left content"),
|
489
|
+
lambda col: col.h2("Right").paragraph("Right content")
|
490
|
+
)
|
491
|
+
|
492
|
+
# Custom ratios:
|
493
|
+
builder.columns(
|
494
|
+
lambda col: col.h2("Main").paragraph("70% width"),
|
495
|
+
lambda col: col.h2("Sidebar").paragraph("30% width"),
|
496
|
+
width_ratios=[0.7, 0.3]
|
497
|
+
)
|
498
|
+
|
499
|
+
# Three columns with custom ratios:
|
500
|
+
builder.columns(
|
501
|
+
lambda col: col.h3("Nav").paragraph("Navigation"),
|
502
|
+
lambda col: col.h2("Main").paragraph("Main content"),
|
503
|
+
lambda col: col.h3("Ads").paragraph("Advertisement"),
|
504
|
+
width_ratios=[0.2, 0.6, 0.2]
|
505
|
+
)
|
506
|
+
"""
|
507
|
+
if len(builder_funcs) < 2:
|
508
|
+
raise ValueError("Column layout requires at least 2 columns")
|
509
|
+
|
510
|
+
if width_ratios is not None:
|
511
|
+
if len(width_ratios) != len(builder_funcs):
|
512
|
+
raise ValueError(
|
513
|
+
f"width_ratios length ({len(width_ratios)}) must match number of columns ({len(builder_funcs)})"
|
514
|
+
)
|
515
|
+
|
516
|
+
ratio_sum = sum(width_ratios)
|
517
|
+
if not (0.9 <= ratio_sum <= 1.1): # Allow small floating point errors
|
518
|
+
raise ValueError(f"width_ratios should sum to 1.0, got {ratio_sum}")
|
519
|
+
|
520
|
+
# Create all columns
|
521
|
+
columns = []
|
522
|
+
for i, builder_func in enumerate(builder_funcs):
|
523
|
+
width_ratio = width_ratios[i] if width_ratios else None
|
524
|
+
|
525
|
+
col_builder = MarkdownBuilder()
|
526
|
+
builder_func(col_builder)
|
527
|
+
|
528
|
+
column_node = ColumnMarkdownNode(
|
529
|
+
children=col_builder.children, width_ratio=width_ratio
|
530
|
+
)
|
531
|
+
columns.append(column_node)
|
532
|
+
|
533
|
+
self.children.append(ColumnListMarkdownNode(columns=columns))
|
534
|
+
return self
|
535
|
+
|
536
|
+
def column_with_nodes(
|
537
|
+
self, *nodes: MarkdownNode, width_ratio: Optional[float] = None
|
538
|
+
) -> Self:
|
539
|
+
"""
|
540
|
+
Add a column with pre-built MarkdownNode objects.
|
541
|
+
|
542
|
+
Args:
|
543
|
+
*nodes: MarkdownNode objects to include in the column
|
544
|
+
width_ratio: Optional width ratio (0.0 to 1.0)
|
545
|
+
|
546
|
+
Examples:
|
547
|
+
# Original API (unchanged):
|
548
|
+
builder.column_with_nodes(
|
549
|
+
HeadingMarkdownNode(text="Title", level=2),
|
550
|
+
ParagraphMarkdownNode(text="Content")
|
551
|
+
)
|
552
|
+
|
553
|
+
# New API with ratio:
|
554
|
+
builder.column_with_nodes(
|
555
|
+
HeadingMarkdownNode(text="Sidebar", level=2),
|
556
|
+
ParagraphMarkdownNode(text="Narrow content"),
|
557
|
+
width_ratio=0.25
|
558
|
+
)
|
559
|
+
"""
|
560
|
+
from notionary.blocks.column.column_markdown_node import ColumnMarkdownNode
|
561
|
+
|
562
|
+
column_node = ColumnMarkdownNode(children=list(nodes), width_ratio=width_ratio)
|
563
|
+
self.children.append(column_node)
|
564
|
+
return self
|
565
|
+
|
566
|
+
def _column(
|
567
|
+
self, builder_func: Callable[[MarkdownBuilder], MarkdownBuilder]
|
568
|
+
) -> ColumnMarkdownNode:
|
569
|
+
"""
|
570
|
+
Internal helper to create a single column.
|
571
|
+
Use columns() instead for public API.
|
572
|
+
"""
|
573
|
+
col_builder = MarkdownBuilder()
|
574
|
+
builder_func(col_builder)
|
575
|
+
return ColumnMarkdownNode(children=col_builder.children)
|
576
|
+
|
577
|
+
def space(self) -> Self:
|
578
|
+
"""Add vertical spacing."""
|
579
|
+
return self.paragraph("")
|
580
|
+
|
581
|
+
def build(self) -> str:
|
582
|
+
"""Build and return the final markdown string."""
|
583
|
+
return "\n\n".join(
|
584
|
+
child.to_markdown() for child in self.children if child is not None
|
585
|
+
)
|
586
|
+
|
587
|
+
def _add_heading(self, params: HeadingMarkdownBlockParams) -> None:
|
588
|
+
"""Add a heading block."""
|
589
|
+
self.children.append(HeadingMarkdownNode.from_params(params))
|
590
|
+
|
591
|
+
def _add_paragraph(self, params: ParagraphMarkdownBlockParams) -> None:
|
592
|
+
"""Add a paragraph block."""
|
593
|
+
self.children.append(ParagraphMarkdownNode.from_params(params))
|
594
|
+
|
595
|
+
def _add_quote(self, params: QuoteMarkdownBlockParams) -> None:
|
596
|
+
"""Add a quote block."""
|
597
|
+
self.children.append(QuoteMarkdownNode.from_params(params))
|
598
|
+
|
599
|
+
def _add_bulleted_list(self, params: BulletedListMarkdownBlockParams) -> None:
|
600
|
+
"""Add a bulleted list block."""
|
601
|
+
self.children.append(BulletedListMarkdownNode.from_params(params))
|
602
|
+
|
603
|
+
def _add_numbered_list(self, params: NumberedListMarkdownBlockParams) -> None:
|
604
|
+
"""Add a numbered list block."""
|
605
|
+
self.children.append(NumberedListMarkdownNode.from_params(params))
|
606
|
+
|
607
|
+
def _add_todo(self, params: TodoMarkdownBlockParams) -> None:
|
608
|
+
"""Add a todo block."""
|
609
|
+
self.children.append(TodoMarkdownNode.from_params(params))
|
610
|
+
|
611
|
+
def _add_callout(self, params: CalloutMarkdownBlockParams) -> None:
|
612
|
+
"""Add a callout block."""
|
613
|
+
self.children.append(CalloutMarkdownNode.from_params(params))
|
614
|
+
|
615
|
+
def _add_code(self, params: CodeBlock) -> None:
|
616
|
+
"""Add a code block."""
|
617
|
+
self.children.append(CodeMarkdownNode.from_params(params))
|
618
|
+
|
619
|
+
def _add_image(self, params: ImageMarkdownBlockParams) -> None:
|
620
|
+
"""Add an image block."""
|
621
|
+
self.children.append(ImageMarkdownNode.from_params(params))
|
622
|
+
|
623
|
+
def _add_video(self, params: VideoMarkdownBlockParams) -> None:
|
624
|
+
"""Add a video block."""
|
625
|
+
self.children.append(VideoMarkdownNode.from_params(params))
|
626
|
+
|
627
|
+
def _add_audio(self, params: AudioMarkdownBlockParams) -> None:
|
628
|
+
"""Add an audio block."""
|
629
|
+
self.children.append(AudioMarkdownNode.from_params(params))
|
630
|
+
|
631
|
+
def _add_file(self, params: FileMarkdownNodeParams) -> None:
|
632
|
+
"""Add a file block."""
|
633
|
+
self.children.append(FileMarkdownNode.from_params(params))
|
634
|
+
|
635
|
+
def _add_pdf(self, params: PdfMarkdownNodeParams) -> None:
|
636
|
+
"""Add a PDF block."""
|
637
|
+
self.children.append(PdfMarkdownNode.from_params(params))
|
638
|
+
|
639
|
+
def _add_bookmark(self, params: BookmarkMarkdownBlockParams) -> None:
|
640
|
+
"""Add a bookmark block."""
|
641
|
+
self.children.append(BookmarkMarkdownNode.from_params(params))
|
642
|
+
|
643
|
+
def _add_embed(self, params: EmbedMarkdownBlockParams) -> None:
|
644
|
+
"""Add an embed block."""
|
645
|
+
self.children.append(EmbedMarkdownNode.from_params(params))
|
646
|
+
|
647
|
+
def _add_table(self, params: TableMarkdownBlockParams) -> None:
|
648
|
+
"""Add a table block."""
|
649
|
+
self.children.append(TableMarkdownNode.from_params(params))
|
650
|
+
|
651
|
+
def _add_divider(self, params: DividerMarkdownBlockParams) -> None:
|
652
|
+
"""Add a divider block."""
|
653
|
+
self.children.append(DividerMarkdownNode.from_params(params))
|
654
|
+
|
655
|
+
def _add_equation(self, params: EquationMarkdownBlockParams) -> None:
|
656
|
+
"""Add an equation block."""
|
657
|
+
self.children.append(EquationMarkdownNode.from_params(params))
|
658
|
+
|
659
|
+
def _add_table_of_contents(
|
660
|
+
self, params: TableOfContentsMarkdownBlockParams
|
661
|
+
) -> None:
|
662
|
+
"""Add a table of contents block."""
|
663
|
+
self.children.append(TableOfContentsMarkdownNode.from_params(params))
|
664
|
+
|
665
|
+
def _add_toggle(self, params: ToggleMarkdownBlockParams) -> None:
|
666
|
+
"""Add a toggle block."""
|
667
|
+
child_builder = MarkdownBuilder()
|
668
|
+
child_builder._process_blocks(params.children)
|
669
|
+
self.children.append(
|
670
|
+
ToggleMarkdownNode(title=params.title, children=child_builder.children)
|
671
|
+
)
|
672
|
+
|
673
|
+
def _add_toggleable_heading(
|
674
|
+
self, params: ToggleableHeadingMarkdownBlockParams
|
675
|
+
) -> None:
|
676
|
+
"""Add a toggleable heading block."""
|
677
|
+
# Create nested builder for children
|
678
|
+
child_builder = MarkdownBuilder()
|
679
|
+
child_builder._process_blocks(params.children)
|
680
|
+
self.children.append(
|
681
|
+
ToggleableHeadingMarkdownNode(
|
682
|
+
text=params.text, level=params.level, children=child_builder.children
|
683
|
+
)
|
684
|
+
)
|
685
|
+
|
686
|
+
def _add_columns(self, params: ColumnListMarkdownBlockParams) -> None:
|
687
|
+
"""Add a columns block."""
|
688
|
+
column_nodes = []
|
689
|
+
|
690
|
+
for i, column_blocks in enumerate(params.columns):
|
691
|
+
width_ratio = (
|
692
|
+
params.width_ratios[i]
|
693
|
+
if params.width_ratios and i < len(params.width_ratios)
|
694
|
+
else None
|
695
|
+
)
|
696
|
+
|
697
|
+
col_builder = MarkdownBuilder()
|
698
|
+
col_builder._process_blocks(column_blocks)
|
699
|
+
|
700
|
+
# Erstelle ColumnMarkdownNode
|
701
|
+
column_nodes.append(
|
702
|
+
ColumnMarkdownNode(
|
703
|
+
children=col_builder.children, width_ratio=width_ratio
|
704
|
+
)
|
705
|
+
)
|
706
|
+
|
707
|
+
self.children.append(ColumnListMarkdownNode(columns=column_nodes))
|
708
|
+
|
709
|
+
def _add_breadcrumb(self, params) -> None:
|
710
|
+
"""Add a breadcrumb block."""
|
711
|
+
self.children.append(BreadcrumbMarkdownNode())
|
712
|
+
|
713
|
+
def _add_space(self, params) -> None:
|
714
|
+
"""Add a space block."""
|
715
|
+
self.children.append(ParagraphMarkdownNode(text=""))
|
716
|
+
|
717
|
+
def _process_blocks(self, blocks: list[MarkdownBlock]) -> None:
|
718
|
+
"""Process blocks using explicit mapping - type-safe and clear."""
|
719
|
+
for block in blocks:
|
720
|
+
processor = self._block_processors.get(block.type)
|
721
|
+
if processor:
|
722
|
+
processor(block.params)
|
723
|
+
else:
|
724
|
+
# More explicit error handling
|
725
|
+
available_types = ", ".join(sorted(self._block_processors.keys()))
|
726
|
+
raise ValueError(
|
727
|
+
f"Unsupported block type '{block.type}'. "
|
728
|
+
f"Available types: {available_types}"
|
729
|
+
)
|