notionary 0.2.18__py3-none-any.whl → 0.2.21__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.
Files changed (204) hide show
  1. notionary/__init__.py +8 -4
  2. notionary/base_notion_client.py +3 -1
  3. notionary/blocks/__init__.py +2 -91
  4. notionary/blocks/_bootstrap.py +263 -0
  5. notionary/blocks/audio/__init__.py +8 -2
  6. notionary/blocks/audio/audio_element.py +42 -104
  7. notionary/blocks/audio/audio_markdown_node.py +3 -1
  8. notionary/blocks/audio/audio_models.py +6 -55
  9. notionary/blocks/base_block_element.py +30 -0
  10. notionary/blocks/bookmark/__init__.py +9 -2
  11. notionary/blocks/bookmark/bookmark_element.py +46 -139
  12. notionary/blocks/bookmark/bookmark_markdown_node.py +3 -1
  13. notionary/blocks/bookmark/bookmark_models.py +15 -0
  14. notionary/blocks/breadcrumbs/__init__.py +17 -0
  15. notionary/blocks/breadcrumbs/breadcrumb_element.py +39 -0
  16. notionary/blocks/breadcrumbs/breadcrumb_markdown_node.py +32 -0
  17. notionary/blocks/breadcrumbs/breadcrumb_models.py +12 -0
  18. notionary/blocks/bulleted_list/__init__.py +12 -2
  19. notionary/blocks/bulleted_list/bulleted_list_element.py +40 -55
  20. notionary/blocks/bulleted_list/bulleted_list_markdown_node.py +2 -1
  21. notionary/blocks/bulleted_list/bulleted_list_models.py +18 -0
  22. notionary/blocks/callout/__init__.py +9 -2
  23. notionary/blocks/callout/callout_element.py +40 -89
  24. notionary/blocks/callout/callout_markdown_node.py +3 -1
  25. notionary/blocks/callout/callout_models.py +33 -0
  26. notionary/blocks/child_database/__init__.py +7 -0
  27. notionary/blocks/child_database/child_database_models.py +19 -0
  28. notionary/blocks/child_page/__init__.py +9 -0
  29. notionary/blocks/child_page/child_page_models.py +12 -0
  30. notionary/blocks/{shared/block_client.py → client.py} +55 -54
  31. notionary/blocks/code/__init__.py +6 -2
  32. notionary/blocks/code/code_element.py +53 -187
  33. notionary/blocks/code/code_markdown_node.py +13 -13
  34. notionary/blocks/code/code_models.py +94 -0
  35. notionary/blocks/column/__init__.py +25 -1
  36. notionary/blocks/column/column_element.py +40 -314
  37. notionary/blocks/column/column_list_element.py +37 -0
  38. notionary/blocks/column/column_list_markdown_node.py +50 -0
  39. notionary/blocks/column/column_markdown_node.py +59 -0
  40. notionary/blocks/column/column_models.py +26 -0
  41. notionary/blocks/divider/__init__.py +9 -2
  42. notionary/blocks/divider/divider_element.py +26 -49
  43. notionary/blocks/divider/divider_markdown_node.py +2 -1
  44. notionary/blocks/divider/divider_models.py +12 -0
  45. notionary/blocks/embed/__init__.py +9 -2
  46. notionary/blocks/embed/embed_element.py +47 -114
  47. notionary/blocks/embed/embed_markdown_node.py +3 -1
  48. notionary/blocks/embed/embed_models.py +14 -0
  49. notionary/blocks/equation/__init__.py +14 -0
  50. notionary/blocks/equation/equation_element.py +80 -0
  51. notionary/blocks/equation/equation_element_markdown_node.py +36 -0
  52. notionary/blocks/equation/equation_models.py +11 -0
  53. notionary/blocks/file/__init__.py +25 -0
  54. notionary/blocks/file/file_element.py +93 -0
  55. notionary/blocks/file/file_element_markdown_node.py +35 -0
  56. notionary/blocks/file/file_element_models.py +39 -0
  57. notionary/blocks/heading/__init__.py +16 -2
  58. notionary/blocks/heading/heading_element.py +67 -72
  59. notionary/blocks/heading/heading_markdown_node.py +2 -1
  60. notionary/blocks/heading/heading_models.py +29 -0
  61. notionary/blocks/image_block/__init__.py +13 -0
  62. notionary/blocks/image_block/image_element.py +84 -0
  63. notionary/blocks/{image → image_block}/image_markdown_node.py +3 -1
  64. notionary/blocks/image_block/image_models.py +10 -0
  65. notionary/blocks/models.py +172 -0
  66. notionary/blocks/numbered_list/__init__.py +12 -2
  67. notionary/blocks/numbered_list/numbered_list_element.py +33 -58
  68. notionary/blocks/numbered_list/numbered_list_markdown_node.py +3 -1
  69. notionary/blocks/numbered_list/numbered_list_models.py +17 -0
  70. notionary/blocks/paragraph/__init__.py +12 -2
  71. notionary/blocks/paragraph/paragraph_element.py +27 -69
  72. notionary/blocks/paragraph/paragraph_markdown_node.py +2 -1
  73. notionary/blocks/paragraph/paragraph_models.py +16 -0
  74. notionary/blocks/pdf/__init__.py +13 -0
  75. notionary/blocks/pdf/pdf_element.py +91 -0
  76. notionary/blocks/pdf/pdf_markdown_node.py +35 -0
  77. notionary/blocks/pdf/pdf_models.py +11 -0
  78. notionary/blocks/quote/__init__.py +11 -2
  79. notionary/blocks/quote/quote_element.py +31 -65
  80. notionary/blocks/quote/quote_markdown_node.py +4 -1
  81. notionary/blocks/quote/quote_models.py +18 -0
  82. notionary/blocks/registry/__init__.py +4 -0
  83. notionary/blocks/registry/block_registry.py +75 -91
  84. notionary/blocks/registry/block_registry_builder.py +107 -59
  85. notionary/blocks/rich_text/__init__.py +33 -0
  86. notionary/blocks/rich_text/rich_text_models.py +188 -0
  87. notionary/blocks/rich_text/text_inline_formatter.py +125 -0
  88. notionary/blocks/table/__init__.py +16 -2
  89. notionary/blocks/table/table_element.py +48 -241
  90. notionary/blocks/table/table_markdown_node.py +2 -1
  91. notionary/blocks/table/table_models.py +28 -0
  92. notionary/blocks/table_of_contents/__init__.py +19 -0
  93. notionary/blocks/table_of_contents/table_of_contents_element.py +51 -0
  94. notionary/blocks/table_of_contents/table_of_contents_markdown_node.py +35 -0
  95. notionary/blocks/table_of_contents/table_of_contents_models.py +18 -0
  96. notionary/blocks/todo/__init__.py +9 -2
  97. notionary/blocks/todo/todo_element.py +38 -95
  98. notionary/blocks/todo/todo_markdown_node.py +2 -1
  99. notionary/blocks/todo/todo_models.py +19 -0
  100. notionary/blocks/toggle/__init__.py +13 -3
  101. notionary/blocks/toggle/toggle_element.py +57 -264
  102. notionary/blocks/toggle/toggle_markdown_node.py +24 -14
  103. notionary/blocks/toggle/toggle_models.py +17 -0
  104. notionary/blocks/toggleable_heading/__init__.py +6 -2
  105. notionary/blocks/toggleable_heading/toggleable_heading_element.py +74 -244
  106. notionary/blocks/toggleable_heading/toggleable_heading_markdown_node.py +26 -18
  107. notionary/blocks/types.py +61 -0
  108. notionary/blocks/video/__init__.py +8 -2
  109. notionary/blocks/video/video_element.py +67 -143
  110. notionary/blocks/video/video_element_models.py +10 -0
  111. notionary/blocks/video/video_markdown_node.py +3 -1
  112. notionary/database/client.py +3 -8
  113. notionary/database/database.py +13 -14
  114. notionary/database/database_filter_builder.py +2 -2
  115. notionary/database/database_provider.py +5 -4
  116. notionary/database/models.py +337 -0
  117. notionary/database/notion_database.py +6 -7
  118. notionary/file_upload/client.py +5 -7
  119. notionary/file_upload/models.py +2 -1
  120. notionary/file_upload/notion_file_upload.py +2 -3
  121. notionary/markdown/markdown_builder.py +722 -0
  122. notionary/markdown/markdown_document_model.py +228 -0
  123. notionary/{blocks → markdown}/markdown_node.py +1 -0
  124. notionary/models/notion_database_response.py +0 -338
  125. notionary/page/client.py +9 -10
  126. notionary/page/models.py +327 -0
  127. notionary/page/notion_page.py +99 -52
  128. notionary/page/notion_text_length_utils.py +119 -0
  129. notionary/page/{content/page_content_writer.py → page_content_writer.py} +88 -38
  130. notionary/page/reader/handler/__init__.py +17 -0
  131. notionary/page/reader/handler/base_block_renderer.py +44 -0
  132. notionary/page/reader/handler/block_processing_context.py +35 -0
  133. notionary/page/reader/handler/block_rendering_context.py +43 -0
  134. notionary/page/reader/handler/column_list_renderer.py +51 -0
  135. notionary/page/reader/handler/column_renderer.py +60 -0
  136. notionary/page/reader/handler/line_renderer.py +60 -0
  137. notionary/page/reader/handler/toggle_renderer.py +69 -0
  138. notionary/page/reader/handler/toggleable_heading_renderer.py +89 -0
  139. notionary/page/reader/page_content_retriever.py +69 -0
  140. notionary/page/search_filter_builder.py +2 -1
  141. notionary/page/writer/handler/__init__.py +22 -0
  142. notionary/page/writer/handler/code_handler.py +100 -0
  143. notionary/page/writer/handler/column_handler.py +141 -0
  144. notionary/page/writer/handler/column_list_handler.py +139 -0
  145. notionary/page/writer/handler/line_handler.py +35 -0
  146. notionary/page/writer/handler/line_processing_context.py +54 -0
  147. notionary/page/writer/handler/regular_line_handler.py +92 -0
  148. notionary/page/writer/handler/table_handler.py +130 -0
  149. notionary/page/writer/handler/toggle_handler.py +153 -0
  150. notionary/page/writer/handler/toggleable_heading_handler.py +167 -0
  151. notionary/page/writer/markdown_to_notion_converter.py +76 -0
  152. notionary/telemetry/__init__.py +2 -2
  153. notionary/telemetry/service.py +4 -3
  154. notionary/user/__init__.py +2 -2
  155. notionary/user/base_notion_user.py +2 -1
  156. notionary/user/client.py +2 -3
  157. notionary/user/models.py +1 -0
  158. notionary/user/notion_bot_user.py +4 -5
  159. notionary/user/notion_user.py +3 -4
  160. notionary/user/notion_user_manager.py +3 -2
  161. notionary/user/notion_user_provider.py +1 -1
  162. notionary/util/__init__.py +3 -2
  163. notionary/util/fuzzy.py +2 -1
  164. notionary/util/logging_mixin.py +2 -2
  165. notionary/util/singleton_metaclass.py +1 -1
  166. notionary/workspace.py +3 -2
  167. {notionary-0.2.18.dist-info → notionary-0.2.21.dist-info}/METADATA +12 -8
  168. notionary-0.2.21.dist-info/RECORD +185 -0
  169. notionary/blocks/document/__init__.py +0 -7
  170. notionary/blocks/document/document_element.py +0 -102
  171. notionary/blocks/document/document_markdown_node.py +0 -31
  172. notionary/blocks/image/__init__.py +0 -7
  173. notionary/blocks/image/image_element.py +0 -151
  174. notionary/blocks/markdown_builder.py +0 -356
  175. notionary/blocks/mention/__init__.py +0 -7
  176. notionary/blocks/mention/mention_element.py +0 -229
  177. notionary/blocks/mention/mention_markdown_node.py +0 -38
  178. notionary/blocks/prompts/element_prompt_builder.py +0 -83
  179. notionary/blocks/prompts/element_prompt_content.py +0 -41
  180. notionary/blocks/shared/__init__.py +0 -0
  181. notionary/blocks/shared/models.py +0 -710
  182. notionary/blocks/shared/notion_block_element.py +0 -37
  183. notionary/blocks/shared/text_inline_formatter.py +0 -262
  184. notionary/blocks/shared/text_inline_formatter_new.py +0 -139
  185. notionary/blocks/toggleable_heading/toggleable_heading_models.py +0 -0
  186. notionary/database/models/page_result.py +0 -10
  187. notionary/models/notion_block_response.py +0 -264
  188. notionary/models/notion_page_response.py +0 -78
  189. notionary/models/search_response.py +0 -0
  190. notionary/page/__init__.py +0 -0
  191. notionary/page/content/notion_text_length_utils.py +0 -87
  192. notionary/page/content/page_content_retriever.py +0 -52
  193. notionary/page/formatting/line_processor.py +0 -153
  194. notionary/page/formatting/markdown_to_notion_converter.py +0 -153
  195. notionary/page/markdown_syntax_prompt_generator.py +0 -114
  196. notionary/page/notion_to_markdown_converter.py +0 -179
  197. notionary/page/properites/property_value_extractor.py +0 -0
  198. notionary-0.2.18.dist-info/RECORD +0 -149
  199. /notionary/{blocks/document/document_models.py → markdown/___init__.py} +0 -0
  200. /notionary/{blocks/image/image_models.py → markdown/makdown_document_model.py} +0 -0
  201. /notionary/page/{content/markdown_whitespace_processor.py → markdown_whitespace_processor.py} +0 -0
  202. /notionary/{blocks/mention/mention_models.py → page/reader/handler/context.py} +0 -0
  203. {notionary-0.2.18.dist-info → notionary-0.2.21.dist-info}/LICENSE +0 -0
  204. {notionary-0.2.18.dist-info → notionary-0.2.21.dist-info}/WHEEL +0 -0
@@ -1,270 +1,100 @@
1
- import re
2
- from typing import Dict, Any, Optional, List, Tuple, Callable
1
+ from __future__ import annotations
3
2
 
4
- from notionary.blocks import (
5
- ElementPromptContent,
6
- ElementPromptBuilder,
7
- NotionBlockElement,
8
- NotionBlockResult,
3
+ import re
4
+ from typing import Optional
5
+
6
+ from notionary.blocks.base_block_element import BaseBlockElement
7
+ from notionary.blocks.heading.heading_models import (
8
+ CreateHeading1Block,
9
+ CreateHeading2Block,
10
+ CreateHeading3Block,
11
+ HeadingBlock,
9
12
  )
10
- from notionary.blocks.shared.text_inline_formatter import TextInlineFormatter
13
+ from notionary.blocks.models import Block, BlockCreateResult, BlockType
14
+ from notionary.blocks.rich_text.text_inline_formatter import TextInlineFormatter
11
15
 
12
16
 
13
- class ToggleableHeadingElement(NotionBlockElement):
14
- """Handles conversion between Markdown collapsible headings and Notion toggleable heading blocks with pipe syntax."""
17
+ class ToggleableHeadingElement(BaseBlockElement):
18
+ """
19
+ Simplified ToggleableHeadingElement that works with the stack-based converter.
20
+ Children are automatically handled by the StackBasedMarkdownConverter.
21
+ """
15
22
 
16
- PATTERN = re.compile(r"^\+(?P<level>#{1,3})\s+(?P<content>.+)$")
17
- PIPE_CONTENT_PATTERN = re.compile(r"^\|\s?(.*)$")
18
-
19
- @staticmethod
20
- def match_markdown(text: str) -> bool:
21
- """Check if text is a markdown collapsible heading."""
22
- return bool(ToggleableHeadingElement.PATTERN.match(text))
23
+ # Updated pattern for simplified +++# Title syntax (no quotes!)
24
+ PATTERN = re.compile(r"^[+]{3}(?P<level>#{1,3})\s+(.+)$", re.IGNORECASE)
23
25
 
24
26
  @staticmethod
25
- def match_notion(block: Dict[str, Any]) -> bool:
27
+ def match_notion(block: Block) -> bool:
26
28
  """Check if block is a Notion toggleable heading."""
27
- block_type: str = block.get("type", "")
28
- if not block_type.startswith("heading_") or block_type[-1] not in "123":
29
+ # Use BlockType enum for matching heading blocks
30
+ if block.type not in (
31
+ BlockType.HEADING_1,
32
+ BlockType.HEADING_2,
33
+ BlockType.HEADING_3,
34
+ ):
29
35
  return False
30
36
 
31
- # Check if it has the is_toggleable property set to true
32
- heading_data = block.get(block_type, {})
33
- return heading_data.get("is_toggleable", False) is True
34
-
35
- @staticmethod
36
- def markdown_to_notion(text: str) -> NotionBlockResult:
37
- """Convert markdown collapsible heading to Notion toggleable heading block."""
38
- header_match = ToggleableHeadingElement.PATTERN.match(text)
39
- if not header_match:
40
- return None
41
-
42
- level = len(header_match.group(1))
43
- content = header_match.group(2)
44
-
45
- return {
46
- "type": f"heading_{level}",
47
- f"heading_{level}": {
48
- "rich_text": TextInlineFormatter.parse_inline_formatting(content),
49
- "is_toggleable": True,
50
- "color": "default",
51
- "children": [], # Will be populated with nested content if needed
52
- },
53
- }
54
-
55
- @staticmethod
56
- def notion_to_markdown(block: Dict[str, Any]) -> Optional[str]:
57
- """Convert Notion toggleable heading block to markdown collapsible heading with pipe syntax."""
58
- block_type = block.get("type", "")
59
-
60
- if not block_type.startswith("heading_"):
61
- return None
62
-
63
- try:
64
- level = int(block_type[-1])
65
- if not 1 <= level <= 3:
66
- return None
67
- except ValueError:
68
- return None
69
-
70
- heading_data = block.get(block_type, {})
71
-
72
- # Check if it's toggleable
73
- if not heading_data.get("is_toggleable", False):
74
- return None
75
-
76
- rich_text = heading_data.get("rich_text", [])
77
- text = TextInlineFormatter.extract_text_with_formatting(rich_text)
78
- prefix = "#" * level
79
- return f"+{prefix} {text or ''}"
80
-
81
- @staticmethod
82
- def is_multiline() -> bool:
83
- """Collapsible headings can have children, so they're multiline elements."""
84
- return True
37
+ if block.heading_1 and block.heading_1.is_toggleable:
38
+ return True
39
+ if block.heading_2 and block.heading_2.is_toggleable:
40
+ return True
41
+ if block.heading_3 and block.heading_3.is_toggleable:
42
+ return True
85
43
 
86
44
  @classmethod
87
- def find_matches(
88
- cls,
89
- text: str,
90
- process_nested_content: Callable = None,
91
- context_aware: bool = True,
92
- ) -> List[Tuple[int, int, Dict[str, Any]]]:
45
+ def markdown_to_notion(cls, text: str) -> BlockCreateResult:
93
46
  """
94
- Find all collapsible heading matches in the text with pipe syntax for nested content.
95
- Improved version with reduced cognitive complexity.
96
-
97
- Args:
98
- text: The text to process
99
- process_nested_content: Optional callback function to process nested content
100
- context_aware: Whether to consider context when finding collapsible headings
101
-
102
- Returns:
103
- List of (start_pos, end_pos, block) tuples
47
+ Convert markdown collapsible heading to a toggleable Notion HeadingBlock.
48
+ Children are automatically handled by the StackBasedMarkdownConverter.
104
49
  """
105
- if not text:
106
- return []
107
-
108
- collapsible_blocks = []
109
- lines = text.split("\n")
110
- line_index = 0
111
-
112
- while line_index < len(lines):
113
- current_line = lines[line_index]
114
-
115
- # Skip non-collapsible heading lines
116
- if not cls._is_collapsible_heading(current_line):
117
- line_index += 1
118
- continue
119
-
120
- # Process collapsible heading
121
- start_position = cls._calculate_line_position(lines, line_index)
122
- heading_block = cls.markdown_to_notion(current_line)
123
-
124
- if not heading_block:
125
- line_index += 1
126
- continue
50
+ if not (match := cls.PATTERN.match(text.strip())):
51
+ return None
127
52
 
128
- # Extract and process nested content
129
- nested_content, next_line_index = cls._extract_nested_content(
130
- lines, line_index + 1
131
- )
132
- end_position = cls._calculate_block_end_position(
133
- start_position, current_line, nested_content
134
- )
53
+ level = len(match.group("level")) # Count # characters
54
+ content = match.group(2).strip() # group(2) is the title (no quotes needed)
135
55
 
136
- cls._process_nested_content(
137
- heading_block, nested_content, process_nested_content
138
- )
56
+ if level < 1 or level > 3 or not content:
57
+ return None
139
58
 
140
- # Add block to results
141
- collapsible_blocks.append((start_position, end_position, heading_block))
142
- line_index = next_line_index
59
+ rich_text = TextInlineFormatter.parse_inline_formatting(content)
143
60
 
144
- return collapsible_blocks
61
+ heading_content = HeadingBlock(
62
+ rich_text=rich_text, color="default", is_toggleable=True, children=[]
63
+ )
145
64
 
146
- @classmethod
147
- def _is_collapsible_heading(cls, line: str) -> bool:
148
- """Check if a line represents a collapsible heading."""
149
- return bool(cls.PATTERN.match(line))
65
+ if level == 1:
66
+ return CreateHeading1Block(heading_1=heading_content)
67
+ elif level == 2:
68
+ return CreateHeading2Block(heading_2=heading_content)
69
+ else:
70
+ return CreateHeading3Block(heading_3=heading_content)
150
71
 
151
72
  @staticmethod
152
- def _calculate_line_position(lines: List[str], current_index: int) -> int:
153
- """Calculate the character position of a line in the text."""
154
- position = 0
155
- for i in range(current_index):
156
- position += len(lines[i]) + 1 # +1 for newline
157
- return position
158
-
159
- @classmethod
160
- def _extract_nested_content(
161
- cls, lines: List[str], start_index: int
162
- ) -> Tuple[List[str], int]:
163
- """
164
- Extract nested content with pipe syntax from lines following a collapsible heading.
165
-
166
- Args:
167
- lines: All text lines
168
- start_index: Index to start looking for nested content
169
-
170
- Returns:
171
- Tuple of (nested_content, next_line_index)
172
- """
173
- nested_content = []
174
- current_index = start_index
175
-
176
- while current_index < len(lines):
177
- current_line = lines[current_index]
178
-
179
- # Case 1: Empty line - check if it's followed by pipe content
180
- if not current_line.strip():
181
- if cls._is_next_line_pipe_content(lines, current_index):
182
- nested_content.append("")
183
- current_index += 1
184
- continue
185
-
186
- # Case 2: Pipe content line - part of nested content
187
- pipe_content = cls._extract_pipe_content(current_line)
188
- if pipe_content is not None:
189
- nested_content.append(pipe_content)
190
- current_index += 1
191
- continue
192
-
193
- # Case 3: Another collapsible heading - ends current heading's content
194
- if cls.PATTERN.match(current_line):
195
- break
196
-
197
- # Case 4: Any other line - ends nested content
198
- break
199
-
200
- return nested_content, current_index
201
-
202
- @classmethod
203
- def _is_next_line_pipe_content(cls, lines: List[str], current_index: int) -> bool:
204
- """Check if the next line uses pipe syntax for nested content."""
205
- next_index = current_index + 1
206
- if next_index >= len(lines):
207
- return False
208
- return bool(cls.PIPE_CONTENT_PATTERN.match(lines[next_index]))
209
-
210
- @classmethod
211
- def _extract_pipe_content(cls, line: str) -> Optional[str]:
212
- """Extract content from a line with pipe prefix."""
213
- pipe_match = cls.PIPE_CONTENT_PATTERN.match(line)
214
- if not pipe_match:
73
+ def notion_to_markdown(block: Block) -> Optional[str]:
74
+ """Convert Notion toggleable heading block to markdown collapsible heading."""
75
+ # Only handle heading blocks via BlockType enum
76
+ if block.type not in (
77
+ BlockType.HEADING_1,
78
+ BlockType.HEADING_2,
79
+ BlockType.HEADING_3,
80
+ ):
215
81
  return None
216
- return pipe_match.group(1)
217
-
218
- @staticmethod
219
- def _calculate_block_end_position(
220
- start_position: int, heading_line: str, nested_content: List[str]
221
- ) -> int:
222
- """Calculate the end position of a collapsible heading block including nested content."""
223
- block_length = len(heading_line)
224
- if nested_content:
225
- # Add length of each nested content line plus newline
226
- nested_length = sum(len(line) + 1 for line in nested_content)
227
- block_length += nested_length
228
- return start_position + block_length
229
82
 
230
- @classmethod
231
- def _process_nested_content(
232
- cls,
233
- heading_block: Dict[str, Any],
234
- nested_content: List[str],
235
- processor: Optional[Callable],
236
- ) -> None:
237
- """Process nested content with the provided callback function if available."""
238
- if not (nested_content and processor):
239
- return
83
+ # Determine heading level from enum
84
+ if block.type == BlockType.HEADING_1:
85
+ level = 1
86
+ elif block.type == BlockType.HEADING_2:
87
+ level = 2
88
+ else:
89
+ level = 3
240
90
 
241
- nested_text = "\n".join(nested_content)
242
- nested_blocks = processor(nested_text)
243
-
244
- if nested_blocks:
245
- block_type = heading_block["type"]
246
- heading_block[block_type]["children"] = nested_blocks
91
+ heading_content = getattr(block, block.type.value)
92
+ if not isinstance(heading_content, HeadingBlock):
93
+ return None
247
94
 
248
- @classmethod
249
- def get_llm_prompt_content(cls) -> ElementPromptContent:
250
- """
251
- Returns structured LLM prompt metadata for the collapsible heading element with pipe syntax.
252
- """
253
- return (
254
- ElementPromptBuilder()
255
- .with_description(
256
- "Collapsible headings combine heading structure with toggleable visibility."
257
- )
258
- .with_usage_guidelines(
259
- "Use when you want to create a structured section that can be expanded or collapsed."
260
- )
261
- .with_syntax("+# Collapsible Heading\n| Content with pipe prefix")
262
- .with_examples(
263
- [
264
- "+# Main Collapsible Section\n| Content under the section",
265
- "+## Subsection\n| This content is hidden until expanded",
266
- "+### Detailed Information\n| Technical details go here",
267
- ]
268
- )
269
- .build()
95
+ text = TextInlineFormatter.extract_text_with_formatting(
96
+ heading_content.rich_text
270
97
  )
98
+ prefix = "#" * level
99
+
100
+ return f'+++{prefix} {text or ""}'
@@ -1,43 +1,51 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Optional, List
4
3
  from pydantic import BaseModel
5
- from notionary.blocks.markdown_node import MarkdownNode
4
+
5
+ from notionary.markdown.markdown_node import MarkdownNode
6
6
 
7
7
 
8
8
  class ToggleableHeadingMarkdownBlockParams(BaseModel):
9
9
  text: str
10
- level: int = 1
11
- content: Optional[List[str]] = None
10
+ level: int
11
+ children: list[MarkdownNode]
12
+ model_config = {"arbitrary_types_allowed": True}
12
13
 
13
14
 
14
15
  class ToggleableHeadingMarkdownNode(MarkdownNode):
15
16
  """
16
- Programmatic interface for creating collapsible Markdown headings (toggleable headings).
17
- Pipe-prefixed lines are used for the collapsible content.
18
- Example:
19
- +# Section
20
- | Hidden content
21
- +## Subsection
22
- | Details
17
+ Clean programmatic interface for creating collapsible Markdown headings (toggleable headings)
18
+ with pipe-prefixed nested content using MarkdownNode children.
19
+
20
+ Example syntax for a level-2 toggleable heading:
21
+ +++## Advanced Section
22
+ some content
23
+ +++
23
24
  """
24
25
 
25
- def __init__(self, text: str, level: int = 1, content: Optional[list[str]] = None):
26
+ def __init__(self, text: str, level: int, children: list[MarkdownNode]):
26
27
  if not (1 <= level <= 3):
27
28
  raise ValueError("Only heading levels 1-3 are supported (H1, H2, H3)")
28
29
  self.text = text
29
30
  self.level = level
30
- self.content = content or []
31
+ self.children = children
31
32
 
32
33
  @classmethod
33
34
  def from_params(
34
35
  cls, params: ToggleableHeadingMarkdownBlockParams
35
36
  ) -> ToggleableHeadingMarkdownNode:
36
- return cls(text=params.text, level=params.level, content=params.content)
37
+ return cls(text=params.text, level=params.level, children=params.children)
37
38
 
38
39
  def to_markdown(self) -> str:
39
- prefix = "+" + ("#" * self.level)
40
+ prefix = "+++" + ("#" * self.level)
40
41
  result = f"{prefix} {self.text}"
41
- if self.content:
42
- result += "\n" + "\n".join([f"| {line}" for line in self.content])
43
- return result
42
+
43
+ if not self.children:
44
+ result += "\n+++"
45
+ return result
46
+
47
+ # Convert children to markdown
48
+ content_parts = [child.to_markdown() for child in self.children]
49
+ content_text = "\n\n".join(content_parts)
50
+
51
+ return result + "\n" + content_text + "\n+++"
@@ -0,0 +1,61 @@
1
+ from __future__ import annotations
2
+
3
+ from enum import Enum
4
+
5
+
6
+ class BlockColor(str, Enum):
7
+ BLUE = "blue"
8
+ BLUE_BACKGROUND = "blue_background"
9
+ BROWN = "brown"
10
+ BROWN_BACKGROUND = "brown_background"
11
+ DEFAULT = "default"
12
+ GRAY = "gray"
13
+ GRAY_BACKGROUND = "gray_background"
14
+ GREEN = "green"
15
+ GREEN_BACKGROUND = "green_background"
16
+ ORANGE = "orange"
17
+ ORANGE_BACKGROUND = "orange_background"
18
+ YELLOW = "yellow"
19
+ YELLOW_BACKGROUND = "yellow_background"
20
+ PINK = "pink"
21
+ PINK_BACKGROUND = "pink_background"
22
+ PURPLE = "purple"
23
+ PURPLE_BACKGROUND = "purple_background"
24
+ RED = "red"
25
+ RED_BACKGROUND = "red_background"
26
+ DEFAULT_BACKGROUND = "default_background"
27
+
28
+
29
+ class BlockType(str, Enum):
30
+ BOOKMARK = "bookmark"
31
+ BREADCRUMB = "breadcrumb"
32
+ BULLETED_LIST_ITEM = "bulleted_list_item"
33
+ CALLOUT = "callout"
34
+ CHILD_DATABASE = "child_database"
35
+ CHILD_PAGE = "child_page"
36
+ COLUMN = "column"
37
+ COLUMN_LIST = "column_list"
38
+ CODE = "code"
39
+ DIVIDER = "divider"
40
+ EMBED = "embed"
41
+ EQUATION = "equation"
42
+ FILE = "file"
43
+ HEADING_1 = "heading_1"
44
+ HEADING_2 = "heading_2"
45
+ HEADING_3 = "heading_3"
46
+ IMAGE = "image"
47
+ LINK_PREVIEW = "link_preview"
48
+ LINK_TO_PAGE = "link_to_page"
49
+ NUMBERED_LIST_ITEM = "numbered_list_item"
50
+ PARAGRAPH = "paragraph"
51
+ PDF = "pdf"
52
+ QUOTE = "quote"
53
+ SYNCED_BLOCK = "synced_block"
54
+ TABLE = "table"
55
+ TABLE_OF_CONTENTS = "table_of_contents"
56
+ TABLE_ROW = "table_row"
57
+ TO_DO = "to_do"
58
+ TOGGLE = "toggle"
59
+ UNSUPPORTED = "unsupported"
60
+ VIDEO = "video"
61
+ AUDIO = "audio"
@@ -1,7 +1,13 @@
1
- from .video_element import VideoElement
2
- from .video_markdown_node import VideoMarkdownNode
1
+ from notionary.blocks.video.video_element import VideoElement
2
+ from notionary.blocks.video.video_element_models import CreateVideoBlock
3
+ from notionary.blocks.video.video_markdown_node import (
4
+ VideoMarkdownBlockParams,
5
+ VideoMarkdownNode,
6
+ )
3
7
 
4
8
  __all__ = [
5
9
  "VideoElement",
10
+ "CreateVideoBlock",
6
11
  "VideoMarkdownNode",
12
+ "VideoMarkdownBlockParams",
7
13
  ]