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,7 +1,14 @@
|
|
1
|
-
from .divider_element import DividerElement
|
2
|
-
from .divider_markdown_node import
|
1
|
+
from notionary.blocks.divider.divider_element import DividerElement
|
2
|
+
from notionary.blocks.divider.divider_markdown_node import (
|
3
|
+
DividerMarkdownBlockParams,
|
4
|
+
DividerMarkdownNode,
|
5
|
+
)
|
6
|
+
from notionary.blocks.divider.divider_models import CreateDividerBlock, DividerBlock
|
3
7
|
|
4
8
|
__all__ = [
|
5
9
|
"DividerElement",
|
10
|
+
"DividerBlock",
|
11
|
+
"CreateDividerBlock",
|
6
12
|
"DividerMarkdownNode",
|
13
|
+
"DividerMarkdownBlockParams",
|
7
14
|
]
|
@@ -1,15 +1,15 @@
|
|
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
|
-
)
|
6
|
+
from notionary.blocks.base_block_element import BaseBlockElement
|
7
|
+
from notionary.blocks.divider.divider_models import CreateDividerBlock, DividerBlock
|
8
|
+
from notionary.blocks.models import Block, BlockCreateResult
|
9
|
+
from notionary.blocks.types import BlockType
|
10
10
|
|
11
11
|
|
12
|
-
class DividerElement(
|
12
|
+
class DividerElement(BaseBlockElement):
|
13
13
|
"""
|
14
14
|
Handles conversion between Markdown horizontal dividers and Notion divider blocks.
|
15
15
|
|
@@ -20,53 +20,22 @@ class DividerElement(NotionBlockElement):
|
|
20
20
|
PATTERN = re.compile(r"^\s*-{3,}\s*$")
|
21
21
|
|
22
22
|
@classmethod
|
23
|
-
def
|
24
|
-
"""Check if
|
25
|
-
return
|
26
|
-
|
27
|
-
@classmethod
|
28
|
-
def match_notion(cls, block: Dict[str, Any]) -> bool:
|
29
|
-
"""Check if block is a Notion divider."""
|
30
|
-
return block.get("type") == "divider"
|
23
|
+
def match_notion(cls, block: Block) -> bool:
|
24
|
+
"""Check if this element can handle the given Notion block."""
|
25
|
+
return block.type == BlockType.DIVIDER and block.divider
|
31
26
|
|
32
27
|
@classmethod
|
33
|
-
def markdown_to_notion(cls, text: str) ->
|
34
|
-
"""Convert markdown
|
35
|
-
if not
|
28
|
+
async def markdown_to_notion(cls, text: str) -> BlockCreateResult:
|
29
|
+
"""Convert markdown horizontal rule to Notion divider, with preceding empty paragraph."""
|
30
|
+
if not cls.PATTERN.match(text.strip()):
|
36
31
|
return None
|
37
32
|
|
38
|
-
|
33
|
+
divider = DividerBlock()
|
39
34
|
|
40
|
-
|
41
|
-
|
42
|
-
return [empty_paragraph, divider_block]
|
35
|
+
return CreateDividerBlock(divider=divider)
|
43
36
|
|
44
37
|
@classmethod
|
45
|
-
def notion_to_markdown(cls, block:
|
46
|
-
|
47
|
-
if block.get("type") != "divider":
|
38
|
+
async def notion_to_markdown(cls, block: Block) -> Optional[str]:
|
39
|
+
if block.type != BlockType.DIVIDER or not block.divider:
|
48
40
|
return None
|
49
|
-
|
50
41
|
return "---"
|
51
|
-
|
52
|
-
@classmethod
|
53
|
-
def is_multiline(cls) -> bool:
|
54
|
-
return False
|
55
|
-
|
56
|
-
@classmethod
|
57
|
-
def get_llm_prompt_content(cls) -> ElementPromptContent:
|
58
|
-
"""Returns structured LLM prompt metadata for the divider element."""
|
59
|
-
return (
|
60
|
-
ElementPromptBuilder()
|
61
|
-
.with_description(
|
62
|
-
"Creates a horizontal divider line to visually separate sections of content."
|
63
|
-
)
|
64
|
-
.with_usage_guidelines(
|
65
|
-
"Use dividers only sparingly and only when the user explicitly asks for them. Dividers create strong visual breaks between content sections, so they should not be used unless specifically requested by the user."
|
66
|
-
)
|
67
|
-
.with_syntax("---")
|
68
|
-
.with_examples(
|
69
|
-
["## Section 1\nContent\n\n---\n\n## Section 2\nMore content"]
|
70
|
-
)
|
71
|
-
.build()
|
72
|
-
)
|
@@ -1,7 +1,14 @@
|
|
1
|
-
from .embed_element import EmbedElement
|
2
|
-
from .embed_markdown_node import
|
1
|
+
from notionary.blocks.embed.embed_element import EmbedElement
|
2
|
+
from notionary.blocks.embed.embed_markdown_node import (
|
3
|
+
EmbedMarkdownBlockParams,
|
4
|
+
EmbedMarkdownNode,
|
5
|
+
)
|
6
|
+
from notionary.blocks.embed.embed_models import CreateEmbedBlock, EmbedBlock
|
3
7
|
|
4
8
|
__all__ = [
|
5
9
|
"EmbedElement",
|
10
|
+
"EmbedBlock",
|
11
|
+
"CreateEmbedBlock",
|
6
12
|
"EmbedMarkdownNode",
|
13
|
+
"EmbedMarkdownBlockParams",
|
7
14
|
]
|
@@ -1,144 +1,98 @@
|
|
1
|
-
import
|
2
|
-
from typing import Dict, Any, Optional, List
|
1
|
+
from __future__ import annotations
|
3
2
|
|
4
|
-
|
5
|
-
from
|
6
|
-
|
7
|
-
|
8
|
-
|
3
|
+
import re
|
4
|
+
from typing import Optional
|
5
|
+
|
6
|
+
from notionary.blocks.base_block_element import BaseBlockElement
|
7
|
+
from notionary.blocks.embed.embed_models import CreateEmbedBlock, EmbedBlock
|
8
|
+
from notionary.blocks.file.file_element_models import (
|
9
|
+
ExternalFile,
|
10
|
+
FileUploadFile,
|
11
|
+
NotionHostedFile,
|
9
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.rich_text_models import RichTextObject
|
16
|
+
from notionary.blocks.rich_text.text_inline_formatter import TextInlineFormatter
|
10
17
|
|
11
18
|
|
12
|
-
class EmbedElement(
|
19
|
+
class EmbedElement(BaseBlockElement):
|
13
20
|
"""
|
14
21
|
Handles conversion between Markdown embeds and Notion embed blocks.
|
15
22
|
|
16
23
|
Markdown embed syntax:
|
17
|
-
- [embed](https://example.com) -
|
18
|
-
- [embed](https://example.com "Caption") -
|
19
|
-
|
20
|
-
Where:
|
21
|
-
- URL is the required embed URL
|
22
|
-
- Caption is an optional descriptive text (enclosed in quotes)
|
23
|
-
|
24
|
-
Supports various URL types including websites, PDFs, Google Maps, Google Drive,
|
25
|
-
Twitter/X posts, and other sources that Notion can embed.
|
24
|
+
- [embed](https://example.com) - URL only
|
25
|
+
- [embed](https://example.com "Caption") - URL + caption
|
26
26
|
"""
|
27
27
|
|
28
|
-
# Regex pattern for embed syntax with optional caption
|
29
28
|
PATTERN = re.compile(
|
30
|
-
r"^\[embed\]\(" #
|
31
|
-
|
32
|
-
|
33
|
-
|
29
|
+
r"^\[embed\]\(" # prefix
|
30
|
+
r"(https?://[^\s\"]+)" # URL
|
31
|
+
r"(?:\s+\"([^\"]+)\")?" # optional caption
|
32
|
+
r"\)$"
|
34
33
|
)
|
35
34
|
|
36
35
|
@classmethod
|
37
|
-
def
|
38
|
-
|
39
|
-
return text.strip().startswith("[embed]") and bool(
|
40
|
-
EmbedElement.PATTERN.match(text.strip())
|
41
|
-
)
|
36
|
+
def match_notion(cls, block: Block) -> bool:
|
37
|
+
return block.type == BlockType.EMBED and block.embed
|
42
38
|
|
43
39
|
@classmethod
|
44
|
-
def
|
45
|
-
"""
|
46
|
-
|
47
|
-
|
48
|
-
@classmethod
|
49
|
-
def markdown_to_notion(cls, text: str) -> NotionBlockResult:
|
50
|
-
"""Convert markdown embed to Notion embed block."""
|
51
|
-
embed_match = EmbedElement.PATTERN.match(text.strip())
|
52
|
-
if not embed_match:
|
40
|
+
async def markdown_to_notion(cls, text: str) -> BlockCreateResult:
|
41
|
+
"""Convert markdown embed syntax to Notion EmbedBlock."""
|
42
|
+
match = cls.PATTERN.match(text.strip())
|
43
|
+
if not match:
|
53
44
|
return None
|
54
45
|
|
55
|
-
url =
|
56
|
-
caption = embed_match.group(2)
|
57
|
-
|
58
|
-
if not url:
|
59
|
-
return None
|
46
|
+
url, rich_text = match.group(1), match.group(2) or ""
|
60
47
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
else:
|
67
|
-
embed_data["caption"] = []
|
48
|
+
# Build EmbedBlock
|
49
|
+
embed_block = EmbedBlock(url=url, caption=[])
|
50
|
+
if rich_text.strip():
|
51
|
+
rich_text_obj = RichTextObject.from_plain_text(rich_text.strip())
|
52
|
+
embed_block.caption = [rich_text_obj]
|
68
53
|
|
69
|
-
|
70
|
-
embed_block = {"type": "embed", "embed": embed_data}
|
71
|
-
|
72
|
-
# Add empty paragraph after embed
|
73
|
-
empty_paragraph = {"type": "paragraph", "paragraph": {"rich_text": []}}
|
74
|
-
|
75
|
-
return [embed_block, empty_paragraph]
|
54
|
+
return CreateEmbedBlock(embed=embed_block)
|
76
55
|
|
77
56
|
@classmethod
|
78
|
-
def notion_to_markdown(cls, block:
|
79
|
-
|
80
|
-
if block.get("type") != "embed":
|
57
|
+
async def notion_to_markdown(cls, block: Block) -> Optional[str]:
|
58
|
+
if block.type != BlockType.EMBED or not block.embed:
|
81
59
|
return None
|
82
60
|
|
83
|
-
|
84
|
-
url = embed_data.get("url", "")
|
61
|
+
fo = block.embed
|
85
62
|
|
86
|
-
if
|
63
|
+
if isinstance(fo, (ExternalFile, NotionHostedFile)):
|
64
|
+
url = fo.url
|
65
|
+
elif isinstance(fo, FileUploadFile):
|
66
|
+
return None
|
67
|
+
else:
|
87
68
|
return None
|
88
69
|
|
89
|
-
|
90
|
-
|
91
|
-
if not caption_rich_text:
|
92
|
-
# Simple embed with URL only
|
70
|
+
if not fo.caption:
|
93
71
|
return f"[embed]({url})"
|
94
72
|
|
95
|
-
|
96
|
-
|
73
|
+
text_parts = []
|
74
|
+
for rt in fo.caption:
|
75
|
+
if rt.plain_text:
|
76
|
+
text_parts.append(rt.plain_text)
|
77
|
+
else:
|
78
|
+
formatted_text = await TextInlineFormatter.extract_text_with_formatting(
|
79
|
+
[rt]
|
80
|
+
)
|
81
|
+
text_parts.append(formatted_text)
|
82
|
+
text = "".join(text_parts)
|
97
83
|
|
98
|
-
|
99
|
-
return f'[embed]({url} "{caption}")'
|
100
|
-
|
101
|
-
return f"[embed]({url})"
|
102
|
-
|
103
|
-
@classmethod
|
104
|
-
def is_multiline(cls) -> bool:
|
105
|
-
"""Embeds are single-line elements."""
|
106
|
-
return False
|
107
|
-
|
108
|
-
@classmethod
|
109
|
-
def _extract_text_content(cls, rich_text: List[Dict[str, Any]]) -> str:
|
110
|
-
"""Extract plain text content from Notion rich_text elements."""
|
111
|
-
result = ""
|
112
|
-
for text_obj in rich_text:
|
113
|
-
if text_obj.get("type") == "text":
|
114
|
-
result += text_obj.get("text", {}).get("content", "")
|
115
|
-
elif "plain_text" in text_obj:
|
116
|
-
result += text_obj.get("plain_text", "")
|
117
|
-
return result
|
84
|
+
return f'[embed]({url} "{text}")'
|
118
85
|
|
119
86
|
@classmethod
|
120
|
-
def
|
121
|
-
"""
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
"
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
"Embeds are great for interactive content, reference materials, or live data sources."
|
132
|
-
)
|
133
|
-
.with_syntax('[embed](https://example.com "Optional caption")')
|
134
|
-
.with_examples(
|
135
|
-
[
|
136
|
-
"[embed](https://drive.google.com/file/d/123456/view)",
|
137
|
-
'[embed](https://www.google.com/maps?q=San+Francisco "Our office location")',
|
138
|
-
'[embed](https://twitter.com/NotionHQ/status/1234567890 "Latest announcement")',
|
139
|
-
'[embed](https://github.com/username/repo "Project documentation")',
|
140
|
-
'[embed](https://example.com/important-reference.pdf "Course materials")',
|
141
|
-
]
|
142
|
-
)
|
143
|
-
.build()
|
87
|
+
def get_system_prompt_information(cls) -> Optional[BlockElementMarkdownInformation]:
|
88
|
+
"""Get system prompt information for embed blocks."""
|
89
|
+
return BlockElementMarkdownInformation(
|
90
|
+
block_type=cls.__name__,
|
91
|
+
description="Embed blocks display interactive content from external URLs like videos, maps, or widgets",
|
92
|
+
syntax_examples=[
|
93
|
+
"[embed](https://youtube.com/watch?v=123)",
|
94
|
+
'[embed](https://maps.google.com/location "Map Location")',
|
95
|
+
'[embed](https://codepen.io/pen/123 "Interactive Demo")',
|
96
|
+
],
|
97
|
+
usage_guidelines="Use for embedding interactive content that supports iframe embedding. URL must be from a supported platform. Caption describes the embedded content.",
|
144
98
|
)
|
@@ -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 EmbedMarkdownBlockParams(BaseModel):
|
@@ -0,0 +1,14 @@
|
|
1
|
+
from pydantic import BaseModel, Field
|
2
|
+
from typing_extensions import Literal
|
3
|
+
|
4
|
+
from notionary.blocks.rich_text.rich_text_models import RichTextObject
|
5
|
+
|
6
|
+
|
7
|
+
class EmbedBlock(BaseModel):
|
8
|
+
url: str
|
9
|
+
caption: list[RichTextObject] = Field(default_factory=list)
|
10
|
+
|
11
|
+
|
12
|
+
class CreateEmbedBlock(BaseModel):
|
13
|
+
type: Literal["embed"] = "embed"
|
14
|
+
embed: EmbedBlock
|
@@ -0,0 +1,14 @@
|
|
1
|
+
from notionary.blocks.equation.equation_element import EquationElement
|
2
|
+
from notionary.blocks.equation.equation_element_markdown_node import (
|
3
|
+
EquationMarkdownBlockParams,
|
4
|
+
EquationMarkdownNode,
|
5
|
+
)
|
6
|
+
from notionary.blocks.equation.equation_models import CreateEquationBlock, EquationBlock
|
7
|
+
|
8
|
+
__all__ = [
|
9
|
+
"EquationElement",
|
10
|
+
"EquationBlock",
|
11
|
+
"CreateEquationBlock",
|
12
|
+
"EquationMarkdownNode",
|
13
|
+
"EquationMarkdownBlockParams",
|
14
|
+
]
|
@@ -0,0 +1,133 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import re
|
4
|
+
import textwrap
|
5
|
+
from typing import Optional
|
6
|
+
|
7
|
+
from notionary.blocks.base_block_element import BaseBlockElement
|
8
|
+
from notionary.blocks.equation.equation_models import CreateEquationBlock, EquationBlock
|
9
|
+
from notionary.blocks.syntax_prompt_builder import BlockElementMarkdownInformation
|
10
|
+
from notionary.blocks.models import Block, BlockCreateResult
|
11
|
+
from notionary.blocks.types import BlockType
|
12
|
+
|
13
|
+
|
14
|
+
class EquationElement(BaseBlockElement):
|
15
|
+
"""
|
16
|
+
Supports standard Markdown equation syntax:
|
17
|
+
|
18
|
+
- $$E = mc^2$$ # simple equations
|
19
|
+
- $$E = mc^2 + \\frac{a}{b}$$ # complex equations with LaTeX
|
20
|
+
|
21
|
+
Uses $$...$$ parsing for block equations.
|
22
|
+
"""
|
23
|
+
|
24
|
+
_EQUATION_PATTERN = re.compile(
|
25
|
+
r"^\$\$\s*(?P<expression>.*?)\s*\$\$$",
|
26
|
+
re.DOTALL,
|
27
|
+
)
|
28
|
+
|
29
|
+
@classmethod
|
30
|
+
def match_notion(cls, block: Block) -> bool:
|
31
|
+
return block.type == BlockType.EQUATION and block.equation
|
32
|
+
|
33
|
+
@classmethod
|
34
|
+
async def markdown_to_notion(cls, text: str) -> BlockCreateResult:
|
35
|
+
input_text = text.strip()
|
36
|
+
|
37
|
+
equation_match = cls._EQUATION_PATTERN.match(input_text)
|
38
|
+
if not equation_match:
|
39
|
+
return None
|
40
|
+
|
41
|
+
expression = equation_match.group("expression").strip()
|
42
|
+
if not expression:
|
43
|
+
return None
|
44
|
+
|
45
|
+
return CreateEquationBlock(equation=EquationBlock(expression=expression))
|
46
|
+
|
47
|
+
@classmethod
|
48
|
+
def create_from_markdown_block(
|
49
|
+
cls, opening_line: str, equation_lines: list[str]
|
50
|
+
) -> BlockCreateResult:
|
51
|
+
"""
|
52
|
+
Create a complete equation block from markdown components.
|
53
|
+
Handles multiline equations like:
|
54
|
+
$$
|
55
|
+
some
|
56
|
+
inline formula here
|
57
|
+
$$
|
58
|
+
|
59
|
+
Automatically handles:
|
60
|
+
- Indentation removal from multiline strings
|
61
|
+
- Single backslash conversion to double backslash for LaTeX line breaks
|
62
|
+
"""
|
63
|
+
# Check if opening line is just $$
|
64
|
+
if opening_line.strip() != "$$":
|
65
|
+
return None
|
66
|
+
|
67
|
+
# Process equation lines if any exist
|
68
|
+
if equation_lines:
|
69
|
+
# Remove common indentation from all lines
|
70
|
+
raw_content = "\n".join(equation_lines)
|
71
|
+
dedented_content = textwrap.dedent(raw_content)
|
72
|
+
|
73
|
+
# Fix single backslashes at line ends for LaTeX line breaks
|
74
|
+
fixed_lines = cls._fix_latex_line_breaks(dedented_content.splitlines())
|
75
|
+
expression = "\n".join(fixed_lines).strip()
|
76
|
+
|
77
|
+
if expression:
|
78
|
+
return CreateEquationBlock(
|
79
|
+
equation=EquationBlock(expression=expression)
|
80
|
+
)
|
81
|
+
|
82
|
+
return None
|
83
|
+
|
84
|
+
@classmethod
|
85
|
+
def _fix_latex_line_breaks(cls, lines: list[str]) -> list[str]:
|
86
|
+
"""
|
87
|
+
Fix lines that end with single backslashes by converting them to double backslashes.
|
88
|
+
This makes LaTeX line breaks work correctly when users write single backslashes.
|
89
|
+
|
90
|
+
Examples:
|
91
|
+
- "a = b + c \" -> "a = b + c \\"
|
92
|
+
- "a = b + c \\\\" -> "a = b + c \\\\" (unchanged)
|
93
|
+
"""
|
94
|
+
fixed_lines = []
|
95
|
+
|
96
|
+
for line in lines:
|
97
|
+
# Check if line ends with backslashes
|
98
|
+
backslash_match = re.search(r"(\\+)$", line)
|
99
|
+
if backslash_match:
|
100
|
+
backslashes = backslash_match.group(1)
|
101
|
+
# If odd number of backslashes, the last one needs to be doubled
|
102
|
+
if len(backslashes) % 2 == 1:
|
103
|
+
line = line + "\\"
|
104
|
+
|
105
|
+
fixed_lines.append(line)
|
106
|
+
|
107
|
+
return fixed_lines
|
108
|
+
|
109
|
+
@classmethod
|
110
|
+
async def notion_to_markdown(cls, block: Block) -> Optional[str]:
|
111
|
+
if block.type != BlockType.EQUATION or not block.equation:
|
112
|
+
return None
|
113
|
+
|
114
|
+
expression = (block.equation.expression or "").strip()
|
115
|
+
if not expression:
|
116
|
+
return None
|
117
|
+
|
118
|
+
return f"$${expression}$$"
|
119
|
+
|
120
|
+
@classmethod
|
121
|
+
def get_system_prompt_information(cls) -> Optional[BlockElementMarkdownInformation]:
|
122
|
+
"""Get system prompt information for equation blocks."""
|
123
|
+
return BlockElementMarkdownInformation(
|
124
|
+
block_type=cls.__name__,
|
125
|
+
description="Mathematical equations using standard Markdown LaTeX syntax",
|
126
|
+
syntax_examples=[
|
127
|
+
"$$E = mc^2$$",
|
128
|
+
"$$\\frac{a}{b} + \\sqrt{c}$$",
|
129
|
+
"$$\\int_0^\\infty e^{-x} dx = 1$$",
|
130
|
+
"$$\\sum_{i=1}^n i = \\frac{n(n+1)}{2}$$",
|
131
|
+
],
|
132
|
+
usage_guidelines="Use for mathematical expressions and formulas. Supports LaTeX syntax. Wrap equations in double dollar signs ($$).",
|
133
|
+
)
|
@@ -0,0 +1,35 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from pydantic import BaseModel
|
4
|
+
|
5
|
+
from notionary.markdown.markdown_node import MarkdownNode
|
6
|
+
|
7
|
+
|
8
|
+
class EquationMarkdownBlockParams(BaseModel):
|
9
|
+
expression: str
|
10
|
+
|
11
|
+
|
12
|
+
class EquationMarkdownNode(MarkdownNode):
|
13
|
+
"""
|
14
|
+
Programmatic interface for creating Markdown equation blocks.
|
15
|
+
Uses standard Markdown equation syntax with double dollar signs.
|
16
|
+
|
17
|
+
Examples:
|
18
|
+
$$E = mc^2$$
|
19
|
+
$$\\frac{a}{b} + \\sqrt{c}$$
|
20
|
+
$$\\int_0^\\infty e^{-x} dx = 1$$
|
21
|
+
"""
|
22
|
+
|
23
|
+
def __init__(self, expression: str):
|
24
|
+
self.expression = expression
|
25
|
+
|
26
|
+
@classmethod
|
27
|
+
def from_params(cls, params: EquationMarkdownBlockParams) -> EquationMarkdownNode:
|
28
|
+
return cls(expression=params.expression)
|
29
|
+
|
30
|
+
def to_markdown(self) -> str:
|
31
|
+
expr = self.expression.strip()
|
32
|
+
if not expr:
|
33
|
+
return "$$$$"
|
34
|
+
|
35
|
+
return f"$${expr}$$"
|
@@ -0,0 +1,25 @@
|
|
1
|
+
from notionary.blocks.file.file_element import FileElement
|
2
|
+
from notionary.blocks.file.file_element_markdown_node import (
|
3
|
+
FileMarkdownNode,
|
4
|
+
FileMarkdownNodeParams,
|
5
|
+
)
|
6
|
+
from notionary.blocks.file.file_element_models import (
|
7
|
+
CreateFileBlock,
|
8
|
+
ExternalFile,
|
9
|
+
FileBlock,
|
10
|
+
FileType,
|
11
|
+
FileUploadFile,
|
12
|
+
NotionHostedFile,
|
13
|
+
)
|
14
|
+
|
15
|
+
__all__ = [
|
16
|
+
"FileElement",
|
17
|
+
"FileType",
|
18
|
+
"ExternalFile",
|
19
|
+
"NotionHostedFile",
|
20
|
+
"FileUploadFile",
|
21
|
+
"FileBlock",
|
22
|
+
"CreateFileBlock",
|
23
|
+
"FileMarkdownNode",
|
24
|
+
"FileMarkdownNodeParams",
|
25
|
+
]
|