notionary 0.2.19__py3-none-any.whl → 0.2.22__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 +8 -4
- notionary/base_notion_client.py +3 -1
- notionary/blocks/__init__.py +2 -91
- notionary/blocks/_bootstrap.py +271 -0
- notionary/blocks/audio/__init__.py +8 -2
- notionary/blocks/audio/audio_element.py +69 -106
- notionary/blocks/audio/audio_markdown_node.py +13 -5
- notionary/blocks/audio/audio_models.py +6 -55
- notionary/blocks/base_block_element.py +42 -0
- notionary/blocks/bookmark/__init__.py +9 -2
- notionary/blocks/bookmark/bookmark_element.py +49 -139
- notionary/blocks/bookmark/bookmark_markdown_node.py +19 -18
- notionary/blocks/bookmark/bookmark_models.py +15 -0
- notionary/blocks/breadcrumbs/__init__.py +17 -0
- notionary/blocks/breadcrumbs/breadcrumb_element.py +39 -0
- notionary/blocks/breadcrumbs/breadcrumb_markdown_node.py +32 -0
- notionary/blocks/breadcrumbs/breadcrumb_models.py +12 -0
- notionary/blocks/bulleted_list/__init__.py +12 -2
- notionary/blocks/bulleted_list/bulleted_list_element.py +55 -53
- notionary/blocks/bulleted_list/bulleted_list_markdown_node.py +2 -1
- notionary/blocks/bulleted_list/bulleted_list_models.py +18 -0
- notionary/blocks/callout/__init__.py +9 -2
- notionary/blocks/callout/callout_element.py +53 -86
- notionary/blocks/callout/callout_markdown_node.py +3 -1
- notionary/blocks/callout/callout_models.py +33 -0
- notionary/blocks/child_database/__init__.py +14 -0
- notionary/blocks/child_database/child_database_element.py +61 -0
- notionary/blocks/child_database/child_database_models.py +12 -0
- notionary/blocks/child_page/__init__.py +9 -0
- notionary/blocks/child_page/child_page_element.py +94 -0
- notionary/blocks/child_page/child_page_models.py +12 -0
- notionary/blocks/{shared/block_client.py → client.py} +54 -54
- notionary/blocks/code/__init__.py +6 -2
- notionary/blocks/code/code_element.py +96 -181
- notionary/blocks/code/code_markdown_node.py +64 -13
- notionary/blocks/code/code_models.py +94 -0
- notionary/blocks/column/__init__.py +25 -1
- notionary/blocks/column/column_element.py +44 -312
- notionary/blocks/column/column_list_element.py +52 -0
- notionary/blocks/column/column_list_markdown_node.py +50 -0
- notionary/blocks/column/column_markdown_node.py +59 -0
- notionary/blocks/column/column_models.py +26 -0
- notionary/blocks/divider/__init__.py +9 -2
- notionary/blocks/divider/divider_element.py +18 -49
- notionary/blocks/divider/divider_markdown_node.py +2 -1
- notionary/blocks/divider/divider_models.py +12 -0
- notionary/blocks/embed/__init__.py +9 -2
- notionary/blocks/embed/embed_element.py +65 -111
- notionary/blocks/embed/embed_markdown_node.py +3 -1
- notionary/blocks/embed/embed_models.py +14 -0
- notionary/blocks/equation/__init__.py +14 -0
- notionary/blocks/equation/equation_element.py +133 -0
- notionary/blocks/equation/equation_element_markdown_node.py +35 -0
- notionary/blocks/equation/equation_models.py +11 -0
- notionary/blocks/file/__init__.py +25 -0
- notionary/blocks/file/file_element.py +112 -0
- notionary/blocks/file/file_element_markdown_node.py +37 -0
- notionary/blocks/file/file_element_models.py +39 -0
- notionary/blocks/guards.py +22 -0
- notionary/blocks/heading/__init__.py +16 -2
- notionary/blocks/heading/heading_element.py +83 -69
- notionary/blocks/heading/heading_markdown_node.py +2 -1
- notionary/blocks/heading/heading_models.py +29 -0
- notionary/blocks/image_block/__init__.py +13 -0
- notionary/blocks/image_block/image_element.py +89 -0
- notionary/blocks/{image → image_block}/image_markdown_node.py +13 -6
- notionary/blocks/image_block/image_models.py +10 -0
- notionary/blocks/mixins/captions/__init__.py +4 -0
- notionary/blocks/mixins/captions/caption_markdown_node_mixin.py +31 -0
- notionary/blocks/mixins/captions/caption_mixin.py +92 -0
- notionary/blocks/models.py +174 -0
- notionary/blocks/numbered_list/__init__.py +12 -2
- notionary/blocks/numbered_list/numbered_list_element.py +48 -56
- notionary/blocks/numbered_list/numbered_list_markdown_node.py +3 -1
- notionary/blocks/numbered_list/numbered_list_models.py +17 -0
- notionary/blocks/paragraph/__init__.py +12 -2
- notionary/blocks/paragraph/paragraph_element.py +40 -66
- notionary/blocks/paragraph/paragraph_markdown_node.py +2 -1
- notionary/blocks/paragraph/paragraph_models.py +16 -0
- notionary/blocks/pdf/__init__.py +13 -0
- notionary/blocks/pdf/pdf_element.py +97 -0
- notionary/blocks/pdf/pdf_markdown_node.py +37 -0
- notionary/blocks/pdf/pdf_models.py +11 -0
- notionary/blocks/quote/__init__.py +11 -2
- notionary/blocks/quote/quote_element.py +45 -62
- notionary/blocks/quote/quote_markdown_node.py +6 -3
- notionary/blocks/quote/quote_models.py +18 -0
- notionary/blocks/registry/__init__.py +4 -0
- notionary/blocks/registry/block_registry.py +60 -121
- notionary/blocks/registry/block_registry_builder.py +115 -59
- notionary/blocks/rich_text/__init__.py +33 -0
- notionary/blocks/rich_text/name_to_id_resolver.py +205 -0
- notionary/blocks/rich_text/rich_text_models.py +221 -0
- notionary/blocks/rich_text/text_inline_formatter.py +456 -0
- notionary/blocks/syntax_prompt_builder.py +137 -0
- notionary/blocks/table/__init__.py +16 -2
- notionary/blocks/table/table_element.py +136 -228
- notionary/blocks/table/table_markdown_node.py +2 -1
- notionary/blocks/table/table_models.py +28 -0
- notionary/blocks/table_of_contents/__init__.py +19 -0
- notionary/blocks/table_of_contents/table_of_contents_element.py +68 -0
- notionary/blocks/table_of_contents/table_of_contents_markdown_node.py +35 -0
- notionary/blocks/table_of_contents/table_of_contents_models.py +18 -0
- notionary/blocks/todo/__init__.py +9 -2
- notionary/blocks/todo/todo_element.py +52 -92
- notionary/blocks/todo/todo_markdown_node.py +2 -1
- notionary/blocks/todo/todo_models.py +19 -0
- notionary/blocks/toggle/__init__.py +13 -3
- notionary/blocks/toggle/toggle_element.py +69 -260
- notionary/blocks/toggle/toggle_markdown_node.py +25 -15
- notionary/blocks/toggle/toggle_models.py +17 -0
- notionary/blocks/toggleable_heading/__init__.py +6 -2
- notionary/blocks/toggleable_heading/toggleable_heading_element.py +86 -241
- notionary/blocks/toggleable_heading/toggleable_heading_markdown_node.py +26 -18
- notionary/blocks/types.py +130 -0
- notionary/blocks/video/__init__.py +8 -2
- notionary/blocks/video/video_element.py +70 -141
- notionary/blocks/video/video_element_models.py +10 -0
- notionary/blocks/video/video_markdown_node.py +13 -6
- notionary/database/client.py +26 -8
- notionary/database/database.py +13 -14
- notionary/database/database_filter_builder.py +2 -2
- notionary/database/database_provider.py +5 -4
- notionary/database/models.py +337 -0
- notionary/database/notion_database.py +6 -7
- notionary/file_upload/client.py +5 -7
- notionary/file_upload/models.py +3 -2
- notionary/file_upload/notion_file_upload.py +2 -3
- notionary/markdown/markdown_builder.py +729 -0
- notionary/markdown/markdown_document_model.py +228 -0
- notionary/{blocks → markdown}/markdown_node.py +1 -0
- notionary/models/notion_database_response.py +0 -338
- notionary/page/client.py +34 -15
- notionary/page/models.py +327 -0
- notionary/page/notion_page.py +136 -58
- notionary/page/{content/page_content_writer.py → page_content_deleting_service.py} +25 -59
- notionary/page/page_content_writer.py +177 -0
- notionary/page/page_context.py +65 -0
- notionary/page/reader/handler/__init__.py +19 -0
- notionary/page/reader/handler/base_block_renderer.py +44 -0
- notionary/page/reader/handler/block_processing_context.py +35 -0
- notionary/page/reader/handler/block_rendering_context.py +48 -0
- notionary/page/reader/handler/column_list_renderer.py +51 -0
- notionary/page/reader/handler/column_renderer.py +60 -0
- notionary/page/reader/handler/line_renderer.py +73 -0
- notionary/page/reader/handler/numbered_list_renderer.py +85 -0
- notionary/page/reader/handler/toggle_renderer.py +69 -0
- notionary/page/reader/handler/toggleable_heading_renderer.py +89 -0
- notionary/page/reader/page_content_retriever.py +81 -0
- notionary/page/search_filter_builder.py +2 -1
- notionary/page/writer/handler/__init__.py +24 -0
- notionary/page/writer/handler/code_handler.py +72 -0
- notionary/page/writer/handler/column_handler.py +141 -0
- notionary/page/writer/handler/column_list_handler.py +139 -0
- notionary/page/writer/handler/equation_handler.py +74 -0
- notionary/page/writer/handler/line_handler.py +35 -0
- notionary/page/writer/handler/line_processing_context.py +54 -0
- notionary/page/writer/handler/regular_line_handler.py +86 -0
- notionary/page/writer/handler/table_handler.py +66 -0
- notionary/page/writer/handler/toggle_handler.py +155 -0
- notionary/page/writer/handler/toggleable_heading_handler.py +173 -0
- notionary/page/writer/markdown_to_notion_converter.py +95 -0
- notionary/page/writer/markdown_to_notion_converter_context.py +30 -0
- notionary/page/writer/markdown_to_notion_formatting_post_processor.py +73 -0
- notionary/page/writer/notion_text_length_processor.py +150 -0
- notionary/telemetry/__init__.py +2 -2
- notionary/telemetry/service.py +3 -3
- notionary/user/__init__.py +2 -2
- notionary/user/base_notion_user.py +2 -1
- notionary/user/client.py +2 -3
- notionary/user/models.py +1 -0
- notionary/user/notion_bot_user.py +4 -5
- notionary/user/notion_user.py +3 -4
- notionary/user/notion_user_manager.py +23 -95
- notionary/util/__init__.py +3 -2
- notionary/util/fuzzy.py +2 -1
- notionary/util/logging_mixin.py +2 -2
- notionary/util/singleton_metaclass.py +1 -1
- notionary/workspace.py +6 -5
- notionary-0.2.22.dist-info/METADATA +237 -0
- notionary-0.2.22.dist-info/RECORD +200 -0
- notionary/blocks/document/__init__.py +0 -7
- notionary/blocks/document/document_element.py +0 -102
- notionary/blocks/document/document_markdown_node.py +0 -31
- notionary/blocks/image/__init__.py +0 -7
- notionary/blocks/image/image_element.py +0 -151
- notionary/blocks/markdown_builder.py +0 -356
- notionary/blocks/mention/__init__.py +0 -7
- notionary/blocks/mention/mention_element.py +0 -229
- notionary/blocks/mention/mention_markdown_node.py +0 -38
- notionary/blocks/prompts/element_prompt_builder.py +0 -83
- notionary/blocks/prompts/element_prompt_content.py +0 -41
- notionary/blocks/shared/models.py +0 -713
- notionary/blocks/shared/notion_block_element.py +0 -37
- notionary/blocks/shared/text_inline_formatter.py +0 -262
- notionary/blocks/shared/text_inline_formatter_new.py +0 -139
- notionary/database/models/page_result.py +0 -10
- notionary/models/notion_block_response.py +0 -264
- notionary/models/notion_page_response.py +0 -78
- notionary/models/search_response.py +0 -0
- notionary/page/__init__.py +0 -0
- notionary/page/content/markdown_whitespace_processor.py +0 -80
- notionary/page/content/notion_text_length_utils.py +0 -87
- notionary/page/content/page_content_retriever.py +0 -60
- notionary/page/formatting/line_processor.py +0 -153
- notionary/page/formatting/markdown_to_notion_converter.py +0 -153
- notionary/page/markdown_syntax_prompt_generator.py +0 -114
- notionary/page/notion_to_markdown_converter.py +0 -179
- notionary/page/properites/property_value_extractor.py +0 -0
- notionary/user/notion_user_provider.py +0 -1
- notionary-0.2.19.dist-info/METADATA +0 -225
- notionary-0.2.19.dist-info/RECORD +0 -150
- /notionary/{blocks/document/document_models.py → markdown/___init__.py} +0 -0
- /notionary/{blocks/image/image_models.py → markdown/makdown_document_model.py} +0 -0
- /notionary/{blocks/mention/mention_models.py → page/reader/handler/equation_renderer.py} +0 -0
- /notionary/{blocks/shared/__init__.py → page/writer/markdown_to_notion_post_processor.py} +0 -0
- /notionary/{blocks/toggleable_heading/toggleable_heading_models.py → page/writer/markdown_to_notion_text_length_post_processor.py} +0 -0
- /notionary/{elements/__init__.py → util/concurrency_limiter.py} +0 -0
- {notionary-0.2.19.dist-info → notionary-0.2.22.dist-info}/LICENSE +0 -0
- {notionary-0.2.19.dist-info → notionary-0.2.22.dist-info}/WHEEL +0 -0
@@ -1,356 +0,0 @@
|
|
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
|
-
)
|
@@ -1,229 +0,0 @@
|
|
1
|
-
import re
|
2
|
-
from typing import Dict, Any, Optional, List
|
3
|
-
|
4
|
-
from notionary.blocks import NotionBlockElement
|
5
|
-
from notionary.blocks import (
|
6
|
-
ElementPromptContent,
|
7
|
-
ElementPromptBuilder,
|
8
|
-
NotionBlockResult,
|
9
|
-
)
|
10
|
-
|
11
|
-
|
12
|
-
class MentionElement(NotionBlockElement):
|
13
|
-
"""
|
14
|
-
Handles conversion between Markdown mentions and Notion mention elements.
|
15
|
-
|
16
|
-
Markdown mention syntax:
|
17
|
-
- @[page-id] - Mention a page by its ID
|
18
|
-
- @date[YYYY-MM-DD] - Mention a date
|
19
|
-
- @db[database-id] - Mention a database by its ID
|
20
|
-
"""
|
21
|
-
|
22
|
-
MENTION_TYPES = {
|
23
|
-
"page": {
|
24
|
-
"pattern": r"@\[([0-9a-f-]+)\]",
|
25
|
-
"create_mention": lambda id_value: {
|
26
|
-
"type": "mention",
|
27
|
-
"mention": {"type": "page", "page": {"id": id_value}},
|
28
|
-
},
|
29
|
-
"get_plain_text": lambda mention: f"Page {mention['mention']['page']['id']}",
|
30
|
-
"to_markdown": lambda mention: f"@[{mention['mention']['page']['id']}]",
|
31
|
-
},
|
32
|
-
"date": {
|
33
|
-
"pattern": r"@date\[(\d{4}-\d{2}-\d{2})\]",
|
34
|
-
"create_mention": lambda date_value: {
|
35
|
-
"type": "mention",
|
36
|
-
"mention": {"type": "date", "date": {"start": date_value, "end": None}},
|
37
|
-
},
|
38
|
-
"get_plain_text": lambda mention: mention["mention"]["date"]["start"],
|
39
|
-
"to_markdown": lambda mention: f"@date[{mention['mention']['date']['start']}]",
|
40
|
-
},
|
41
|
-
"database": {
|
42
|
-
"pattern": r"@db\[([0-9a-f-]+)\]",
|
43
|
-
"create_mention": lambda db_id: {
|
44
|
-
"type": "mention",
|
45
|
-
"mention": {"type": "database", "database": {"id": db_id}},
|
46
|
-
},
|
47
|
-
"get_plain_text": lambda mention: f"Database {mention['mention']['database']['id']}",
|
48
|
-
"to_markdown": lambda mention: f"@db[{mention['mention']['database']['id']}]",
|
49
|
-
},
|
50
|
-
}
|
51
|
-
|
52
|
-
@classmethod
|
53
|
-
def match_markdown(cls, text: str) -> bool:
|
54
|
-
"""Check if text contains a markdown mention."""
|
55
|
-
for mention_type in MentionElement.MENTION_TYPES.values():
|
56
|
-
if re.search(mention_type["pattern"], text):
|
57
|
-
return True
|
58
|
-
return False
|
59
|
-
|
60
|
-
@classmethod
|
61
|
-
def match_notion(cls, block: Dict[str, Any]) -> bool:
|
62
|
-
"""Check if block contains a mention."""
|
63
|
-
supported_block_types = [
|
64
|
-
"paragraph",
|
65
|
-
"heading_1",
|
66
|
-
"heading_2",
|
67
|
-
"heading_3",
|
68
|
-
"bulleted_list_item",
|
69
|
-
"numbered_list_item",
|
70
|
-
]
|
71
|
-
|
72
|
-
if block.get("type") not in supported_block_types:
|
73
|
-
return False
|
74
|
-
|
75
|
-
block_content = block.get(block.get("type"), {})
|
76
|
-
rich_text = block_content.get("rich_text", [])
|
77
|
-
|
78
|
-
return any(text_item.get("type") == "mention" for text_item in rich_text)
|
79
|
-
|
80
|
-
@classmethod
|
81
|
-
def markdown_to_notion(cls, text: str) -> NotionBlockResult:
|
82
|
-
"""Convert markdown text with mentions to a Notion paragraph block."""
|
83
|
-
if not MentionElement.match_markdown(text):
|
84
|
-
return None
|
85
|
-
|
86
|
-
rich_text = MentionElement._process_markdown_with_mentions(text)
|
87
|
-
|
88
|
-
return {
|
89
|
-
"type": "paragraph",
|
90
|
-
"paragraph": {"rich_text": rich_text, "color": "default"},
|
91
|
-
}
|
92
|
-
|
93
|
-
@classmethod
|
94
|
-
def _process_markdown_with_mentions(cls, text: str) -> List[Dict[str, Any]]:
|
95
|
-
"""Convert markdown mentions to Notion rich_text format."""
|
96
|
-
mentions = []
|
97
|
-
|
98
|
-
for mention_type, config in MentionElement.MENTION_TYPES.items():
|
99
|
-
for match in re.finditer(config["pattern"], text):
|
100
|
-
mentions.append(
|
101
|
-
{
|
102
|
-
"start": match.start(),
|
103
|
-
"end": match.end(),
|
104
|
-
"type": mention_type,
|
105
|
-
"value": match.group(1),
|
106
|
-
"original": match.group(0),
|
107
|
-
}
|
108
|
-
)
|
109
|
-
|
110
|
-
mentions.sort(key=lambda m: m["start"])
|
111
|
-
|
112
|
-
# Build rich_text list
|
113
|
-
rich_text = []
|
114
|
-
position = 0
|
115
|
-
|
116
|
-
for mention in mentions:
|
117
|
-
if mention["start"] > position:
|
118
|
-
rich_text.append(
|
119
|
-
MentionElement._create_text_item(text[position : mention["start"]])
|
120
|
-
)
|
121
|
-
|
122
|
-
# Add the mention
|
123
|
-
mention_obj = MentionElement.MENTION_TYPES[mention["type"]][
|
124
|
-
"create_mention"
|
125
|
-
](mention["value"])
|
126
|
-
|
127
|
-
# Add annotations and plain text
|
128
|
-
mention_obj["annotations"] = MentionElement._default_annotations()
|
129
|
-
mention_obj["plain_text"] = MentionElement.MENTION_TYPES[mention["type"]][
|
130
|
-
"get_plain_text"
|
131
|
-
](mention_obj)
|
132
|
-
|
133
|
-
rich_text.append(mention_obj)
|
134
|
-
position = mention["end"]
|
135
|
-
|
136
|
-
# Add remaining text if any
|
137
|
-
if position < len(text):
|
138
|
-
rich_text.append(MentionElement._create_text_item(text[position:]))
|
139
|
-
|
140
|
-
return rich_text
|
141
|
-
|
142
|
-
@classmethod
|
143
|
-
def _create_text_item(cls, content: str) -> Dict[str, Any]:
|
144
|
-
"""Create a text item with default annotations."""
|
145
|
-
text_item = {
|
146
|
-
"type": "text",
|
147
|
-
"text": {"content": content, "link": None},
|
148
|
-
"annotations": MentionElement._default_annotations(),
|
149
|
-
"plain_text": content,
|
150
|
-
}
|
151
|
-
return text_item
|
152
|
-
|
153
|
-
@classmethod
|
154
|
-
def _default_annotations(cls) -> Dict[str, Any]:
|
155
|
-
"""Return default annotations for rich text."""
|
156
|
-
return {
|
157
|
-
"bold": False,
|
158
|
-
"italic": False,
|
159
|
-
"strikethrough": False,
|
160
|
-
"underline": False,
|
161
|
-
"code": False,
|
162
|
-
"color": "default",
|
163
|
-
}
|
164
|
-
|
165
|
-
@classmethod
|
166
|
-
def notion_to_markdown(cls, block: Dict[str, Any]) -> Optional[str]:
|
167
|
-
"""Extract mentions from Notion block and convert to markdown format."""
|
168
|
-
block_type = block.get("type")
|
169
|
-
if not block_type or block_type not in block:
|
170
|
-
return None
|
171
|
-
|
172
|
-
block_content = block.get(block_type, {})
|
173
|
-
rich_text = block_content.get("rich_text", [])
|
174
|
-
|
175
|
-
processed_text = MentionElement._process_rich_text_with_mentions(rich_text)
|
176
|
-
|
177
|
-
if processed_text:
|
178
|
-
return processed_text
|
179
|
-
|
180
|
-
return None
|
181
|
-
|
182
|
-
@classmethod
|
183
|
-
def _process_rich_text_with_mentions(cls, rich_text: List[Dict[str, Any]]) -> str:
|
184
|
-
"""Convert rich text with mentions to markdown string."""
|
185
|
-
result = []
|
186
|
-
|
187
|
-
for item in rich_text:
|
188
|
-
if item.get("type") == "mention":
|
189
|
-
mention = item.get("mention", {})
|
190
|
-
mention_type = mention.get("type")
|
191
|
-
|
192
|
-
if mention_type in MentionElement.MENTION_TYPES:
|
193
|
-
result.append(
|
194
|
-
MentionElement.MENTION_TYPES[mention_type]["to_markdown"](item)
|
195
|
-
)
|
196
|
-
else:
|
197
|
-
result.append(item.get("plain_text", "@[unknown]"))
|
198
|
-
else:
|
199
|
-
result.append(item.get("plain_text", ""))
|
200
|
-
|
201
|
-
return "".join(result)
|
202
|
-
|
203
|
-
@classmethod
|
204
|
-
def is_multiline(cls) -> bool:
|
205
|
-
return False
|
206
|
-
|
207
|
-
@classmethod
|
208
|
-
def get_llm_prompt_content(cls) -> ElementPromptContent:
|
209
|
-
"""
|
210
|
-
Returns structured LLM prompt metadata for the mention element.
|
211
|
-
"""
|
212
|
-
return (
|
213
|
-
ElementPromptBuilder()
|
214
|
-
.with_description(
|
215
|
-
"References to Notion pages, databases, or dates within text content."
|
216
|
-
)
|
217
|
-
.with_usage_guidelines(
|
218
|
-
"When you want to link to other Notion content within your text."
|
219
|
-
)
|
220
|
-
.with_syntax("@[page-id]")
|
221
|
-
.with_examples(
|
222
|
-
[
|
223
|
-
"Check the meeting notes at @[1a6389d5-7bd3-80c5-9a87-e90b034989d0]",
|
224
|
-
"Deadline is @date[2023-12-31]",
|
225
|
-
"Use the structure in @db[1a6389d5-7bd3-80e9-b199-000cfb3fa0b3]",
|
226
|
-
]
|
227
|
-
)
|
228
|
-
.build()
|
229
|
-
)
|
@@ -1,38 +0,0 @@
|
|
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}]"
|