notionary 0.2.18__py3-none-any.whl → 0.2.21__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 +263 -0
- notionary/blocks/audio/__init__.py +8 -2
- notionary/blocks/audio/audio_element.py +42 -104
- notionary/blocks/audio/audio_markdown_node.py +3 -1
- notionary/blocks/audio/audio_models.py +6 -55
- notionary/blocks/base_block_element.py +30 -0
- notionary/blocks/bookmark/__init__.py +9 -2
- notionary/blocks/bookmark/bookmark_element.py +46 -139
- notionary/blocks/bookmark/bookmark_markdown_node.py +3 -1
- 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 +40 -55
- 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 +40 -89
- notionary/blocks/callout/callout_markdown_node.py +3 -1
- notionary/blocks/callout/callout_models.py +33 -0
- notionary/blocks/child_database/__init__.py +7 -0
- notionary/blocks/child_database/child_database_models.py +19 -0
- notionary/blocks/child_page/__init__.py +9 -0
- notionary/blocks/child_page/child_page_models.py +12 -0
- notionary/blocks/{shared/block_client.py → client.py} +55 -54
- notionary/blocks/code/__init__.py +6 -2
- notionary/blocks/code/code_element.py +53 -187
- notionary/blocks/code/code_markdown_node.py +13 -13
- notionary/blocks/code/code_models.py +94 -0
- notionary/blocks/column/__init__.py +25 -1
- notionary/blocks/column/column_element.py +40 -314
- notionary/blocks/column/column_list_element.py +37 -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 +26 -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 +47 -114
- 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 +80 -0
- notionary/blocks/equation/equation_element_markdown_node.py +36 -0
- notionary/blocks/equation/equation_models.py +11 -0
- notionary/blocks/file/__init__.py +25 -0
- notionary/blocks/file/file_element.py +93 -0
- notionary/blocks/file/file_element_markdown_node.py +35 -0
- notionary/blocks/file/file_element_models.py +39 -0
- notionary/blocks/heading/__init__.py +16 -2
- notionary/blocks/heading/heading_element.py +67 -72
- 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 +84 -0
- notionary/blocks/{image → image_block}/image_markdown_node.py +3 -1
- notionary/blocks/image_block/image_models.py +10 -0
- notionary/blocks/models.py +172 -0
- notionary/blocks/numbered_list/__init__.py +12 -2
- notionary/blocks/numbered_list/numbered_list_element.py +33 -58
- 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 +27 -69
- 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 +91 -0
- notionary/blocks/pdf/pdf_markdown_node.py +35 -0
- notionary/blocks/pdf/pdf_models.py +11 -0
- notionary/blocks/quote/__init__.py +11 -2
- notionary/blocks/quote/quote_element.py +31 -65
- notionary/blocks/quote/quote_markdown_node.py +4 -1
- notionary/blocks/quote/quote_models.py +18 -0
- notionary/blocks/registry/__init__.py +4 -0
- notionary/blocks/registry/block_registry.py +75 -91
- notionary/blocks/registry/block_registry_builder.py +107 -59
- notionary/blocks/rich_text/__init__.py +33 -0
- notionary/blocks/rich_text/rich_text_models.py +188 -0
- notionary/blocks/rich_text/text_inline_formatter.py +125 -0
- notionary/blocks/table/__init__.py +16 -2
- notionary/blocks/table/table_element.py +48 -241
- 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 +51 -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 +38 -95
- 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 +57 -264
- notionary/blocks/toggle/toggle_markdown_node.py +24 -14
- notionary/blocks/toggle/toggle_models.py +17 -0
- notionary/blocks/toggleable_heading/__init__.py +6 -2
- notionary/blocks/toggleable_heading/toggleable_heading_element.py +74 -244
- notionary/blocks/toggleable_heading/toggleable_heading_markdown_node.py +26 -18
- notionary/blocks/types.py +61 -0
- notionary/blocks/video/__init__.py +8 -2
- notionary/blocks/video/video_element.py +67 -143
- notionary/blocks/video/video_element_models.py +10 -0
- notionary/blocks/video/video_markdown_node.py +3 -1
- notionary/database/client.py +3 -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 +2 -1
- notionary/file_upload/notion_file_upload.py +2 -3
- notionary/markdown/markdown_builder.py +722 -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 +9 -10
- notionary/page/models.py +327 -0
- notionary/page/notion_page.py +99 -52
- notionary/page/notion_text_length_utils.py +119 -0
- notionary/page/{content/page_content_writer.py → page_content_writer.py} +88 -38
- notionary/page/reader/handler/__init__.py +17 -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 +43 -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 +60 -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 +69 -0
- notionary/page/search_filter_builder.py +2 -1
- notionary/page/writer/handler/__init__.py +22 -0
- notionary/page/writer/handler/code_handler.py +100 -0
- notionary/page/writer/handler/column_handler.py +141 -0
- notionary/page/writer/handler/column_list_handler.py +139 -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 +92 -0
- notionary/page/writer/handler/table_handler.py +130 -0
- notionary/page/writer/handler/toggle_handler.py +153 -0
- notionary/page/writer/handler/toggleable_heading_handler.py +167 -0
- notionary/page/writer/markdown_to_notion_converter.py +76 -0
- notionary/telemetry/__init__.py +2 -2
- notionary/telemetry/service.py +4 -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 +3 -2
- notionary/user/notion_user_provider.py +1 -1
- 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 +3 -2
- {notionary-0.2.18.dist-info → notionary-0.2.21.dist-info}/METADATA +12 -8
- notionary-0.2.21.dist-info/RECORD +185 -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/__init__.py +0 -0
- notionary/blocks/shared/models.py +0 -710
- 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/blocks/toggleable_heading/toggleable_heading_models.py +0 -0
- 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/notion_text_length_utils.py +0 -87
- notionary/page/content/page_content_retriever.py +0 -52
- 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-0.2.18.dist-info/RECORD +0 -149
- /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/page/{content/markdown_whitespace_processor.py → markdown_whitespace_processor.py} +0 -0
- /notionary/{blocks/mention/mention_models.py → page/reader/handler/context.py} +0 -0
- {notionary-0.2.18.dist-info → notionary-0.2.21.dist-info}/LICENSE +0 -0
- {notionary-0.2.18.dist-info → notionary-0.2.21.dist-info}/WHEEL +0 -0
@@ -0,0 +1,30 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from abc import ABC
|
4
|
+
from typing import Optional
|
5
|
+
|
6
|
+
from notionary.blocks.models import Block, BlockCreateResult
|
7
|
+
|
8
|
+
|
9
|
+
class BaseBlockElement(ABC):
|
10
|
+
"""Base class for elements that can be converted between Markdown and Notion."""
|
11
|
+
|
12
|
+
@classmethod
|
13
|
+
def markdown_to_notion(cls, text: str) -> BlockCreateResult:
|
14
|
+
"""
|
15
|
+
Convert markdown to Notion block content.
|
16
|
+
|
17
|
+
Returns:
|
18
|
+
- BlockContent: Single block content (e.g., ToDoBlock, ParagraphBlock)
|
19
|
+
- list[BlockContent]: Multiple block contents
|
20
|
+
- None: Cannot convert this markdown
|
21
|
+
"""
|
22
|
+
|
23
|
+
@classmethod
|
24
|
+
def notion_to_markdown(cls, block: Block) -> Optional[str]:
|
25
|
+
"""Convert Notion block to markdown."""
|
26
|
+
|
27
|
+
@classmethod
|
28
|
+
def match_notion(cls, block: Block) -> bool:
|
29
|
+
"""Check if this element can handle the given Notion block."""
|
30
|
+
return bool(cls.notion_to_markdown(block)) # Now calls the class's version
|
@@ -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,80 @@
|
|
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
|
-
NotionBlockResult,
|
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.models import Block, BlockCreateResult, BlockType
|
9
|
+
from notionary.blocks.rich_text.text_inline_formatter import TextInlineFormatter
|
11
10
|
|
12
11
|
|
13
|
-
|
12
|
+
# BookmarkElement implementation using BlockType enum and TextInlineFormatter
|
13
|
+
class BookmarkElement(BaseBlockElement):
|
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 "Title") -
|
20
|
-
- [bookmark](https://example.com "Title" "Description") -
|
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 "Title") - URL + title
|
20
|
+
- [bookmark](https://example.com "Title" "Description") - URL + title + description
|
26
21
|
"""
|
27
22
|
|
28
|
-
# Regex pattern for bookmark syntax with optional title and description
|
29
23
|
PATTERN = re.compile(
|
30
|
-
r"^\[bookmark\]\(" #
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
24
|
+
r"^\[bookmark\]\(" # prefix
|
25
|
+
r"(https?://[^\s\"]+)" # URL
|
26
|
+
r"(?:\s+\"([^\"]+)\")?" # optional Title
|
27
|
+
r"(?:\s+\"([^\"]+)\")?" # optional Description
|
28
|
+
r"\)$"
|
35
29
|
)
|
36
30
|
|
37
31
|
@classmethod
|
38
|
-
def
|
39
|
-
|
40
|
-
return text.strip().startswith("[bookmark]") and bool(
|
41
|
-
cls.PATTERN.match(text.strip())
|
42
|
-
)
|
43
|
-
|
44
|
-
@classmethod
|
45
|
-
def match_notion(cls, block: Dict[str, Any]) -> bool:
|
46
|
-
"""Check if block is a Notion bookmark."""
|
47
|
-
return block.get("type") in ["bookmark", "external-bookmark"]
|
32
|
+
def match_notion(cls, block: Block) -> bool:
|
33
|
+
return block.type == BlockType.BOOKMARK and block.bookmark
|
48
34
|
|
49
35
|
@classmethod
|
50
|
-
def markdown_to_notion(cls, text: str) ->
|
51
|
-
"""
|
52
|
-
|
53
|
-
|
36
|
+
def markdown_to_notion(cls, text: str) -> BlockCreateResult:
|
37
|
+
"""
|
38
|
+
Convert a markdown bookmark into a Notion BookmarkBlock.
|
39
|
+
"""
|
40
|
+
if not (m := cls.PATTERN.match(text.strip())):
|
54
41
|
return None
|
55
42
|
|
56
|
-
url =
|
57
|
-
title = bookmark_match.group(2)
|
58
|
-
description = bookmark_match.group(3)
|
59
|
-
|
60
|
-
bookmark_data = {"url": url}
|
43
|
+
url, title, description = m.group(1), m.group(2), m.group(3)
|
61
44
|
|
62
|
-
# Build caption
|
63
|
-
|
45
|
+
# Build caption texts
|
46
|
+
parts: list[str] = []
|
64
47
|
if title:
|
65
|
-
|
48
|
+
parts.append(title)
|
66
49
|
if description:
|
67
|
-
|
50
|
+
parts.append(description)
|
68
51
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
else:
|
74
|
-
bookmark_data["caption"] = []
|
52
|
+
caption = []
|
53
|
+
if parts:
|
54
|
+
joined = " – ".join(parts)
|
55
|
+
caption = TextInlineFormatter.parse_inline_formatting(joined)
|
75
56
|
|
76
|
-
|
57
|
+
bookmark_data = BookmarkBlock(url=url, caption=caption)
|
58
|
+
return CreateBookmarkBlock(bookmark=bookmark_data)
|
77
59
|
|
78
60
|
@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:
|
61
|
+
def notion_to_markdown(cls, block: Block) -> Optional[str]:
|
62
|
+
if block.type != BlockType.BOOKMARK or block.bookmark is None:
|
92
63
|
return None
|
93
64
|
|
94
|
-
|
95
|
-
|
65
|
+
bm = block.bookmark
|
66
|
+
url = bm.url
|
96
67
|
if not url:
|
97
68
|
return None
|
98
69
|
|
99
|
-
|
100
|
-
|
101
|
-
if not caption:
|
102
|
-
# Simple bookmark with URL only
|
70
|
+
captions = bm.caption or []
|
71
|
+
if not captions:
|
103
72
|
return f"[bookmark]({url})"
|
104
73
|
|
105
|
-
|
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}")'
|
113
|
-
|
114
|
-
return f"[bookmark]({url})"
|
115
|
-
|
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
|
-
return result
|
131
|
-
|
132
|
-
@classmethod
|
133
|
-
def _parse_caption(cls, caption: List[Dict[str, Any]]) -> Tuple[str, str]:
|
134
|
-
"""
|
135
|
-
Parse Notion caption into title and description components.
|
136
|
-
Returns a tuple of (title, description).
|
137
|
-
"""
|
138
|
-
if not caption:
|
139
|
-
return "", ""
|
140
|
-
|
141
|
-
full_text = BookmarkElement._extract_text_content(caption)
|
142
|
-
|
143
|
-
if " - " in full_text:
|
144
|
-
parts = full_text.split(" - ", 1)
|
145
|
-
return parts[0].strip(), parts[1].strip()
|
74
|
+
text = TextInlineFormatter.extract_text_with_formatting(captions)
|
146
75
|
|
147
|
-
|
76
|
+
if " - " in text:
|
77
|
+
title, desc = map(str.strip, text.split(" - ", 1))
|
78
|
+
return f'[bookmark]({url} "{title}" "{desc}")'
|
148
79
|
|
149
|
-
|
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()
|
173
|
-
)
|
80
|
+
return f'[bookmark]({url} "{text}")'
|
@@ -1,8 +1,10 @@
|
|
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
|
6
8
|
|
7
9
|
|
8
10
|
class BookmarkMarkdownBlockParams(BaseModel):
|
@@ -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
|
+
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
|
+
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
|
]
|
@@ -1,72 +1,57 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import re
|
2
|
-
from typing import
|
3
|
-
from notionary.blocks import NotionBlockElement
|
4
|
-
from notionary.blocks import (
|
5
|
-
ElementPromptContent,
|
6
|
-
ElementPromptBuilder,
|
7
|
-
NotionBlockResult,
|
8
|
-
)
|
4
|
+
from typing import Optional
|
9
5
|
|
10
|
-
from notionary.blocks.
|
6
|
+
from notionary.blocks.base_block_element import BaseBlockElement
|
7
|
+
from notionary.blocks.bulleted_list.bulleted_list_models import (
|
8
|
+
BulletedListItemBlock,
|
9
|
+
CreateBulletedListItemBlock,
|
10
|
+
)
|
11
|
+
from notionary.blocks.models import Block, BlockCreateResult, BlockType
|
12
|
+
from notionary.blocks.rich_text.text_inline_formatter import TextInlineFormatter
|
11
13
|
|
12
14
|
|
13
|
-
class BulletedListElement(
|
15
|
+
class BulletedListElement(BaseBlockElement):
|
14
16
|
"""Class for converting between Markdown bullet lists and Notion bulleted list items."""
|
15
17
|
|
18
|
+
# Regex for markdown bullets (excluding todo items [ ] or [x])
|
19
|
+
PATTERN = re.compile(r"^(\s*)[*\-+]\s+(?!\[[ x]\])(.+)$")
|
20
|
+
|
21
|
+
@classmethod
|
22
|
+
def match_notion(cls, block: Block) -> bool:
|
23
|
+
"""Check if this element can handle the given Notion block."""
|
24
|
+
return block.type == BlockType.BULLETED_LIST_ITEM and block.bulleted_list_item
|
25
|
+
|
16
26
|
@classmethod
|
17
|
-
def markdown_to_notion(cls, text: str) ->
|
18
|
-
"""
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
list_match = pattern.match(text)
|
23
|
-
if not list_match:
|
27
|
+
def markdown_to_notion(cls, text: str) -> BlockCreateResult:
|
28
|
+
"""
|
29
|
+
Convert a markdown bulleted list item into a Notion BulletedListItemBlock.
|
30
|
+
"""
|
31
|
+
if not (match := cls.PATTERN.match(text.strip())):
|
24
32
|
return None
|
25
33
|
|
26
|
-
content
|
34
|
+
# Extract the content part (second capture group)
|
35
|
+
content = match.group(2)
|
27
36
|
|
28
|
-
#
|
37
|
+
# Parse inline markdown formatting into RichTextObject list
|
29
38
|
rich_text = TextInlineFormatter.parse_inline_formatting(content)
|
30
39
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
40
|
+
# Return a properly typed Notion block
|
41
|
+
bulleted_list_content = BulletedListItemBlock(
|
42
|
+
rich_text=rich_text, color="default"
|
43
|
+
)
|
44
|
+
return CreateBulletedListItemBlock(bulleted_list_item=bulleted_list_content)
|
35
45
|
|
36
46
|
@classmethod
|
37
|
-
def notion_to_markdown(cls, block:
|
38
|
-
"""Convert Notion
|
39
|
-
if block.
|
47
|
+
def notion_to_markdown(cls, block: Block) -> Optional[str]:
|
48
|
+
"""Convert Notion bulleted_list_item block to Markdown."""
|
49
|
+
if block.type != BlockType.BULLETED_LIST_ITEM or not block.bulleted_list_item:
|
40
50
|
return None
|
41
51
|
|
42
|
-
|
43
|
-
|
52
|
+
rich_list = block.bulleted_list_item.rich_text
|
53
|
+
if not rich_list:
|
54
|
+
return "-"
|
44
55
|
|
45
|
-
|
46
|
-
|
47
|
-
@classmethod
|
48
|
-
def match_markdown(cls, text: str) -> bool:
|
49
|
-
"""Check if this element can handle the given markdown text."""
|
50
|
-
pattern = re.compile(r"^(\s*)[*\-+]\s+(?!\[[ x]\])(.+)$")
|
51
|
-
return bool(pattern.match(text))
|
52
|
-
|
53
|
-
@classmethod
|
54
|
-
def match_notion(cls, block: Dict[str, Any]) -> bool:
|
55
|
-
"""Check if this element can handle the given Notion block."""
|
56
|
-
return block.get("type") == "bulleted_list_item"
|
57
|
-
|
58
|
-
@classmethod
|
59
|
-
def get_llm_prompt_content(cls) -> ElementPromptContent:
|
60
|
-
"""
|
61
|
-
Returns structured LLM prompt metadata for the bulleted list element.
|
62
|
-
"""
|
63
|
-
return (
|
64
|
-
ElementPromptBuilder()
|
65
|
-
.with_description("Creates bulleted list items for unordered lists.")
|
66
|
-
.with_usage_guidelines(
|
67
|
-
"Use for lists where order doesn't matter, such as features, options, or items without hierarchy."
|
68
|
-
)
|
69
|
-
.with_syntax("- Item text")
|
70
|
-
.with_standard_markdown()
|
71
|
-
.build()
|
72
|
-
)
|
56
|
+
text = TextInlineFormatter.extract_text_with_formatting(rich_list)
|
57
|
+
return f"- {text}"
|
@@ -0,0 +1,18 @@
|
|
1
|
+
from typing import Literal
|
2
|
+
|
3
|
+
from pydantic import BaseModel, Field
|
4
|
+
|
5
|
+
from notionary.blocks.models import Block
|
6
|
+
from notionary.blocks.rich_text import RichTextObject
|
7
|
+
from notionary.blocks.types import BlockColor
|
8
|
+
|
9
|
+
|
10
|
+
class BulletedListItemBlock(BaseModel):
|
11
|
+
rich_text: list[RichTextObject]
|
12
|
+
color: BlockColor = BlockColor.DEFAULT
|
13
|
+
children: list[Block] = Field(default_factory=list)
|
14
|
+
|
15
|
+
|
16
|
+
class CreateBulletedListItemBlock(BaseModel):
|
17
|
+
type: Literal["bulleted_list_item"] = "bulleted_list_item"
|
18
|
+
bulleted_list_item: BulletedListItemBlock
|
@@ -1,7 +1,14 @@
|
|
1
|
-
from .callout_element import CalloutElement
|
2
|
-
from .callout_markdown_node import
|
1
|
+
from notionary.blocks.callout.callout_element import CalloutElement
|
2
|
+
from notionary.blocks.callout.callout_markdown_node import (
|
3
|
+
CalloutMarkdownBlockParams,
|
4
|
+
CalloutMarkdownNode,
|
5
|
+
)
|
6
|
+
from notionary.blocks.callout.callout_models import CalloutBlock, CreateCalloutBlock
|
3
7
|
|
4
8
|
__all__ = [
|
5
9
|
"CalloutElement",
|
10
|
+
"CalloutBlock",
|
11
|
+
"CreateCalloutBlock",
|
6
12
|
"CalloutMarkdownNode",
|
13
|
+
"CalloutMarkdownBlockParams",
|
7
14
|
]
|