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,72 +1,74 @@
|
|
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.syntax_prompt_builder import BlockElementMarkdownInformation
|
12
|
+
from notionary.blocks.models import Block, BlockCreateResult, BlockType
|
13
|
+
from notionary.blocks.rich_text.text_inline_formatter import TextInlineFormatter
|
11
14
|
|
12
15
|
|
13
|
-
class BulletedListElement(
|
16
|
+
class BulletedListElement(BaseBlockElement):
|
14
17
|
"""Class for converting between Markdown bullet lists and Notion bulleted list items."""
|
15
18
|
|
19
|
+
# Regex for markdown bullets (excluding todo items [ ] or [x])
|
20
|
+
PATTERN = re.compile(r"^(\s*)[*\-+]\s+(?!\[[ x]\])(.+)$")
|
21
|
+
|
16
22
|
@classmethod
|
17
|
-
def
|
18
|
-
"""
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
23
|
+
def match_notion(cls, block: Block) -> bool:
|
24
|
+
"""Check if this element can handle the given Notion block."""
|
25
|
+
return block.type == BlockType.BULLETED_LIST_ITEM and block.bulleted_list_item
|
26
|
+
|
27
|
+
@classmethod
|
28
|
+
async def markdown_to_notion(cls, text: str) -> BlockCreateResult:
|
29
|
+
"""
|
30
|
+
Convert a markdown bulleted list item into a Notion BulletedListItemBlock.
|
31
|
+
"""
|
32
|
+
if not (match := cls.PATTERN.match(text.strip())):
|
24
33
|
return None
|
25
34
|
|
26
|
-
content
|
35
|
+
# Extract the content part (second capture group)
|
36
|
+
content = match.group(2)
|
27
37
|
|
28
|
-
#
|
29
|
-
rich_text = TextInlineFormatter.parse_inline_formatting(content)
|
38
|
+
# Parse inline markdown formatting into RichTextObject list
|
39
|
+
rich_text = await TextInlineFormatter.parse_inline_formatting(content)
|
30
40
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
41
|
+
# Return a properly typed Notion block
|
42
|
+
bulleted_list_content = BulletedListItemBlock(
|
43
|
+
rich_text=rich_text, color="default"
|
44
|
+
)
|
45
|
+
return CreateBulletedListItemBlock(bulleted_list_item=bulleted_list_content)
|
35
46
|
|
36
47
|
@classmethod
|
37
|
-
def notion_to_markdown(cls, block:
|
38
|
-
"""Convert Notion
|
39
|
-
if block.
|
48
|
+
async def notion_to_markdown(cls, block: Block) -> Optional[str]:
|
49
|
+
"""Convert Notion bulleted_list_item block to Markdown."""
|
50
|
+
if block.type != BlockType.BULLETED_LIST_ITEM or not block.bulleted_list_item:
|
40
51
|
return None
|
41
52
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
return f"- {content}"
|
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))
|
53
|
+
rich_list = block.bulleted_list_item.rich_text
|
54
|
+
if not rich_list:
|
55
|
+
return "-"
|
52
56
|
|
53
|
-
|
54
|
-
|
55
|
-
"""Check if this element can handle the given Notion block."""
|
56
|
-
return block.get("type") == "bulleted_list_item"
|
57
|
+
text = await TextInlineFormatter.extract_text_with_formatting(rich_list)
|
58
|
+
return f"- {text}"
|
57
59
|
|
58
60
|
@classmethod
|
59
|
-
def
|
60
|
-
"""
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
"
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
.
|
61
|
+
def get_system_prompt_information(cls) -> Optional[BlockElementMarkdownInformation]:
|
62
|
+
"""Get system prompt information for bulleted list blocks."""
|
63
|
+
return BlockElementMarkdownInformation(
|
64
|
+
block_type=cls.__name__,
|
65
|
+
description="Bulleted list items create unordered lists with bullet points",
|
66
|
+
syntax_examples=[
|
67
|
+
"- First item",
|
68
|
+
"* Second item",
|
69
|
+
"+ Third item",
|
70
|
+
"- Item with **bold text**",
|
71
|
+
"- Item with *italic text*",
|
72
|
+
],
|
73
|
+
usage_guidelines="Use -, *, or + to create bullet points. Supports inline formatting like bold, italic, and links. Do not use for todo items (use [ ] or [x] for those).",
|
72
74
|
)
|
@@ -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
|
]
|
@@ -1,16 +1,21 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import re
|
2
|
-
from typing import
|
3
|
-
|
4
|
-
from notionary.blocks.
|
5
|
-
from notionary.blocks import (
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
4
|
+
from typing import Optional
|
5
|
+
|
6
|
+
from notionary.blocks.base_block_element import BaseBlockElement
|
7
|
+
from notionary.blocks.callout.callout_models import (
|
8
|
+
CalloutBlock,
|
9
|
+
CreateCalloutBlock,
|
10
|
+
EmojiIcon,
|
11
|
+
IconObject,
|
10
12
|
)
|
13
|
+
from notionary.blocks.syntax_prompt_builder import BlockElementMarkdownInformation
|
14
|
+
from notionary.blocks.models import Block, BlockCreateResult, BlockType
|
15
|
+
from notionary.blocks.rich_text.text_inline_formatter import TextInlineFormatter
|
11
16
|
|
12
17
|
|
13
|
-
class CalloutElement(
|
18
|
+
class CalloutElement(BaseBlockElement):
|
14
19
|
"""
|
15
20
|
Handles conversion between Markdown callouts and Notion callout blocks.
|
16
21
|
|
@@ -23,110 +28,72 @@ class CalloutElement(NotionBlockElement):
|
|
23
28
|
- emoji is an optional emoji character (enclosed in quotes)
|
24
29
|
"""
|
25
30
|
|
26
|
-
# Regex pattern for callout syntax with optional emoji
|
27
31
|
PATTERN = re.compile(
|
28
|
-
r"^\[callout\]\(" #
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
+
r"^\[callout\]\(" # prefix
|
33
|
+
r"([^\"]+?)" # content
|
34
|
+
r"(?:\s+\"([^\"]+)\")?" # optional emoji
|
35
|
+
r"\)$"
|
32
36
|
)
|
33
37
|
|
34
|
-
# Default values
|
35
38
|
DEFAULT_EMOJI = "💡"
|
36
39
|
DEFAULT_COLOR = "gray_background"
|
37
40
|
|
38
41
|
@classmethod
|
39
|
-
def
|
40
|
-
|
41
|
-
return text.strip().startswith("[callout]") and bool(
|
42
|
-
CalloutElement.PATTERN.match(text.strip())
|
43
|
-
)
|
44
|
-
|
45
|
-
@classmethod
|
46
|
-
def match_notion(cls, block: Dict[str, Any]) -> bool:
|
47
|
-
"""Check if block is a Notion callout."""
|
48
|
-
return block.get("type") == "callout"
|
42
|
+
def match_notion(cls, block: Block) -> bool:
|
43
|
+
return block.type == BlockType.CALLOUT and block.callout
|
49
44
|
|
50
45
|
@classmethod
|
51
|
-
def markdown_to_notion(cls, text: str) ->
|
52
|
-
"""Convert markdown callout
|
53
|
-
|
54
|
-
if not
|
46
|
+
async def markdown_to_notion(cls, text: str) -> BlockCreateResult:
|
47
|
+
"""Convert a markdown callout into a Notion CalloutBlock."""
|
48
|
+
match = cls.PATTERN.match(text.strip())
|
49
|
+
if not match:
|
55
50
|
return None
|
56
51
|
|
57
|
-
content =
|
58
|
-
emoji = callout_match.group(2)
|
59
|
-
|
52
|
+
content, emoji = match.group(1), match.group(2)
|
60
53
|
if not content:
|
61
54
|
return None
|
62
55
|
|
63
|
-
# Use default emoji if none provided
|
64
56
|
if not emoji:
|
65
|
-
emoji =
|
57
|
+
emoji = cls.DEFAULT_EMOJI
|
66
58
|
|
67
|
-
|
68
|
-
"rich_text": TextInlineFormatter.parse_inline_formatting(content.strip()),
|
69
|
-
"icon": {"type": "emoji", "emoji": emoji},
|
70
|
-
"color": CalloutElement.DEFAULT_COLOR,
|
71
|
-
}
|
59
|
+
rich_text = await TextInlineFormatter.parse_inline_formatting(content.strip())
|
72
60
|
|
73
|
-
|
61
|
+
callout_content = CalloutBlock(
|
62
|
+
rich_text=rich_text,
|
63
|
+
icon=EmojiIcon(emoji=emoji),
|
64
|
+
color=cls.DEFAULT_COLOR,
|
65
|
+
)
|
66
|
+
return CreateCalloutBlock(callout=callout_content)
|
74
67
|
|
75
68
|
@classmethod
|
76
|
-
def notion_to_markdown(cls, block:
|
77
|
-
|
78
|
-
if block.get("type") != "callout":
|
69
|
+
async def notion_to_markdown(cls, block: Block) -> Optional[str]:
|
70
|
+
if block.type != BlockType.CALLOUT or not block.callout:
|
79
71
|
return None
|
80
72
|
|
81
|
-
|
82
|
-
rich_text = callout_data.get("rich_text", [])
|
83
|
-
icon = callout_data.get("icon", {})
|
73
|
+
data = block.callout
|
84
74
|
|
85
|
-
content = TextInlineFormatter.extract_text_with_formatting(rich_text)
|
75
|
+
content = await TextInlineFormatter.extract_text_with_formatting(data.rich_text)
|
86
76
|
if not content:
|
87
77
|
return None
|
88
78
|
|
89
|
-
|
90
|
-
|
91
|
-
if emoji and emoji != CalloutElement.DEFAULT_EMOJI:
|
92
|
-
return f'[callout]({content} "{emoji}")'
|
79
|
+
icon: Optional[IconObject] = block.callout.icon
|
80
|
+
emoji_char = icon.emoji if isinstance(icon, EmojiIcon) else cls.DEFAULT_EMOJI
|
93
81
|
|
82
|
+
if emoji_char and emoji_char != cls.DEFAULT_EMOJI:
|
83
|
+
return f'[callout]({content} "{emoji_char}")'
|
94
84
|
return f"[callout]({content})"
|
95
85
|
|
96
86
|
@classmethod
|
97
|
-
def
|
98
|
-
"""
|
99
|
-
return
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
def get_llm_prompt_content(cls) -> ElementPromptContent:
|
110
|
-
"""
|
111
|
-
Returns structured LLM prompt metadata for the callout element.
|
112
|
-
"""
|
113
|
-
return (
|
114
|
-
ElementPromptBuilder()
|
115
|
-
.with_description(
|
116
|
-
"Creates a callout block to highlight important information with an icon."
|
117
|
-
)
|
118
|
-
.with_usage_guidelines(
|
119
|
-
"Use callouts when you want to draw attention to important information, "
|
120
|
-
"tips, warnings, or notes that stand out from the main content."
|
121
|
-
)
|
122
|
-
.with_syntax('[callout](Text content "Optional emoji")')
|
123
|
-
.with_examples(
|
124
|
-
[
|
125
|
-
"[callout](This is a default callout with the light bulb emoji)",
|
126
|
-
'[callout](This is a callout with a bell emoji "🔔")',
|
127
|
-
'[callout](Warning: This is an important note "⚠️")',
|
128
|
-
'[callout](Tip: Add emoji that matches your content\'s purpose "💡")',
|
129
|
-
]
|
130
|
-
)
|
131
|
-
.build()
|
87
|
+
def get_system_prompt_information(cls) -> Optional[BlockElementMarkdownInformation]:
|
88
|
+
"""Get system prompt information for callout blocks."""
|
89
|
+
return BlockElementMarkdownInformation(
|
90
|
+
block_type=cls.__name__,
|
91
|
+
description="Callout blocks create highlighted text boxes with optional custom emojis for emphasis",
|
92
|
+
syntax_examples=[
|
93
|
+
"[callout](This is important information)",
|
94
|
+
'[callout](Warning message "⚠️")',
|
95
|
+
'[callout](Success message "✅")',
|
96
|
+
"[callout](Note with default emoji)",
|
97
|
+
],
|
98
|
+
usage_guidelines="Use for highlighting important information, warnings, tips, or notes. Default emoji is 💡. Custom emoji should be provided in quotes after the text content.",
|
132
99
|
)
|
@@ -1,8 +1,10 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
from typing import Optional
|
4
|
+
|
4
5
|
from pydantic import BaseModel
|
5
|
-
|
6
|
+
|
7
|
+
from notionary.markdown.markdown_node import MarkdownNode
|
6
8
|
|
7
9
|
|
8
10
|
class CalloutMarkdownBlockParams(BaseModel):
|
@@ -0,0 +1,33 @@
|
|
1
|
+
from typing import Literal, Optional, Union
|
2
|
+
|
3
|
+
from pydantic import BaseModel, Field
|
4
|
+
|
5
|
+
from notionary.blocks.file.file_element_models import FileBlock
|
6
|
+
from notionary.blocks.models import Block
|
7
|
+
from notionary.blocks.rich_text.rich_text_models import RichTextObject
|
8
|
+
from notionary.blocks.types import BlockColor
|
9
|
+
|
10
|
+
|
11
|
+
class EmojiIcon(BaseModel):
|
12
|
+
type: Literal["emoji"] = "emoji"
|
13
|
+
emoji: str
|
14
|
+
|
15
|
+
|
16
|
+
class FileIcon(BaseModel):
|
17
|
+
type: Literal["file"] = "file"
|
18
|
+
file: FileBlock
|
19
|
+
|
20
|
+
|
21
|
+
IconObject = Union[EmojiIcon, FileIcon]
|
22
|
+
|
23
|
+
|
24
|
+
class CalloutBlock(BaseModel):
|
25
|
+
rich_text: list[RichTextObject]
|
26
|
+
icon: Optional[IconObject] = None
|
27
|
+
color: BlockColor = BlockColor.DEFAULT
|
28
|
+
children: list[Block] = Field(default_factory=list)
|
29
|
+
|
30
|
+
|
31
|
+
class CreateCalloutBlock(BaseModel):
|
32
|
+
type: Literal["callout"] = "callout"
|
33
|
+
callout: CalloutBlock
|
@@ -0,0 +1,14 @@
|
|
1
|
+
"""
|
2
|
+
Child Database Block Module
|
3
|
+
|
4
|
+
This module provides functionality for handling Notion child database blocks.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from .child_database_element import ChildDatabaseElement
|
8
|
+
from .child_database_models import ChildDatabaseBlock, CreateChildDatabaseBlock
|
9
|
+
|
10
|
+
__all__ = [
|
11
|
+
"ChildDatabaseElement",
|
12
|
+
"ChildDatabaseBlock",
|
13
|
+
"CreateChildDatabaseBlock",
|
14
|
+
]
|
@@ -0,0 +1,61 @@
|
|
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.syntax_prompt_builder import BlockElementMarkdownInformation
|
8
|
+
from notionary.blocks.models import Block, BlockType
|
9
|
+
from notionary.util import LoggingMixin
|
10
|
+
|
11
|
+
class ChildDatabaseElement(BaseBlockElement, LoggingMixin):
|
12
|
+
"""
|
13
|
+
Handles conversion between Markdown database references and Notion child database blocks.
|
14
|
+
|
15
|
+
Creates new databases when converting from markdown.
|
16
|
+
"""
|
17
|
+
|
18
|
+
PATTERN_BRACKET = re.compile(r"^\[database:\s*(.+)\]$", re.IGNORECASE)
|
19
|
+
PATTERN_EMOJI = re.compile(r"^📊\s*(.+)$")
|
20
|
+
|
21
|
+
@classmethod
|
22
|
+
def match_notion(cls, block: Block) -> bool:
|
23
|
+
return block.type == BlockType.CHILD_DATABASE and block.child_database
|
24
|
+
|
25
|
+
@classmethod
|
26
|
+
async def markdown_to_notion(
|
27
|
+
cls, text: str
|
28
|
+
) -> Optional[str]:
|
29
|
+
"""
|
30
|
+
Convert markdown database syntax to actual Notion database.
|
31
|
+
Returns the database_id if successful, None otherwise.
|
32
|
+
"""
|
33
|
+
cls.logger.warning("Creating database from markdown is not supported via the block api. Call the create_child_page method in NotionPage instead.s")
|
34
|
+
return None
|
35
|
+
|
36
|
+
@classmethod
|
37
|
+
async def notion_to_markdown(cls, block: Block) -> Optional[str]:
|
38
|
+
if block.type != BlockType.CHILD_DATABASE or not block.child_database:
|
39
|
+
return None
|
40
|
+
|
41
|
+
title = block.child_database.title
|
42
|
+
if not title or not title.strip():
|
43
|
+
return None
|
44
|
+
|
45
|
+
# Use bracket syntax for output
|
46
|
+
return f"[database: {title.strip()}]"
|
47
|
+
|
48
|
+
@classmethod
|
49
|
+
def get_system_prompt_information(cls) -> Optional[BlockElementMarkdownInformation]:
|
50
|
+
"""Get system prompt information for child database blocks."""
|
51
|
+
return BlockElementMarkdownInformation(
|
52
|
+
block_type=cls.__name__,
|
53
|
+
description="Creates new embedded databases within a Notion page",
|
54
|
+
syntax_examples=[
|
55
|
+
"[database: Project Tasks]",
|
56
|
+
"[database: Customer Information]",
|
57
|
+
"📊 Sales Pipeline",
|
58
|
+
"📊 Team Directory",
|
59
|
+
],
|
60
|
+
usage_guidelines="Use to create new databases that will be embedded in the page. The database will be created with a basic 'Name' property and can be customized later.",
|
61
|
+
)
|
@@ -0,0 +1,12 @@
|
|
1
|
+
from typing import Literal
|
2
|
+
|
3
|
+
from pydantic import BaseModel
|
4
|
+
|
5
|
+
|
6
|
+
class ChildDatabaseBlock(BaseModel):
|
7
|
+
title: str
|
8
|
+
|
9
|
+
|
10
|
+
class CreateChildDatabaseBlock(BaseModel):
|
11
|
+
type: Literal["child_database"] = "child_database"
|
12
|
+
child_database: ChildDatabaseBlock
|
@@ -0,0 +1,94 @@
|
|
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.syntax_prompt_builder import BlockElementMarkdownInformation
|
8
|
+
from notionary.blocks.models import Block, BlockCreateResult, BlockType
|
9
|
+
from notionary.page.page_context import get_page_context
|
10
|
+
|
11
|
+
|
12
|
+
class ChildPageElement(BaseBlockElement):
|
13
|
+
"""
|
14
|
+
Handles conversion between Markdown page references and Notion child page blocks.
|
15
|
+
|
16
|
+
Creates new pages when converting from markdown.
|
17
|
+
"""
|
18
|
+
|
19
|
+
PATTERN_BRACKET = re.compile(r"^\[page:\s*(.+)\]$", re.IGNORECASE)
|
20
|
+
PATTERN_EMOJI = re.compile(r"^[📝📄]\s*(.+)$")
|
21
|
+
|
22
|
+
@classmethod
|
23
|
+
def match_notion(cls, block: Block) -> bool:
|
24
|
+
return block.type == BlockType.CHILD_PAGE and getattr(block, "child_page", None)
|
25
|
+
|
26
|
+
@classmethod
|
27
|
+
async def markdown_to_notion(cls, text: str) -> Optional[BlockCreateResult]:
|
28
|
+
"""
|
29
|
+
Convert markdown page syntax to an actual Notion page.
|
30
|
+
Returns None since child_page blocks are created implicitly via Pages API (not Blocks API).
|
31
|
+
"""
|
32
|
+
context = get_page_context()
|
33
|
+
|
34
|
+
text = text.strip()
|
35
|
+
|
36
|
+
match = cls.PATTERN_BRACKET.match(text)
|
37
|
+
if not match:
|
38
|
+
match = cls.PATTERN_EMOJI.match(text)
|
39
|
+
|
40
|
+
if not match:
|
41
|
+
return None
|
42
|
+
|
43
|
+
title = match.group(1).strip()
|
44
|
+
if not title:
|
45
|
+
return None
|
46
|
+
|
47
|
+
# Reject multiline titles
|
48
|
+
if "\n" in title or "\r" in title:
|
49
|
+
return None
|
50
|
+
|
51
|
+
try:
|
52
|
+
# Create the actual page using context
|
53
|
+
await context.page_client.create_page(
|
54
|
+
title=title,
|
55
|
+
parent_page_id=context.page_id,
|
56
|
+
)
|
57
|
+
# Return None as per BaseBlockElement convention:
|
58
|
+
# child_page blocks cannot be written through the Blocks API directly.
|
59
|
+
# Creating a page under the parent page will automatically insert a child_page block.
|
60
|
+
return None
|
61
|
+
|
62
|
+
except Exception as e:
|
63
|
+
print(f"Failed to create page '{title}': {e}")
|
64
|
+
return None
|
65
|
+
|
66
|
+
@classmethod
|
67
|
+
async def notion_to_markdown(cls, block: Block) -> Optional[str]:
|
68
|
+
if block.type != BlockType.CHILD_PAGE or not getattr(block, "child_page", None):
|
69
|
+
return None
|
70
|
+
|
71
|
+
title = block.child_page.title
|
72
|
+
if not title or not title.strip():
|
73
|
+
return None
|
74
|
+
|
75
|
+
# Use bracket syntax for output
|
76
|
+
return f"[page: {title.strip()}]"
|
77
|
+
|
78
|
+
@classmethod
|
79
|
+
def get_system_prompt_information(cls) -> Optional[BlockElementMarkdownInformation]:
|
80
|
+
"""Get system prompt information for child page blocks."""
|
81
|
+
return BlockElementMarkdownInformation(
|
82
|
+
block_type=cls.__name__,
|
83
|
+
description="Creates new sub-pages within a Notion page.",
|
84
|
+
syntax_examples=[
|
85
|
+
"[page: Meeting Notes]",
|
86
|
+
"[page: Ideas]",
|
87
|
+
"📝 Project Overview",
|
88
|
+
"📄 Research Log",
|
89
|
+
],
|
90
|
+
usage_guidelines=(
|
91
|
+
"Use to create new pages that will appear as child_page blocks in the current page. "
|
92
|
+
"Pages are created via the Pages API with the current page as parent."
|
93
|
+
),
|
94
|
+
)
|