notionary 0.1.18__tar.gz → 0.1.20__tar.gz
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-0.1.18 → notionary-0.1.20}/PKG-INFO +3 -8
- {notionary-0.1.18 → notionary-0.1.20}/README.md +2 -2
- {notionary-0.1.18 → notionary-0.1.20}/notionary/__init__.py +2 -2
- {notionary-0.1.18 → notionary-0.1.20}/notionary/database/notion_database.py +3 -1
- {notionary-0.1.18 → notionary-0.1.20}/notionary/elements/audio_element.py +7 -11
- {notionary-0.1.18 → notionary-0.1.20}/notionary/elements/bookmark_element.py +17 -29
- notionary-0.1.20/notionary/elements/bulleted_list_element.py +69 -0
- notionary-0.1.20/notionary/elements/callout_element.py +117 -0
- {notionary-0.1.18 → notionary-0.1.20}/notionary/elements/code_block_element.py +15 -10
- {notionary-0.1.18 → notionary-0.1.20}/notionary/elements/column_element.py +39 -26
- notionary-0.1.20/notionary/elements/divider_element.py +56 -0
- {notionary-0.1.18 → notionary-0.1.20}/notionary/elements/embed_element.py +10 -18
- {notionary-0.1.18 → notionary-0.1.20}/notionary/elements/heading_element.py +10 -12
- {notionary-0.1.18 → notionary-0.1.20}/notionary/elements/image_element.py +9 -15
- {notionary-0.1.18 → notionary-0.1.20}/notionary/elements/mention_element.py +6 -15
- {notionary-0.1.18 → notionary-0.1.20}/notionary/elements/notion_block_element.py +12 -11
- notionary-0.1.20/notionary/elements/numbered_list_element.py +68 -0
- {notionary-0.1.18 → notionary-0.1.20}/notionary/elements/paragraph_element.py +11 -7
- notionary-0.1.20/notionary/elements/prompts/element_prompt_content.py +20 -0
- notionary-0.1.20/notionary/elements/prompts/synthax_prompt_builder.py +92 -0
- {notionary-0.1.18 → notionary-0.1.20}/notionary/elements/qoute_element.py +20 -89
- notionary-0.1.20/notionary/elements/registry/block_element_registry.py +90 -0
- {notionary-0.1.18/notionary/elements → notionary-0.1.20/notionary/elements/registry}/block_element_registry_builder.py +15 -124
- {notionary-0.1.18 → notionary-0.1.20}/notionary/elements/table_element.py +4 -16
- {notionary-0.1.18 → notionary-0.1.20}/notionary/elements/text_inline_formatter.py +15 -78
- {notionary-0.1.18 → notionary-0.1.20}/notionary/elements/todo_lists.py +14 -18
- {notionary-0.1.18 → notionary-0.1.20}/notionary/elements/toggle_element.py +19 -1
- {notionary-0.1.18 → notionary-0.1.20}/notionary/elements/video_element.py +10 -19
- {notionary-0.1.18 → notionary-0.1.20}/notionary/notion_client.py +2 -2
- {notionary-0.1.18 → notionary-0.1.20}/notionary/page/content/page_content_manager.py +2 -3
- {notionary-0.1.18 → notionary-0.1.20}/notionary/page/markdown_to_notion_converter.py +3 -3
- {notionary-0.1.18 → notionary-0.1.20}/notionary/page/notion_page.py +3 -3
- {notionary-0.1.18 → notionary-0.1.20}/notionary/page/notion_to_markdown_converter.py +3 -3
- {notionary-0.1.18 → notionary-0.1.20}/notionary/page/relations/notion_page_relation_manager.py +24 -24
- {notionary-0.1.18 → notionary-0.1.20}/notionary/page/relations/notion_page_title_resolver.py +1 -2
- {notionary-0.1.18 → notionary-0.1.20}/notionary.egg-info/PKG-INFO +3 -8
- {notionary-0.1.18 → notionary-0.1.20}/notionary.egg-info/SOURCES.txt +6 -3
- notionary-0.1.20/notionary.egg-info/requires.txt +2 -0
- {notionary-0.1.18 → notionary-0.1.20}/setup.py +1 -6
- notionary-0.1.18/notionary/elements/block_element_registry.py +0 -233
- notionary-0.1.18/notionary/elements/callout_element.py +0 -179
- notionary-0.1.18/notionary/elements/divider_element.py +0 -73
- notionary-0.1.18/notionary/elements/list_element.py +0 -130
- notionary-0.1.18/notionary.egg-info/requires.txt +0 -7
- {notionary-0.1.18 → notionary-0.1.20}/LICENSE +0 -0
- {notionary-0.1.18 → notionary-0.1.20}/notionary/database/database_discovery.py +0 -0
- {notionary-0.1.18 → notionary-0.1.20}/notionary/database/database_info_service.py +0 -0
- {notionary-0.1.18 → notionary-0.1.20}/notionary/database/models/page_result.py +0 -0
- {notionary-0.1.18 → notionary-0.1.20}/notionary/database/notion_database_factory.py +0 -0
- {notionary-0.1.18 → notionary-0.1.20}/notionary/exceptions/database_exceptions.py +0 -0
- {notionary-0.1.18 → notionary-0.1.20}/notionary/exceptions/page_creation_exception.py +0 -0
- {notionary-0.1.18 → notionary-0.1.20}/notionary/page/content/notion_page_content_chunker.py +0 -0
- {notionary-0.1.18 → notionary-0.1.20}/notionary/page/metadata/metadata_editor.py +0 -0
- {notionary-0.1.18 → notionary-0.1.20}/notionary/page/metadata/notion_icon_manager.py +0 -0
- {notionary-0.1.18 → notionary-0.1.20}/notionary/page/metadata/notion_page_cover_manager.py +0 -0
- {notionary-0.1.18 → notionary-0.1.20}/notionary/page/notion_page_factory.py +0 -0
- {notionary-0.1.18 → notionary-0.1.20}/notionary/page/properites/database_property_service.py +0 -0
- {notionary-0.1.18 → notionary-0.1.20}/notionary/page/properites/page_property_manager.py +0 -0
- {notionary-0.1.18 → notionary-0.1.20}/notionary/page/properites/property_formatter.py +0 -0
- {notionary-0.1.18 → notionary-0.1.20}/notionary/page/properites/property_operation_result.py +0 -0
- {notionary-0.1.18 → notionary-0.1.20}/notionary/page/properites/property_value_extractor.py +0 -0
- {notionary-0.1.18 → notionary-0.1.20}/notionary/page/relations/page_database_relation.py +0 -0
- {notionary-0.1.18 → notionary-0.1.20}/notionary/page/relations/relation_operation_result.py +0 -0
- {notionary-0.1.18 → notionary-0.1.20}/notionary/util/logging_mixin.py +0 -0
- {notionary-0.1.18 → notionary-0.1.20}/notionary/util/page_id_utils.py +0 -0
- {notionary-0.1.18 → notionary-0.1.20}/notionary/util/singleton_decorator.py +0 -0
- {notionary-0.1.18 → notionary-0.1.20}/notionary.egg-info/dependency_links.txt +0 -0
- {notionary-0.1.18 → notionary-0.1.20}/notionary.egg-info/top_level.txt +0 -0
- {notionary-0.1.18 → notionary-0.1.20}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: notionary
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.20
|
4
4
|
Summary: A toolkit to convert between Markdown and Notion blocks
|
5
5
|
Home-page: https://github.com/mathisarends/notionary
|
6
6
|
Author: Mathis Arends
|
@@ -10,13 +10,8 @@ Classifier: License :: OSI Approved :: MIT License
|
|
10
10
|
Requires-Python: >=3.7
|
11
11
|
Description-Content-Type: text/markdown
|
12
12
|
License-File: LICENSE
|
13
|
-
Requires-Dist: notion-client>=2.0.0
|
14
|
-
Requires-Dist: markdown-it-py>=3.0.0
|
15
|
-
Requires-Dist: beautifulsoup4>=4.13.0
|
16
13
|
Requires-Dist: httpx>=0.28.0
|
17
14
|
Requires-Dist: python-dotenv>=1.1.0
|
18
|
-
Requires-Dist: lxml>=5.3.0
|
19
|
-
Requires-Dist: attrs>=25.3.0
|
20
15
|
Dynamic: author
|
21
16
|
Dynamic: author-email
|
22
17
|
Dynamic: classifier
|
@@ -194,7 +189,7 @@ from notionary import NotionPage
|
|
194
189
|
from notionary.elements.block_element_registry_builder import BlockElementRegistryBuilder
|
195
190
|
|
196
191
|
# Create a registry with standard Notion elements
|
197
|
-
registry = BlockElementRegistryBuilder.
|
192
|
+
registry = BlockElementRegistryBuilder.create_full_registry()
|
198
193
|
|
199
194
|
# Or build a custom registry with only the elements you need
|
200
195
|
custom_registry = (
|
@@ -225,7 +220,7 @@ Notionary can automatically generate comprehensive system prompts for LLMs to un
|
|
225
220
|
```python
|
226
221
|
from notionary.elements.block_element_registry_builder import BlockElementRegistryBuilder
|
227
222
|
|
228
|
-
registry = BlockElementRegistryBuilder.
|
223
|
+
registry = BlockElementRegistryBuilder.create_full_registry()
|
229
224
|
llm_system_prompt = registry.generate_llm_prompt()
|
230
225
|
|
231
226
|
# Use this prompt with your LLM to generate Notion-compatible Markdown
|
@@ -164,7 +164,7 @@ from notionary import NotionPage
|
|
164
164
|
from notionary.elements.block_element_registry_builder import BlockElementRegistryBuilder
|
165
165
|
|
166
166
|
# Create a registry with standard Notion elements
|
167
|
-
registry = BlockElementRegistryBuilder.
|
167
|
+
registry = BlockElementRegistryBuilder.create_full_registry()
|
168
168
|
|
169
169
|
# Or build a custom registry with only the elements you need
|
170
170
|
custom_registry = (
|
@@ -195,7 +195,7 @@ Notionary can automatically generate comprehensive system prompts for LLMs to un
|
|
195
195
|
```python
|
196
196
|
from notionary.elements.block_element_registry_builder import BlockElementRegistryBuilder
|
197
197
|
|
198
|
-
registry = BlockElementRegistryBuilder.
|
198
|
+
registry = BlockElementRegistryBuilder.create_full_registry()
|
199
199
|
llm_system_prompt = registry.generate_llm_prompt()
|
200
200
|
|
201
201
|
# Use this prompt with your LLM to generate Notion-compatible Markdown
|
@@ -7,8 +7,8 @@ from .database.database_discovery import DatabaseDiscovery
|
|
7
7
|
from .page.notion_page import NotionPage
|
8
8
|
from .page.notion_page_factory import NotionPageFactory
|
9
9
|
|
10
|
-
from .elements.block_element_registry import BlockElementRegistry
|
11
|
-
from .elements.block_element_registry_builder import (
|
10
|
+
from .elements.registry.block_element_registry import BlockElementRegistry
|
11
|
+
from .elements.registry.block_element_registry_builder import (
|
12
12
|
BlockElementRegistryBuilder,
|
13
13
|
)
|
14
14
|
|
@@ -230,7 +230,9 @@ class NotionDatabase(LoggingMixin):
|
|
230
230
|
if response and "last_edited_time" in response:
|
231
231
|
return response["last_edited_time"]
|
232
232
|
|
233
|
-
self.logger.warning(
|
233
|
+
self.logger.warning(
|
234
|
+
"Could not retrieve last_edited_time for database %s", self.database_id
|
235
|
+
)
|
234
236
|
return None
|
235
237
|
|
236
238
|
except Exception as e:
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import re
|
2
2
|
from typing import Dict, Any, Optional, List
|
3
3
|
from notionary.elements.notion_block_element import NotionBlockElement
|
4
|
+
from notionary.elements.prompts.element_prompt_content import ElementPromptContent
|
4
5
|
|
5
6
|
|
6
7
|
class AudioElement(NotionBlockElement):
|
@@ -121,23 +122,18 @@ class AudioElement(NotionBlockElement):
|
|
121
122
|
return result
|
122
123
|
|
123
124
|
@classmethod
|
124
|
-
def get_llm_prompt_content(cls) ->
|
125
|
+
def get_llm_prompt_content(cls) -> ElementPromptContent:
|
125
126
|
"""Returns information for LLM prompts about this element."""
|
126
127
|
return {
|
127
128
|
"description": "Embeds audio content from external sources like CDNs or direct audio URLs.",
|
128
|
-
"
|
129
|
-
"syntax": [
|
130
|
-
"$[](https://example.com/audio.mp3) - Audio without caption",
|
131
|
-
"$[Caption text](https://example.com/audio.mp3) - Audio with caption",
|
132
|
-
],
|
133
|
-
"supported_sources": [
|
134
|
-
"Direct links to audio files (.mp3, .wav, .ogg, etc.)",
|
135
|
-
"Google Cloud Storage links (storage.googleapis.com)",
|
136
|
-
"Other audio hosting platforms supported by Notion",
|
137
|
-
],
|
129
|
+
"syntax": "$[Caption](https://example.com/audio.mp3)",
|
138
130
|
"examples": [
|
139
131
|
"$[Podcast Episode](https://storage.googleapis.com/audio_summaries/ep_ai_summary_127d02ec-ca12-4312-a5ed-cb14b185480c.mp3)",
|
140
132
|
"$[Voice recording](https://example.com/audio/recording.mp3)",
|
141
133
|
"$[](https://storage.googleapis.com/audio_summaries/example.mp3)",
|
142
134
|
],
|
135
|
+
"when_to_use": (
|
136
|
+
"Use audio embeds when you want to include audio content directly in your document. "
|
137
|
+
"Audio embeds are useful for podcasts, music, voice recordings, or any content that benefits from audio explanation."
|
138
|
+
),
|
143
139
|
}
|
@@ -1,8 +1,8 @@
|
|
1
1
|
import re
|
2
2
|
from typing import Dict, Any, Optional, List, Tuple
|
3
|
-
from typing_extensions import override
|
4
3
|
|
5
4
|
from notionary.elements.notion_block_element import NotionBlockElement
|
5
|
+
from notionary.elements.prompts.element_prompt_content import ElementPromptContent
|
6
6
|
|
7
7
|
|
8
8
|
class BookmarkElement(NotionBlockElement):
|
@@ -29,7 +29,6 @@ class BookmarkElement(NotionBlockElement):
|
|
29
29
|
+ r"\)$" # closing parenthesis
|
30
30
|
)
|
31
31
|
|
32
|
-
@override
|
33
32
|
@staticmethod
|
34
33
|
def match_markdown(text: str) -> bool:
|
35
34
|
"""Check if text is a markdown bookmark."""
|
@@ -37,14 +36,11 @@ class BookmarkElement(NotionBlockElement):
|
|
37
36
|
BookmarkElement.PATTERN.match(text.strip())
|
38
37
|
)
|
39
38
|
|
40
|
-
@override
|
41
39
|
@staticmethod
|
42
40
|
def match_notion(block: Dict[str, Any]) -> bool:
|
43
41
|
"""Check if block is a Notion bookmark."""
|
44
|
-
# Handle both standard "bookmark" type and "external-bookmark" type
|
45
42
|
return block.get("type") in ["bookmark", "external-bookmark"]
|
46
43
|
|
47
|
-
@override
|
48
44
|
@staticmethod
|
49
45
|
def markdown_to_notion(text: str) -> Optional[Dict[str, Any]]:
|
50
46
|
"""Convert markdown bookmark to Notion bookmark block."""
|
@@ -124,7 +120,6 @@ class BookmarkElement(NotionBlockElement):
|
|
124
120
|
|
125
121
|
return {"type": "bookmark", "bookmark": bookmark_data}
|
126
122
|
|
127
|
-
@override
|
128
123
|
@staticmethod
|
129
124
|
def notion_to_markdown(block: Dict[str, Any]) -> Optional[str]:
|
130
125
|
"""Convert Notion bookmark block to markdown bookmark."""
|
@@ -133,13 +128,10 @@ class BookmarkElement(NotionBlockElement):
|
|
133
128
|
if block_type == "bookmark":
|
134
129
|
bookmark_data = block.get("bookmark", {})
|
135
130
|
elif block_type == "external-bookmark":
|
136
|
-
# Handle external-bookmark type
|
137
|
-
# Extract URL from the external-bookmark structure
|
138
131
|
url = block.get("url", "")
|
139
132
|
if not url:
|
140
133
|
return None
|
141
134
|
|
142
|
-
# For external bookmarks, create a simple bookmark format
|
143
135
|
return f"[bookmark]({url})"
|
144
136
|
else:
|
145
137
|
return None
|
@@ -160,12 +152,12 @@ class BookmarkElement(NotionBlockElement):
|
|
160
152
|
|
161
153
|
if title and description:
|
162
154
|
return f'[bookmark]({url} "{title}" "{description}")'
|
163
|
-
|
155
|
+
|
156
|
+
if title:
|
164
157
|
return f'[bookmark]({url} "{title}")'
|
165
|
-
else:
|
166
|
-
return f"[bookmark]({url})"
|
167
158
|
|
168
|
-
|
159
|
+
return f"[bookmark]({url})"
|
160
|
+
|
169
161
|
@staticmethod
|
170
162
|
def is_multiline() -> bool:
|
171
163
|
"""Bookmarks are single-line elements."""
|
@@ -191,34 +183,30 @@ class BookmarkElement(NotionBlockElement):
|
|
191
183
|
if not caption:
|
192
184
|
return "", ""
|
193
185
|
|
194
|
-
# Extract the full text content from caption
|
195
186
|
full_text = BookmarkElement._extract_text_content(caption)
|
196
187
|
|
197
|
-
# Check if the text contains a separator
|
198
188
|
if " - " in full_text:
|
199
189
|
parts = full_text.split(" - ", 1)
|
200
190
|
return parts[0].strip(), parts[1].strip()
|
201
|
-
else:
|
202
|
-
# If no separator, assume the whole content is the title
|
203
|
-
return full_text.strip(), ""
|
204
191
|
|
205
|
-
|
192
|
+
return full_text.strip(), ""
|
193
|
+
|
206
194
|
@classmethod
|
207
|
-
def get_llm_prompt_content(cls) ->
|
195
|
+
def get_llm_prompt_content(cls) -> ElementPromptContent:
|
208
196
|
"""
|
209
|
-
Returns
|
210
|
-
Includes description, usage guidance, syntax options, and examples.
|
197
|
+
Returns structured LLM prompt metadata for the bookmark element.
|
211
198
|
"""
|
212
199
|
return {
|
213
200
|
"description": "Creates a bookmark that links to an external website.",
|
214
|
-
"when_to_use":
|
215
|
-
|
216
|
-
"
|
217
|
-
|
218
|
-
|
219
|
-
],
|
201
|
+
"when_to_use": (
|
202
|
+
"Use bookmarks when you want to reference external content while keeping the page clean and organized. "
|
203
|
+
"Bookmarks display a preview card for the linked content."
|
204
|
+
),
|
205
|
+
"syntax": '[bookmark](https://example.com "Optional Title" "Optional Description")',
|
220
206
|
"examples": [
|
221
|
-
|
207
|
+
"[bookmark](https://example.com)",
|
208
|
+
'[bookmark](https://example.com "Example Title")',
|
209
|
+
'[bookmark](https://example.com "Example Title" "Example description of the site")',
|
222
210
|
'[bookmark](https://github.com "GitHub" "Where the world builds software")',
|
223
211
|
],
|
224
212
|
}
|
@@ -0,0 +1,69 @@
|
|
1
|
+
import re
|
2
|
+
from typing import Dict, Any, Optional
|
3
|
+
from typing_extensions import override
|
4
|
+
from notionary.elements.notion_block_element import NotionBlockElement
|
5
|
+
from notionary.elements.prompts.element_prompt_content import ElementPromptContent
|
6
|
+
from notionary.elements.text_inline_formatter import TextInlineFormatter
|
7
|
+
|
8
|
+
|
9
|
+
class BulletedListElement(NotionBlockElement):
|
10
|
+
"""Class for converting between Markdown bullet lists and Notion bulleted list items."""
|
11
|
+
|
12
|
+
@override
|
13
|
+
@staticmethod
|
14
|
+
def markdown_to_notion(text: str) -> Optional[Dict[str, Any]]:
|
15
|
+
"""Convert markdown bulleted list item to Notion block."""
|
16
|
+
pattern = re.compile(
|
17
|
+
r"^(\s*)[*\-+]\s+(?!\[[ x]\])(.+)$"
|
18
|
+
) # Avoid matching todo items
|
19
|
+
list_match = pattern.match(text)
|
20
|
+
if not list_match:
|
21
|
+
return None
|
22
|
+
|
23
|
+
content = list_match.group(2)
|
24
|
+
|
25
|
+
# Use parse_inline_formatting to handle rich text
|
26
|
+
rich_text = TextInlineFormatter.parse_inline_formatting(content)
|
27
|
+
|
28
|
+
return {
|
29
|
+
"type": "bulleted_list_item",
|
30
|
+
"bulleted_list_item": {"rich_text": rich_text, "color": "default"},
|
31
|
+
}
|
32
|
+
|
33
|
+
@staticmethod
|
34
|
+
def notion_to_markdown(block: Dict[str, Any]) -> Optional[str]:
|
35
|
+
"""Convert Notion bulleted list item block to markdown."""
|
36
|
+
if block.get("type") != "bulleted_list_item":
|
37
|
+
return None
|
38
|
+
|
39
|
+
rich_text = block.get("bulleted_list_item", {}).get("rich_text", [])
|
40
|
+
content = TextInlineFormatter.extract_text_with_formatting(rich_text)
|
41
|
+
|
42
|
+
return f"- {content}"
|
43
|
+
|
44
|
+
@staticmethod
|
45
|
+
def match_markdown(text: str) -> bool:
|
46
|
+
"""Check if this element can handle the given markdown text."""
|
47
|
+
pattern = re.compile(r"^(\s*)[*\-+]\s+(?!\[[ x]\])(.+)$")
|
48
|
+
return bool(pattern.match(text))
|
49
|
+
|
50
|
+
@staticmethod
|
51
|
+
def match_notion(block: Dict[str, Any]) -> bool:
|
52
|
+
"""Check if this element can handle the given Notion block."""
|
53
|
+
return block.get("type") == "bulleted_list_item"
|
54
|
+
|
55
|
+
@classmethod
|
56
|
+
def get_llm_prompt_content(cls) -> ElementPromptContent:
|
57
|
+
"""
|
58
|
+
Returns structured LLM prompt metadata for the bulleted list element.
|
59
|
+
"""
|
60
|
+
return {
|
61
|
+
"description": "Creates bulleted list items for unordered lists.",
|
62
|
+
"when_to_use": "Use for lists where order doesn't matter, such as features, options, or items without hierarchy.",
|
63
|
+
"syntax": "- Item text",
|
64
|
+
"examples": [
|
65
|
+
"- First item\n- Second item\n- Third item",
|
66
|
+
"* Apple\n* Banana\n* Cherry",
|
67
|
+
"+ Task A\n+ Task B",
|
68
|
+
],
|
69
|
+
}
|
@@ -0,0 +1,117 @@
|
|
1
|
+
import re
|
2
|
+
from typing import Dict, Any, Optional
|
3
|
+
from typing_extensions import override
|
4
|
+
|
5
|
+
from notionary.elements.prompts.element_prompt_content import ElementPromptContent
|
6
|
+
from notionary.elements.text_inline_formatter import TextInlineFormatter
|
7
|
+
from notionary.elements.notion_block_element import NotionBlockElement
|
8
|
+
|
9
|
+
|
10
|
+
class CalloutElement(NotionBlockElement):
|
11
|
+
"""
|
12
|
+
Handles conversion between Markdown callouts and Notion callout blocks.
|
13
|
+
|
14
|
+
Markdown callout syntax:
|
15
|
+
- !> [emoji] Text - Callout with custom emoji
|
16
|
+
- !> Text - Simple callout with default emoji
|
17
|
+
|
18
|
+
Where:
|
19
|
+
- [emoji] is any emoji character
|
20
|
+
- Text is the callout content with optional inline formatting
|
21
|
+
"""
|
22
|
+
|
23
|
+
EMOJI_PATTERN = r"(?:\[([^\]]+)\])?\s*"
|
24
|
+
TEXT_PATTERN = r"(.+)"
|
25
|
+
|
26
|
+
PATTERN = re.compile(r"^!>\s+" + EMOJI_PATTERN + TEXT_PATTERN + r"$")
|
27
|
+
|
28
|
+
DEFAULT_EMOJI = "💡"
|
29
|
+
DEFAULT_COLOR = "gray_background"
|
30
|
+
|
31
|
+
@override
|
32
|
+
@staticmethod
|
33
|
+
def match_markdown(text: str) -> bool:
|
34
|
+
"""Check if text is a markdown callout."""
|
35
|
+
return text.strip().startswith("!>") and bool(
|
36
|
+
CalloutElement.PATTERN.match(text)
|
37
|
+
)
|
38
|
+
|
39
|
+
@override
|
40
|
+
@staticmethod
|
41
|
+
def match_notion(block: Dict[str, Any]) -> bool:
|
42
|
+
"""Check if block is a Notion callout."""
|
43
|
+
return block.get("type") == "callout"
|
44
|
+
|
45
|
+
@override
|
46
|
+
@staticmethod
|
47
|
+
def markdown_to_notion(text: str) -> Optional[Dict[str, Any]]:
|
48
|
+
"""Convert markdown callout to Notion callout block."""
|
49
|
+
callout_match = CalloutElement.PATTERN.match(text)
|
50
|
+
if not callout_match:
|
51
|
+
return None
|
52
|
+
|
53
|
+
emoji = callout_match.group(1)
|
54
|
+
content = callout_match.group(2)
|
55
|
+
|
56
|
+
if not emoji:
|
57
|
+
emoji = CalloutElement.DEFAULT_EMOJI
|
58
|
+
|
59
|
+
return {
|
60
|
+
"type": "callout",
|
61
|
+
"callout": {
|
62
|
+
"rich_text": TextInlineFormatter.parse_inline_formatting(content),
|
63
|
+
"icon": {"type": "emoji", "emoji": emoji},
|
64
|
+
"color": CalloutElement.DEFAULT_COLOR,
|
65
|
+
},
|
66
|
+
}
|
67
|
+
|
68
|
+
@override
|
69
|
+
@staticmethod
|
70
|
+
def notion_to_markdown(block: Dict[str, Any]) -> Optional[str]:
|
71
|
+
"""Convert Notion callout block to markdown callout."""
|
72
|
+
if block.get("type") != "callout":
|
73
|
+
return None
|
74
|
+
|
75
|
+
callout_data = block.get("callout", {})
|
76
|
+
rich_text = callout_data.get("rich_text", [])
|
77
|
+
icon = callout_data.get("icon", {})
|
78
|
+
|
79
|
+
text = TextInlineFormatter.extract_text_with_formatting(rich_text)
|
80
|
+
if not text:
|
81
|
+
return None
|
82
|
+
|
83
|
+
emoji = ""
|
84
|
+
if icon and icon.get("type") == "emoji":
|
85
|
+
emoji = icon.get("emoji", "")
|
86
|
+
|
87
|
+
emoji_str = ""
|
88
|
+
if emoji and emoji != CalloutElement.DEFAULT_EMOJI:
|
89
|
+
emoji_str = f"[{emoji}] "
|
90
|
+
|
91
|
+
return f"!> {emoji_str}{text}"
|
92
|
+
|
93
|
+
@override
|
94
|
+
@staticmethod
|
95
|
+
def is_multiline() -> bool:
|
96
|
+
return False
|
97
|
+
|
98
|
+
@classmethod
|
99
|
+
def get_llm_prompt_content(cls) -> ElementPromptContent:
|
100
|
+
"""
|
101
|
+
Returns a dictionary with all information needed for LLM prompts about this element.
|
102
|
+
Includes description, usage guidance, syntax options, and examples.
|
103
|
+
"""
|
104
|
+
return {
|
105
|
+
"description": "Creates a callout block to highlight important information with an icon.",
|
106
|
+
"when_to_use": (
|
107
|
+
"Use callouts when you want to draw attention to important information, "
|
108
|
+
"tips, warnings, or notes that stand out from the main content."
|
109
|
+
),
|
110
|
+
"syntax": "!> [emoji] Text",
|
111
|
+
"examples": [
|
112
|
+
"!> This is a default callout with the light bulb emoji",
|
113
|
+
"!> [🔔] This is a callout with a bell emoji",
|
114
|
+
"!> [⚠️] Warning: This is an important note to pay attention to",
|
115
|
+
"!> [💡] Tip: Add emoji that matches your content's purpose",
|
116
|
+
],
|
117
|
+
}
|
@@ -1,7 +1,7 @@
|
|
1
|
-
from typing import Dict, Any, Optional, List, Tuple
|
2
|
-
from typing_extensions import override
|
3
1
|
import re
|
2
|
+
from typing import Dict, Any, Optional, List, Tuple
|
4
3
|
from notionary.elements.notion_block_element import NotionBlockElement
|
4
|
+
from notionary.elements.prompts.element_prompt_content import ElementPromptContent
|
5
5
|
|
6
6
|
|
7
7
|
class CodeBlockElement(NotionBlockElement):
|
@@ -20,19 +20,16 @@ class CodeBlockElement(NotionBlockElement):
|
|
20
20
|
|
21
21
|
PATTERN = re.compile(r"```(\w*)\n([\s\S]+?)```", re.MULTILINE)
|
22
22
|
|
23
|
-
@override
|
24
23
|
@staticmethod
|
25
24
|
def match_markdown(text: str) -> bool:
|
26
25
|
"""Check if text contains a markdown code block."""
|
27
26
|
return bool(CodeBlockElement.PATTERN.search(text))
|
28
27
|
|
29
|
-
@override
|
30
28
|
@staticmethod
|
31
29
|
def match_notion(block: Dict[str, Any]) -> bool:
|
32
30
|
"""Check if block is a Notion code block."""
|
33
31
|
return block.get("type") == "code"
|
34
32
|
|
35
|
-
@override
|
36
33
|
@staticmethod
|
37
34
|
def markdown_to_notion(text: str) -> Optional[Dict[str, Any]]:
|
38
35
|
"""Convert markdown code block to Notion code block."""
|
@@ -68,7 +65,6 @@ class CodeBlockElement(NotionBlockElement):
|
|
68
65
|
},
|
69
66
|
}
|
70
67
|
|
71
|
-
@override
|
72
68
|
@staticmethod
|
73
69
|
def notion_to_markdown(block: Dict[str, Any]) -> Optional[str]:
|
74
70
|
"""Convert Notion code block to markdown code block."""
|
@@ -134,20 +130,29 @@ class CodeBlockElement(NotionBlockElement):
|
|
134
130
|
|
135
131
|
return matches
|
136
132
|
|
137
|
-
@override
|
138
133
|
@staticmethod
|
139
134
|
def is_multiline() -> bool:
|
140
135
|
return True
|
141
136
|
|
142
137
|
@classmethod
|
143
|
-
def get_llm_prompt_content(cls) ->
|
138
|
+
def get_llm_prompt_content(cls) -> ElementPromptContent:
|
144
139
|
"""
|
145
|
-
Returns
|
140
|
+
Returns structured LLM prompt metadata for the code block element.
|
146
141
|
"""
|
147
142
|
return {
|
148
|
-
"description":
|
143
|
+
"description": (
|
144
|
+
"Use fenced code blocks to format content as code. Supports language annotations like "
|
145
|
+
"'python', 'json', or 'mermaid'. Useful for displaying code, configurations, command-line "
|
146
|
+
"examples, or diagram syntax. Also suitable for explaining or visualizing systems with diagram languages."
|
147
|
+
),
|
148
|
+
"when_to_use": (
|
149
|
+
"Use code blocks when you want to present technical content like code snippets, terminal commands, "
|
150
|
+
"JSON structures, or system diagrams. Especially helpful when structure and formatting are essential."
|
151
|
+
),
|
152
|
+
"syntax": "```language\ncode content\n```",
|
149
153
|
"examples": [
|
150
154
|
"```python\nprint('Hello, world!')\n```",
|
155
|
+
'```json\n{"name": "Alice", "age": 30}\n```',
|
151
156
|
"```mermaid\nflowchart TD\n A --> B\n```",
|
152
157
|
],
|
153
158
|
}
|
@@ -1,8 +1,8 @@
|
|
1
1
|
import re
|
2
2
|
from typing import Dict, Any, Optional, List, Tuple, Callable
|
3
|
-
from typing_extensions import override
|
4
3
|
|
5
4
|
from notionary.elements.notion_block_element import NotionBlockElement
|
5
|
+
from notionary.elements.prompts.element_prompt_content import ElementPromptContent
|
6
6
|
|
7
7
|
|
8
8
|
class ColumnElement(NotionBlockElement):
|
@@ -40,19 +40,16 @@ class ColumnElement(NotionBlockElement):
|
|
40
40
|
"""
|
41
41
|
cls._converter_callback = callback
|
42
42
|
|
43
|
-
@override
|
44
43
|
@staticmethod
|
45
44
|
def match_markdown(text: str) -> bool:
|
46
45
|
"""Check if text starts a columns block."""
|
47
46
|
return bool(ColumnElement.COLUMNS_START.match(text.strip()))
|
48
47
|
|
49
|
-
@override
|
50
48
|
@staticmethod
|
51
49
|
def match_notion(block: Dict[str, Any]) -> bool:
|
52
50
|
"""Check if block is a Notion column_list."""
|
53
51
|
return block.get("type") == "column_list"
|
54
52
|
|
55
|
-
@override
|
56
53
|
@staticmethod
|
57
54
|
def markdown_to_notion(text: str) -> Optional[Dict[str, Any]]:
|
58
55
|
"""
|
@@ -68,7 +65,6 @@ class ColumnElement(NotionBlockElement):
|
|
68
65
|
# Child columns will be added by the column processor
|
69
66
|
return {"type": "column_list", "column_list": {"children": []}}
|
70
67
|
|
71
|
-
@override
|
72
68
|
@staticmethod
|
73
69
|
def notion_to_markdown(block: Dict[str, Any]) -> Optional[str]:
|
74
70
|
"""Convert Notion column_list block to markdown column syntax."""
|
@@ -95,7 +91,6 @@ class ColumnElement(NotionBlockElement):
|
|
95
91
|
|
96
92
|
return "\n".join(result)
|
97
93
|
|
98
|
-
@override
|
99
94
|
@staticmethod
|
100
95
|
def is_multiline() -> bool:
|
101
96
|
"""Column blocks span multiple lines."""
|
@@ -264,31 +259,49 @@ class ColumnElement(NotionBlockElement):
|
|
264
259
|
columns_children.append(column_block)
|
265
260
|
|
266
261
|
@classmethod
|
267
|
-
def get_llm_prompt_content(cls) ->
|
262
|
+
def get_llm_prompt_content(cls) -> ElementPromptContent:
|
268
263
|
"""
|
269
|
-
Returns
|
270
|
-
Includes description, usage guidance, syntax options, and examples.
|
264
|
+
Returns structured LLM prompt metadata for the column layout element.
|
271
265
|
"""
|
272
266
|
return {
|
273
267
|
"description": "Creates a multi-column layout that displays content side by side.",
|
274
|
-
"when_to_use":
|
275
|
-
|
276
|
-
"
|
277
|
-
"
|
278
|
-
|
279
|
-
|
280
|
-
":::
|
281
|
-
"
|
268
|
+
"when_to_use": (
|
269
|
+
"Use columns sparingly, only for direct comparisons or when parallel presentation significantly improves readability. "
|
270
|
+
"Best for pros/cons lists, feature comparisons, or pairing images with descriptions. "
|
271
|
+
"Avoid overusing as it can complicate document structure."
|
272
|
+
),
|
273
|
+
"syntax": (
|
274
|
+
"::: columns\n"
|
275
|
+
"::: column\n"
|
276
|
+
"Content for first column\n"
|
277
|
+
":::\n"
|
278
|
+
"::: column\n"
|
279
|
+
"Content for second column\n"
|
280
|
+
":::\n"
|
281
|
+
":::"
|
282
|
+
),
|
283
|
+
"examples": [
|
284
|
+
"::: columns\n"
|
285
|
+
"::: column\n"
|
286
|
+
"## Features\n"
|
287
|
+
"- Fast response time\n"
|
288
|
+
"- Intuitive interface\n"
|
289
|
+
"- Regular updates\n"
|
290
|
+
":::\n"
|
291
|
+
"::: column\n"
|
292
|
+
"## Benefits\n"
|
293
|
+
"- Increased productivity\n"
|
294
|
+
"- Better collaboration\n"
|
295
|
+
"- Simplified workflows\n"
|
296
|
+
":::\n"
|
282
297
|
":::",
|
298
|
+
"::: columns\n"
|
299
|
+
"::: column\n"
|
300
|
+
"\n"
|
301
|
+
":::\n"
|
302
|
+
"::: column\n"
|
303
|
+
"This text appears next to the image, creating a media-with-caption style layout that's perfect for documentation or articles.\n"
|
304
|
+
":::\n"
|
283
305
|
":::",
|
284
306
|
],
|
285
|
-
"notes": [
|
286
|
-
"Any Notion block can be placed within columns",
|
287
|
-
"Add more columns with additional '::: column' sections",
|
288
|
-
"Each column must close with ':::' and the entire columns section with another ':::'",
|
289
|
-
],
|
290
|
-
"examples": [
|
291
|
-
"::: columns\n::: column\n## Features\n- Fast response time\n- Intuitive interface\n- Regular updates\n:::\n::: column\n## Benefits\n- Increased productivity\n- Better collaboration\n- Simplified workflows\n:::\n:::",
|
292
|
-
"::: columns\n::: column\n\n:::\n::: column\nThis text appears next to the image, creating a media-with-caption style layout that's perfect for documentation or articles.\n:::\n:::",
|
293
|
-
],
|
294
307
|
}
|
@@ -0,0 +1,56 @@
|
|
1
|
+
import re
|
2
|
+
from typing import Dict, Any, Optional
|
3
|
+
|
4
|
+
from notionary.elements.notion_block_element import NotionBlockElement
|
5
|
+
from notionary.elements.prompts.element_prompt_content import ElementPromptContent
|
6
|
+
|
7
|
+
|
8
|
+
class DividerElement(NotionBlockElement):
|
9
|
+
"""
|
10
|
+
Handles conversion between Markdown horizontal dividers and Notion divider blocks.
|
11
|
+
|
12
|
+
Markdown divider syntax:
|
13
|
+
- Three or more hyphens (---) on a line by themselves
|
14
|
+
"""
|
15
|
+
|
16
|
+
PATTERN = re.compile(r"^\s*-{3,}\s*$")
|
17
|
+
|
18
|
+
@staticmethod
|
19
|
+
def match_markdown(text: str) -> bool:
|
20
|
+
"""Check if text is a markdown divider."""
|
21
|
+
return bool(DividerElement.PATTERN.match(text))
|
22
|
+
|
23
|
+
@staticmethod
|
24
|
+
def match_notion(block: Dict[str, Any]) -> bool:
|
25
|
+
"""Check if block is a Notion divider."""
|
26
|
+
return block.get("type") == "divider"
|
27
|
+
|
28
|
+
@staticmethod
|
29
|
+
def markdown_to_notion(text: str) -> Optional[Dict[str, Any]]:
|
30
|
+
"""Convert markdown divider to Notion divider block."""
|
31
|
+
if not DividerElement.match_markdown(text):
|
32
|
+
return None
|
33
|
+
|
34
|
+
return {"type": "divider", "divider": {}}
|
35
|
+
|
36
|
+
@staticmethod
|
37
|
+
def notion_to_markdown(block: Dict[str, Any]) -> Optional[str]:
|
38
|
+
"""Convert Notion divider block to markdown divider."""
|
39
|
+
if block.get("type") != "divider":
|
40
|
+
return None
|
41
|
+
|
42
|
+
return "---"
|
43
|
+
|
44
|
+
@staticmethod
|
45
|
+
def is_multiline() -> bool:
|
46
|
+
return False
|
47
|
+
|
48
|
+
@classmethod
|
49
|
+
def get_llm_prompt_content(cls) -> ElementPromptContent:
|
50
|
+
"""Returns compact LLM prompt metadata for the divider element."""
|
51
|
+
return {
|
52
|
+
"description": "Creates a horizontal divider line to visually separate sections of content.",
|
53
|
+
"when_to_use": "Use to create clear visual breaks between different sections without requiring headings.",
|
54
|
+
"syntax": "---",
|
55
|
+
"examples": ["## Section 1\nContent\n\n---\n\n## Section 2\nMore content"],
|
56
|
+
}
|