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,59 +1,10 @@
|
|
1
|
-
from typing import
|
2
|
-
from pydantic import BaseModel
|
3
|
-
|
4
|
-
from notionary.blocks.shared.models import RichTextObject
|
5
|
-
|
6
|
-
|
7
|
-
# TODO: Diesen Kram hier auch verwenden
|
8
|
-
class ExternalAudioSource(BaseModel):
|
9
|
-
"""External audio source."""
|
10
|
-
|
11
|
-
url: str
|
12
|
-
|
13
|
-
|
14
|
-
class NotionAudioData(BaseModel):
|
15
|
-
"""Audio block data."""
|
16
|
-
|
17
|
-
type: str = "external"
|
18
|
-
external: ExternalAudioSource
|
19
|
-
caption: list[dict] = []
|
1
|
+
from typing import Literal
|
20
2
|
|
3
|
+
from pydantic import BaseModel
|
21
4
|
|
22
|
-
|
23
|
-
"""Audio block result."""
|
24
|
-
|
25
|
-
type: str = "audio"
|
26
|
-
audio: NotionAudioData
|
27
|
-
|
28
|
-
|
29
|
-
# Updated method with typed return
|
30
|
-
@classmethod
|
31
|
-
def markdown_to_notion(cls, text: str) -> Optional[NotionAudioBlock]:
|
32
|
-
"""Convert markdown audio embed to Notion audio block."""
|
33
|
-
audio_match = cls.PATTERN.match(text.strip())
|
34
|
-
if not audio_match:
|
35
|
-
return None
|
36
|
-
|
37
|
-
url = audio_match.group(1)
|
38
|
-
caption_text = audio_match.group(2)
|
39
|
-
|
40
|
-
if not url:
|
41
|
-
return None
|
42
|
-
|
43
|
-
# Validate URL if possible
|
44
|
-
if not cls._is_likely_audio_url(url):
|
45
|
-
# Still proceed - user might know better
|
46
|
-
pass
|
5
|
+
from notionary.blocks.file.file_element_models import FileBlock
|
47
6
|
|
48
|
-
# Build caption list
|
49
|
-
caption_list = []
|
50
|
-
if caption_text:
|
51
|
-
caption_rich_text = RichTextObject.from_plain_text(caption_text)
|
52
|
-
caption_list = [caption_rich_text.model_dump()]
|
53
7
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
external=ExternalAudioSource(url=url), caption=caption_list
|
58
|
-
)
|
59
|
-
)
|
8
|
+
class CreateAudioBlock(BaseModel):
|
9
|
+
type: Literal["audio"] = "audio"
|
10
|
+
audio: FileBlock
|
@@ -0,0 +1,42 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from abc import ABC
|
4
|
+
from typing import Optional
|
5
|
+
|
6
|
+
from notionary.blocks.syntax_prompt_builder import BlockElementMarkdownInformation
|
7
|
+
from notionary.blocks.models import Block, BlockCreateResult
|
8
|
+
|
9
|
+
|
10
|
+
class BaseBlockElement(ABC):
|
11
|
+
"""Base class for elements that can be converted between Markdown and Notion."""
|
12
|
+
|
13
|
+
@classmethod
|
14
|
+
async def markdown_to_notion(cls, text: str) -> BlockCreateResult:
|
15
|
+
"""
|
16
|
+
Convert markdown to Notion block content.
|
17
|
+
|
18
|
+
Returns:
|
19
|
+
- BlockContent: Single block content (e.g., ToDoBlock, ParagraphBlock)
|
20
|
+
- list[BlockContent]: Multiple block contents
|
21
|
+
- None: Cannot convert this markdown
|
22
|
+
"""
|
23
|
+
|
24
|
+
@classmethod
|
25
|
+
async def notion_to_markdown(cls, block: Block) -> Optional[str]:
|
26
|
+
"""Convert Notion block to markdown."""
|
27
|
+
|
28
|
+
@classmethod
|
29
|
+
def match_notion(cls, block: Block) -> bool:
|
30
|
+
"""Check if this element can handle the given Notion block."""
|
31
|
+
# Default implementation - subclasses should override this method
|
32
|
+
# Cannot call async notion_to_markdown here
|
33
|
+
return False
|
34
|
+
|
35
|
+
@classmethod
|
36
|
+
def get_system_prompt_information(cls) -> Optional[BlockElementMarkdownInformation]:
|
37
|
+
"""Get system prompt information for this block element.
|
38
|
+
|
39
|
+
Subclasses should override this method to provide their specific information.
|
40
|
+
Return None if the element should not be included in documentation.
|
41
|
+
"""
|
42
|
+
return None
|
@@ -1,7 +1,14 @@
|
|
1
|
-
from .bookmark_element import BookmarkElement
|
2
|
-
from .bookmark_markdown_node import
|
1
|
+
from notionary.blocks.bookmark.bookmark_element import BookmarkElement
|
2
|
+
from notionary.blocks.bookmark.bookmark_markdown_node import (
|
3
|
+
BookmarkMarkdownBlockParams,
|
4
|
+
BookmarkMarkdownNode,
|
5
|
+
)
|
6
|
+
from notionary.blocks.bookmark.bookmark_models import BookmarkBlock, CreateBookmarkBlock
|
3
7
|
|
4
8
|
__all__ = [
|
5
9
|
"BookmarkElement",
|
10
|
+
"BookmarkBlock",
|
11
|
+
"CreateBookmarkBlock",
|
6
12
|
"BookmarkMarkdownNode",
|
13
|
+
"BookmarkMarkdownBlockParams",
|
7
14
|
]
|
@@ -1,173 +1,83 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import re
|
2
|
-
from typing import
|
4
|
+
from typing import Optional
|
3
5
|
|
4
|
-
from notionary.blocks import
|
5
|
-
from notionary.blocks import
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
)
|
10
|
-
from notionary.blocks.shared.models import RichTextObject
|
6
|
+
from notionary.blocks.base_block_element import BaseBlockElement
|
7
|
+
from notionary.blocks.bookmark.bookmark_models import BookmarkBlock, CreateBookmarkBlock
|
8
|
+
from notionary.blocks.mixins.captions import CaptionMixin
|
9
|
+
from notionary.blocks.syntax_prompt_builder import BlockElementMarkdownInformation
|
10
|
+
from notionary.blocks.models import Block, BlockCreateResult, BlockType
|
11
11
|
|
12
12
|
|
13
|
-
class BookmarkElement(
|
13
|
+
class BookmarkElement(BaseBlockElement, CaptionMixin):
|
14
14
|
"""
|
15
15
|
Handles conversion between Markdown bookmarks and Notion bookmark blocks.
|
16
16
|
|
17
17
|
Markdown bookmark syntax:
|
18
|
-
- [bookmark](https://example.com) -
|
19
|
-
- [bookmark](https://example.com
|
20
|
-
- [bookmark](https://example.com
|
21
|
-
|
22
|
-
Where:
|
23
|
-
- URL is the required bookmark URL
|
24
|
-
- Title is an optional title (enclosed in quotes)
|
25
|
-
- Description is an optional description (enclosed in quotes)
|
18
|
+
- [bookmark](https://example.com) - URL only
|
19
|
+
- [bookmark](https://example.com)(caption:This is a caption) - URL with caption
|
20
|
+
- (caption:This is a caption)[bookmark](https://example.com) - caption before URL
|
26
21
|
"""
|
27
22
|
|
28
|
-
#
|
29
|
-
|
30
|
-
r"^\[bookmark\]\(" # [bookmark]( prefix
|
31
|
-
+ r'(https?://[^\s"]+)' # URL (required)
|
32
|
-
+ r'(?:\s+"([^"]+)")?' # Optional title in quotes
|
33
|
-
+ r'(?:\s+"([^"]+)")?' # Optional description in quotes
|
34
|
-
+ r"\)$" # closing parenthesis
|
35
|
-
)
|
23
|
+
# Flexible pattern that can handle caption in any position
|
24
|
+
BOOKMARK_PATTERN = re.compile(r"\[bookmark\]\((https?://[^\s\"]+)\)")
|
36
25
|
|
37
26
|
@classmethod
|
38
|
-
def
|
39
|
-
|
40
|
-
return text.strip().startswith("[bookmark]") and bool(
|
41
|
-
cls.PATTERN.match(text.strip())
|
42
|
-
)
|
27
|
+
def match_notion(cls, block: Block) -> bool:
|
28
|
+
return block.type == BlockType.BOOKMARK and block.bookmark
|
43
29
|
|
44
30
|
@classmethod
|
45
|
-
def
|
46
|
-
"""
|
47
|
-
|
31
|
+
async def markdown_to_notion(cls, text: str) -> BlockCreateResult:
|
32
|
+
"""
|
33
|
+
Convert a markdown bookmark into a Notion BookmarkBlock.
|
34
|
+
"""
|
35
|
+
# First remove captions to get clean text for URL extraction
|
36
|
+
clean_text = cls.remove_caption(text.strip())
|
48
37
|
|
49
|
-
|
50
|
-
|
51
|
-
"""Convert markdown bookmark to Notion bookmark block."""
|
52
|
-
bookmark_match = BookmarkElement.PATTERN.match(text.strip())
|
38
|
+
# Use our own regex to find the bookmark URL
|
39
|
+
bookmark_match = cls.BOOKMARK_PATTERN.search(clean_text)
|
53
40
|
if not bookmark_match:
|
54
41
|
return None
|
55
42
|
|
56
43
|
url = bookmark_match.group(1)
|
57
|
-
title = bookmark_match.group(2)
|
58
|
-
description = bookmark_match.group(3)
|
59
|
-
|
60
|
-
bookmark_data = {"url": url}
|
61
44
|
|
62
|
-
|
63
|
-
|
64
|
-
if title:
|
65
|
-
caption_parts.append(title)
|
66
|
-
if description:
|
67
|
-
caption_parts.append(description)
|
45
|
+
caption_text = cls.extract_caption(text.strip())
|
46
|
+
caption_rich_text = cls.build_caption_rich_text(caption_text or "")
|
68
47
|
|
69
|
-
|
70
|
-
|
71
|
-
caption_rich_text = RichTextObject.from_plain_text(caption_text)
|
72
|
-
bookmark_data["caption"] = [caption_rich_text.model_dump()]
|
73
|
-
else:
|
74
|
-
bookmark_data["caption"] = []
|
75
|
-
|
76
|
-
return [{"type": "bookmark", "bookmark": bookmark_data}]
|
48
|
+
bookmark_data = BookmarkBlock(url=url, caption=caption_rich_text)
|
49
|
+
return CreateBookmarkBlock(bookmark=bookmark_data)
|
77
50
|
|
78
51
|
@classmethod
|
79
|
-
def notion_to_markdown(cls, block:
|
80
|
-
|
81
|
-
block_type = block.get("type", "")
|
82
|
-
|
83
|
-
if block_type == "bookmark":
|
84
|
-
bookmark_data = block.get("bookmark", {})
|
85
|
-
elif block_type == "external-bookmark":
|
86
|
-
url = block.get("url", "")
|
87
|
-
if not url:
|
88
|
-
return None
|
89
|
-
|
90
|
-
return f"[bookmark]({url})"
|
91
|
-
else:
|
52
|
+
async def notion_to_markdown(cls, block: Block) -> Optional[str]:
|
53
|
+
if block.type != BlockType.BOOKMARK or block.bookmark is None:
|
92
54
|
return None
|
93
55
|
|
94
|
-
|
95
|
-
|
56
|
+
bm = block.bookmark
|
57
|
+
url = bm.url
|
96
58
|
if not url:
|
97
59
|
return None
|
98
60
|
|
99
|
-
|
100
|
-
|
101
|
-
if not caption:
|
102
|
-
# Simple bookmark with URL only
|
103
|
-
return f"[bookmark]({url})"
|
104
|
-
|
105
|
-
# Extract title and description from caption
|
106
|
-
title, description = BookmarkElement._parse_caption(caption)
|
107
|
-
|
108
|
-
if title and description:
|
109
|
-
return f'[bookmark]({url} "{title}" "{description}")'
|
110
|
-
|
111
|
-
if title:
|
112
|
-
return f'[bookmark]({url} "{title}")'
|
61
|
+
result = f"[bookmark]({url})"
|
113
62
|
|
114
|
-
|
63
|
+
# Add caption if present
|
64
|
+
caption_markdown = await cls.format_caption_for_markdown(bm.caption or [])
|
65
|
+
if caption_markdown:
|
66
|
+
result += caption_markdown
|
115
67
|
|
116
|
-
@classmethod
|
117
|
-
def is_multiline(cls) -> bool:
|
118
|
-
"""Bookmarks are single-line elements."""
|
119
|
-
return False
|
120
|
-
|
121
|
-
@classmethod
|
122
|
-
def _extract_text_content(cls, rich_text: List[Dict[str, Any]]) -> str:
|
123
|
-
"""Extract plain text content from Notion rich_text elements."""
|
124
|
-
result = ""
|
125
|
-
for text_obj in rich_text:
|
126
|
-
if text_obj.get("type") == "text":
|
127
|
-
result += text_obj.get("text", {}).get("content", "")
|
128
|
-
elif "plain_text" in text_obj:
|
129
|
-
result += text_obj.get("plain_text", "")
|
130
68
|
return result
|
131
69
|
|
132
70
|
@classmethod
|
133
|
-
def
|
134
|
-
"""
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
return parts[0].strip(), parts[1].strip()
|
146
|
-
|
147
|
-
return full_text.strip(), ""
|
148
|
-
|
149
|
-
@classmethod
|
150
|
-
def get_llm_prompt_content(cls) -> ElementPromptContent:
|
151
|
-
"""
|
152
|
-
Returns structured LLM prompt metadata for the bookmark element.
|
153
|
-
"""
|
154
|
-
return (
|
155
|
-
ElementPromptBuilder()
|
156
|
-
.with_description("Creates a bookmark that links to an external website.")
|
157
|
-
.with_usage_guidelines(
|
158
|
-
"Use bookmarks when you want to reference external content while keeping the page clean and organized. "
|
159
|
-
"Bookmarks display a preview card for the linked content."
|
160
|
-
)
|
161
|
-
.with_syntax(
|
162
|
-
'[bookmark](https://example.com "Optional Title" "Optional Description")'
|
163
|
-
)
|
164
|
-
.with_examples(
|
165
|
-
[
|
166
|
-
"[bookmark](https://example.com)",
|
167
|
-
'[bookmark](https://example.com "Example Title")',
|
168
|
-
'[bookmark](https://example.com "Example Title" "Example description of the site")',
|
169
|
-
'[bookmark](https://github.com "GitHub" "Where the world builds software")',
|
170
|
-
]
|
171
|
-
)
|
172
|
-
.build()
|
71
|
+
def get_system_prompt_information(cls) -> Optional[BlockElementMarkdownInformation]:
|
72
|
+
"""Get system prompt information for bookmark blocks."""
|
73
|
+
return BlockElementMarkdownInformation(
|
74
|
+
block_type=cls.__name__,
|
75
|
+
description="Bookmark blocks create previews of web pages with optional captions",
|
76
|
+
syntax_examples=[
|
77
|
+
"[bookmark](https://example.com)",
|
78
|
+
"[bookmark](https://example.com)(caption:This is a caption)",
|
79
|
+
"(caption:Check out this repository)[bookmark](https://github.com/user/repo)",
|
80
|
+
"[bookmark](https://github.com/user/repo)(caption:Check out this awesome repository)",
|
81
|
+
],
|
82
|
+
usage_guidelines="Use for linking to external websites with rich previews. URL is required. Caption supports rich text formatting and is optional.",
|
173
83
|
)
|
@@ -1,43 +1,44 @@
|
|
1
1
|
from __future__ import annotations
|
2
|
+
|
2
3
|
from typing import Optional
|
4
|
+
|
3
5
|
from pydantic import BaseModel
|
4
6
|
|
5
|
-
from notionary.
|
7
|
+
from notionary.markdown.markdown_node import MarkdownNode
|
8
|
+
from notionary.blocks.mixins.captions import CaptionMarkdownNodeMixin
|
6
9
|
|
7
10
|
|
8
11
|
class BookmarkMarkdownBlockParams(BaseModel):
|
9
12
|
url: str
|
10
13
|
title: Optional[str] = None
|
11
|
-
|
14
|
+
caption: Optional[str] = None
|
12
15
|
|
13
16
|
|
14
|
-
class BookmarkMarkdownNode(MarkdownNode):
|
17
|
+
class BookmarkMarkdownNode(MarkdownNode, CaptionMarkdownNodeMixin):
|
15
18
|
"""
|
16
19
|
Programmatic interface for creating Notion-style bookmark Markdown blocks.
|
17
20
|
"""
|
18
21
|
|
19
22
|
def __init__(
|
20
|
-
self, url: str, title: Optional[str] = None,
|
23
|
+
self, url: str, title: Optional[str] = None, caption: Optional[str] = None
|
21
24
|
):
|
22
25
|
self.url = url
|
23
26
|
self.title = title
|
24
|
-
self.
|
27
|
+
self.caption = caption
|
25
28
|
|
26
29
|
@classmethod
|
27
30
|
def from_params(cls, params: BookmarkMarkdownBlockParams) -> BookmarkMarkdownNode:
|
28
|
-
return cls(url=params.url, title=params.title,
|
31
|
+
return cls(url=params.url, title=params.title, caption=params.caption)
|
29
32
|
|
30
33
|
def to_markdown(self) -> str:
|
34
|
+
"""Return the Markdown representation.
|
35
|
+
|
36
|
+
Examples:
|
37
|
+
- [bookmark](https://example.com)
|
38
|
+
- [bookmark](https://example.com)(caption:Some caption)
|
31
39
|
"""
|
32
|
-
|
33
|
-
[bookmark](
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
parts.append(f'"{self.title}"')
|
38
|
-
if self.description is not None:
|
39
|
-
# Wenn title fehlt, aber description da ist, trotzdem Platzhalter für title:
|
40
|
-
if self.title is None:
|
41
|
-
parts.append('""')
|
42
|
-
parts.append(f'"{self.description}"')
|
43
|
-
return " ".join(parts) + ")"
|
40
|
+
# Use simple bookmark syntax like BookmarkElement
|
41
|
+
base_markdown = f"[bookmark]({self.url})"
|
42
|
+
|
43
|
+
# Append caption using mixin helper
|
44
|
+
return self.append_caption_to_markdown(base_markdown, self.caption)
|
@@ -0,0 +1,15 @@
|
|
1
|
+
from typing import Literal
|
2
|
+
|
3
|
+
from pydantic import BaseModel, Field
|
4
|
+
|
5
|
+
from notionary.blocks.rich_text.rich_text_models import RichTextObject
|
6
|
+
|
7
|
+
|
8
|
+
class BookmarkBlock(BaseModel):
|
9
|
+
caption: list[RichTextObject] = Field(default_factory=list)
|
10
|
+
url: str
|
11
|
+
|
12
|
+
|
13
|
+
class CreateBookmarkBlock(BaseModel):
|
14
|
+
type: Literal["bookmark"] = "bookmark"
|
15
|
+
bookmark: BookmarkBlock
|
@@ -0,0 +1,17 @@
|
|
1
|
+
from notionary.blocks.breadcrumbs.breadcrumb_element import BreadcrumbElement
|
2
|
+
from notionary.blocks.breadcrumbs.breadcrumb_markdown_node import (
|
3
|
+
BreadcrumbMarkdownBlockParams,
|
4
|
+
BreadcrumbMarkdownNode,
|
5
|
+
)
|
6
|
+
from notionary.blocks.breadcrumbs.breadcrumb_models import (
|
7
|
+
BreadcrumbBlock,
|
8
|
+
CreateBreadcrumbBlock,
|
9
|
+
)
|
10
|
+
|
11
|
+
__all__ = [
|
12
|
+
"BreadcrumbElement",
|
13
|
+
"BreadcrumbBlock",
|
14
|
+
"CreateBreadcrumbBlock",
|
15
|
+
"BreadcrumbMarkdownNode",
|
16
|
+
"BreadcrumbMarkdownBlockParams",
|
17
|
+
]
|
@@ -0,0 +1,39 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import re
|
4
|
+
from typing import Optional
|
5
|
+
|
6
|
+
from notionary.blocks.base_block_element import BaseBlockElement
|
7
|
+
from notionary.blocks.breadcrumbs.breadcrumb_models import (
|
8
|
+
BreadcrumbBlock,
|
9
|
+
CreateBreadcrumbBlock,
|
10
|
+
)
|
11
|
+
from notionary.blocks.models import Block, BlockCreateResult, BlockType
|
12
|
+
|
13
|
+
|
14
|
+
class BreadcrumbElement(BaseBlockElement):
|
15
|
+
"""
|
16
|
+
Handles conversion between Markdown breadcrumb marker and Notion breadcrumb blocks.
|
17
|
+
|
18
|
+
Markdown syntax:
|
19
|
+
[breadcrumb]
|
20
|
+
"""
|
21
|
+
|
22
|
+
BREADCRUMB_MARKER = "[breadcrumb]"
|
23
|
+
PATTERN = re.compile(r"^\[breadcrumb\]\s*$", re.IGNORECASE)
|
24
|
+
|
25
|
+
@classmethod
|
26
|
+
def match_notion(cls, block: Block) -> bool:
|
27
|
+
# Kein extra Payload – nur Typ prüfen
|
28
|
+
return block.type == BlockType.BREADCRUMB and block.breadcrumb
|
29
|
+
|
30
|
+
@classmethod
|
31
|
+
async def markdown_to_notion(cls, text: str) -> BlockCreateResult:
|
32
|
+
if not cls.PATTERN.match(text.strip()):
|
33
|
+
return None
|
34
|
+
return CreateBreadcrumbBlock(breadcrumb=BreadcrumbBlock())
|
35
|
+
|
36
|
+
@classmethod
|
37
|
+
async def notion_to_markdown(cls, block: Block) -> Optional[str]:
|
38
|
+
if block.type == BlockType.BREADCRUMB and block.breadcrumb:
|
39
|
+
return cls.BREADCRUMB_MARKER
|
@@ -0,0 +1,32 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from pydantic import BaseModel
|
4
|
+
|
5
|
+
from notionary.markdown.markdown_node import MarkdownNode
|
6
|
+
|
7
|
+
|
8
|
+
class BreadcrumbMarkdownBlockParams(BaseModel):
|
9
|
+
"""Parameters for breadcrumb markdown block. No parameters needed."""
|
10
|
+
|
11
|
+
pass
|
12
|
+
|
13
|
+
|
14
|
+
class BreadcrumbMarkdownNode(MarkdownNode):
|
15
|
+
"""
|
16
|
+
Programmatic interface for creating Markdown breadcrumb blocks.
|
17
|
+
Example:
|
18
|
+
[breadcrumb]
|
19
|
+
"""
|
20
|
+
|
21
|
+
def __init__(self):
|
22
|
+
# No parameters needed for breadcrumb
|
23
|
+
pass
|
24
|
+
|
25
|
+
@classmethod
|
26
|
+
def from_params(
|
27
|
+
cls, params: BreadcrumbMarkdownBlockParams
|
28
|
+
) -> BreadcrumbMarkdownNode:
|
29
|
+
return cls()
|
30
|
+
|
31
|
+
def to_markdown(self) -> str:
|
32
|
+
return "[breadcrumb]"
|
@@ -1,7 +1,17 @@
|
|
1
|
-
from .bulleted_list_element import BulletedListElement
|
2
|
-
from .bulleted_list_markdown_node import
|
1
|
+
from notionary.blocks.bulleted_list.bulleted_list_element import BulletedListElement
|
2
|
+
from notionary.blocks.bulleted_list.bulleted_list_markdown_node import (
|
3
|
+
BulletedListMarkdownBlockParams,
|
4
|
+
BulletedListMarkdownNode,
|
5
|
+
)
|
6
|
+
from notionary.blocks.bulleted_list.bulleted_list_models import (
|
7
|
+
BulletedListItemBlock,
|
8
|
+
CreateBulletedListItemBlock,
|
9
|
+
)
|
3
10
|
|
4
11
|
__all__ = [
|
5
12
|
"BulletedListElement",
|
13
|
+
"BulletedListItemBlock",
|
14
|
+
"CreateBulletedListItemBlock",
|
6
15
|
"BulletedListMarkdownNode",
|
16
|
+
"BulletedListMarkdownBlockParams",
|
7
17
|
]
|