notionary 0.2.21__py3-none-any.whl → 0.2.23__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/blocks/_bootstrap.py +9 -1
- notionary/blocks/audio/audio_element.py +53 -28
- notionary/blocks/audio/audio_markdown_node.py +10 -4
- notionary/blocks/base_block_element.py +15 -3
- notionary/blocks/bookmark/bookmark_element.py +39 -36
- notionary/blocks/bookmark/bookmark_markdown_node.py +16 -17
- notionary/blocks/breadcrumbs/breadcrumb_element.py +2 -2
- notionary/blocks/bulleted_list/bulleted_list_element.py +21 -4
- notionary/blocks/callout/callout_element.py +20 -4
- notionary/blocks/child_database/__init__.py +11 -4
- notionary/blocks/child_database/child_database_element.py +59 -0
- notionary/blocks/child_database/child_database_models.py +7 -14
- notionary/blocks/child_page/child_page_element.py +94 -0
- notionary/blocks/client.py +0 -1
- notionary/blocks/code/code_element.py +51 -2
- notionary/blocks/code/code_markdown_node.py +52 -1
- notionary/blocks/column/column_element.py +9 -3
- notionary/blocks/column/column_list_element.py +18 -3
- notionary/blocks/divider/divider_element.py +3 -11
- notionary/blocks/embed/embed_element.py +27 -6
- notionary/blocks/equation/equation_element.py +94 -41
- notionary/blocks/equation/equation_element_markdown_node.py +8 -9
- notionary/blocks/file/file_element.py +56 -37
- notionary/blocks/file/file_element_markdown_node.py +9 -7
- notionary/blocks/guards.py +22 -0
- notionary/blocks/heading/heading_element.py +23 -4
- notionary/blocks/image_block/image_element.py +43 -38
- notionary/blocks/image_block/image_markdown_node.py +10 -5
- 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 +3 -1
- notionary/blocks/numbered_list/numbered_list_element.py +21 -4
- notionary/blocks/paragraph/paragraph_element.py +21 -5
- notionary/blocks/pdf/pdf_element.py +47 -41
- notionary/blocks/pdf/pdf_markdown_node.py +9 -7
- notionary/blocks/quote/quote_element.py +26 -9
- notionary/blocks/quote/quote_markdown_node.py +2 -2
- notionary/blocks/registry/block_registry.py +1 -46
- notionary/blocks/registry/block_registry_builder.py +8 -0
- notionary/blocks/rich_text/rich_text_models.py +62 -29
- notionary/blocks/rich_text/text_inline_formatter.py +432 -101
- notionary/blocks/syntax_prompt_builder.py +137 -0
- notionary/blocks/table/table_element.py +110 -9
- notionary/blocks/table_of_contents/table_of_contents_element.py +19 -2
- notionary/blocks/todo/todo_element.py +21 -4
- notionary/blocks/toggle/toggle_element.py +19 -3
- notionary/blocks/toggle/toggle_markdown_node.py +1 -1
- notionary/blocks/toggleable_heading/toggleable_heading_element.py +19 -4
- notionary/blocks/types.py +69 -0
- notionary/blocks/video/video_element.py +44 -39
- notionary/blocks/video/video_markdown_node.py +10 -5
- notionary/comments/__init__.py +26 -0
- notionary/comments/client.py +211 -0
- notionary/comments/models.py +129 -0
- notionary/database/client.py +23 -0
- notionary/file_upload/models.py +2 -2
- notionary/markdown/markdown_builder.py +34 -27
- notionary/page/client.py +21 -6
- notionary/page/notion_page.py +77 -2
- notionary/page/page_content_deleting_service.py +117 -0
- notionary/page/page_content_writer.py +89 -113
- notionary/page/page_context.py +64 -0
- notionary/page/reader/handler/__init__.py +2 -0
- notionary/page/reader/handler/base_block_renderer.py +4 -4
- notionary/page/reader/handler/block_rendering_context.py +5 -0
- notionary/page/reader/handler/line_renderer.py +16 -3
- notionary/page/reader/handler/numbered_list_renderer.py +85 -0
- notionary/page/reader/page_content_retriever.py +17 -5
- notionary/page/writer/handler/__init__.py +2 -0
- notionary/page/writer/handler/code_handler.py +12 -40
- notionary/page/writer/handler/column_handler.py +12 -12
- notionary/page/writer/handler/column_list_handler.py +13 -13
- notionary/page/writer/handler/equation_handler.py +74 -0
- notionary/page/writer/handler/line_handler.py +4 -4
- notionary/page/writer/handler/regular_line_handler.py +31 -37
- notionary/page/writer/handler/table_handler.py +8 -72
- notionary/page/writer/handler/toggle_handler.py +14 -12
- notionary/page/writer/handler/toggleable_heading_handler.py +22 -16
- notionary/page/writer/markdown_to_notion_converter.py +28 -9
- 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/markdown_to_notion_post_processor.py +0 -0
- notionary/page/writer/markdown_to_notion_text_length_post_processor.py +0 -0
- notionary/page/writer/notion_text_length_processor.py +150 -0
- notionary/shared/__init__.py +5 -0
- notionary/shared/name_to_id_resolver.py +203 -0
- notionary/telemetry/service.py +0 -1
- notionary/user/notion_user_manager.py +22 -95
- notionary/util/concurrency_limiter.py +0 -0
- notionary/workspace.py +4 -4
- notionary-0.2.23.dist-info/METADATA +235 -0
- {notionary-0.2.21.dist-info → notionary-0.2.23.dist-info}/RECORD +96 -77
- notionary/page/markdown_whitespace_processor.py +0 -80
- notionary/page/notion_text_length_utils.py +0 -119
- notionary/user/notion_user_provider.py +0 -1
- notionary-0.2.21.dist-info/METADATA +0 -229
- /notionary/page/reader/handler/{context.py → equation_renderer.py} +0 -0
- {notionary-0.2.21.dist-info → notionary-0.2.23.dist-info}/LICENSE +0 -0
- {notionary-0.2.21.dist-info → notionary-0.2.23.dist-info}/WHEEL +0 -0
@@ -5,35 +5,28 @@ from typing import Optional
|
|
5
5
|
|
6
6
|
from notionary.blocks.base_block_element import BaseBlockElement
|
7
7
|
from notionary.blocks.file.file_element_models import ExternalFile, FileBlock, FileType
|
8
|
+
from notionary.blocks.mixins.captions import CaptionMixin
|
9
|
+
from notionary.blocks.syntax_prompt_builder import BlockElementMarkdownInformation
|
8
10
|
from notionary.blocks.models import Block, BlockCreateResult, BlockType
|
9
|
-
from notionary.blocks.paragraph.paragraph_models import (
|
10
|
-
CreateParagraphBlock,
|
11
|
-
ParagraphBlock,
|
12
|
-
)
|
13
11
|
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
12
|
|
17
13
|
|
18
|
-
class PdfElement(BaseBlockElement):
|
14
|
+
class PdfElement(BaseBlockElement, CaptionMixin):
|
19
15
|
"""
|
20
16
|
Handles conversion between Markdown PDF embeds and Notion PDF blocks.
|
21
17
|
|
22
18
|
Markdown PDF syntax:
|
23
|
-
- [pdf](https://example.com/document.pdf
|
24
|
-
- [pdf](
|
25
|
-
- [pdf](
|
26
|
-
- [pdf](
|
19
|
+
- [pdf](https://example.com/document.pdf) - External URL
|
20
|
+
- [pdf](https://example.com/document.pdf)(caption:Annual Report 2024) - URL with caption
|
21
|
+
- (caption:User Manual)[pdf](https://example.com/manual.pdf) - caption before URL
|
22
|
+
- [pdf](notion://file_id_here)(caption:Notion hosted file) - Notion hosted file
|
23
|
+
- [pdf](upload://upload_id_here)(caption:File upload) - File upload
|
27
24
|
|
28
25
|
Supports all three PDF types: external, notion-hosted, and file uploads.
|
29
26
|
"""
|
30
27
|
|
31
|
-
|
32
|
-
|
33
|
-
r'((?:https?://|notion://|upload://)[^\s\)"]+)' # URL with protocol
|
34
|
-
r'(?:\s+"([^"]*)")?' # optional caption
|
35
|
-
r"\)$"
|
36
|
-
)
|
28
|
+
# Flexible pattern that can handle caption in any position
|
29
|
+
PDF_PATTERN = re.compile(r"\[pdf\]\(((?:https?://|notion://|upload://)[^\s\"]+)\)")
|
37
30
|
|
38
31
|
@classmethod
|
39
32
|
def match_notion(cls, block: Block) -> bool:
|
@@ -41,51 +34,64 @@ class PdfElement(BaseBlockElement):
|
|
41
34
|
return block.type == BlockType.PDF and block.pdf
|
42
35
|
|
43
36
|
@classmethod
|
44
|
-
def markdown_to_notion(cls, text: str) -> BlockCreateResult:
|
45
|
-
"""Convert markdown PDF link to Notion FileBlock (used for PDF)
|
46
|
-
|
47
|
-
|
37
|
+
async def markdown_to_notion(cls, text: str) -> BlockCreateResult:
|
38
|
+
"""Convert markdown PDF link to Notion FileBlock (used for PDF)."""
|
39
|
+
# Use our own regex to find the PDF URL
|
40
|
+
pdf_match = cls.PDF_PATTERN.search(text.strip())
|
41
|
+
if not pdf_match:
|
48
42
|
return None
|
49
43
|
|
50
|
-
url
|
44
|
+
url = pdf_match.group(1)
|
45
|
+
|
46
|
+
# Use mixin to extract caption (if present anywhere in text)
|
47
|
+
caption_text = cls.extract_caption(text.strip())
|
48
|
+
caption_rich_text = cls.build_caption_rich_text(caption_text or "")
|
51
49
|
|
52
50
|
# Build FileBlock using FileType enum (reused for PDF)
|
53
51
|
pdf_block = FileBlock(
|
54
|
-
type=FileType.EXTERNAL,
|
52
|
+
type=FileType.EXTERNAL,
|
53
|
+
external=ExternalFile(url=url),
|
54
|
+
caption=caption_rich_text,
|
55
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
56
|
|
62
|
-
return
|
63
|
-
CreatePdfBlock(pdf=pdf_block),
|
64
|
-
CreateParagraphBlock(paragraph=empty_para),
|
65
|
-
]
|
57
|
+
return CreatePdfBlock(pdf=pdf_block)
|
66
58
|
|
67
59
|
@classmethod
|
68
|
-
def notion_to_markdown(cls, block: Block) -> Optional[str]:
|
60
|
+
async def notion_to_markdown(cls, block: Block) -> Optional[str]:
|
69
61
|
if block.type != BlockType.PDF or not block.pdf:
|
70
62
|
return None
|
71
63
|
|
72
64
|
pb: FileBlock = block.pdf
|
73
65
|
|
74
|
-
# Determine URL (only external and file types are valid for Markdown)
|
75
66
|
if pb.type == FileType.EXTERNAL and pb.external:
|
76
67
|
url = pb.external.url
|
77
68
|
elif pb.type == FileType.FILE and pb.file:
|
78
69
|
url = pb.file.url
|
79
70
|
elif pb.type == FileType.FILE_UPLOAD:
|
80
|
-
# Uploaded PDF has no stable URL for Markdown
|
81
71
|
return None
|
82
72
|
else:
|
83
73
|
return None
|
84
74
|
|
85
|
-
|
86
|
-
|
75
|
+
result = f"[pdf]({url})"
|
76
|
+
|
77
|
+
# Add caption if present
|
78
|
+
caption_markdown = await cls.format_caption_for_markdown(pb.caption or [])
|
79
|
+
if caption_markdown:
|
80
|
+
result += caption_markdown
|
87
81
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
82
|
+
return result
|
83
|
+
|
84
|
+
@classmethod
|
85
|
+
def get_system_prompt_information(cls) -> Optional[BlockElementMarkdownInformation]:
|
86
|
+
"""Get system prompt information for PDF blocks."""
|
87
|
+
return BlockElementMarkdownInformation(
|
88
|
+
block_type=cls.__name__,
|
89
|
+
description="PDF blocks embed and display PDF documents from external URLs with optional captions",
|
90
|
+
syntax_examples=[
|
91
|
+
"[pdf](https://example.com/document.pdf)",
|
92
|
+
"[pdf](https://example.com/report.pdf)(caption:Annual Report 2024)",
|
93
|
+
"(caption:User Manual)[pdf](https://example.com/manual.pdf)",
|
94
|
+
"[pdf](https://example.com/guide.pdf)(caption:**Important** documentation)",
|
95
|
+
],
|
96
|
+
usage_guidelines="Use for embedding PDF documents that can be viewed inline. Supports external URLs and Notion-hosted files. Caption supports rich text formatting and should describe the PDF content.",
|
97
|
+
)
|
@@ -5,6 +5,7 @@ from typing import Optional
|
|
5
5
|
from pydantic import BaseModel
|
6
6
|
|
7
7
|
from notionary.markdown.markdown_node import MarkdownNode
|
8
|
+
from notionary.blocks.mixins.captions import CaptionMarkdownNodeMixin
|
8
9
|
|
9
10
|
|
10
11
|
class PdfMarkdownNodeParams(BaseModel):
|
@@ -12,10 +13,9 @@ class PdfMarkdownNodeParams(BaseModel):
|
|
12
13
|
caption: Optional[str] = None
|
13
14
|
|
14
15
|
|
15
|
-
class PdfMarkdownNode(MarkdownNode):
|
16
|
+
class PdfMarkdownNode(MarkdownNode, CaptionMarkdownNodeMixin):
|
16
17
|
"""
|
17
18
|
Programmatic interface for creating Notion-style Markdown PDF embeds.
|
18
|
-
Example: [pdf](https://example.com/document.pdf "My Caption")
|
19
19
|
"""
|
20
20
|
|
21
21
|
def __init__(self, url: str, caption: Optional[str] = None):
|
@@ -27,9 +27,11 @@ class PdfMarkdownNode(MarkdownNode):
|
|
27
27
|
return cls(url=params.url, caption=params.caption)
|
28
28
|
|
29
29
|
def to_markdown(self) -> str:
|
30
|
+
"""Return the Markdown representation.
|
31
|
+
|
32
|
+
Examples:
|
33
|
+
- [pdf](https://example.com/document.pdf)
|
34
|
+
- [pdf](https://example.com/document.pdf)(caption:Critical safety information)
|
30
35
|
"""
|
31
|
-
|
32
|
-
|
33
|
-
if self.caption:
|
34
|
-
return f'[pdf]({self.url} "{self.caption}")'
|
35
|
-
return f"[pdf]({self.url})"
|
36
|
+
base_markdown = f"[pdf]({self.url})"
|
37
|
+
return self.append_caption_to_markdown(base_markdown, self.caption)
|
@@ -4,6 +4,7 @@ import re
|
|
4
4
|
from typing import Optional
|
5
5
|
|
6
6
|
from notionary.blocks.base_block_element import BaseBlockElement
|
7
|
+
from notionary.blocks.syntax_prompt_builder import BlockElementMarkdownInformation
|
7
8
|
from notionary.blocks.models import Block, BlockCreateResult, BlockType
|
8
9
|
from notionary.blocks.quote.quote_models import CreateQuoteBlock, QuoteBlock
|
9
10
|
from notionary.blocks.rich_text.text_inline_formatter import TextInlineFormatter
|
@@ -15,19 +16,19 @@ class QuoteElement(BaseBlockElement):
|
|
15
16
|
Handles conversion between Markdown quotes and Notion quote blocks.
|
16
17
|
|
17
18
|
Markdown quote syntax:
|
18
|
-
-
|
19
|
+
- > Simple quote text
|
19
20
|
|
20
21
|
Only single-line quotes without author metadata.
|
21
22
|
"""
|
22
23
|
|
23
|
-
PATTERN = re.compile(r"
|
24
|
+
PATTERN = re.compile(r"^>\s*(.+)$")
|
24
25
|
|
25
26
|
@classmethod
|
26
27
|
def match_notion(cls, block: Block) -> bool:
|
27
28
|
return block.type == BlockType.QUOTE and block.quote
|
28
29
|
|
29
30
|
@classmethod
|
30
|
-
def markdown_to_notion(cls, text: str) -> BlockCreateResult:
|
31
|
+
async def markdown_to_notion(cls, text: str) -> BlockCreateResult:
|
31
32
|
"""Convert markdown quote to Notion QuoteBlock."""
|
32
33
|
match = cls.PATTERN.match(text.strip())
|
33
34
|
if not match:
|
@@ -37,22 +38,38 @@ class QuoteElement(BaseBlockElement):
|
|
37
38
|
if not content:
|
38
39
|
return None
|
39
40
|
|
40
|
-
#
|
41
|
-
|
41
|
+
# Reject multiline quotes
|
42
|
+
if "\n" in content or "\r" in content:
|
43
|
+
return None
|
44
|
+
|
45
|
+
rich_text = await TextInlineFormatter.parse_inline_formatting(content)
|
42
46
|
|
43
|
-
# Return a typed QuoteBlock
|
44
47
|
quote_content = QuoteBlock(rich_text=rich_text, color=BlockColor.DEFAULT)
|
45
48
|
return CreateQuoteBlock(quote=quote_content)
|
46
49
|
|
47
50
|
@classmethod
|
48
|
-
def notion_to_markdown(cls, block: Block) -> Optional[str]:
|
51
|
+
async def notion_to_markdown(cls, block: Block) -> Optional[str]:
|
49
52
|
if block.type != BlockType.QUOTE or not block.quote:
|
50
53
|
return None
|
51
54
|
|
52
55
|
rich = block.quote.rich_text
|
53
|
-
text = TextInlineFormatter.extract_text_with_formatting(rich)
|
56
|
+
text = await TextInlineFormatter.extract_text_with_formatting(rich)
|
54
57
|
|
55
58
|
if not text.strip():
|
56
59
|
return None
|
57
60
|
|
58
|
-
return f"
|
61
|
+
return f"> {text.strip()}"
|
62
|
+
|
63
|
+
@classmethod
|
64
|
+
def get_system_prompt_information(cls) -> Optional[BlockElementMarkdownInformation]:
|
65
|
+
"""Get system prompt information for quote blocks."""
|
66
|
+
return BlockElementMarkdownInformation(
|
67
|
+
block_type=cls.__name__,
|
68
|
+
description="Quote blocks display highlighted quotations or emphasized text",
|
69
|
+
syntax_examples=[
|
70
|
+
"> This is an important quote",
|
71
|
+
"> The only way to do great work is to love what you do",
|
72
|
+
"> Innovation distinguishes between a leader and a follower",
|
73
|
+
],
|
74
|
+
usage_guidelines="Use for quotations, important statements, or text that should be visually emphasized. Content should be meaningful and stand out from regular paragraphs.",
|
75
|
+
)
|
@@ -12,7 +12,7 @@ class QuoteMarkdownBlockParams(BaseModel):
|
|
12
12
|
class QuoteMarkdownNode(MarkdownNode):
|
13
13
|
"""
|
14
14
|
Programmatic interface for creating Notion-style quote blocks.
|
15
|
-
Example:
|
15
|
+
Example: > This is a quote
|
16
16
|
"""
|
17
17
|
|
18
18
|
def __init__(self, text: str):
|
@@ -23,4 +23,4 @@ class QuoteMarkdownNode(MarkdownNode):
|
|
23
23
|
return cls(text=params.text)
|
24
24
|
|
25
25
|
def to_markdown(self) -> str:
|
26
|
-
return f"
|
26
|
+
return f"> {self.text}"
|
@@ -3,11 +3,8 @@ from __future__ import annotations
|
|
3
3
|
from typing import Optional, Type
|
4
4
|
|
5
5
|
from notionary.blocks.base_block_element import BaseBlockElement
|
6
|
-
from notionary.blocks.models import Block, BlockCreateResult
|
7
6
|
from notionary.blocks.registry.block_registry_builder import BlockRegistryBuilder
|
8
7
|
from notionary.telemetry import (
|
9
|
-
MarkdownToNotionConversionEvent,
|
10
|
-
NotionToMarkdownConversionEvent,
|
11
8
|
ProductTelemetry,
|
12
9
|
)
|
13
10
|
|
@@ -61,6 +58,7 @@ class BlockRegistry:
|
|
61
58
|
.with_equation()
|
62
59
|
.with_table_of_contents()
|
63
60
|
.with_breadcrumbs()
|
61
|
+
.with_child_database()
|
64
62
|
.with_paragraphs() # position here is important - its a fallback!
|
65
63
|
)
|
66
64
|
|
@@ -92,49 +90,6 @@ class BlockRegistry:
|
|
92
90
|
"""
|
93
91
|
return element_class.__name__ in self._builder._elements
|
94
92
|
|
95
|
-
def find_markdown_handler(self, text: str) -> Optional[Type[BaseBlockElement]]:
|
96
|
-
"""Find an element that can handle the given markdown text."""
|
97
|
-
for element in self._builder._elements.values():
|
98
|
-
if element.match_markdown(text):
|
99
|
-
return element
|
100
|
-
return None
|
101
|
-
|
102
|
-
def markdown_to_notion(self, text: str) -> "BlockCreateResult":
|
103
|
-
"""Convert markdown to Notion block using registered elements."""
|
104
|
-
handler = self.find_markdown_handler(text)
|
105
|
-
|
106
|
-
if handler:
|
107
|
-
self.telemetry.capture(
|
108
|
-
MarkdownToNotionConversionEvent(
|
109
|
-
handler_element_name=handler.__name__,
|
110
|
-
)
|
111
|
-
)
|
112
|
-
|
113
|
-
return handler.markdown_to_notion(text)
|
114
|
-
return None
|
115
|
-
|
116
|
-
def notion_to_markdown(self, block: "Block") -> Optional[str]:
|
117
|
-
"""Convert Notion block to markdown using registered elements."""
|
118
|
-
handler = self._find_notion_handler(block)
|
119
|
-
|
120
|
-
if not handler:
|
121
|
-
return None
|
122
|
-
|
123
|
-
self.telemetry.capture(
|
124
|
-
NotionToMarkdownConversionEvent(
|
125
|
-
handler_element_name=handler.__name__,
|
126
|
-
)
|
127
|
-
)
|
128
|
-
|
129
|
-
return handler.notion_to_markdown(block)
|
130
|
-
|
131
93
|
def get_elements(self) -> list[Type[BaseBlockElement]]:
|
132
94
|
"""Get all registered elements."""
|
133
95
|
return list(self._builder._elements.values())
|
134
|
-
|
135
|
-
def _find_notion_handler(self, block: Block) -> Optional[Type[BaseBlockElement]]:
|
136
|
-
"""Find an element that can handle the given Notion block."""
|
137
|
-
for element in self._builder._elements.values():
|
138
|
-
if element.match_notion(block):
|
139
|
-
return element
|
140
|
-
return None
|
@@ -9,6 +9,7 @@ from notionary.blocks.bookmark import BookmarkElement
|
|
9
9
|
from notionary.blocks.breadcrumbs import BreadcrumbElement
|
10
10
|
from notionary.blocks.bulleted_list import BulletedListElement
|
11
11
|
from notionary.blocks.callout import CalloutElement
|
12
|
+
from notionary.blocks.child_database import ChildDatabaseElement
|
12
13
|
from notionary.blocks.code import CodeElement
|
13
14
|
from notionary.blocks.column import ColumnElement, ColumnListElement
|
14
15
|
from notionary.blocks.divider import DividerElement
|
@@ -70,6 +71,7 @@ class BlockRegistryBuilder:
|
|
70
71
|
.with_equation()
|
71
72
|
.with_table_of_contents()
|
72
73
|
.with_breadcrumbs()
|
74
|
+
.with_child_database()
|
73
75
|
).build()
|
74
76
|
|
75
77
|
def remove_element(self, element_class: Type[BaseBlockElement]) -> Self:
|
@@ -151,6 +153,9 @@ class BlockRegistryBuilder:
|
|
151
153
|
def with_breadcrumbs(self) -> Self:
|
152
154
|
return self._add_element(BreadcrumbElement)
|
153
155
|
|
156
|
+
def with_child_database(self) -> Self:
|
157
|
+
return self._add_element(ChildDatabaseElement)
|
158
|
+
|
154
159
|
def without_headings(self) -> Self:
|
155
160
|
return self.remove_element(HeadingElement)
|
156
161
|
|
@@ -213,6 +218,9 @@ class BlockRegistryBuilder:
|
|
213
218
|
def without_breadcrumbs(self) -> Self:
|
214
219
|
return self.remove_element(BreadcrumbElement)
|
215
220
|
|
221
|
+
def without_child_database(self) -> Self:
|
222
|
+
return self.remove_element(ChildDatabaseElement)
|
223
|
+
|
216
224
|
def build(self) -> BlockRegistry:
|
217
225
|
"""
|
218
226
|
Build and return the configured BlockRegistry instance.
|
@@ -1,9 +1,35 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
+
from enum import Enum
|
3
4
|
from typing import Optional
|
4
5
|
|
5
6
|
from pydantic import BaseModel
|
6
|
-
|
7
|
+
|
8
|
+
|
9
|
+
class RichTextType(str, Enum):
|
10
|
+
"""Types of rich text objects."""
|
11
|
+
|
12
|
+
TEXT = "text"
|
13
|
+
MENTION = "mention"
|
14
|
+
EQUATION = "equation"
|
15
|
+
|
16
|
+
|
17
|
+
class MentionType(str, Enum):
|
18
|
+
"""Types of mention objects."""
|
19
|
+
|
20
|
+
USER = "user"
|
21
|
+
PAGE = "page"
|
22
|
+
DATABASE = "database"
|
23
|
+
DATE = "date"
|
24
|
+
LINK_PREVIEW = "link_preview"
|
25
|
+
TEMPLATE_MENTION = "template_mention"
|
26
|
+
|
27
|
+
|
28
|
+
class TemplateMentionType(str, Enum):
|
29
|
+
"""Types of template mentions."""
|
30
|
+
|
31
|
+
USER = "template_mention_user"
|
32
|
+
DATE = "template_mention_date"
|
7
33
|
|
8
34
|
|
9
35
|
class TextAnnotations(BaseModel):
|
@@ -53,13 +79,11 @@ class MentionDate(BaseModel):
|
|
53
79
|
|
54
80
|
class MentionTemplateMention(BaseModel):
|
55
81
|
# Notion hat zwei Template-Mention-Typen
|
56
|
-
type:
|
82
|
+
type: TemplateMentionType
|
57
83
|
|
58
84
|
|
59
85
|
class MentionObject(BaseModel):
|
60
|
-
type:
|
61
|
-
"user", "page", "database", "date", "link_preview", "template_mention"
|
62
|
-
]
|
86
|
+
type: MentionType
|
63
87
|
user: Optional[MentionUserRef] = None
|
64
88
|
page: Optional[MentionPageRef] = None
|
65
89
|
database: Optional[MentionDatabaseRef] = None
|
@@ -69,7 +93,7 @@ class MentionObject(BaseModel):
|
|
69
93
|
|
70
94
|
|
71
95
|
class RichTextObject(BaseModel):
|
72
|
-
type:
|
96
|
+
type: RichTextType = RichTextType.TEXT
|
73
97
|
|
74
98
|
text: Optional[TextContent] = None
|
75
99
|
annotations: Optional[TextAnnotations] = None
|
@@ -83,26 +107,30 @@ class RichTextObject(BaseModel):
|
|
83
107
|
@classmethod
|
84
108
|
def from_plain_text(cls, content: str, **ann) -> RichTextObject:
|
85
109
|
return cls(
|
86
|
-
type=
|
110
|
+
type=RichTextType.TEXT,
|
87
111
|
text=TextContent(content=content),
|
88
112
|
annotations=TextAnnotations(**ann) if ann else TextAnnotations(),
|
89
113
|
plain_text=content,
|
90
114
|
)
|
91
115
|
|
92
116
|
@classmethod
|
93
|
-
def
|
94
|
-
# keine annotations setzen → Notion Code-Highlight bleibt an
|
117
|
+
def for_caption(cls, content: str) -> RichTextObject:
|
95
118
|
return cls(
|
96
|
-
type=
|
119
|
+
type=RichTextType.TEXT,
|
97
120
|
text=TextContent(content=content),
|
98
121
|
annotations=None,
|
99
122
|
plain_text=content,
|
100
123
|
)
|
101
124
|
|
125
|
+
@classmethod
|
126
|
+
def for_code_block(cls, content: str) -> RichTextObject:
|
127
|
+
# keine annotations setzen → Notion Code-Highlight bleibt an
|
128
|
+
return cls.for_caption(content)
|
129
|
+
|
102
130
|
@classmethod
|
103
131
|
def for_link(cls, content: str, url: str, **ann) -> RichTextObject:
|
104
132
|
return cls(
|
105
|
-
type=
|
133
|
+
type=RichTextType.TEXT,
|
106
134
|
text=TextContent(content=content, link=LinkObject(url=url)),
|
107
135
|
annotations=TextAnnotations(**ann) if ann else TextAnnotations(),
|
108
136
|
plain_text=content,
|
@@ -111,25 +139,29 @@ class RichTextObject(BaseModel):
|
|
111
139
|
@classmethod
|
112
140
|
def mention_user(cls, user_id: str) -> RichTextObject:
|
113
141
|
return cls(
|
114
|
-
type=
|
115
|
-
mention=MentionObject(
|
142
|
+
type=RichTextType.MENTION,
|
143
|
+
mention=MentionObject(
|
144
|
+
type=MentionType.USER, user=MentionUserRef(id=user_id)
|
145
|
+
),
|
116
146
|
annotations=TextAnnotations(),
|
117
147
|
)
|
118
148
|
|
119
149
|
@classmethod
|
120
150
|
def mention_page(cls, page_id: str) -> RichTextObject:
|
121
151
|
return cls(
|
122
|
-
type=
|
123
|
-
mention=MentionObject(
|
152
|
+
type=RichTextType.MENTION,
|
153
|
+
mention=MentionObject(
|
154
|
+
type=MentionType.PAGE, page=MentionPageRef(id=page_id)
|
155
|
+
),
|
124
156
|
annotations=TextAnnotations(),
|
125
157
|
)
|
126
158
|
|
127
159
|
@classmethod
|
128
160
|
def mention_database(cls, database_id: str) -> RichTextObject:
|
129
161
|
return cls(
|
130
|
-
type=
|
162
|
+
type=RichTextType.MENTION,
|
131
163
|
mention=MentionObject(
|
132
|
-
type=
|
164
|
+
type=MentionType.DATABASE, database=MentionDatabaseRef(id=database_id)
|
133
165
|
),
|
134
166
|
annotations=TextAnnotations(),
|
135
167
|
)
|
@@ -137,9 +169,9 @@ class RichTextObject(BaseModel):
|
|
137
169
|
@classmethod
|
138
170
|
def mention_link_preview(cls, url: str) -> RichTextObject:
|
139
171
|
return cls(
|
140
|
-
type=
|
172
|
+
type=RichTextType.MENTION,
|
141
173
|
mention=MentionObject(
|
142
|
-
type=
|
174
|
+
type=MentionType.LINK_PREVIEW, link_preview=MentionLinkPreview(url=url)
|
143
175
|
),
|
144
176
|
annotations=TextAnnotations(),
|
145
177
|
)
|
@@ -149,9 +181,10 @@ class RichTextObject(BaseModel):
|
|
149
181
|
cls, start: str, end: str | None = None, time_zone: str | None = None
|
150
182
|
) -> RichTextObject:
|
151
183
|
return cls(
|
152
|
-
type=
|
184
|
+
type=RichTextType.MENTION,
|
153
185
|
mention=MentionObject(
|
154
|
-
type=
|
186
|
+
type=MentionType.DATE,
|
187
|
+
date=MentionDate(start=start, end=end, time_zone=time_zone),
|
155
188
|
),
|
156
189
|
annotations=TextAnnotations(),
|
157
190
|
)
|
@@ -159,10 +192,10 @@ class RichTextObject(BaseModel):
|
|
159
192
|
@classmethod
|
160
193
|
def mention_template_user(cls) -> RichTextObject:
|
161
194
|
return cls(
|
162
|
-
type=
|
195
|
+
type=RichTextType.MENTION,
|
163
196
|
mention=MentionObject(
|
164
|
-
type=
|
165
|
-
template_mention=MentionTemplateMention(type=
|
197
|
+
type=MentionType.TEMPLATE_MENTION,
|
198
|
+
template_mention=MentionTemplateMention(type=TemplateMentionType.USER),
|
166
199
|
),
|
167
200
|
annotations=TextAnnotations(),
|
168
201
|
)
|
@@ -170,10 +203,10 @@ class RichTextObject(BaseModel):
|
|
170
203
|
@classmethod
|
171
204
|
def mention_template_date(cls) -> RichTextObject:
|
172
205
|
return cls(
|
173
|
-
type=
|
206
|
+
type=RichTextType.MENTION,
|
174
207
|
mention=MentionObject(
|
175
|
-
type=
|
176
|
-
template_mention=MentionTemplateMention(type=
|
208
|
+
type=MentionType.TEMPLATE_MENTION,
|
209
|
+
template_mention=MentionTemplateMention(type=TemplateMentionType.DATE),
|
177
210
|
),
|
178
211
|
annotations=TextAnnotations(),
|
179
212
|
)
|
@@ -181,8 +214,8 @@ class RichTextObject(BaseModel):
|
|
181
214
|
@classmethod
|
182
215
|
def equation_inline(cls, expression: str) -> RichTextObject:
|
183
216
|
return cls(
|
184
|
-
type=
|
217
|
+
type=RichTextType.EQUATION,
|
185
218
|
equation=EquationObject(expression=expression),
|
186
219
|
annotations=TextAnnotations(),
|
187
|
-
plain_text=expression,
|
220
|
+
plain_text=expression,
|
188
221
|
)
|