notionary 0.2.19__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 (205) 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.19.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 -713
  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/elements/__init__.py +0 -0
  188. notionary/models/notion_block_response.py +0 -264
  189. notionary/models/notion_page_response.py +0 -78
  190. notionary/models/search_response.py +0 -0
  191. notionary/page/__init__.py +0 -0
  192. notionary/page/content/notion_text_length_utils.py +0 -87
  193. notionary/page/content/page_content_retriever.py +0 -60
  194. notionary/page/formatting/line_processor.py +0 -153
  195. notionary/page/formatting/markdown_to_notion_converter.py +0 -153
  196. notionary/page/markdown_syntax_prompt_generator.py +0 -114
  197. notionary/page/notion_to_markdown_converter.py +0 -179
  198. notionary/page/properites/property_value_extractor.py +0 -0
  199. notionary-0.2.19.dist-info/RECORD +0 -150
  200. /notionary/{blocks/document/document_models.py → markdown/___init__.py} +0 -0
  201. /notionary/{blocks/image/image_models.py → markdown/makdown_document_model.py} +0 -0
  202. /notionary/page/{content/markdown_whitespace_processor.py → markdown_whitespace_processor.py} +0 -0
  203. /notionary/{blocks/mention/mention_models.py → page/reader/handler/context.py} +0 -0
  204. {notionary-0.2.19.dist-info → notionary-0.2.21.dist-info}/LICENSE +0 -0
  205. {notionary-0.2.19.dist-info → notionary-0.2.21.dist-info}/WHEEL +0 -0
@@ -1,37 +0,0 @@
1
- from typing import Optional, Any, TypeAlias, Union
2
- from abc import ABC
3
-
4
- from notionary.blocks.prompts.element_prompt_content import ElementPromptContent
5
-
6
- NotionBlock: TypeAlias = dict[str, Any]
7
- NotionBlockResult: TypeAlias = Optional[Union[list[dict[str, Any]], dict[str, Any]]]
8
-
9
-
10
- class NotionBlockElement(ABC):
11
- """Base class for elements that can be converted between Markdown and Notion."""
12
-
13
- @classmethod
14
- def markdown_to_notion(cls, text: str) -> NotionBlockResult:
15
- """Convert markdown to Notion blocks (can return multiple blocks or single block)."""
16
-
17
- @classmethod
18
- def notion_to_markdown(cls, block: dict[str, any]) -> Optional[str]:
19
- """Convert Notion block to markdown."""
20
-
21
- @classmethod
22
- def match_markdown(cls, text: str) -> bool:
23
- """Check if this element can handle the given markdown text."""
24
- return bool(cls.markdown_to_notion(text)) # Now calls the class's version
25
-
26
- @classmethod
27
- def match_notion(cls, block: dict[str, any]) -> bool:
28
- """Check if this element can handle the given Notion block."""
29
- return bool(cls.notion_to_markdown(block)) # Now calls the class's version
30
-
31
- @classmethod
32
- def is_multiline(cls) -> bool:
33
- return False
34
-
35
- @classmethod
36
- def get_llm_prompt_content(cls) -> ElementPromptContent:
37
- """Returns a dictionary with information for LLM prompts about this element."""
@@ -1,262 +0,0 @@
1
- from typing import Any
2
- import re
3
-
4
- from notionary.blocks import ElementPromptBuilder, ElementPromptContent
5
-
6
-
7
- class TextInlineFormatter:
8
- """
9
- Handles conversion between Markdown inline formatting and Notion rich text elements.
10
-
11
- Supports various formatting options:
12
- - Bold: **text**
13
- - Italic: *text* or _text_
14
- - Underline: __text__
15
- - Strikethrough: ~~text~~
16
- - Code: `text`
17
- - Links: [text](url)
18
- """
19
-
20
- # Format patterns for matching Markdown formatting
21
- FORMAT_PATTERNS = [
22
- (r"\*\*(.+?)\*\*", {"bold": True}),
23
- (r"\*(.+?)\*", {"italic": True}),
24
- (r"_(.+?)_", {"italic": True}),
25
- (r"__(.+?)__", {"underline": True}),
26
- (r"~~(.+?)~~", {"strikethrough": True}),
27
- (r"`(.+?)`", {"code": True}),
28
- (r"\[(.+?)\]\((.+?)\)", {"link": True}),
29
- (r"@\[([0-9a-f-]+)\]", {"mention": True}),
30
- ]
31
-
32
- @classmethod
33
- def parse_inline_formatting(cls, text: str) -> list[dict[str, Any]]:
34
- """
35
- Parse inline text formatting into Notion rich_text format.
36
-
37
- Args:
38
- text: Markdown text with inline formatting
39
-
40
- Returns:
41
- list of Notion rich_text objects
42
- """
43
- if not text:
44
- return []
45
-
46
- return cls._split_text_into_segments(text, cls.FORMAT_PATTERNS)
47
-
48
- @classmethod
49
- def _split_text_into_segments(
50
- cls, text: str, format_patterns: list[tuple]
51
- ) -> list[dict[str, Any]]:
52
- """
53
- Split text into segments by formatting markers and convert to Notion rich_text format.
54
-
55
- Args:
56
- text: Text to split
57
- format_patterns: list of (regex pattern, formatting dict) tuples
58
-
59
- Returns:
60
- list of Notion rich_text objects
61
- """
62
- segments = []
63
- remaining_text = text
64
-
65
- while remaining_text:
66
- earliest_match = None
67
- earliest_format = None
68
- earliest_pos = len(remaining_text)
69
-
70
- # Find the earliest formatting marker
71
- for pattern, formatting in format_patterns:
72
- match = re.search(pattern, remaining_text)
73
- if match and match.start() < earliest_pos:
74
- earliest_match = match
75
- earliest_format = formatting
76
- earliest_pos = match.start()
77
-
78
- if earliest_match is None:
79
- if remaining_text:
80
- segments.append(cls._create_text_element(remaining_text, {}))
81
- break
82
-
83
- if earliest_pos > 0:
84
- segments.append(
85
- cls._create_text_element(remaining_text[:earliest_pos], {})
86
- )
87
-
88
- if "link" in earliest_format:
89
- content = earliest_match.group(1)
90
- url = earliest_match.group(2)
91
- segments.append(cls._create_link_element(content, url))
92
-
93
- elif "mention" in earliest_format:
94
- id = earliest_match.group(1)
95
- segments.append(cls._create_mention_element(id))
96
-
97
- else:
98
- content = earliest_match.group(1)
99
- segments.append(cls._create_text_element(content, earliest_format))
100
-
101
- # Move past the processed segment
102
- remaining_text = remaining_text[
103
- earliest_pos + len(earliest_match.group(0)) :
104
- ]
105
-
106
- return segments
107
-
108
- @classmethod
109
- def _create_text_element(
110
- cls, text: str, formatting: dict[str, Any]
111
- ) -> dict[str, Any]:
112
- """
113
- Create a Notion text element with formatting.
114
-
115
- Args:
116
- text: The text content
117
- formatting: Dictionary of formatting options
118
-
119
- Returns:
120
- Notion rich_text element
121
- """
122
- annotations = cls._default_annotations()
123
-
124
- # Apply formatting
125
- for key, value in formatting.items():
126
- if key == "color":
127
- annotations["color"] = value
128
- elif key in annotations:
129
- annotations[key] = value
130
-
131
- return {
132
- "type": "text",
133
- "text": {"content": text},
134
- "annotations": annotations,
135
- "plain_text": text,
136
- }
137
-
138
- @classmethod
139
- def _create_link_element(cls, text: str, url: str) -> dict[str, Any]:
140
- """
141
- Create a Notion link element.
142
-
143
- Args:
144
- text: The link text
145
- url: The URL
146
-
147
- Returns:
148
- Notion rich_text element with link
149
- """
150
- return {
151
- "type": "text",
152
- "text": {"content": text, "link": {"url": url}},
153
- "annotations": cls._default_annotations(),
154
- "plain_text": text,
155
- }
156
-
157
- @classmethod
158
- def _create_mention_element(cls, id: str) -> dict[str, Any]:
159
- """
160
- Create a Notion mention element.
161
-
162
- Args:
163
- id: The page ID
164
-
165
- Returns:
166
- Notion rich_text element with mention
167
- """
168
- return {
169
- "type": "mention",
170
- "mention": {"type": "page", "page": {"id": id}},
171
- "annotations": cls._default_annotations(),
172
- }
173
-
174
- @classmethod
175
- def extract_text_with_formatting(cls, rich_text: list[dict[str, Any]]) -> str:
176
- """
177
- Convert Notion rich_text elements back to Markdown formatted text.
178
-
179
- Args:
180
- rich_text: list of Notion rich_text elements
181
-
182
- Returns:
183
- Markdown formatted text
184
- """
185
- formatted_parts = []
186
-
187
- for text_obj in rich_text:
188
- # Fallback: If plain_text is missing, use text['content']
189
- content = text_obj.get("plain_text")
190
- if content is None:
191
- content = text_obj.get("text", {}).get("content", "")
192
-
193
- annotations = text_obj.get("annotations", {})
194
-
195
- if annotations.get("code", False):
196
- content = f"`{content}`"
197
- if annotations.get("strikethrough", False):
198
- content = f"~~{content}~~"
199
- if annotations.get("underline", False):
200
- content = f"__{content}__"
201
- if annotations.get("italic", False):
202
- content = f"*{content}*"
203
- if annotations.get("bold", False):
204
- content = f"**{content}**"
205
-
206
- text_data = text_obj.get("text", {})
207
- link_data = text_data.get("link")
208
- if link_data:
209
- url = link_data.get("url", "")
210
- content = f"[{content}]({url})"
211
-
212
- formatted_parts.append(content)
213
-
214
- return "".join(formatted_parts)
215
-
216
- @classmethod
217
- def _default_annotations(cls) -> dict[str, bool]:
218
- """
219
- Create default annotations object.
220
-
221
- Returns:
222
- Default Notion text annotations
223
- """
224
- return {
225
- "bold": False,
226
- "italic": False,
227
- "strikethrough": False,
228
- "underline": False,
229
- "code": False,
230
- "color": "default",
231
- }
232
-
233
- @classmethod
234
- def get_llm_prompt_content(cls) -> ElementPromptContent:
235
- """
236
- Returns structured LLM prompt metadata for inline formatting.
237
- """
238
- return (
239
- ElementPromptBuilder()
240
- .with_description(
241
- "Inline formatting can be used within most block types to style your text. You can combine multiple formatting options."
242
- )
243
- .with_usage_guidelines(
244
- "Use inline formatting to highlight important words, provide emphasis, show code or paths, or add hyperlinks. "
245
- "This helps create visual hierarchy and improves readability."
246
- )
247
- .with_syntax(
248
- "**bold**, *italic*, `code`, ~~strikethrough~~, __underline__, [text](url)"
249
- )
250
- .with_examples(
251
- [
252
- "This text has a **bold** word.",
253
- "This text has an *italic* word.",
254
- "This text has `code` formatting.",
255
- "This text has ~~strikethrough~~ formatting.",
256
- "This text has __underlined__ formatting.",
257
- "This has a [hyperlink](https://example.com).",
258
- "You can **combine *different* formatting** styles.",
259
- ]
260
- )
261
- .build()
262
- )
@@ -1,139 +0,0 @@
1
- from typing import Optional
2
- import re
3
-
4
- # TODO: Use this inline formatting here
5
- from notionary.blocks.shared.models import (
6
- MentionRichText,
7
- RichTextObject,
8
- TextAnnotations,
9
- TextContent,
10
- )
11
-
12
- FORMAT_PATTERNS = [
13
- (r"\*\*(.+?)\*\*", {"bold": True}),
14
- (r"\*(.+?)\*", {"italic": True}),
15
- (r"_(.+?)_", {"italic": True}),
16
- (r"__(.+?)__", {"underline": True}),
17
- (r"~~(.+?)~~", {"strikethrough": True}),
18
- (r"`(.+?)`", {"code": True}),
19
- (r"\[(.+?)\]\((.+?)\)", {"link": True}),
20
- (r"@\[([0-9a-f-]+)\]", {"mention": True}),
21
- ]
22
-
23
-
24
- def parse_inline_formatting(text: str) -> list[dict[str, any]]:
25
- """Parse inline text formatting into Notion rich_text format."""
26
- if not text:
27
- return []
28
-
29
- return _split_text_into_segments(text)
30
-
31
-
32
- def _split_text_into_segments(text: str) -> list[dict[str, any]]:
33
- """Split text into segments by formatting markers."""
34
- segments = []
35
- remaining_text = text
36
-
37
- while remaining_text:
38
- match_info = _find_earliest_match(remaining_text)
39
-
40
- # No more formatting found - add remaining text and exit
41
- if not match_info:
42
- segments.append(_create_plain_text(remaining_text))
43
- break
44
-
45
- match, formatting, pos = match_info
46
-
47
- # Add text before match if exists
48
- if pos > 0:
49
- segments.append(_create_plain_text(remaining_text[:pos]))
50
-
51
- # Add formatted segment
52
- segments.append(_create_formatted_segment(match, formatting))
53
-
54
- # Update remaining text
55
- remaining_text = remaining_text[pos + len(match.group(0)) :]
56
-
57
- return segments
58
-
59
-
60
- def _find_earliest_match(text: str) -> Optional[tuple]:
61
- """Find the earliest formatting match in text."""
62
- earliest_match = None
63
- earliest_format = None
64
- earliest_pos = len(text)
65
-
66
- for pattern, formatting in FORMAT_PATTERNS:
67
- match = re.search(pattern, text)
68
- if match and match.start() < earliest_pos:
69
- earliest_match = match
70
- earliest_format = formatting
71
- earliest_pos = match.start()
72
-
73
- return (earliest_match, earliest_format, earliest_pos) if earliest_match else None
74
-
75
-
76
- def _create_formatted_segment(match: re.Match, formatting: dict) -> dict[str, any]:
77
- """Create a formatted segment based on match and formatting."""
78
- if "link" in formatting:
79
- return _create_link_text(match.group(1), match.group(2))
80
- elif "mention" in formatting:
81
- return _create_mention_text(match.group(1))
82
- else:
83
- return _create_formatted_text(match.group(1), **formatting)
84
-
85
-
86
- def _create_plain_text(content: str) -> dict[str, any]:
87
- """Create plain text rich text object."""
88
- return RichTextObject.from_plain_text(content).model_dump()
89
-
90
-
91
- def _create_formatted_text(content: str, **formatting) -> dict[str, any]:
92
- """Create formatted text rich text object."""
93
- return RichTextObject.from_plain_text(content, **formatting).model_dump()
94
-
95
-
96
- def _create_link_text(content: str, url: str) -> dict[str, any]:
97
- """Create link text rich text object."""
98
- text_content = TextContent(content=content, link=url)
99
- annotations = TextAnnotations()
100
-
101
- rich_text = RichTextObject(
102
- text=text_content, annotations=annotations, plain_text=content, href=url
103
- )
104
- return rich_text.model_dump()
105
-
106
-
107
- def _create_mention_text(page_id: str) -> dict[str, any]:
108
- """Create mention rich text object."""
109
- return MentionRichText.from_page_id(page_id).model_dump()
110
-
111
-
112
- def extract_text_with_formatting(rich_text: list[dict[str, any]]) -> str:
113
- """Convert Notion rich_text elements back to Markdown."""
114
- return "".join(_rich_text_to_markdown(item) for item in rich_text)
115
-
116
-
117
- def _rich_text_to_markdown(text_obj: dict[str, any]) -> str:
118
- """Convert single rich text object to markdown."""
119
- content = text_obj.get("plain_text", text_obj.get("text", {}).get("content", ""))
120
- annotations = text_obj.get("annotations", {})
121
-
122
- # Apply formatting in reverse order
123
- if annotations.get("code", False):
124
- content = f"`{content}`"
125
- if annotations.get("strikethrough", False):
126
- content = f"~~{content}~~"
127
- if annotations.get("underline", False):
128
- content = f"__{content}__"
129
- if annotations.get("italic", False):
130
- content = f"*{content}*"
131
- if annotations.get("bold", False):
132
- content = f"**{content}**"
133
-
134
- # Handle links
135
- link_data = text_obj.get("text", {}).get("link")
136
- if link_data and link_data.get("url"):
137
- content = f"[{content}]({link_data['url']})"
138
-
139
- return content
@@ -1,10 +0,0 @@
1
- from typing import Optional, TypedDict
2
-
3
-
4
- class PageResult(TypedDict, total=False):
5
- """Type definition for page operation results."""
6
-
7
- success: bool
8
- page_id: str
9
- url: Optional[str]
10
- message: Optional[str]
File without changes