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,270 +1,115 @@
|
|
1
|
-
import
|
2
|
-
from typing import Dict, Any, Optional, List, Tuple, Callable
|
1
|
+
from __future__ import annotations
|
3
2
|
|
4
|
-
|
5
|
-
|
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.heading.heading_models import (
|
8
|
+
CreateHeading1Block,
|
9
|
+
CreateHeading2Block,
|
10
|
+
CreateHeading3Block,
|
11
|
+
HeadingBlock,
|
9
12
|
)
|
10
|
-
from notionary.blocks.
|
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 ToggleableHeadingElement(
|
14
|
-
"""
|
18
|
+
class ToggleableHeadingElement(BaseBlockElement):
|
19
|
+
"""
|
20
|
+
Simplified ToggleableHeadingElement that works with the stack-based converter.
|
21
|
+
Children are automatically handled by the StackBasedMarkdownConverter.
|
22
|
+
"""
|
15
23
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
@staticmethod
|
20
|
-
def match_markdown(text: str) -> bool:
|
21
|
-
"""Check if text is a markdown collapsible heading."""
|
22
|
-
return bool(ToggleableHeadingElement.PATTERN.match(text))
|
24
|
+
# Updated pattern for simplified +++# Title syntax (no quotes!)
|
25
|
+
PATTERN = re.compile(r"^[+]{3}(?P<level>#{1,3})\s+(.+)$", re.IGNORECASE)
|
23
26
|
|
24
27
|
@staticmethod
|
25
|
-
def match_notion(block:
|
28
|
+
def match_notion(block: Block) -> bool:
|
26
29
|
"""Check if block is a Notion toggleable heading."""
|
27
|
-
|
28
|
-
if
|
30
|
+
# Use BlockType enum for matching heading blocks
|
31
|
+
if block.type not in (
|
32
|
+
BlockType.HEADING_1,
|
33
|
+
BlockType.HEADING_2,
|
34
|
+
BlockType.HEADING_3,
|
35
|
+
):
|
29
36
|
return False
|
30
37
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
"""Convert markdown collapsible heading to Notion toggleable heading block."""
|
38
|
-
header_match = ToggleableHeadingElement.PATTERN.match(text)
|
39
|
-
if not header_match:
|
40
|
-
return None
|
41
|
-
|
42
|
-
level = len(header_match.group(1))
|
43
|
-
content = header_match.group(2)
|
44
|
-
|
45
|
-
return {
|
46
|
-
"type": f"heading_{level}",
|
47
|
-
f"heading_{level}": {
|
48
|
-
"rich_text": TextInlineFormatter.parse_inline_formatting(content),
|
49
|
-
"is_toggleable": True,
|
50
|
-
"color": "default",
|
51
|
-
"children": [], # Will be populated with nested content if needed
|
52
|
-
},
|
53
|
-
}
|
54
|
-
|
55
|
-
@staticmethod
|
56
|
-
def notion_to_markdown(block: Dict[str, Any]) -> Optional[str]:
|
57
|
-
"""Convert Notion toggleable heading block to markdown collapsible heading with pipe syntax."""
|
58
|
-
block_type = block.get("type", "")
|
59
|
-
|
60
|
-
if not block_type.startswith("heading_"):
|
61
|
-
return None
|
62
|
-
|
63
|
-
try:
|
64
|
-
level = int(block_type[-1])
|
65
|
-
if not 1 <= level <= 3:
|
66
|
-
return None
|
67
|
-
except ValueError:
|
68
|
-
return None
|
69
|
-
|
70
|
-
heading_data = block.get(block_type, {})
|
71
|
-
|
72
|
-
# Check if it's toggleable
|
73
|
-
if not heading_data.get("is_toggleable", False):
|
74
|
-
return None
|
75
|
-
|
76
|
-
rich_text = heading_data.get("rich_text", [])
|
77
|
-
text = TextInlineFormatter.extract_text_with_formatting(rich_text)
|
78
|
-
prefix = "#" * level
|
79
|
-
return f"+{prefix} {text or ''}"
|
80
|
-
|
81
|
-
@staticmethod
|
82
|
-
def is_multiline() -> bool:
|
83
|
-
"""Collapsible headings can have children, so they're multiline elements."""
|
84
|
-
return True
|
38
|
+
if block.heading_1 and block.heading_1.is_toggleable:
|
39
|
+
return True
|
40
|
+
if block.heading_2 and block.heading_2.is_toggleable:
|
41
|
+
return True
|
42
|
+
if block.heading_3 and block.heading_3.is_toggleable:
|
43
|
+
return True
|
85
44
|
|
86
45
|
@classmethod
|
87
|
-
def
|
88
|
-
cls,
|
89
|
-
text: str,
|
90
|
-
process_nested_content: Callable = None,
|
91
|
-
context_aware: bool = True,
|
92
|
-
) -> List[Tuple[int, int, Dict[str, Any]]]:
|
46
|
+
async def markdown_to_notion(cls, text: str) -> BlockCreateResult:
|
93
47
|
"""
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
Args:
|
98
|
-
text: The text to process
|
99
|
-
process_nested_content: Optional callback function to process nested content
|
100
|
-
context_aware: Whether to consider context when finding collapsible headings
|
101
|
-
|
102
|
-
Returns:
|
103
|
-
List of (start_pos, end_pos, block) tuples
|
48
|
+
Convert markdown collapsible heading to a toggleable Notion HeadingBlock.
|
49
|
+
Children are automatically handled by the StackBasedMarkdownConverter.
|
104
50
|
"""
|
105
|
-
if not text:
|
106
|
-
return
|
107
|
-
|
108
|
-
collapsible_blocks = []
|
109
|
-
lines = text.split("\n")
|
110
|
-
line_index = 0
|
111
|
-
|
112
|
-
while line_index < len(lines):
|
113
|
-
current_line = lines[line_index]
|
114
|
-
|
115
|
-
# Skip non-collapsible heading lines
|
116
|
-
if not cls._is_collapsible_heading(current_line):
|
117
|
-
line_index += 1
|
118
|
-
continue
|
119
|
-
|
120
|
-
# Process collapsible heading
|
121
|
-
start_position = cls._calculate_line_position(lines, line_index)
|
122
|
-
heading_block = cls.markdown_to_notion(current_line)
|
123
|
-
|
124
|
-
if not heading_block:
|
125
|
-
line_index += 1
|
126
|
-
continue
|
51
|
+
if not (match := cls.PATTERN.match(text.strip())):
|
52
|
+
return None
|
127
53
|
|
128
|
-
|
129
|
-
|
130
|
-
lines, line_index + 1
|
131
|
-
)
|
132
|
-
end_position = cls._calculate_block_end_position(
|
133
|
-
start_position, current_line, nested_content
|
134
|
-
)
|
54
|
+
level = len(match.group("level")) # Count # characters
|
55
|
+
content = match.group(2).strip() # group(2) is the title (no quotes needed)
|
135
56
|
|
136
|
-
|
137
|
-
|
138
|
-
)
|
57
|
+
if level < 1 or level > 3 or not content:
|
58
|
+
return None
|
139
59
|
|
140
|
-
|
141
|
-
collapsible_blocks.append((start_position, end_position, heading_block))
|
142
|
-
line_index = next_line_index
|
60
|
+
rich_text = await TextInlineFormatter.parse_inline_formatting(content)
|
143
61
|
|
144
|
-
|
62
|
+
heading_content = HeadingBlock(
|
63
|
+
rich_text=rich_text, color="default", is_toggleable=True, children=[]
|
64
|
+
)
|
145
65
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
66
|
+
if level == 1:
|
67
|
+
return CreateHeading1Block(heading_1=heading_content)
|
68
|
+
elif level == 2:
|
69
|
+
return CreateHeading2Block(heading_2=heading_content)
|
70
|
+
else:
|
71
|
+
return CreateHeading3Block(heading_3=heading_content)
|
150
72
|
|
151
73
|
@staticmethod
|
152
|
-
def
|
153
|
-
"""
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
def _extract_nested_content(
|
161
|
-
cls, lines: List[str], start_index: int
|
162
|
-
) -> Tuple[List[str], int]:
|
163
|
-
"""
|
164
|
-
Extract nested content with pipe syntax from lines following a collapsible heading.
|
165
|
-
|
166
|
-
Args:
|
167
|
-
lines: All text lines
|
168
|
-
start_index: Index to start looking for nested content
|
169
|
-
|
170
|
-
Returns:
|
171
|
-
Tuple of (nested_content, next_line_index)
|
172
|
-
"""
|
173
|
-
nested_content = []
|
174
|
-
current_index = start_index
|
175
|
-
|
176
|
-
while current_index < len(lines):
|
177
|
-
current_line = lines[current_index]
|
178
|
-
|
179
|
-
# Case 1: Empty line - check if it's followed by pipe content
|
180
|
-
if not current_line.strip():
|
181
|
-
if cls._is_next_line_pipe_content(lines, current_index):
|
182
|
-
nested_content.append("")
|
183
|
-
current_index += 1
|
184
|
-
continue
|
185
|
-
|
186
|
-
# Case 2: Pipe content line - part of nested content
|
187
|
-
pipe_content = cls._extract_pipe_content(current_line)
|
188
|
-
if pipe_content is not None:
|
189
|
-
nested_content.append(pipe_content)
|
190
|
-
current_index += 1
|
191
|
-
continue
|
192
|
-
|
193
|
-
# Case 3: Another collapsible heading - ends current heading's content
|
194
|
-
if cls.PATTERN.match(current_line):
|
195
|
-
break
|
196
|
-
|
197
|
-
# Case 4: Any other line - ends nested content
|
198
|
-
break
|
199
|
-
|
200
|
-
return nested_content, current_index
|
201
|
-
|
202
|
-
@classmethod
|
203
|
-
def _is_next_line_pipe_content(cls, lines: List[str], current_index: int) -> bool:
|
204
|
-
"""Check if the next line uses pipe syntax for nested content."""
|
205
|
-
next_index = current_index + 1
|
206
|
-
if next_index >= len(lines):
|
207
|
-
return False
|
208
|
-
return bool(cls.PIPE_CONTENT_PATTERN.match(lines[next_index]))
|
209
|
-
|
210
|
-
@classmethod
|
211
|
-
def _extract_pipe_content(cls, line: str) -> Optional[str]:
|
212
|
-
"""Extract content from a line with pipe prefix."""
|
213
|
-
pipe_match = cls.PIPE_CONTENT_PATTERN.match(line)
|
214
|
-
if not pipe_match:
|
74
|
+
async def notion_to_markdown(block: Block) -> Optional[str]:
|
75
|
+
"""Convert Notion toggleable heading block to markdown collapsible heading."""
|
76
|
+
# Only handle heading blocks via BlockType enum
|
77
|
+
if block.type not in (
|
78
|
+
BlockType.HEADING_1,
|
79
|
+
BlockType.HEADING_2,
|
80
|
+
BlockType.HEADING_3,
|
81
|
+
):
|
215
82
|
return None
|
216
|
-
return pipe_match.group(1)
|
217
83
|
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
# Add length of each nested content line plus newline
|
226
|
-
nested_length = sum(len(line) + 1 for line in nested_content)
|
227
|
-
block_length += nested_length
|
228
|
-
return start_position + block_length
|
84
|
+
# Determine heading level from enum
|
85
|
+
if block.type == BlockType.HEADING_1:
|
86
|
+
level = 1
|
87
|
+
elif block.type == BlockType.HEADING_2:
|
88
|
+
level = 2
|
89
|
+
else:
|
90
|
+
level = 3
|
229
91
|
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
heading_block: Dict[str, Any],
|
234
|
-
nested_content: List[str],
|
235
|
-
processor: Optional[Callable],
|
236
|
-
) -> None:
|
237
|
-
"""Process nested content with the provided callback function if available."""
|
238
|
-
if not (nested_content and processor):
|
239
|
-
return
|
92
|
+
heading_content = getattr(block, block.type.value)
|
93
|
+
if not isinstance(heading_content, HeadingBlock):
|
94
|
+
return None
|
240
95
|
|
241
|
-
|
242
|
-
|
96
|
+
text = await TextInlineFormatter.extract_text_with_formatting(
|
97
|
+
heading_content.rich_text
|
98
|
+
)
|
99
|
+
prefix = "#" * level
|
243
100
|
|
244
|
-
|
245
|
-
block_type = heading_block["type"]
|
246
|
-
heading_block[block_type]["children"] = nested_blocks
|
101
|
+
return f'+++{prefix} {text or ""}'
|
247
102
|
|
248
103
|
@classmethod
|
249
|
-
def
|
250
|
-
"""
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
"
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
)
|
261
|
-
.with_syntax("+# Collapsible Heading\n| Content with pipe prefix")
|
262
|
-
.with_examples(
|
263
|
-
[
|
264
|
-
"+# Main Collapsible Section\n| Content under the section",
|
265
|
-
"+## Subsection\n| This content is hidden until expanded",
|
266
|
-
"+### Detailed Information\n| Technical details go here",
|
267
|
-
]
|
268
|
-
)
|
269
|
-
.build()
|
104
|
+
def get_system_prompt_information(cls) -> Optional[BlockElementMarkdownInformation]:
|
105
|
+
"""Get system prompt information for toggleable heading blocks."""
|
106
|
+
return BlockElementMarkdownInformation(
|
107
|
+
block_type=cls.__name__,
|
108
|
+
description="Toggleable heading blocks create collapsible sections with heading-style titles",
|
109
|
+
syntax_examples=[
|
110
|
+
"+++# Main Section\nContent goes here\n+++",
|
111
|
+
"+++## Subsection\nSubsection content\n+++",
|
112
|
+
"+++### Details\nDetailed information\n+++",
|
113
|
+
],
|
114
|
+
usage_guidelines="Use for collapsible sections with heading structure. Combines heading levels (1-3) with toggle functionality. Great for organizing hierarchical expandable content.",
|
270
115
|
)
|
@@ -1,43 +1,51 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
from typing import Optional, List
|
4
3
|
from pydantic import BaseModel
|
5
|
-
|
4
|
+
|
5
|
+
from notionary.markdown.markdown_node import MarkdownNode
|
6
6
|
|
7
7
|
|
8
8
|
class ToggleableHeadingMarkdownBlockParams(BaseModel):
|
9
9
|
text: str
|
10
|
-
level: int
|
11
|
-
|
10
|
+
level: int
|
11
|
+
children: list[MarkdownNode]
|
12
|
+
model_config = {"arbitrary_types_allowed": True}
|
12
13
|
|
13
14
|
|
14
15
|
class ToggleableHeadingMarkdownNode(MarkdownNode):
|
15
16
|
"""
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
17
|
+
Clean programmatic interface for creating collapsible Markdown headings (toggleable headings)
|
18
|
+
with pipe-prefixed nested content using MarkdownNode children.
|
19
|
+
|
20
|
+
Example syntax for a level-2 toggleable heading:
|
21
|
+
+++## Advanced Section
|
22
|
+
some content
|
23
|
+
+++
|
23
24
|
"""
|
24
25
|
|
25
|
-
def __init__(self, text: str, level: int
|
26
|
+
def __init__(self, text: str, level: int, children: list[MarkdownNode]):
|
26
27
|
if not (1 <= level <= 3):
|
27
28
|
raise ValueError("Only heading levels 1-3 are supported (H1, H2, H3)")
|
28
29
|
self.text = text
|
29
30
|
self.level = level
|
30
|
-
self.
|
31
|
+
self.children = children
|
31
32
|
|
32
33
|
@classmethod
|
33
34
|
def from_params(
|
34
35
|
cls, params: ToggleableHeadingMarkdownBlockParams
|
35
36
|
) -> ToggleableHeadingMarkdownNode:
|
36
|
-
return cls(text=params.text, level=params.level,
|
37
|
+
return cls(text=params.text, level=params.level, children=params.children)
|
37
38
|
|
38
39
|
def to_markdown(self) -> str:
|
39
|
-
prefix = "
|
40
|
+
prefix = "+++" + ("#" * self.level)
|
40
41
|
result = f"{prefix} {self.text}"
|
41
|
-
|
42
|
-
|
43
|
-
|
42
|
+
|
43
|
+
if not self.children:
|
44
|
+
result += "\n+++"
|
45
|
+
return result
|
46
|
+
|
47
|
+
# Convert children to markdown
|
48
|
+
content_parts = [child.to_markdown() for child in self.children]
|
49
|
+
content_text = "\n\n".join(content_parts)
|
50
|
+
|
51
|
+
return result + "\n" + content_text + "\n+++"
|
@@ -0,0 +1,130 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from enum import Enum
|
4
|
+
|
5
|
+
from typing import Protocol, TYPE_CHECKING
|
6
|
+
|
7
|
+
if TYPE_CHECKING:
|
8
|
+
from notionary.blocks.models import BlockCreateRequest
|
9
|
+
from notionary.blocks.rich_text.rich_text_models import RichTextObject
|
10
|
+
|
11
|
+
|
12
|
+
class BlockColor(str, Enum):
|
13
|
+
BLUE = "blue"
|
14
|
+
BLUE_BACKGROUND = "blue_background"
|
15
|
+
BROWN = "brown"
|
16
|
+
BROWN_BACKGROUND = "brown_background"
|
17
|
+
DEFAULT = "default"
|
18
|
+
GRAY = "gray"
|
19
|
+
GRAY_BACKGROUND = "gray_background"
|
20
|
+
GREEN = "green"
|
21
|
+
GREEN_BACKGROUND = "green_background"
|
22
|
+
ORANGE = "orange"
|
23
|
+
ORANGE_BACKGROUND = "orange_background"
|
24
|
+
YELLOW = "yellow"
|
25
|
+
YELLOW_BACKGROUND = "yellow_background"
|
26
|
+
PINK = "pink"
|
27
|
+
PINK_BACKGROUND = "pink_background"
|
28
|
+
PURPLE = "purple"
|
29
|
+
PURPLE_BACKGROUND = "purple_background"
|
30
|
+
RED = "red"
|
31
|
+
RED_BACKGROUND = "red_background"
|
32
|
+
DEFAULT_BACKGROUND = "default_background"
|
33
|
+
|
34
|
+
|
35
|
+
class BlockType(str, Enum):
|
36
|
+
BOOKMARK = "bookmark"
|
37
|
+
BREADCRUMB = "breadcrumb"
|
38
|
+
BULLETED_LIST_ITEM = "bulleted_list_item"
|
39
|
+
CALLOUT = "callout"
|
40
|
+
CHILD_DATABASE = "child_database"
|
41
|
+
CHILD_PAGE = "child_page"
|
42
|
+
COLUMN = "column"
|
43
|
+
COLUMN_LIST = "column_list"
|
44
|
+
CODE = "code"
|
45
|
+
DIVIDER = "divider"
|
46
|
+
EMBED = "embed"
|
47
|
+
EQUATION = "equation"
|
48
|
+
FILE = "file"
|
49
|
+
HEADING_1 = "heading_1"
|
50
|
+
HEADING_2 = "heading_2"
|
51
|
+
HEADING_3 = "heading_3"
|
52
|
+
IMAGE = "image"
|
53
|
+
LINK_PREVIEW = "link_preview"
|
54
|
+
LINK_TO_PAGE = "link_to_page"
|
55
|
+
NUMBERED_LIST_ITEM = "numbered_list_item"
|
56
|
+
PARAGRAPH = "paragraph"
|
57
|
+
PDF = "pdf"
|
58
|
+
QUOTE = "quote"
|
59
|
+
SYNCED_BLOCK = "synced_block"
|
60
|
+
TABLE = "table"
|
61
|
+
TABLE_OF_CONTENTS = "table_of_contents"
|
62
|
+
TABLE_ROW = "table_row"
|
63
|
+
TO_DO = "to_do"
|
64
|
+
TOGGLE = "toggle"
|
65
|
+
UNSUPPORTED = "unsupported"
|
66
|
+
VIDEO = "video"
|
67
|
+
AUDIO = "audio"
|
68
|
+
|
69
|
+
|
70
|
+
class MarkdownBlockType(str, Enum):
|
71
|
+
"""
|
72
|
+
Extended block types for the MarkdownBuilder.
|
73
|
+
Includes all BlockType values and adds user-friendly aliases
|
74
|
+
for blocks with no direct Notion API counterpart.
|
75
|
+
"""
|
76
|
+
|
77
|
+
# All BlockType values
|
78
|
+
BOOKMARK = "bookmark"
|
79
|
+
BREADCRUMB = "breadcrumb"
|
80
|
+
BULLETED_LIST_ITEM = "bulleted_list_item"
|
81
|
+
CALLOUT = "callout"
|
82
|
+
CHILD_DATABASE = "child_database"
|
83
|
+
CHILD_PAGE = "child_page"
|
84
|
+
COLUMN = "column"
|
85
|
+
COLUMN_LIST = "column_list"
|
86
|
+
CODE = "code"
|
87
|
+
DIVIDER = "divider"
|
88
|
+
EMBED = "embed"
|
89
|
+
EQUATION = "equation"
|
90
|
+
FILE = "file"
|
91
|
+
HEADING_1 = "heading_1"
|
92
|
+
HEADING_2 = "heading_2"
|
93
|
+
HEADING_3 = "heading_3"
|
94
|
+
IMAGE = "image"
|
95
|
+
LINK_PREVIEW = "link_preview"
|
96
|
+
LINK_TO_PAGE = "link_to_page"
|
97
|
+
NUMBERED_LIST_ITEM = "numbered_list_item"
|
98
|
+
PARAGRAPH = "paragraph"
|
99
|
+
PDF = "pdf"
|
100
|
+
QUOTE = "quote"
|
101
|
+
SYNCED_BLOCK = "synced_block"
|
102
|
+
TABLE = "table"
|
103
|
+
TABLE_OF_CONTENTS = "table_of_contents"
|
104
|
+
TABLE_ROW = "table_row"
|
105
|
+
TO_DO = "to_do"
|
106
|
+
TOGGLE = "toggle"
|
107
|
+
UNSUPPORTED = "unsupported"
|
108
|
+
VIDEO = "video"
|
109
|
+
AUDIO = "audio"
|
110
|
+
|
111
|
+
# Markdown-specific aliases
|
112
|
+
HEADING = "heading"
|
113
|
+
BULLETED_LIST = "bulleted_list"
|
114
|
+
NUMBERED_LIST = "numbered_list"
|
115
|
+
TODO = "todo"
|
116
|
+
TOGGLEABLE_HEADING = "toggleable_heading"
|
117
|
+
COLUMNS = "columns"
|
118
|
+
SPACE = "space"
|
119
|
+
|
120
|
+
|
121
|
+
class HasRichText(Protocol):
|
122
|
+
"""Protocol for objects that have a rich_text attribute."""
|
123
|
+
|
124
|
+
rich_text: list[RichTextObject]
|
125
|
+
|
126
|
+
|
127
|
+
class HasChildren(Protocol):
|
128
|
+
"""Protocol for objects that have children blocks."""
|
129
|
+
|
130
|
+
children: list[BlockCreateRequest]
|
@@ -1,7 +1,13 @@
|
|
1
|
-
from .video_element import VideoElement
|
2
|
-
from .
|
1
|
+
from notionary.blocks.video.video_element import VideoElement
|
2
|
+
from notionary.blocks.video.video_element_models import CreateVideoBlock
|
3
|
+
from notionary.blocks.video.video_markdown_node import (
|
4
|
+
VideoMarkdownBlockParams,
|
5
|
+
VideoMarkdownNode,
|
6
|
+
)
|
3
7
|
|
4
8
|
__all__ = [
|
5
9
|
"VideoElement",
|
10
|
+
"CreateVideoBlock",
|
6
11
|
"VideoMarkdownNode",
|
12
|
+
"VideoMarkdownBlockParams",
|
7
13
|
]
|