notionary 0.2.17__py3-none-any.whl → 0.2.19__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 (113) hide show
  1. notionary/__init__.py +3 -2
  2. notionary/blocks/__init__.py +54 -25
  3. notionary/blocks/audio/__init__.py +7 -0
  4. notionary/blocks/audio/audio_element.py +152 -0
  5. notionary/blocks/audio/audio_markdown_node.py +29 -0
  6. notionary/blocks/audio/audio_models.py +59 -0
  7. notionary/blocks/bookmark/__init__.py +7 -0
  8. notionary/blocks/{bookmark_element.py → bookmark/bookmark_element.py} +20 -65
  9. notionary/blocks/bookmark/bookmark_markdown_node.py +43 -0
  10. notionary/blocks/bookmark/bookmark_models.py +0 -0
  11. notionary/blocks/bulleted_list/__init__.py +7 -0
  12. notionary/blocks/{bulleted_list_element.py → bulleted_list/bulleted_list_element.py} +7 -3
  13. notionary/blocks/bulleted_list/bulleted_list_markdown_node.py +33 -0
  14. notionary/blocks/bulleted_list/bulleted_list_models.py +0 -0
  15. notionary/blocks/callout/__init__.py +7 -0
  16. notionary/blocks/callout/callout_element.py +132 -0
  17. notionary/blocks/callout/callout_markdown_node.py +31 -0
  18. notionary/blocks/callout/callout_models.py +0 -0
  19. notionary/blocks/code/__init__.py +7 -0
  20. notionary/blocks/{code_block_element.py → code/code_element.py} +72 -40
  21. notionary/blocks/code/code_markdown_node.py +43 -0
  22. notionary/blocks/code/code_models.py +0 -0
  23. notionary/blocks/column/__init__.py +5 -0
  24. notionary/blocks/{column_element.py → column/column_element.py} +24 -55
  25. notionary/blocks/column/column_models.py +0 -0
  26. notionary/blocks/divider/__init__.py +7 -0
  27. notionary/blocks/{divider_element.py → divider/divider_element.py} +11 -3
  28. notionary/blocks/divider/divider_markdown_node.py +24 -0
  29. notionary/blocks/divider/divider_models.py +0 -0
  30. notionary/blocks/document/__init__.py +7 -0
  31. notionary/blocks/document/document_element.py +102 -0
  32. notionary/blocks/document/document_markdown_node.py +31 -0
  33. notionary/blocks/document/document_models.py +0 -0
  34. notionary/blocks/embed/__init__.py +7 -0
  35. notionary/blocks/{embed_element.py → embed/embed_element.py} +50 -32
  36. notionary/blocks/embed/embed_markdown_node.py +30 -0
  37. notionary/blocks/embed/embed_models.py +0 -0
  38. notionary/blocks/heading/__init__.py +7 -0
  39. notionary/blocks/{heading_element.py → heading/heading_element.py} +25 -17
  40. notionary/blocks/heading/heading_markdown_node.py +29 -0
  41. notionary/blocks/heading/heading_models.py +0 -0
  42. notionary/blocks/image/__init__.py +7 -0
  43. notionary/blocks/{image_element.py → image/image_element.py} +62 -42
  44. notionary/blocks/image/image_markdown_node.py +33 -0
  45. notionary/blocks/image/image_models.py +0 -0
  46. notionary/blocks/markdown_builder.py +356 -0
  47. notionary/blocks/markdown_node.py +29 -0
  48. notionary/blocks/mention/__init__.py +7 -0
  49. notionary/blocks/{mention_element.py → mention/mention_element.py} +6 -2
  50. notionary/blocks/mention/mention_markdown_node.py +38 -0
  51. notionary/blocks/mention/mention_models.py +0 -0
  52. notionary/blocks/numbered_list/__init__.py +7 -0
  53. notionary/blocks/{numbered_list_element.py → numbered_list/numbered_list_element.py} +10 -6
  54. notionary/blocks/numbered_list/numbered_list_markdown_node.py +29 -0
  55. notionary/blocks/numbered_list/numbered_list_models.py +0 -0
  56. notionary/blocks/paragraph/__init__.py +7 -0
  57. notionary/blocks/{paragraph_element.py → paragraph/paragraph_element.py} +7 -3
  58. notionary/blocks/paragraph/paragraph_markdown_node.py +25 -0
  59. notionary/blocks/paragraph/paragraph_models.py +0 -0
  60. notionary/blocks/quote/__init__.py +7 -0
  61. notionary/blocks/quote/quote_element.py +92 -0
  62. notionary/blocks/quote/quote_markdown_node.py +23 -0
  63. notionary/blocks/quote/quote_models.py +0 -0
  64. notionary/blocks/registry/block_registry.py +17 -3
  65. notionary/blocks/registry/block_registry_builder.py +90 -178
  66. notionary/blocks/shared/__init__.py +0 -0
  67. notionary/blocks/shared/block_client.py +256 -0
  68. notionary/blocks/shared/models.py +713 -0
  69. notionary/blocks/{notion_block_element.py → shared/notion_block_element.py} +8 -5
  70. notionary/blocks/{text_inline_formatter.py → shared/text_inline_formatter.py} +14 -14
  71. notionary/blocks/shared/text_inline_formatter_new.py +139 -0
  72. notionary/blocks/table/__init__.py +7 -0
  73. notionary/blocks/{table_element.py → table/table_element.py} +23 -11
  74. notionary/blocks/table/table_markdown_node.py +40 -0
  75. notionary/blocks/table/table_models.py +0 -0
  76. notionary/blocks/todo/__init__.py +7 -0
  77. notionary/blocks/{todo_element.py → todo/todo_element.py} +8 -4
  78. notionary/blocks/todo/todo_markdown_node.py +31 -0
  79. notionary/blocks/todo/todo_models.py +0 -0
  80. notionary/blocks/toggle/__init__.py +4 -0
  81. notionary/blocks/{toggle_element.py → toggle/toggle_element.py} +7 -3
  82. notionary/blocks/toggle/toggle_markdown_node.py +35 -0
  83. notionary/blocks/toggle/toggle_models.py +0 -0
  84. notionary/blocks/toggleable_heading/__init__.py +9 -0
  85. notionary/blocks/{toggleable_heading_element.py → toggleable_heading/toggleable_heading_element.py} +8 -4
  86. notionary/blocks/toggleable_heading/toggleable_heading_markdown_node.py +43 -0
  87. notionary/blocks/toggleable_heading/toggleable_heading_models.py +0 -0
  88. notionary/blocks/video/__init__.py +7 -0
  89. notionary/blocks/{video_element.py → video/video_element.py} +82 -57
  90. notionary/blocks/video/video_markdown_node.py +30 -0
  91. notionary/file_upload/notion_file_upload.py +1 -1
  92. notionary/page/content/markdown_whitespace_processor.py +80 -0
  93. notionary/page/content/notion_text_length_utils.py +87 -0
  94. notionary/page/content/page_content_retriever.py +18 -10
  95. notionary/page/content/page_content_writer.py +97 -148
  96. notionary/page/formatting/line_processor.py +153 -0
  97. notionary/page/formatting/markdown_to_notion_converter.py +104 -425
  98. notionary/page/notion_page.py +9 -11
  99. notionary/page/notion_to_markdown_converter.py +9 -13
  100. notionary/util/factory_decorator.py +0 -0
  101. notionary/workspace.py +0 -1
  102. {notionary-0.2.17.dist-info → notionary-0.2.19.dist-info}/METADATA +1 -1
  103. notionary-0.2.19.dist-info/RECORD +150 -0
  104. notionary/blocks/audio_element.py +0 -144
  105. notionary/blocks/callout_element.py +0 -122
  106. notionary/blocks/document_element.py +0 -194
  107. notionary/blocks/notion_block_client.py +0 -26
  108. notionary/blocks/qoute_element.py +0 -169
  109. notionary/page/content/notion_page_content_chunker.py +0 -84
  110. notionary/page/formatting/spacer_rules.py +0 -483
  111. notionary-0.2.17.dist-info/RECORD +0 -85
  112. {notionary-0.2.17.dist-info → notionary-0.2.19.dist-info}/LICENSE +0 -0
  113. {notionary-0.2.17.dist-info → notionary-0.2.19.dist-info}/WHEEL +0 -0
@@ -0,0 +1,33 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Optional
4
+ from pydantic import BaseModel
5
+ from notionary.blocks.markdown_node import MarkdownNode
6
+
7
+
8
+ class ImageMarkdownBlockParams(BaseModel):
9
+ url: str
10
+ caption: Optional[str] = None
11
+
12
+
13
+ class ImageMarkdownNode(MarkdownNode):
14
+ """
15
+ Programmatic interface for creating Notion-style image blocks.
16
+ Example: [image](https://example.com/image.jpg "Optional caption")
17
+ """
18
+
19
+ def __init__(
20
+ self, url: str, caption: Optional[str] = None, alt: Optional[str] = None
21
+ ):
22
+ self.url = url
23
+ self.caption = caption
24
+ # Note: 'alt' is kept for API compatibility but not used in Notion syntax
25
+
26
+ @classmethod
27
+ def from_params(cls, params: ImageMarkdownBlockParams) -> ImageMarkdownNode:
28
+ return cls(url=params.url, caption=params.caption)
29
+
30
+ def to_markdown(self) -> str:
31
+ if self.caption:
32
+ return f'[image]({self.url} "{self.caption}")'
33
+ return f"[image]({self.url})"
File without changes
@@ -0,0 +1,356 @@
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
+ from typing import Optional, Self, Union
11
+
12
+ from notionary.blocks import (
13
+ HeadingMarkdownNode,
14
+ ImageMarkdownNode,
15
+ ParagraphMarkdownNode,
16
+ AudioMarkdownNode,
17
+ BookmarkMarkdownNode,
18
+ CalloutMarkdownNode,
19
+ CodeMarkdownNode,
20
+ DividerMarkdownNode,
21
+ DocumentMarkdownNode,
22
+ EmbedMarkdownNode,
23
+ MentionMarkdownNode,
24
+ NumberedListMarkdownNode,
25
+ BulletedListMarkdownNode,
26
+ QuoteMarkdownNode,
27
+ TableMarkdownNode,
28
+ TodoMarkdownNode,
29
+ ToggleMarkdownNode,
30
+ ToggleableHeadingMarkdownNode,
31
+ VideoMarkdownNode,
32
+ MarkdownNode,
33
+ )
34
+
35
+
36
+ class MarkdownBuilder:
37
+ """
38
+ Fluent interface builder for creating Notion content with clean, direct methods.
39
+ """
40
+
41
+ def __init__(self) -> None:
42
+ self.children: list[MarkdownNode] = []
43
+
44
+ def h1(self, text: str) -> Self:
45
+ """
46
+ Add an H1 heading.
47
+
48
+ Args:
49
+ text: The heading text content
50
+ """
51
+ self.children.append(HeadingMarkdownNode(text=text, level=1))
52
+ return self
53
+
54
+ def h2(self, text: str) -> Self:
55
+ """
56
+ Add an H2 heading.
57
+
58
+ Args:
59
+ text: The heading text content
60
+ """
61
+ self.children.append(HeadingMarkdownNode(text=text, level=2))
62
+ return self
63
+
64
+ def h3(self, text: str) -> Self:
65
+ """
66
+ Add an H3 heading.
67
+
68
+ Args:
69
+ text: The heading text content
70
+ """
71
+ self.children.append(HeadingMarkdownNode(text=text, level=3))
72
+ return self
73
+
74
+ def heading(self, text: str, level: int = 2) -> Self:
75
+ """
76
+ Add a heading with specified level.
77
+
78
+ Args:
79
+ text: The heading text content
80
+ level: Heading level (1-3), defaults to 2
81
+ """
82
+ self.children.append(HeadingMarkdownNode(text=text, level=level))
83
+ return self
84
+
85
+ def paragraph(self, text: str) -> Self:
86
+ """
87
+ Add a paragraph block.
88
+
89
+ Args:
90
+ text: The paragraph text content
91
+ """
92
+ self.children.append(ParagraphMarkdownNode(text=text))
93
+ return self
94
+
95
+ def text(self, content: str) -> Self:
96
+ """
97
+ Add a text paragraph (alias for paragraph).
98
+
99
+ Args:
100
+ content: The text content
101
+ """
102
+ return self.paragraph(content)
103
+
104
+ def quote(self, text: str, author: Optional[str] = None) -> Self:
105
+ """
106
+ Add a blockquote.
107
+
108
+ Args:
109
+ text: Quote text content
110
+ author: Optional quote author/attribution
111
+ """
112
+ self.children.append(QuoteMarkdownNode(text=text, author=author))
113
+ return self
114
+
115
+ def divider(self) -> Self:
116
+ """Add a horizontal divider."""
117
+ self.children.append(DividerMarkdownNode())
118
+ return self
119
+
120
+ def numbered_list(self, items: list[str]) -> Self:
121
+ """
122
+ Add a numbered list.
123
+
124
+ Args:
125
+ items: List of text items for the numbered list
126
+ """
127
+ self.children.append(NumberedListMarkdownNode(texts=items))
128
+ return self
129
+
130
+ def bulleted_list(self, items: list[str]) -> Self:
131
+ """
132
+ Add a bulleted list.
133
+
134
+ Args:
135
+ items: List of text items for the bulleted list
136
+ """
137
+ self.children.append(BulletedListMarkdownNode(texts=items))
138
+ return self
139
+
140
+ def todo(self, text: str, checked: bool = False) -> Self:
141
+ """
142
+ Add a single todo item.
143
+
144
+ Args:
145
+ text: The todo item text
146
+ checked: Whether the todo item is completed, defaults to False
147
+ """
148
+ self.children.append(TodoMarkdownNode(text=text, checked=checked))
149
+ return self
150
+
151
+ def todo_list(
152
+ self, items: list[str], completed: Optional[list[bool]] = None
153
+ ) -> Self:
154
+ """
155
+ Add multiple todo items.
156
+
157
+ Args:
158
+ items: List of todo item texts
159
+ completed: List of completion states for each item, defaults to all False
160
+ """
161
+ if completed is None:
162
+ completed = [False] * len(items)
163
+
164
+ for i, item in enumerate(items):
165
+ is_done = completed[i] if i < len(completed) else False
166
+ self.children.append(TodoMarkdownNode(text=item, checked=is_done))
167
+ return self
168
+
169
+ def callout(self, text: str, emoji: Optional[str] = None) -> Self:
170
+ """
171
+ Add a callout block.
172
+
173
+ Args:
174
+ text: The callout text content
175
+ emoji: Optional emoji for the callout icon
176
+ """
177
+ self.children.append(CalloutMarkdownNode(text=text, emoji=emoji))
178
+ return self
179
+
180
+ def toggle(self, title: str, content: Optional[list[str]] = None) -> Self:
181
+ """
182
+ Add a toggle block.
183
+
184
+ Args:
185
+ title: The toggle title/header text
186
+ content: Optional list of content items inside the toggle
187
+ """
188
+ self.children.append(ToggleMarkdownNode(title=title, content=content))
189
+ return self
190
+
191
+ def toggleable_heading(
192
+ self, text: str, level: int = 2, content: Optional[list[str]] = None
193
+ ) -> Self:
194
+ """
195
+ Add a toggleable heading.
196
+
197
+ Args:
198
+ text: The heading text content
199
+ level: Heading level (1-3), defaults to 2
200
+ content: Optional list of content items inside the toggleable heading
201
+ """
202
+ self.children.append(
203
+ ToggleableHeadingMarkdownNode(text=text, level=level, content=content)
204
+ )
205
+ return self
206
+
207
+ def image(
208
+ self, url: str, caption: Optional[str] = None, alt: Optional[str] = None
209
+ ) -> Self:
210
+ """
211
+ Add an image.
212
+
213
+ Args:
214
+ url: Image URL or file path
215
+ caption: Optional image caption text
216
+ alt: Optional alternative text for accessibility
217
+ """
218
+ self.children.append(ImageMarkdownNode(url=url, caption=caption, alt=alt))
219
+ return self
220
+
221
+ def video(self, url: str, caption: Optional[str] = None) -> Self:
222
+ """
223
+ Add a video.
224
+
225
+ Args:
226
+ url: Video URL or file path
227
+ caption: Optional video caption text
228
+ """
229
+ self.children.append(VideoMarkdownNode(url=url, caption=caption))
230
+ return self
231
+
232
+ def audio(self, url: str, caption: Optional[str] = None) -> Self:
233
+ """
234
+ Add audio content.
235
+
236
+ Args:
237
+ url: Audio file URL or path
238
+ caption: Optional audio caption text
239
+ """
240
+ self.children.append(AudioMarkdownNode(url=url, caption=caption))
241
+ return self
242
+
243
+ def document(self, url: str, caption: Optional[str] = None) -> Self:
244
+ """
245
+ Add a document file.
246
+
247
+ Args:
248
+ url: Document file URL or path
249
+ caption: Optional document caption text
250
+ """
251
+ self.children.append(DocumentMarkdownNode(url=url, caption=caption))
252
+ return self
253
+
254
+ def bookmark(
255
+ self, url: str, title: Optional[str] = None, description: Optional[str] = None
256
+ ) -> Self:
257
+ """
258
+ Add a bookmark.
259
+
260
+ Args:
261
+ url: Bookmark URL
262
+ title: Optional bookmark title
263
+ description: Optional bookmark description text
264
+ """
265
+ self.children.append(
266
+ BookmarkMarkdownNode(url=url, title=title, description=description)
267
+ )
268
+ return self
269
+
270
+ def embed(self, url: str, caption: Optional[str] = None) -> Self:
271
+ """
272
+ Add an embed.
273
+
274
+ Args:
275
+ url: URL to embed (e.g., YouTube, Twitter, etc.)
276
+ caption: Optional embed caption text
277
+ """
278
+ self.children.append(EmbedMarkdownNode(url=url, caption=caption))
279
+ return self
280
+
281
+ def code(
282
+ self, code: str, language: Optional[str] = None, caption: Optional[str] = None
283
+ ) -> Self:
284
+ """
285
+ Add a code block.
286
+
287
+ Args:
288
+ code: The source code content
289
+ language: Optional programming language for syntax highlighting
290
+ caption: Optional code block caption text
291
+ """
292
+ self.children.append(
293
+ CodeMarkdownNode(code=code, language=language, caption=caption)
294
+ )
295
+ return self
296
+
297
+ def table(self, headers: list[str], rows: list[list[str]]) -> Self:
298
+ """
299
+ Add a table.
300
+
301
+ Args:
302
+ headers: List of column header texts
303
+ rows: List of rows, where each row is a list of cell texts
304
+ """
305
+ self.children.append(TableMarkdownNode(headers=headers, rows=rows))
306
+ return self
307
+
308
+ def mention_page(self, page_id: str) -> Self:
309
+ """
310
+ Add a page mention.
311
+
312
+ Args:
313
+ page_id: The ID of the page to mention
314
+ """
315
+ self.children.append(MentionMarkdownNode("page", page_id))
316
+ return self
317
+
318
+ def mention_database(self, database_id: str) -> Self:
319
+ """
320
+ Add a database mention.
321
+
322
+ Args:
323
+ database_id: The ID of the database to mention
324
+ """
325
+ self.children.append(MentionMarkdownNode("database", database_id))
326
+ return self
327
+
328
+ def mention_date(self, date: str) -> Self:
329
+ """
330
+ Add a date mention.
331
+
332
+ Args:
333
+ date: Date in YYYY-MM-DD format
334
+ """
335
+ self.children.append(MentionMarkdownNode("date", date))
336
+ return self
337
+
338
+ def add_custom(self, node: MarkdownNode) -> Self:
339
+ """
340
+ Add a custom MarkdownNode.
341
+
342
+ Args:
343
+ node: A custom MarkdownNode instance
344
+ """
345
+ self.children.append(node)
346
+ return self
347
+
348
+ def space(self) -> Self:
349
+ """Add vertical spacing."""
350
+ return self.paragraph("")
351
+
352
+ def build(self) -> str:
353
+ """Build and return the final markdown string."""
354
+ return "\n\n".join(
355
+ child.to_markdown() for child in self.children if child is not None
356
+ )
@@ -0,0 +1,29 @@
1
+ from __future__ import annotations
2
+ from abc import ABC, abstractmethod
3
+
4
+
5
+ class MarkdownNode(ABC):
6
+ """
7
+ Abstract base class for all Markdown block elements.
8
+ Enforces implementation of to_markdown().
9
+ """
10
+
11
+ @abstractmethod
12
+ def to_markdown(self) -> str:
13
+ """
14
+ Returns the Markdown representation of the block.
15
+ Must be implemented by subclasses.
16
+ """
17
+ pass
18
+
19
+ @classmethod
20
+ @abstractmethod
21
+ def from_params(cls, params) -> MarkdownNode:
22
+ """
23
+ Creates an instance from a params object.
24
+ Must be implemented by subclasses.
25
+ """
26
+ pass
27
+
28
+ def __str__(self):
29
+ return self.to_markdown()
@@ -0,0 +1,7 @@
1
+ from .mention_element import MentionElement
2
+ from .mention_markdown_node import MentionMarkdownNode
3
+
4
+ __all__ = [
5
+ "MentionElement",
6
+ "MentionMarkdownNode",
7
+ ]
@@ -2,7 +2,11 @@ import re
2
2
  from typing import Dict, Any, Optional, List
3
3
 
4
4
  from notionary.blocks import NotionBlockElement
5
- from notionary.blocks import ElementPromptContent, ElementPromptBuilder
5
+ from notionary.blocks import (
6
+ ElementPromptContent,
7
+ ElementPromptBuilder,
8
+ NotionBlockResult,
9
+ )
6
10
 
7
11
 
8
12
  class MentionElement(NotionBlockElement):
@@ -74,7 +78,7 @@ class MentionElement(NotionBlockElement):
74
78
  return any(text_item.get("type") == "mention" for text_item in rich_text)
75
79
 
76
80
  @classmethod
77
- def markdown_to_notion(cls, text: str) -> Optional[Dict[str, Any]]:
81
+ def markdown_to_notion(cls, text: str) -> NotionBlockResult:
78
82
  """Convert markdown text with mentions to a Notion paragraph block."""
79
83
  if not MentionElement.match_markdown(text):
80
84
  return None
@@ -0,0 +1,38 @@
1
+ from __future__ import annotations
2
+ from typing import Literal
3
+ from pydantic import BaseModel
4
+ from notionary.blocks.markdown_node import MarkdownNode
5
+
6
+
7
+ class MentionMarkdownBlockParams(BaseModel):
8
+ mention_type: Literal["page", "database", "date"]
9
+ value: str
10
+
11
+
12
+ class MentionMarkdownNode(MarkdownNode):
13
+ """
14
+ Programmatic interface for creating Notion-style Markdown mentions.
15
+ Supports: page, database, date.
16
+ Examples: @[page-id], @db[database-id], @date[YYYY-MM-DD]
17
+ """
18
+
19
+ def __init__(self, mention_type: str, value: str):
20
+ allowed = {"page", "database", "date"}
21
+ if mention_type not in allowed:
22
+ raise ValueError(f"mention_type must be one of {allowed}")
23
+ self.mention_type = mention_type
24
+ self.value = value
25
+
26
+ @classmethod
27
+ def from_params(cls, params: MentionMarkdownBlockParams) -> MentionMarkdownNode:
28
+ return cls(mention_type=params.mention_type, value=params.value)
29
+
30
+ def to_markdown(self) -> str:
31
+ if self.mention_type == "page":
32
+ return f"@[{self.value}]"
33
+ elif self.mention_type == "database":
34
+ return f"@db[{self.value}]"
35
+ elif self.mention_type == "date":
36
+ return f"@date[{self.value}]"
37
+ else:
38
+ return f"@[{self.value}]"
File without changes
@@ -0,0 +1,7 @@
1
+ from .numbered_list_element import NumberedListElement
2
+ from .numbered_list_markdown_node import NumberedListMarkdownNode
3
+
4
+ __all__ = [
5
+ "NumberedListElement",
6
+ "NumberedListMarkdownNode",
7
+ ]
@@ -1,15 +1,19 @@
1
1
  import re
2
- from typing import Dict, Any, Optional
2
+ from typing import Any, Optional
3
3
  from notionary.blocks import NotionBlockElement
4
- from notionary.blocks import ElementPromptContent, ElementPromptBuilder
5
- from notionary.blocks.text_inline_formatter import TextInlineFormatter
4
+ from notionary.blocks import (
5
+ ElementPromptContent,
6
+ ElementPromptBuilder,
7
+ NotionBlockResult,
8
+ )
9
+ from notionary.blocks.shared.text_inline_formatter import TextInlineFormatter
6
10
 
7
11
 
8
12
  class NumberedListElement(NotionBlockElement):
9
13
  """Class for converting between Markdown numbered lists and Notion numbered list items."""
10
14
 
11
15
  @classmethod
12
- def markdown_to_notion(cls, text: str) -> Optional[Dict[str, Any]]:
16
+ def markdown_to_notion(cls, text: str) -> NotionBlockResult:
13
17
  """Convert markdown numbered list item to Notion block."""
14
18
  pattern = re.compile(r"^\s*(\d+)\.\s+(.+)$")
15
19
  numbered_match = pattern.match(text)
@@ -27,7 +31,7 @@ class NumberedListElement(NotionBlockElement):
27
31
  }
28
32
 
29
33
  @classmethod
30
- def notion_to_markdown(cls, block: Dict[str, Any]) -> Optional[str]:
34
+ def notion_to_markdown(cls, block: dict[str, Any]) -> Optional[str]:
31
35
  """Convert Notion numbered list item block to markdown."""
32
36
  if block.get("type") != "numbered_list_item":
33
37
  return None
@@ -44,7 +48,7 @@ class NumberedListElement(NotionBlockElement):
44
48
  return bool(pattern.match(text))
45
49
 
46
50
  @classmethod
47
- def match_notion(cls, block: Dict[str, Any]) -> bool:
51
+ def match_notion(cls, block: dict[str, Any]) -> bool:
48
52
  """Check if this element can handle the given Notion block."""
49
53
  return block.get("type") == "numbered_list_item"
50
54
 
@@ -0,0 +1,29 @@
1
+ from __future__ import annotations
2
+ from pydantic import BaseModel
3
+ from notionary.blocks.markdown_node import MarkdownNode
4
+
5
+
6
+ class NumberedListMarkdownBlockParams(BaseModel):
7
+ texts: list[str]
8
+
9
+
10
+ class NumberedListMarkdownNode(MarkdownNode):
11
+ """
12
+ Programmatic interface for creating Markdown numbered list items.
13
+ Example:
14
+ 1. First step
15
+ 2. Second step
16
+ 3. Third step
17
+ """
18
+
19
+ def __init__(self, texts: list[str]):
20
+ self.texts = texts
21
+
22
+ @classmethod
23
+ def from_params(
24
+ cls, params: NumberedListMarkdownBlockParams
25
+ ) -> NumberedListMarkdownNode:
26
+ return cls(texts=params.texts)
27
+
28
+ def to_markdown(self) -> str:
29
+ return "\n".join(f"{i + 1}. {text}" for i, text in enumerate(self.texts))
File without changes
@@ -0,0 +1,7 @@
1
+ from .paragraph_element import ParagraphElement
2
+ from .paragraph_markdown_node import ParagraphMarkdownNode
3
+
4
+ __all__ = [
5
+ "ParagraphElement",
6
+ "ParagraphMarkdownNode",
7
+ ]
@@ -1,8 +1,12 @@
1
1
  from typing import Dict, Any, Optional
2
2
 
3
3
  from notionary.blocks import NotionBlockElement
4
- from notionary.blocks import ElementPromptContent, ElementPromptBuilder
5
- from notionary.blocks.text_inline_formatter import TextInlineFormatter
4
+ from notionary.blocks import (
5
+ ElementPromptContent,
6
+ ElementPromptBuilder,
7
+ NotionBlockResult,
8
+ )
9
+ from notionary.blocks.shared.text_inline_formatter import TextInlineFormatter
6
10
 
7
11
 
8
12
  class ParagraphElement(NotionBlockElement):
@@ -23,7 +27,7 @@ class ParagraphElement(NotionBlockElement):
23
27
  return block.get("type") == "paragraph"
24
28
 
25
29
  @classmethod
26
- def markdown_to_notion(cls, text: str) -> Optional[Dict[str, Any]]:
30
+ def markdown_to_notion(cls, text: str) -> NotionBlockResult:
27
31
  """Convert markdown paragraph to Notion paragraph block."""
28
32
  if not text.strip():
29
33
  return None
@@ -0,0 +1,25 @@
1
+ from __future__ import annotations
2
+
3
+ from pydantic import BaseModel
4
+ from notionary.blocks.markdown_node import MarkdownNode
5
+
6
+
7
+ class ParagraphMarkdownBlockParams(BaseModel):
8
+ text: str
9
+
10
+
11
+ class ParagraphMarkdownNode(MarkdownNode):
12
+ """
13
+ Programmatic interface for creating Markdown paragraphs.
14
+ Paragraphs are standard text without special block formatting.
15
+ """
16
+
17
+ def __init__(self, text: str):
18
+ self.text = text
19
+
20
+ @classmethod
21
+ def from_params(cls, params: ParagraphMarkdownBlockParams) -> ParagraphMarkdownNode:
22
+ return cls(text=params.text)
23
+
24
+ def to_markdown(self) -> str:
25
+ return self.text
File without changes
@@ -0,0 +1,7 @@
1
+ from .quote_element import QuoteElement
2
+ from .quote_markdown_node import QuoteMarkdownNode
3
+
4
+ __all__ = [
5
+ "QuoteElement",
6
+ "QuoteMarkdownNode",
7
+ ]