notionary 0.2.19__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.19.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 -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/blocks/toggleable_heading/toggleable_heading_models.py +0 -0
- notionary/database/models/page_result.py +0 -10
- notionary/elements/__init__.py +0 -0
- 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 -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-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/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.19.dist-info → notionary-0.2.21.dist-info}/LICENSE +0 -0
- {notionary-0.2.19.dist-info → notionary-0.2.21.dist-info}/WHEEL +0 -0
@@ -1,7 +1,17 @@
|
|
1
|
-
from .paragraph_element import ParagraphElement
|
2
|
-
from .paragraph_markdown_node import
|
1
|
+
from notionary.blocks.paragraph.paragraph_element import ParagraphElement
|
2
|
+
from notionary.blocks.paragraph.paragraph_markdown_node import (
|
3
|
+
ParagraphMarkdownBlockParams,
|
4
|
+
ParagraphMarkdownNode,
|
5
|
+
)
|
6
|
+
from notionary.blocks.paragraph.paragraph_models import (
|
7
|
+
CreateParagraphBlock,
|
8
|
+
ParagraphBlock,
|
9
|
+
)
|
3
10
|
|
4
11
|
__all__ = [
|
5
12
|
"ParagraphElement",
|
13
|
+
"ParagraphBlock",
|
14
|
+
"CreateParagraphBlock",
|
6
15
|
"ParagraphMarkdownNode",
|
16
|
+
"ParagraphMarkdownBlockParams",
|
7
17
|
]
|
@@ -1,84 +1,42 @@
|
|
1
|
-
from
|
1
|
+
from __future__ import annotations
|
2
2
|
|
3
|
-
from
|
4
|
-
from notionary.blocks import (
|
5
|
-
ElementPromptContent,
|
6
|
-
ElementPromptBuilder,
|
7
|
-
NotionBlockResult,
|
8
|
-
)
|
9
|
-
from notionary.blocks.shared.text_inline_formatter import TextInlineFormatter
|
3
|
+
from typing import Optional
|
10
4
|
|
5
|
+
from notionary.blocks.base_block_element import BaseBlockElement
|
6
|
+
from notionary.blocks.models import Block, BlockCreateResult
|
7
|
+
from notionary.blocks.paragraph.paragraph_models import (
|
8
|
+
CreateParagraphBlock,
|
9
|
+
ParagraphBlock,
|
10
|
+
)
|
11
|
+
from notionary.blocks.rich_text.text_inline_formatter import TextInlineFormatter
|
12
|
+
from notionary.blocks.types import BlockColor, BlockType
|
11
13
|
|
12
|
-
class ParagraphElement(NotionBlockElement):
|
13
|
-
"""Handles conversion between Markdown paragraphs and Notion paragraph blocks."""
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
Paragraphs are essentially any text that isn't matched by other block elements.
|
20
|
-
Since paragraphs are the fallback element, this always returns True.
|
21
|
-
"""
|
22
|
-
return True
|
15
|
+
class ParagraphElement(BaseBlockElement):
|
16
|
+
"""
|
17
|
+
Handles conversion between Markdown paragraphs and Notion paragraph blocks.
|
18
|
+
"""
|
23
19
|
|
24
20
|
@classmethod
|
25
|
-
def match_notion(cls, block:
|
26
|
-
|
27
|
-
return block.get("type") == "paragraph"
|
21
|
+
def match_notion(cls, block: Block) -> bool:
|
22
|
+
return block.type == "paragraph" and block.paragraph
|
28
23
|
|
29
24
|
@classmethod
|
30
|
-
def markdown_to_notion(cls, text: str) ->
|
31
|
-
"""Convert markdown
|
25
|
+
def markdown_to_notion(cls, text: str) -> BlockCreateResult:
|
26
|
+
"""Convert markdown text to a Notion ParagraphBlock."""
|
32
27
|
if not text.strip():
|
33
28
|
return None
|
34
29
|
|
35
|
-
|
36
|
-
"type": "paragraph",
|
37
|
-
"paragraph": {
|
38
|
-
"rich_text": TextInlineFormatter.parse_inline_formatting(text)
|
39
|
-
},
|
40
|
-
}
|
30
|
+
rich = TextInlineFormatter.parse_inline_formatting(text)
|
41
31
|
|
42
|
-
|
43
|
-
|
44
|
-
"""Convert Notion paragraph block to markdown paragraph."""
|
45
|
-
if block.get("type") != "paragraph":
|
46
|
-
return None
|
47
|
-
|
48
|
-
paragraph_data = block.get("paragraph", {})
|
49
|
-
rich_text = paragraph_data.get("rich_text", [])
|
50
|
-
|
51
|
-
text = TextInlineFormatter.extract_text_with_formatting(rich_text)
|
52
|
-
return text if text else None
|
32
|
+
paragraph_content = ParagraphBlock(rich_text=rich, color=BlockColor.DEFAULT)
|
33
|
+
return CreateParagraphBlock(paragraph=paragraph_content)
|
53
34
|
|
54
35
|
@classmethod
|
55
|
-
def
|
56
|
-
|
36
|
+
def notion_to_markdown(cls, block: Block) -> Optional[str]:
|
37
|
+
if block.type != "paragraph" or not block.paragraph:
|
38
|
+
return None
|
57
39
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
Returns structured LLM prompt metadata for the paragraph element,
|
62
|
-
including information about supported inline formatting.
|
63
|
-
"""
|
64
|
-
return (
|
65
|
-
ElementPromptBuilder()
|
66
|
-
.with_description(
|
67
|
-
"Creates standard paragraph blocks for regular text content with support for inline formatting: "
|
68
|
-
"**bold**, *italic*, `code`, ~~strikethrough~~, __underline__, and [links](url)."
|
69
|
-
)
|
70
|
-
.with_usage_guidelines(
|
71
|
-
"Use for normal text content. Paragraphs are the default block type when no specific formatting is applied. "
|
72
|
-
"Apply inline formatting to highlight key points or provide links to resources."
|
73
|
-
)
|
74
|
-
.with_syntax("Just write text normally without any special prefix")
|
75
|
-
.with_examples(
|
76
|
-
[
|
77
|
-
"This is a simple paragraph with plain text.",
|
78
|
-
"This paragraph has **bold** and *italic* formatting.",
|
79
|
-
"You can include [links](https://example.com) or `inline code`.",
|
80
|
-
"Advanced formatting: ~~strikethrough~~ and __underlined text__.",
|
81
|
-
]
|
82
|
-
)
|
83
|
-
.build()
|
84
|
-
)
|
40
|
+
rich_list = block.paragraph.rich_text
|
41
|
+
markdown = TextInlineFormatter.extract_text_with_formatting(rich_list)
|
42
|
+
return markdown or None
|
@@ -0,0 +1,16 @@
|
|
1
|
+
from typing import Literal
|
2
|
+
|
3
|
+
from pydantic import BaseModel
|
4
|
+
|
5
|
+
from notionary.blocks.rich_text.rich_text_models import RichTextObject
|
6
|
+
from notionary.blocks.types import BlockColor
|
7
|
+
|
8
|
+
|
9
|
+
class ParagraphBlock(BaseModel):
|
10
|
+
rich_text: list[RichTextObject]
|
11
|
+
color: BlockColor = BlockColor.DEFAULT.value
|
12
|
+
|
13
|
+
|
14
|
+
class CreateParagraphBlock(BaseModel):
|
15
|
+
type: Literal["paragraph"] = "paragraph"
|
16
|
+
paragraph: ParagraphBlock
|
@@ -0,0 +1,13 @@
|
|
1
|
+
from notionary.blocks.pdf.pdf_element import PdfElement
|
2
|
+
from notionary.blocks.pdf.pdf_markdown_node import (
|
3
|
+
PdfMarkdownNode,
|
4
|
+
PdfMarkdownNodeParams,
|
5
|
+
)
|
6
|
+
from notionary.blocks.pdf.pdf_models import CreatePdfBlock
|
7
|
+
|
8
|
+
__all__ = [
|
9
|
+
"PdfElement",
|
10
|
+
"CreatePdfBlock",
|
11
|
+
"PdfMarkdownNode",
|
12
|
+
"PdfMarkdownNodeParams",
|
13
|
+
]
|
@@ -0,0 +1,91 @@
|
|
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.file.file_element_models import ExternalFile, FileBlock, FileType
|
8
|
+
from notionary.blocks.models import Block, BlockCreateResult, BlockType
|
9
|
+
from notionary.blocks.paragraph.paragraph_models import (
|
10
|
+
CreateParagraphBlock,
|
11
|
+
ParagraphBlock,
|
12
|
+
)
|
13
|
+
from notionary.blocks.pdf.pdf_models import CreatePdfBlock
|
14
|
+
from notionary.blocks.rich_text.rich_text_models import RichTextObject
|
15
|
+
from notionary.blocks.rich_text.text_inline_formatter import TextInlineFormatter
|
16
|
+
|
17
|
+
|
18
|
+
class PdfElement(BaseBlockElement):
|
19
|
+
"""
|
20
|
+
Handles conversion between Markdown PDF embeds and Notion PDF blocks.
|
21
|
+
|
22
|
+
Markdown PDF syntax:
|
23
|
+
- [pdf](https://example.com/document.pdf "Caption") # External URL
|
24
|
+
- [pdf](notion://file_id_here "Caption") # Notion hosted file
|
25
|
+
- [pdf](upload://upload_id_here "Caption") # File upload
|
26
|
+
- [pdf](https://example.com/document.pdf) # Without caption
|
27
|
+
|
28
|
+
Supports all three PDF types: external, notion-hosted, and file uploads.
|
29
|
+
"""
|
30
|
+
|
31
|
+
PATTERN = re.compile(
|
32
|
+
r"^\[pdf\]\(" # prefix
|
33
|
+
r'((?:https?://|notion://|upload://)[^\s\)"]+)' # URL with protocol
|
34
|
+
r'(?:\s+"([^"]*)")?' # optional caption
|
35
|
+
r"\)$"
|
36
|
+
)
|
37
|
+
|
38
|
+
@classmethod
|
39
|
+
def match_notion(cls, block: Block) -> bool:
|
40
|
+
# Notion PDF block covers PDFs
|
41
|
+
return block.type == BlockType.PDF and block.pdf
|
42
|
+
|
43
|
+
@classmethod
|
44
|
+
def markdown_to_notion(cls, text: str) -> BlockCreateResult:
|
45
|
+
"""Convert markdown PDF link to Notion FileBlock (used for PDF) followed by an empty paragraph."""
|
46
|
+
match = cls.PATTERN.match(text.strip())
|
47
|
+
if not match:
|
48
|
+
return None
|
49
|
+
|
50
|
+
url, caption_text = match.group(1), match.group(2) or ""
|
51
|
+
|
52
|
+
# Build FileBlock using FileType enum (reused for PDF)
|
53
|
+
pdf_block = FileBlock(
|
54
|
+
type=FileType.EXTERNAL, external=ExternalFile(url=url), caption=[]
|
55
|
+
)
|
56
|
+
if caption_text.strip():
|
57
|
+
rt = RichTextObject.from_plain_text(caption_text)
|
58
|
+
pdf_block.caption = [rt]
|
59
|
+
|
60
|
+
empty_para = ParagraphBlock(rich_text=[])
|
61
|
+
|
62
|
+
return [
|
63
|
+
CreatePdfBlock(pdf=pdf_block),
|
64
|
+
CreateParagraphBlock(paragraph=empty_para),
|
65
|
+
]
|
66
|
+
|
67
|
+
@classmethod
|
68
|
+
def notion_to_markdown(cls, block: Block) -> Optional[str]:
|
69
|
+
if block.type != BlockType.PDF or not block.pdf:
|
70
|
+
return None
|
71
|
+
|
72
|
+
pb: FileBlock = block.pdf
|
73
|
+
|
74
|
+
# Determine URL (only external and file types are valid for Markdown)
|
75
|
+
if pb.type == FileType.EXTERNAL and pb.external:
|
76
|
+
url = pb.external.url
|
77
|
+
elif pb.type == FileType.FILE and pb.file:
|
78
|
+
url = pb.file.url
|
79
|
+
elif pb.type == FileType.FILE_UPLOAD:
|
80
|
+
# Uploaded PDF has no stable URL for Markdown
|
81
|
+
return None
|
82
|
+
else:
|
83
|
+
return None
|
84
|
+
|
85
|
+
if not pb.caption:
|
86
|
+
return f"[pdf]({url})"
|
87
|
+
|
88
|
+
caption_md = TextInlineFormatter.extract_text_with_formatting(pb.caption)
|
89
|
+
if caption_md:
|
90
|
+
return f'[pdf]({url} "{caption_md}")'
|
91
|
+
return f"[pdf]({url})"
|
@@ -0,0 +1,35 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import Optional
|
4
|
+
|
5
|
+
from pydantic import BaseModel
|
6
|
+
|
7
|
+
from notionary.markdown.markdown_node import MarkdownNode
|
8
|
+
|
9
|
+
|
10
|
+
class PdfMarkdownNodeParams(BaseModel):
|
11
|
+
url: str
|
12
|
+
caption: Optional[str] = None
|
13
|
+
|
14
|
+
|
15
|
+
class PdfMarkdownNode(MarkdownNode):
|
16
|
+
"""
|
17
|
+
Programmatic interface for creating Notion-style Markdown PDF embeds.
|
18
|
+
Example: [pdf](https://example.com/document.pdf "My Caption")
|
19
|
+
"""
|
20
|
+
|
21
|
+
def __init__(self, url: str, caption: Optional[str] = None):
|
22
|
+
self.url = url
|
23
|
+
self.caption = caption or ""
|
24
|
+
|
25
|
+
@classmethod
|
26
|
+
def from_params(cls, params: PdfMarkdownNodeParams) -> PdfMarkdownNode:
|
27
|
+
return cls(url=params.url, caption=params.caption)
|
28
|
+
|
29
|
+
def to_markdown(self) -> str:
|
30
|
+
"""
|
31
|
+
Convert to markdown as [pdf](url "caption") or [pdf](url) if caption is empty.
|
32
|
+
"""
|
33
|
+
if self.caption:
|
34
|
+
return f'[pdf]({self.url} "{self.caption}")'
|
35
|
+
return f"[pdf]({self.url})"
|
@@ -0,0 +1,11 @@
|
|
1
|
+
from typing import Literal
|
2
|
+
|
3
|
+
from pydantic import BaseModel
|
4
|
+
|
5
|
+
from notionary.blocks.file.file_element_models import FileBlock
|
6
|
+
from notionary.blocks.rich_text.rich_text_models import RichTextObject
|
7
|
+
|
8
|
+
|
9
|
+
class CreatePdfBlock(BaseModel):
|
10
|
+
type: Literal["pdf"] = "pdf"
|
11
|
+
pdf: FileBlock
|
@@ -1,7 +1,16 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
"""Quote block handling for Notionary."""
|
2
|
+
|
3
|
+
from notionary.blocks.quote.quote_element import QuoteElement
|
4
|
+
from notionary.blocks.quote.quote_markdown_node import (
|
5
|
+
QuoteMarkdownBlockParams,
|
6
|
+
QuoteMarkdownNode,
|
7
|
+
)
|
8
|
+
from notionary.blocks.quote.quote_models import CreateQuoteBlock, QuoteBlock
|
3
9
|
|
4
10
|
__all__ = [
|
5
11
|
"QuoteElement",
|
12
|
+
"QuoteBlock",
|
13
|
+
"CreateQuoteBlock",
|
6
14
|
"QuoteMarkdownNode",
|
15
|
+
"QuoteMarkdownBlockParams",
|
7
16
|
]
|
@@ -1,34 +1,34 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import re
|
2
|
-
from typing import
|
4
|
+
from typing import Optional
|
5
|
+
|
6
|
+
from notionary.blocks.base_block_element import BaseBlockElement
|
7
|
+
from notionary.blocks.models import Block, BlockCreateResult, BlockType
|
8
|
+
from notionary.blocks.quote.quote_models import CreateQuoteBlock, QuoteBlock
|
9
|
+
from notionary.blocks.rich_text.text_inline_formatter import TextInlineFormatter
|
10
|
+
from notionary.blocks.types import BlockColor
|
3
11
|
|
4
|
-
from notionary.blocks import NotionBlockElement, NotionBlockResult
|
5
|
-
from notionary.blocks import ElementPromptContent, ElementPromptBuilder
|
6
12
|
|
7
|
-
class QuoteElement(
|
13
|
+
class QuoteElement(BaseBlockElement):
|
8
14
|
"""
|
9
15
|
Handles conversion between Markdown quotes and Notion quote blocks.
|
16
|
+
|
10
17
|
Markdown quote syntax:
|
11
18
|
- [quote](Simple quote text)
|
19
|
+
|
20
|
+
Only single-line quotes without author metadata.
|
12
21
|
"""
|
13
22
|
|
14
|
-
|
15
|
-
PATTERN = re.compile(r'^\[quote\]\(([^\n\r]+)\)$')
|
23
|
+
PATTERN = re.compile(r"^\[quote\]\(([^\n\r]+)\)$")
|
16
24
|
|
17
25
|
@classmethod
|
18
|
-
def
|
19
|
-
|
20
|
-
# Nur gültig, wenn etwas nicht-leeres drinsteht
|
21
|
-
return bool(m and m.group(1).strip())
|
26
|
+
def match_notion(cls, block: Block) -> bool:
|
27
|
+
return block.type == BlockType.QUOTE and block.quote
|
22
28
|
|
23
29
|
@classmethod
|
24
|
-
def
|
25
|
-
|
26
|
-
|
27
|
-
@classmethod
|
28
|
-
def markdown_to_notion(cls, text: str) -> NotionBlockResult:
|
29
|
-
if not text:
|
30
|
-
return None
|
31
|
-
|
30
|
+
def markdown_to_notion(cls, text: str) -> BlockCreateResult:
|
31
|
+
"""Convert markdown quote to Notion QuoteBlock."""
|
32
32
|
match = cls.PATTERN.match(text.strip())
|
33
33
|
if not match:
|
34
34
|
return None
|
@@ -37,56 +37,22 @@ class QuoteElement(NotionBlockElement):
|
|
37
37
|
if not content:
|
38
38
|
return None
|
39
39
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
@classmethod
|
44
|
-
def notion_to_markdown(cls, block: Dict[str, Any]) -> Optional[str]:
|
45
|
-
if block.get("type") != "quote":
|
46
|
-
return None
|
47
|
-
|
48
|
-
rich_text = block.get("quote", {}).get("rich_text", [])
|
49
|
-
content = cls._extract_text_content(rich_text)
|
50
|
-
if not content.strip():
|
51
|
-
return None
|
40
|
+
# Parse inline formatting into rich text objects
|
41
|
+
rich_text = TextInlineFormatter.parse_inline_formatting(content)
|
52
42
|
|
53
|
-
|
43
|
+
# Return a typed QuoteBlock
|
44
|
+
quote_content = QuoteBlock(rich_text=rich_text, color=BlockColor.DEFAULT)
|
45
|
+
return CreateQuoteBlock(quote=quote_content)
|
54
46
|
|
55
47
|
@classmethod
|
56
|
-
def
|
57
|
-
|
58
|
-
|
59
|
-
candidate = match.group(0)
|
60
|
-
block = cls.markdown_to_notion(candidate)
|
61
|
-
if block:
|
62
|
-
matches.append((match.start(), match.end(), block))
|
63
|
-
return matches
|
48
|
+
def notion_to_markdown(cls, block: Block) -> Optional[str]:
|
49
|
+
if block.type != BlockType.QUOTE or not block.quote:
|
50
|
+
return None
|
64
51
|
|
65
|
-
|
66
|
-
|
67
|
-
return False
|
52
|
+
rich = block.quote.rich_text
|
53
|
+
text = TextInlineFormatter.extract_text_with_formatting(rich)
|
68
54
|
|
69
|
-
|
70
|
-
|
71
|
-
return "".join(
|
72
|
-
t.get("text", {}).get("content", "")
|
73
|
-
for t in rich_text
|
74
|
-
if t.get("type") == "text"
|
75
|
-
)
|
55
|
+
if not text.strip():
|
56
|
+
return None
|
76
57
|
|
77
|
-
|
78
|
-
def get_llm_prompt_content(cls) -> ElementPromptContent:
|
79
|
-
return (
|
80
|
-
ElementPromptBuilder()
|
81
|
-
.with_description("Creates blockquotes that visually distinguish quoted text.")
|
82
|
-
.with_usage_guidelines(
|
83
|
-
"Use quotes for quoting external sources, highlighting important statements, "
|
84
|
-
"or creating visual emphasis for key information."
|
85
|
-
)
|
86
|
-
.with_syntax('[quote](Quote text)')
|
87
|
-
.with_examples([
|
88
|
-
"[quote](This is a simple blockquote)",
|
89
|
-
"[quote](Knowledge is power)",
|
90
|
-
])
|
91
|
-
.build()
|
92
|
-
)
|
58
|
+
return f"[quote]({text.strip()})"
|
@@ -1,11 +1,14 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
from pydantic import BaseModel
|
4
|
-
|
4
|
+
|
5
|
+
from notionary.markdown.markdown_node import MarkdownNode
|
6
|
+
|
5
7
|
|
6
8
|
class QuoteMarkdownBlockParams(BaseModel):
|
7
9
|
text: str
|
8
10
|
|
11
|
+
|
9
12
|
class QuoteMarkdownNode(MarkdownNode):
|
10
13
|
"""
|
11
14
|
Programmatic interface for creating Notion-style quote blocks.
|
@@ -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.rich_text_models import RichTextObject
|
7
|
+
from notionary.blocks.types import BlockColor
|
8
|
+
|
9
|
+
|
10
|
+
class QuoteBlock(BaseModel):
|
11
|
+
rich_text: list[RichTextObject]
|
12
|
+
color: BlockColor = BlockColor.DEFAULT
|
13
|
+
children: list[Block] = Field(default_factory=list)
|
14
|
+
|
15
|
+
|
16
|
+
class CreateQuoteBlock(BaseModel):
|
17
|
+
type: Literal["quote"] = "quote"
|
18
|
+
quote: QuoteBlock
|