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,303 +1,96 @@
1
+ from __future__ import annotations
2
+
1
3
  import re
2
- from typing import Dict, Any, Optional, List, Tuple, Callable
4
+ from typing import Optional
3
5
 
4
- from notionary.blocks import (
5
- NotionBlockElement,
6
- NotionBlockResult,
7
- ElementPromptContent,
8
- ElementPromptBuilder,
9
- )
6
+ from notionary.blocks.base_block_element import BaseBlockElement
7
+ from notionary.blocks.models import Block, BlockCreateResult, BlockType
8
+ from notionary.blocks.rich_text.rich_text_models import RichTextObject
9
+ from notionary.blocks.rich_text.text_inline_formatter import TextInlineFormatter
10
+ from notionary.blocks.toggle.toggle_models import CreateToggleBlock, ToggleBlock
11
+ from notionary.blocks.types import BlockColor
10
12
 
11
13
 
12
- class ToggleElement(NotionBlockElement):
14
+ class ToggleElement(BaseBlockElement):
13
15
  """
14
- Improved ToggleElement class using pipe syntax instead of indentation.
16
+ Simplified ToggleElement class that works with the stack-based converter.
17
+ Children are automatically handled by the StackBasedMarkdownConverter.
15
18
  """
16
19
 
17
- TOGGLE_PATTERN = re.compile(r"^[+]{3}\s+(.+)$")
18
- PIPE_CONTENT_PATTERN = re.compile(r"^\|\s?(.*)$")
19
-
20
- TRANSCRIPT_TOGGLE_PATTERN = re.compile(r"^[+]{3}\s+Transcript$")
21
-
22
- @classmethod
23
- def match_markdown(cls, text: str) -> bool:
24
- """Check if the text is a markdown toggle."""
25
- return bool(ToggleElement.TOGGLE_PATTERN.match(text.strip()))
20
+ # Updated pattern for ultra-simplified +++ Title syntax (no quotes!)
21
+ TOGGLE_PATTERN = re.compile(r"^[+]{3}\s+(.+)$", re.IGNORECASE)
22
+ TRANSCRIPT_TOGGLE_PATTERN = re.compile(r"^[+]{3}\s+Transcript$", re.IGNORECASE)
26
23
 
27
24
  @classmethod
28
- def match_notion(cls, block: Dict[str, Any]) -> bool:
25
+ def match_notion(cls, block: Block) -> bool:
29
26
  """Check if the block is a Notion toggle block."""
30
- return block.get("type") == "toggle"
31
-
32
- @classmethod
33
- def markdown_to_notion(cls, text: str) -> NotionBlockResult:
34
- """Convert markdown toggle line to Notion toggle block."""
35
- toggle_match = ToggleElement.TOGGLE_PATTERN.match(text.strip())
36
- if not toggle_match:
37
- return None
38
-
39
- # Extract toggle title
40
- title = toggle_match.group(1)
41
-
42
- return {
43
- "type": "toggle",
44
- "toggle": {
45
- "rich_text": [{"type": "text", "text": {"content": title}}],
46
- "color": "default",
47
- "children": [],
48
- },
49
- }
27
+ return block.type == BlockType.TOGGLE
50
28
 
51
29
  @classmethod
52
- def extract_nested_content(
53
- cls, lines: List[str], start_index: int
54
- ) -> Tuple[List[str], int]:
30
+ def markdown_to_notion(cls, text: str) -> BlockCreateResult:
55
31
  """
56
- Extracts the nested content lines of a toggle block using pipe syntax.
57
-
58
- Args:
59
- lines: All lines of text.
60
- start_index: Starting index to look for nested content.
61
-
62
- Returns:
63
- Tuple of (nested_content_lines, next_line_index)
32
+ Convert markdown toggle line to Notion ToggleBlock.
33
+ Children are automatically handled by the StackBasedMarkdownConverter.
64
34
  """
65
- nested_content = []
66
- current_index = start_index
67
-
68
- while current_index < len(lines):
69
- current_line = lines[current_index]
70
-
71
- # Case 1: Empty line - could be part of the content if next line is a pipe line
72
- if not current_line.strip():
73
- if ToggleElement.is_next_line_pipe_content(lines, current_index):
74
- nested_content.append("")
75
- current_index += 1
76
- continue
77
- else:
78
- # Empty line not followed by pipe ends the block
79
- break
80
-
81
- # Case 2: Pipe-prefixed line - part of the nested content
82
- pipe_content = ToggleElement.extract_pipe_content(current_line)
83
- if pipe_content is not None:
84
- nested_content.append(pipe_content)
85
- current_index += 1
86
- continue
87
-
88
- # Case 3: Regular line - end of nested content
89
- break
35
+ if not (match := cls.TOGGLE_PATTERN.match(text.strip())):
36
+ return None
90
37
 
91
- return nested_content, current_index
38
+ title = match.group(1).strip()
39
+ rich_text = TextInlineFormatter.parse_inline_formatting(title)
92
40
 
93
- @classmethod
94
- def is_next_line_pipe_content(cls, lines: List[str], current_index: int) -> bool:
95
- """Checks if the next line starts with a pipe prefix."""
96
- next_index = current_index + 1
97
- return (
98
- next_index < len(lines)
99
- and ToggleElement.PIPE_CONTENT_PATTERN.match(lines[next_index]) is not None
41
+ # Create toggle block with empty children - they will be populated automatically
42
+ toggle_content = ToggleBlock(
43
+ rich_text=rich_text, color=BlockColor.DEFAULT, children=[]
100
44
  )
101
45
 
102
- @classmethod
103
- def extract_pipe_content(cls, line: str) -> Optional[str]:
104
- """
105
- Extracts content from a line with pipe prefix.
106
-
107
- Returns:
108
- The content without the pipe, or None if not a pipe-prefixed line.
109
- """
110
- pipe_match = ToggleElement.PIPE_CONTENT_PATTERN.match(line)
111
- if pipe_match:
112
- return pipe_match.group(1)
113
- return None
46
+ return CreateToggleBlock(toggle=toggle_content)
114
47
 
115
48
  @classmethod
116
- def notion_to_markdown(cls, block: Dict[str, Any]) -> Optional[str]:
49
+ def notion_to_markdown(cls, block: Block) -> Optional[str]:
117
50
  """
118
- Converts a Notion toggle block into markdown using pipe-prefixed lines.
51
+ Converts a Notion toggle block into markdown using the ultra-simplified +++ syntax.
119
52
  """
120
- if block.get("type") != "toggle":
53
+ if block.type != BlockType.TOGGLE:
54
+ return None
55
+
56
+ if not block.toggle:
121
57
  return None
122
58
 
123
- toggle_data = block.get("toggle", {})
59
+ toggle_data = block.toggle
124
60
 
125
61
  # Extract title from rich_text
126
- title = ToggleElement._extract_text_content(toggle_data.get("rich_text", []))
62
+ title = cls._extract_text_content(toggle_data.rich_text or [])
127
63
 
128
- # Create toggle line
64
+ # Create toggle line with ultra-simplified syntax (no quotes!)
129
65
  toggle_line = f"+++ {title}"
130
66
 
131
67
  # Process children if available
132
- children = toggle_data.get("children", [])
68
+ children = toggle_data.children or []
133
69
  if not children:
134
- return toggle_line
70
+ return toggle_line + "\n+++"
135
71
 
136
- # Add a placeholder line for each child using pipe syntax
137
- child_lines = ["| [Nested content]" for _ in children]
72
+ # Add a placeholder line for each child
73
+ child_lines = ["[Nested content]" for _ in children]
138
74
 
139
- return toggle_line + "\n" + "\n".join(child_lines)
75
+ return toggle_line + "\n" + "\n".join(child_lines) + "\n+++"
140
76
 
141
77
  @classmethod
142
- def is_multiline(cls) -> bool:
143
- """Toggle blocks can span multiple lines due to nested content."""
144
- return True
145
-
146
- @classmethod
147
- def _extract_text_content(cls, rich_text: List[Dict[str, Any]]) -> str:
78
+ def _extract_text_content(cls, rich_text: list[RichTextObject]) -> str:
148
79
  """Extracts plain text content from Notion rich_text blocks."""
149
80
  result = ""
150
81
  for text_obj in rich_text:
151
- if text_obj.get("type") == "text":
152
- result += text_obj.get("text", {}).get("content", "")
153
- elif "plain_text" in text_obj:
154
- result += text_obj.get("plain_text", "")
155
- return result
156
-
157
- @classmethod
158
- def find_matches(
159
- cls,
160
- text: str,
161
- process_nested_content: Callable = None,
162
- context_aware: bool = True,
163
- ) -> List[Tuple[int, int, Dict[str, Any]]]:
164
- """
165
- Finds all toggle elements in markdown using pipe syntax for nested content.
166
-
167
- Args:
168
- text: The markdown input.
169
- process_nested_content: Optional function to parse nested content into blocks.
170
- context_aware: Whether to skip contextually irrelevant transcript toggles.
171
-
172
- Returns:
173
- List of (start_pos, end_pos, block) tuples.
174
- """
175
- if not text:
176
- return []
177
-
178
- toggle_blocks = []
179
- lines = text.split("\n")
180
- current_line_index = 0
181
-
182
- while current_line_index < len(lines):
183
- current_line = lines[current_line_index]
184
-
185
- # Check if the current line is a toggle
186
- if not cls._is_toggle_line(current_line):
187
- current_line_index += 1
188
- continue
189
-
190
- # Skip transcript toggles if required by context
191
- if cls._should_skip_transcript_toggle(
192
- current_line, lines, current_line_index, context_aware
82
+ if hasattr(text_obj, "plain_text"):
83
+ result += text_obj.plain_text or ""
84
+ elif (
85
+ hasattr(text_obj, "type")
86
+ and text_obj.type == "text"
87
+ and hasattr(text_obj, "text")
193
88
  ):
194
- current_line_index += 1
195
- continue
196
-
197
- # Create toggle block and determine character positions
198
- start_position = cls._calculate_start_position(lines, current_line_index)
199
- toggle_block = cls.markdown_to_notion(current_line)
200
-
201
- if not toggle_block:
202
- current_line_index += 1
203
- continue
204
-
205
- # Extract nested content
206
- nested_content, next_line_index = cls.extract_nested_content(
207
- lines, current_line_index + 1
208
- )
209
- end_position = cls._calculate_end_position(
210
- start_position, current_line, nested_content
211
- )
212
-
213
- # Process nested content if needed
214
- cls._process_nested_content_if_needed(
215
- nested_content, process_nested_content, toggle_block
216
- )
217
-
218
- # Save result
219
- toggle_blocks.append((start_position, end_position, toggle_block))
220
- current_line_index = next_line_index
221
-
222
- return toggle_blocks
223
-
224
- @classmethod
225
- def _is_toggle_line(cls, line: str) -> bool:
226
- """Checks whether the given line is a markdown toggle."""
227
- return bool(ToggleElement.TOGGLE_PATTERN.match(line.strip()))
228
-
229
- @classmethod
230
- def _should_skip_transcript_toggle(
231
- cls, line: str, lines: List[str], current_index: int, context_aware: bool
232
- ) -> bool:
233
- """Determines if a transcript toggle should be skipped based on the surrounding context."""
234
- is_transcript_toggle = cls.TRANSCRIPT_TOGGLE_PATTERN.match(line.strip())
235
-
236
- if not (context_aware and is_transcript_toggle):
237
- return False
238
-
239
- # Only keep transcript toggles that follow a list item
240
- has_list_item_before = current_index > 0 and lines[
241
- current_index - 1
242
- ].strip().startswith("- ")
243
- return not has_list_item_before
244
-
245
- @classmethod
246
- def _calculate_start_position(cls, lines: List[str], current_index: int) -> int:
247
- """Calculates the character start position of a line within the full text."""
248
- start_pos = 0
249
- for index in range(current_index):
250
- start_pos += len(lines[index]) + 1 # +1 for line break
251
- return start_pos
252
-
253
- @classmethod
254
- def _calculate_end_position(
255
- cls, start_pos: int, current_line: str, nested_content: List[str]
256
- ) -> int:
257
- """Calculates the end position of a toggle block including nested lines."""
258
- line_length = len(current_line)
259
- nested_content_length = sum(
260
- len(line) + 1 for line in nested_content
261
- ) # +1 for each line break
262
- return start_pos + line_length + nested_content_length
263
-
264
- @classmethod
265
- def _process_nested_content_if_needed(
266
- cls,
267
- nested_content: List[str],
268
- process_function: Optional[Callable],
269
- toggle_block: Dict[str, Any],
270
- ) -> None:
271
- """Processes nested content using the provided function if applicable."""
272
- if not (nested_content and process_function):
273
- return
274
-
275
- nested_text = "\n".join(nested_content)
276
- nested_blocks = process_function(nested_text)
277
-
278
- if nested_blocks:
279
- toggle_block["toggle"]["children"] = nested_blocks
280
-
281
- @classmethod
282
- def get_llm_prompt_content(cls) -> ElementPromptContent:
283
- """
284
- Returns structured LLM prompt metadata for the toggle element with pipe syntax examples.
285
- """
286
- return (
287
- ElementPromptBuilder()
288
- .with_description(
289
- "Toggle elements are collapsible sections that help organize and hide detailed information."
290
- )
291
- .with_usage_guidelines(
292
- "Use toggles for supplementary information that's not essential for the first reading, "
293
- "such as details, examples, or technical information."
294
- )
295
- .with_syntax("+++ Toggle Title\n| Toggle content with pipe prefix")
296
- .with_examples(
297
- [
298
- "+++ Key Findings\n| The research demonstrates **three main conclusions**:\n| 1. First important point\n| 2. Second important point",
299
- "+++ FAQ\n| **Q: When should I use toggles?**\n| *A: Use toggles for supplementary information.*",
300
- ]
301
- )
302
- .build()
303
- )
89
+ result += text_obj.text.content or ""
90
+ # Fallback for dict-style access (backward compatibility)
91
+ elif isinstance(text_obj, dict):
92
+ if text_obj.get("type") == "text":
93
+ result += text_obj.get("text", {}).get("content", "")
94
+ elif "plain_text" in text_obj:
95
+ result += text_obj.get("plain_text", "")
96
+ return result
@@ -1,35 +1,45 @@
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 ToggleMarkdownBlockParams(BaseModel):
9
9
  title: str
10
- content: Optional[List[str]] = None
10
+ children: list[MarkdownNode]
11
+ model_config = {"arbitrary_types_allowed": True}
11
12
 
12
13
 
13
14
  class ToggleMarkdownNode(MarkdownNode):
14
15
  """
15
- Programmatic interface for creating Notion-style Markdown toggle blocks
16
- with pipe-prefixed nested content.
16
+ Clean programmatic interface for creating Notion-style Markdown toggle blocks
17
+ with the simplified +++ "Title" syntax.
18
+
17
19
  Example:
18
- +++ Details
19
- | Here are the details.
20
- | You can add more lines.
20
+ +++ "Advanced Details"
21
+ Content here
22
+ More content
23
+ +++
21
24
  """
22
25
 
23
- def __init__(self, title: str, content: Optional[List[str]] = None):
26
+ def __init__(self, title: str, children: list[MarkdownNode]):
24
27
  self.title = title
25
- self.content = content or []
28
+ self.children = children
26
29
 
27
30
  @classmethod
28
31
  def from_params(cls, params: ToggleMarkdownBlockParams) -> ToggleMarkdownNode:
29
- return cls(title=params.title, content=params.content)
32
+ return cls(title=params.title, children=params.children)
30
33
 
31
34
  def to_markdown(self) -> str:
32
35
  result = f"+++ {self.title}"
33
- if self.content:
34
- result += "\n" + "\n".join([f"| {line}" for line in self.content])
35
- return result
36
+
37
+ if not self.children:
38
+ result += "\n+++"
39
+ return result
40
+
41
+ # Convert children to markdown
42
+ content_parts = [child.to_markdown() for child in self.children]
43
+ content_text = "\n\n".join(content_parts)
44
+
45
+ return result + "\n" + content_text + "\n+++"
@@ -0,0 +1,17 @@
1
+ from pydantic import BaseModel, Field
2
+ from typing_extensions import Literal
3
+
4
+ from notionary.blocks.models import Block
5
+ from notionary.blocks.rich_text.rich_text_models import RichTextObject
6
+ from notionary.blocks.types import BlockColor
7
+
8
+
9
+ class ToggleBlock(BaseModel):
10
+ rich_text: list[RichTextObject]
11
+ color: BlockColor = BlockColor.DEFAULT
12
+ children: list[Block] = Field(default_factory=list)
13
+
14
+
15
+ class CreateToggleBlock(BaseModel):
16
+ type: Literal["toggle"] = "toggle"
17
+ toggle: ToggleBlock
@@ -1,9 +1,13 @@
1
- from .toggleable_heading_element import ToggleableHeadingElement
2
- from .toggleable_heading_markdown_node import (
1
+ from notionary.blocks.toggleable_heading.toggleable_heading_element import (
2
+ ToggleableHeadingElement,
3
+ )
4
+ from notionary.blocks.toggleable_heading.toggleable_heading_markdown_node import (
5
+ ToggleableHeadingMarkdownBlockParams,
3
6
  ToggleableHeadingMarkdownNode,
4
7
  )
5
8
 
6
9
  __all__ = [
7
10
  "ToggleableHeadingElement",
8
11
  "ToggleableHeadingMarkdownNode",
12
+ "ToggleableHeadingMarkdownBlockParams",
9
13
  ]