notionary 0.1.8__py3-none-any.whl → 0.1.10__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/core/converters/elements/embed_element.py +129 -0
- notionary/core/converters/registry/block_element_registry.py +6 -0
- notionary/core/converters/registry/block_element_registry_builder.py +2 -0
- notionary/core/page/notion_page_manager.py +23 -2
- notionary/util/page_id_utils.py +0 -2
- {notionary-0.1.8.dist-info → notionary-0.1.10.dist-info}/METADATA +1 -1
- {notionary-0.1.8.dist-info → notionary-0.1.10.dist-info}/RECORD +10 -9
- {notionary-0.1.8.dist-info → notionary-0.1.10.dist-info}/WHEEL +0 -0
- {notionary-0.1.8.dist-info → notionary-0.1.10.dist-info}/licenses/LICENSE +0 -0
- {notionary-0.1.8.dist-info → notionary-0.1.10.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,129 @@
|
|
1
|
+
import re
|
2
|
+
from typing import Dict, Any, Optional, List
|
3
|
+
from notionary.core.converters.elements.notion_block_element import NotionBlockElement
|
4
|
+
|
5
|
+
|
6
|
+
class EmbedElement(NotionBlockElement):
|
7
|
+
"""
|
8
|
+
Handles conversion between Markdown embeds and Notion embed blocks.
|
9
|
+
|
10
|
+
Markdown embed syntax (custom format):
|
11
|
+
- <embed:Caption>(https://example.com) - Basic embed with caption
|
12
|
+
- <embed>(https://example.com) - Embed without caption
|
13
|
+
|
14
|
+
Supports various URL types including websites, PDFs, Google Maps, Google Drive,
|
15
|
+
Twitter/X posts, and other sources that Notion can embed.
|
16
|
+
"""
|
17
|
+
|
18
|
+
PATTERN = re.compile(
|
19
|
+
r"^<embed(?:\:(.*?))?>(?:\s*)"
|
20
|
+
+ r'\((https?://[^\s"]+)'
|
21
|
+
+ r"\)$"
|
22
|
+
)
|
23
|
+
|
24
|
+
@staticmethod
|
25
|
+
def match_markdown(text: str) -> bool:
|
26
|
+
"""Check if text is a markdown embed."""
|
27
|
+
text = text.strip()
|
28
|
+
return text.startswith("<embed") and bool(EmbedElement.PATTERN.match(text))
|
29
|
+
|
30
|
+
@staticmethod
|
31
|
+
def match_notion(block: Dict[str, Any]) -> bool:
|
32
|
+
"""Check if block is a Notion embed."""
|
33
|
+
return block.get("type") == "embed"
|
34
|
+
|
35
|
+
@staticmethod
|
36
|
+
def markdown_to_notion(text: str) -> Optional[Dict[str, Any]]:
|
37
|
+
"""Convert markdown embed to Notion embed block."""
|
38
|
+
embed_match = EmbedElement.PATTERN.match(text.strip())
|
39
|
+
if not embed_match:
|
40
|
+
return None
|
41
|
+
|
42
|
+
caption = embed_match.group(1) or ""
|
43
|
+
url = embed_match.group(2)
|
44
|
+
|
45
|
+
if not url:
|
46
|
+
return None
|
47
|
+
|
48
|
+
# Prepare the embed block
|
49
|
+
embed_block = {
|
50
|
+
"type": "embed",
|
51
|
+
"embed": {"url": url},
|
52
|
+
}
|
53
|
+
|
54
|
+
# Add caption if provided
|
55
|
+
if caption:
|
56
|
+
embed_block["embed"]["caption"] = [
|
57
|
+
{"type": "text", "text": {"content": caption}}
|
58
|
+
]
|
59
|
+
|
60
|
+
return embed_block
|
61
|
+
|
62
|
+
@staticmethod
|
63
|
+
def notion_to_markdown(block: Dict[str, Any]) -> Optional[str]:
|
64
|
+
"""Convert Notion embed block to markdown embed."""
|
65
|
+
if block.get("type") != "embed":
|
66
|
+
return None
|
67
|
+
|
68
|
+
embed_data = block.get("embed", {})
|
69
|
+
url = embed_data.get("url", "")
|
70
|
+
|
71
|
+
if not url:
|
72
|
+
return None
|
73
|
+
|
74
|
+
# Extract caption if available
|
75
|
+
caption = ""
|
76
|
+
caption_rich_text = embed_data.get("caption", [])
|
77
|
+
if caption_rich_text:
|
78
|
+
caption = EmbedElement._extract_text_content(caption_rich_text)
|
79
|
+
|
80
|
+
if caption:
|
81
|
+
return f"<embed:{caption}>({url})"
|
82
|
+
else:
|
83
|
+
return f"<embed>({url})"
|
84
|
+
|
85
|
+
@staticmethod
|
86
|
+
def is_multiline() -> bool:
|
87
|
+
"""Embeds are single-line elements."""
|
88
|
+
return False
|
89
|
+
|
90
|
+
@staticmethod
|
91
|
+
def _extract_text_content(rich_text: List[Dict[str, Any]]) -> str:
|
92
|
+
"""Extract plain text content from Notion rich_text elements."""
|
93
|
+
result = ""
|
94
|
+
for text_obj in rich_text:
|
95
|
+
if text_obj.get("type") == "text":
|
96
|
+
result += text_obj.get("text", {}).get("content", "")
|
97
|
+
elif "plain_text" in text_obj:
|
98
|
+
result += text_obj.get("plain_text", "")
|
99
|
+
return result
|
100
|
+
|
101
|
+
@classmethod
|
102
|
+
def get_llm_prompt_content(cls) -> dict:
|
103
|
+
"""Returns information for LLM prompts about this element."""
|
104
|
+
return {
|
105
|
+
"description": "Embeds external content from websites, PDFs, Google Maps, and other sources directly in your document.",
|
106
|
+
"when_to_use": "Use embeds when you want to include external content that isn't just a video or image. Embeds are great for interactive content, reference materials, or live data sources.",
|
107
|
+
"syntax": [
|
108
|
+
"<embed>(https://example.com) - Embed without caption",
|
109
|
+
"<embed:Caption text>(https://example.com) - Embed with caption",
|
110
|
+
],
|
111
|
+
"supported_sources": [
|
112
|
+
"Websites and web pages",
|
113
|
+
"PDFs and documents",
|
114
|
+
"Google Maps",
|
115
|
+
"Google Drive files",
|
116
|
+
"Twitter/X posts",
|
117
|
+
"GitHub repositories and code",
|
118
|
+
"Figma designs",
|
119
|
+
"Miro boards",
|
120
|
+
"Many other services supported by Notion's embed feature",
|
121
|
+
],
|
122
|
+
"examples": [
|
123
|
+
"<embed:Course materials>(https://drive.google.com/file/d/123456/view)",
|
124
|
+
"<embed:Our office location>(https://www.google.com/maps?q=San+Francisco)",
|
125
|
+
"<embed:Latest announcement>(https://twitter.com/NotionHQ/status/1234567890)",
|
126
|
+
"<embed:Project documentation>(https://github.com/username/repo)",
|
127
|
+
"<embed>(https://example.com/important-reference.pdf)",
|
128
|
+
],
|
129
|
+
}
|
@@ -95,6 +95,8 @@ class BlockElementRegistry:
|
|
95
95
|
"""
|
96
96
|
# Create a copy of registered elements
|
97
97
|
element_classes = self._elements.copy()
|
98
|
+
|
99
|
+
# TODO: Das hier besser formattieren und über debug level lösen . )
|
98
100
|
print("Elements in registry:", element_classes)
|
99
101
|
|
100
102
|
formatter_names = [e.__name__ for e in element_classes]
|
@@ -232,3 +234,7 @@ paragraphs, lists, quotes, etc.
|
|
232
234
|
element_docs = cls.generate_element_docs(element_classes)
|
233
235
|
|
234
236
|
return cls.SYSTEM_PROMPT_TEMPLATE.format(element_docs=element_docs)
|
237
|
+
|
238
|
+
|
239
|
+
|
240
|
+
# TODO: Testen ob der Inline Formatter hier überhaupt funktionieren tut:
|
@@ -1,6 +1,7 @@
|
|
1
1
|
from typing import List, Type
|
2
2
|
from collections import OrderedDict
|
3
3
|
|
4
|
+
from notionary.core.converters.elements.embed_element import EmbedElement
|
4
5
|
from notionary.core.converters.elements.notion_block_element import NotionBlockElement
|
5
6
|
from notionary.core.converters.registry.block_element_registry import (
|
6
7
|
BlockElementRegistry,
|
@@ -90,6 +91,7 @@ class BlockElementRegistryBuilder:
|
|
90
91
|
.add_element(BookmarkElement)
|
91
92
|
.add_element(ImageElement)
|
92
93
|
.add_element(VideoElement)
|
94
|
+
.add_element(EmbedElement)
|
93
95
|
.add_element(ParagraphElement)
|
94
96
|
) # Add paragraph last as fallback
|
95
97
|
|
@@ -305,6 +305,27 @@ async def main():
|
|
305
305
|
|
306
306
|
print("\nDemonstration abgeschlossen.")
|
307
307
|
|
308
|
+
|
309
|
+
async def demo2():
|
310
|
+
url = "https://www.notion.so/Jarvis-Clipboard-1a3389d57bd380d7a507e67d1b25822c"
|
311
|
+
|
312
|
+
page_manager = NotionPageManager(url=url)
|
313
|
+
|
314
|
+
# Beispiel mit einem Embed-Element im Transcript-Toggle
|
315
|
+
markdown = """
|
316
|
+
## 💪 Muskelaufbau und Kraft
|
317
|
+
- Regelmäßiges Training ist essentiell für den Muskelerhalt im Alter
|
318
|
+
- Richtige Ernährung unterstützt die Regeneration nach dem Training
|
319
|
+
|
320
|
+
+++ Transcript
|
321
|
+
<embed:Listen to this highlight>(https://share.snipd.com/snip/ad3d95b2-d648-4fa9-9036-bd7df653ea32)
|
322
|
+
<!-- spacer -->
|
323
|
+
... In diesem Teil des Podcasts erklärt der Sprecher, wie das Henneman-Größenprinzip funktioniert und wie Muskelfasern rekrutiert werden, basierend auf der Schwere des zu bewegenden Objekts.
|
324
|
+
"""
|
325
|
+
|
326
|
+
await page_manager.append_markdown(markdown)
|
327
|
+
print("Markdown wurde zur Notion-Seite hinzugefügt.")
|
328
|
+
|
308
329
|
if __name__ == "__main__":
|
309
|
-
asyncio.run(
|
310
|
-
print("\nDemonstration completed.")
|
330
|
+
asyncio.run(demo2())
|
331
|
+
print("\nDemonstration completed.")
|
notionary/util/page_id_utils.py
CHANGED
@@ -24,8 +24,6 @@ def format_uuid(value: str) -> Optional[str]:
|
|
24
24
|
return extract_uuid(value)
|
25
25
|
|
26
26
|
def extract_and_validate_page_id(page_id: Optional[str], url: Optional[str]) -> str:
|
27
|
-
print("page_id", page_id)
|
28
|
-
print("=====")
|
29
27
|
if not page_id and not url:
|
30
28
|
raise ValueError("Either page_id or url must be provided")
|
31
29
|
|
@@ -8,6 +8,7 @@ notionary/core/converters/elements/callout_element.py,sha256=rkDoXikjIl-zU3GLawS
|
|
8
8
|
notionary/core/converters/elements/code_block_element.py,sha256=G1iGMsGSK5KPSk-tA8TsPs9XNU9ydjYfOVnjIvdZG74,5189
|
9
9
|
notionary/core/converters/elements/column_element.py,sha256=ZwQsLBEownVJnzyv-GfNjvzJhAfKz9ncggqZUmZHF5A,10722
|
10
10
|
notionary/core/converters/elements/divider_element.py,sha256=Ul0wXHY96qWL72iAvttRQMOoAGuASgwFwPraGnpUkX0,2792
|
11
|
+
notionary/core/converters/elements/embed_element.py,sha256=vEqY7VBOAvtI4uwys6_E1FT9j4bkHJM21OYaT51ahe8,4800
|
11
12
|
notionary/core/converters/elements/heading_element.py,sha256=BCBcpEO_UX92nzCclVHAjlOLFJ5zu9wDlAGbphesaOQ,2788
|
12
13
|
notionary/core/converters/elements/image_element.py,sha256=uU3bY26LvJwD_CAXN11tqYt5Ed84gjUeHWnJmxvH07Y,4861
|
13
14
|
notionary/core/converters/elements/list_element.py,sha256=lM9nVGVG3VtmjMkqxrBj71wiJITuRypwxORu4zghqAM,4878
|
@@ -19,14 +20,14 @@ notionary/core/converters/elements/text_inline_formatter.py,sha256=FE_Sq2cozpu5R
|
|
19
20
|
notionary/core/converters/elements/todo_lists.py,sha256=wgY6YejURBQ5ESdVLZVIy9QKchS-x8odrmS8X4cC5Kc,4265
|
20
21
|
notionary/core/converters/elements/toggle_element.py,sha256=Xv4MuuOyoamvT3IEJX4mynvLEycgtZ9LWt6Nm764KXE,6980
|
21
22
|
notionary/core/converters/elements/video_element.py,sha256=xrBLY3e_SgKNamItZkfPNMbNEh37Ftp4jWIV6nwV-ds,6047
|
22
|
-
notionary/core/converters/registry/block_element_registry.py,sha256=
|
23
|
-
notionary/core/converters/registry/block_element_registry_builder.py,sha256=
|
23
|
+
notionary/core/converters/registry/block_element_registry.py,sha256=sb-wYjRS3B7F0QHcB75JYSuGstDrNnvCji1EA4N3d_o,8788
|
24
|
+
notionary/core/converters/registry/block_element_registry_builder.py,sha256=xT5kAHBTWoYLGhHza4viLkuHHwGwwKntOWFsgPjFYFY,9342
|
24
25
|
notionary/core/database/database_info_service.py,sha256=58k7om0UXP8w0jCJHewccG5UbOvELMBAbQvXOm7F1OM,1341
|
25
26
|
notionary/core/database/notion_database_manager.py,sha256=cEQHf8bTcgA3hLMgsbMGSXhCmccmxWLQ6oOJiINR3ac,8257
|
26
27
|
notionary/core/database/notion_database_manager_factory.py,sha256=SoWUiM5zdajmR1ppYHTdPgHrdZbwuTMdoXW3_tBffyU,6831
|
27
28
|
notionary/core/database/notion_database_schema.py,sha256=WUIjG7I5kusk4GOOdmVSHIKc2Z8SeOgJ1FuGpTn4avQ,3304
|
28
29
|
notionary/core/database/models/page_result.py,sha256=Vmm5_oYpYAkIIJVoTd1ZZGloeC3cmFLMYP255mAmtaw,233
|
29
|
-
notionary/core/page/notion_page_manager.py,sha256=
|
30
|
+
notionary/core/page/notion_page_manager.py,sha256=7nOjUuUsHSrgsMa5K5OPCHxVIhJOvvNjrA5tsCAEozc,14135
|
30
31
|
notionary/core/page/content/page_content_manager.py,sha256=GjgxVwi5NOMsvGwBNj-woq940ZupzALCQegDyIcsgMg,3358
|
31
32
|
notionary/core/page/metadata/metadata_editor.py,sha256=U3Ff9GRk28dqT9M1xsl6Q3Cj47-hB1n2pNJzeDXy4ks,4938
|
32
33
|
notionary/core/page/metadata/notion_icon_manager.py,sha256=v9pUG61TOT8x9UzDqBtQW6S5XQzWostq7IwrURnWvF4,1499
|
@@ -43,10 +44,10 @@ notionary/core/page/relations/relation_operation_result.py,sha256=XkO4rK0ha_FRsf
|
|
43
44
|
notionary/exceptions/database_exceptions.py,sha256=I-Tx6bYRLpi5pjGPtbT-Mqxvz3BFgYTiuZxknJeLxtI,2638
|
44
45
|
notionary/exceptions/page_creation_exception.py,sha256=4v7IuZD6GsQLrqhDLriGjuG3ML638gAO53zDCrLePuU,281
|
45
46
|
notionary/util/logging_mixin.py,sha256=fKsx9t90bwvL74ZX3dU-sXdC4TZCQyO6qU9I8txkw_U,1369
|
46
|
-
notionary/util/page_id_utils.py,sha256=
|
47
|
+
notionary/util/page_id_utils.py,sha256=9XexVGy8jY5iOlueS1MXFWHtRRmZ8js-EO3hT0_wg2E,1381
|
47
48
|
notionary/util/singleton_decorator.py,sha256=GTNMfIlVNRUVMw_c88xqd12-DcqZJjmyidN54yqiNVw,472
|
48
|
-
notionary-0.1.
|
49
|
-
notionary-0.1.
|
50
|
-
notionary-0.1.
|
51
|
-
notionary-0.1.
|
52
|
-
notionary-0.1.
|
49
|
+
notionary-0.1.10.dist-info/licenses/LICENSE,sha256=zOm3cRT1qD49eg7vgw95MI79rpUAZa1kRBFwL2FkAr8,1120
|
50
|
+
notionary-0.1.10.dist-info/METADATA,sha256=dlHfVAPjbYBQH2pNNLmXaWBWmvHKeb1RV4OpOVgLvWY,6154
|
51
|
+
notionary-0.1.10.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
52
|
+
notionary-0.1.10.dist-info/top_level.txt,sha256=fhONa6BMHQXqthx5PanWGbPL0b8rdFqhrJKVLf_adSs,10
|
53
|
+
notionary-0.1.10.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|