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.
- notionary/__init__.py +3 -2
- notionary/blocks/__init__.py +54 -25
- notionary/blocks/audio/__init__.py +7 -0
- notionary/blocks/audio/audio_element.py +152 -0
- notionary/blocks/audio/audio_markdown_node.py +29 -0
- notionary/blocks/audio/audio_models.py +59 -0
- notionary/blocks/bookmark/__init__.py +7 -0
- notionary/blocks/{bookmark_element.py → bookmark/bookmark_element.py} +20 -65
- notionary/blocks/bookmark/bookmark_markdown_node.py +43 -0
- notionary/blocks/bookmark/bookmark_models.py +0 -0
- notionary/blocks/bulleted_list/__init__.py +7 -0
- notionary/blocks/{bulleted_list_element.py → bulleted_list/bulleted_list_element.py} +7 -3
- notionary/blocks/bulleted_list/bulleted_list_markdown_node.py +33 -0
- notionary/blocks/bulleted_list/bulleted_list_models.py +0 -0
- notionary/blocks/callout/__init__.py +7 -0
- notionary/blocks/callout/callout_element.py +132 -0
- notionary/blocks/callout/callout_markdown_node.py +31 -0
- notionary/blocks/callout/callout_models.py +0 -0
- notionary/blocks/code/__init__.py +7 -0
- notionary/blocks/{code_block_element.py → code/code_element.py} +72 -40
- notionary/blocks/code/code_markdown_node.py +43 -0
- notionary/blocks/code/code_models.py +0 -0
- notionary/blocks/column/__init__.py +5 -0
- notionary/blocks/{column_element.py → column/column_element.py} +24 -55
- notionary/blocks/column/column_models.py +0 -0
- notionary/blocks/divider/__init__.py +7 -0
- notionary/blocks/{divider_element.py → divider/divider_element.py} +11 -3
- notionary/blocks/divider/divider_markdown_node.py +24 -0
- notionary/blocks/divider/divider_models.py +0 -0
- notionary/blocks/document/__init__.py +7 -0
- notionary/blocks/document/document_element.py +102 -0
- notionary/blocks/document/document_markdown_node.py +31 -0
- notionary/blocks/document/document_models.py +0 -0
- notionary/blocks/embed/__init__.py +7 -0
- notionary/blocks/{embed_element.py → embed/embed_element.py} +50 -32
- notionary/blocks/embed/embed_markdown_node.py +30 -0
- notionary/blocks/embed/embed_models.py +0 -0
- notionary/blocks/heading/__init__.py +7 -0
- notionary/blocks/{heading_element.py → heading/heading_element.py} +25 -17
- notionary/blocks/heading/heading_markdown_node.py +29 -0
- notionary/blocks/heading/heading_models.py +0 -0
- notionary/blocks/image/__init__.py +7 -0
- notionary/blocks/{image_element.py → image/image_element.py} +62 -42
- notionary/blocks/image/image_markdown_node.py +33 -0
- notionary/blocks/image/image_models.py +0 -0
- notionary/blocks/markdown_builder.py +356 -0
- notionary/blocks/markdown_node.py +29 -0
- notionary/blocks/mention/__init__.py +7 -0
- notionary/blocks/{mention_element.py → mention/mention_element.py} +6 -2
- notionary/blocks/mention/mention_markdown_node.py +38 -0
- notionary/blocks/mention/mention_models.py +0 -0
- notionary/blocks/numbered_list/__init__.py +7 -0
- notionary/blocks/{numbered_list_element.py → numbered_list/numbered_list_element.py} +10 -6
- notionary/blocks/numbered_list/numbered_list_markdown_node.py +29 -0
- notionary/blocks/numbered_list/numbered_list_models.py +0 -0
- notionary/blocks/paragraph/__init__.py +7 -0
- notionary/blocks/{paragraph_element.py → paragraph/paragraph_element.py} +7 -3
- notionary/blocks/paragraph/paragraph_markdown_node.py +25 -0
- notionary/blocks/paragraph/paragraph_models.py +0 -0
- notionary/blocks/quote/__init__.py +7 -0
- notionary/blocks/quote/quote_element.py +92 -0
- notionary/blocks/quote/quote_markdown_node.py +23 -0
- notionary/blocks/quote/quote_models.py +0 -0
- notionary/blocks/registry/block_registry.py +17 -3
- notionary/blocks/registry/block_registry_builder.py +90 -178
- notionary/blocks/shared/__init__.py +0 -0
- notionary/blocks/shared/block_client.py +256 -0
- notionary/blocks/shared/models.py +713 -0
- notionary/blocks/{notion_block_element.py → shared/notion_block_element.py} +8 -5
- notionary/blocks/{text_inline_formatter.py → shared/text_inline_formatter.py} +14 -14
- notionary/blocks/shared/text_inline_formatter_new.py +139 -0
- notionary/blocks/table/__init__.py +7 -0
- notionary/blocks/{table_element.py → table/table_element.py} +23 -11
- notionary/blocks/table/table_markdown_node.py +40 -0
- notionary/blocks/table/table_models.py +0 -0
- notionary/blocks/todo/__init__.py +7 -0
- notionary/blocks/{todo_element.py → todo/todo_element.py} +8 -4
- notionary/blocks/todo/todo_markdown_node.py +31 -0
- notionary/blocks/todo/todo_models.py +0 -0
- notionary/blocks/toggle/__init__.py +4 -0
- notionary/blocks/{toggle_element.py → toggle/toggle_element.py} +7 -3
- notionary/blocks/toggle/toggle_markdown_node.py +35 -0
- notionary/blocks/toggle/toggle_models.py +0 -0
- notionary/blocks/toggleable_heading/__init__.py +9 -0
- notionary/blocks/{toggleable_heading_element.py → toggleable_heading/toggleable_heading_element.py} +8 -4
- notionary/blocks/toggleable_heading/toggleable_heading_markdown_node.py +43 -0
- notionary/blocks/toggleable_heading/toggleable_heading_models.py +0 -0
- notionary/blocks/video/__init__.py +7 -0
- notionary/blocks/{video_element.py → video/video_element.py} +82 -57
- notionary/blocks/video/video_markdown_node.py +30 -0
- notionary/file_upload/notion_file_upload.py +1 -1
- notionary/page/content/markdown_whitespace_processor.py +80 -0
- notionary/page/content/notion_text_length_utils.py +87 -0
- notionary/page/content/page_content_retriever.py +18 -10
- notionary/page/content/page_content_writer.py +97 -148
- notionary/page/formatting/line_processor.py +153 -0
- notionary/page/formatting/markdown_to_notion_converter.py +104 -425
- notionary/page/notion_page.py +9 -11
- notionary/page/notion_to_markdown_converter.py +9 -13
- notionary/util/factory_decorator.py +0 -0
- notionary/workspace.py +0 -1
- {notionary-0.2.17.dist-info → notionary-0.2.19.dist-info}/METADATA +1 -1
- notionary-0.2.19.dist-info/RECORD +150 -0
- notionary/blocks/audio_element.py +0 -144
- notionary/blocks/callout_element.py +0 -122
- notionary/blocks/document_element.py +0 -194
- notionary/blocks/notion_block_client.py +0 -26
- notionary/blocks/qoute_element.py +0 -169
- notionary/page/content/notion_page_content_chunker.py +0 -84
- notionary/page/formatting/spacer_rules.py +0 -483
- notionary-0.2.17.dist-info/RECORD +0 -85
- {notionary-0.2.17.dist-info → notionary-0.2.19.dist-info}/LICENSE +0 -0
- {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()
|
@@ -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
|
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) ->
|
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
|
@@ -1,15 +1,19 @@
|
|
1
1
|
import re
|
2
|
-
from typing import
|
2
|
+
from typing import Any, Optional
|
3
3
|
from notionary.blocks import NotionBlockElement
|
4
|
-
from notionary.blocks import
|
5
|
-
|
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) ->
|
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:
|
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:
|
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
|
@@ -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
|
5
|
-
|
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) ->
|
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
|