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,303 +1,112 @@
|
|
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
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
from notionary.blocks.base_block_element import BaseBlockElement
|
7
|
+
from notionary.blocks.syntax_prompt_builder import BlockElementMarkdownInformation
|
8
|
+
from notionary.blocks.models import Block, BlockCreateResult, BlockType
|
9
|
+
from notionary.blocks.rich_text.rich_text_models import RichTextObject
|
10
|
+
from notionary.blocks.rich_text.text_inline_formatter import TextInlineFormatter
|
11
|
+
from notionary.blocks.toggle.toggle_models import CreateToggleBlock, ToggleBlock
|
12
|
+
from notionary.blocks.types import BlockColor
|
10
13
|
|
11
14
|
|
12
|
-
class ToggleElement(
|
15
|
+
class ToggleElement(BaseBlockElement):
|
13
16
|
"""
|
14
|
-
|
17
|
+
Simplified ToggleElement class that works with the stack-based converter.
|
18
|
+
Children are automatically handled by the StackBasedMarkdownConverter.
|
15
19
|
"""
|
16
20
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
TRANSCRIPT_TOGGLE_PATTERN = re.compile(r"^[+]{3}\s+Transcript$")
|
21
|
-
|
22
|
-
@classmethod
|
23
|
-
def match_markdown(cls, text: str) -> bool:
|
24
|
-
"""Check if the text is a markdown toggle."""
|
25
|
-
return bool(ToggleElement.TOGGLE_PATTERN.match(text.strip()))
|
21
|
+
# Updated pattern for ultra-simplified +++ Title syntax (no quotes!)
|
22
|
+
TOGGLE_PATTERN = re.compile(r"^[+]{3}\s+(.+)$", re.IGNORECASE)
|
23
|
+
TRANSCRIPT_TOGGLE_PATTERN = re.compile(r"^[+]{3}\s+Transcript$", re.IGNORECASE)
|
26
24
|
|
27
25
|
@classmethod
|
28
|
-
def match_notion(cls, block:
|
26
|
+
def match_notion(cls, block: Block) -> bool:
|
29
27
|
"""Check if the block is a Notion toggle block."""
|
30
|
-
return block.
|
28
|
+
return block.type == BlockType.TOGGLE
|
31
29
|
|
32
30
|
@classmethod
|
33
|
-
def markdown_to_notion(cls, text: str) ->
|
34
|
-
"""Convert markdown toggle line to Notion toggle block."""
|
35
|
-
toggle_match = ToggleElement.TOGGLE_PATTERN.match(text.strip())
|
36
|
-
if not toggle_match:
|
37
|
-
return None
|
38
|
-
|
39
|
-
# Extract toggle title
|
40
|
-
title = toggle_match.group(1)
|
41
|
-
|
42
|
-
return {
|
43
|
-
"type": "toggle",
|
44
|
-
"toggle": {
|
45
|
-
"rich_text": [{"type": "text", "text": {"content": title}}],
|
46
|
-
"color": "default",
|
47
|
-
"children": [],
|
48
|
-
},
|
49
|
-
}
|
50
|
-
|
51
|
-
@classmethod
|
52
|
-
def extract_nested_content(
|
53
|
-
cls, lines: List[str], start_index: int
|
54
|
-
) -> Tuple[List[str], int]:
|
31
|
+
async def markdown_to_notion(cls, text: str) -> BlockCreateResult:
|
55
32
|
"""
|
56
|
-
|
57
|
-
|
58
|
-
Args:
|
59
|
-
lines: All lines of text.
|
60
|
-
start_index: Starting index to look for nested content.
|
61
|
-
|
62
|
-
Returns:
|
63
|
-
Tuple of (nested_content_lines, next_line_index)
|
33
|
+
Convert markdown toggle line to Notion ToggleBlock.
|
34
|
+
Children are automatically handled by the StackBasedMarkdownConverter.
|
64
35
|
"""
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
while current_index < len(lines):
|
69
|
-
current_line = lines[current_index]
|
70
|
-
|
71
|
-
# Case 1: Empty line - could be part of the content if next line is a pipe line
|
72
|
-
if not current_line.strip():
|
73
|
-
if ToggleElement.is_next_line_pipe_content(lines, current_index):
|
74
|
-
nested_content.append("")
|
75
|
-
current_index += 1
|
76
|
-
continue
|
77
|
-
else:
|
78
|
-
# Empty line not followed by pipe ends the block
|
79
|
-
break
|
80
|
-
|
81
|
-
# Case 2: Pipe-prefixed line - part of the nested content
|
82
|
-
pipe_content = ToggleElement.extract_pipe_content(current_line)
|
83
|
-
if pipe_content is not None:
|
84
|
-
nested_content.append(pipe_content)
|
85
|
-
current_index += 1
|
86
|
-
continue
|
87
|
-
|
88
|
-
# Case 3: Regular line - end of nested content
|
89
|
-
break
|
36
|
+
if not (match := cls.TOGGLE_PATTERN.match(text.strip())):
|
37
|
+
return None
|
90
38
|
|
91
|
-
|
39
|
+
title = match.group(1).strip()
|
40
|
+
rich_text = await TextInlineFormatter.parse_inline_formatting(title)
|
92
41
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
next_index = current_index + 1
|
97
|
-
return (
|
98
|
-
next_index < len(lines)
|
99
|
-
and ToggleElement.PIPE_CONTENT_PATTERN.match(lines[next_index]) is not None
|
42
|
+
# Create toggle block with empty children - they will be populated automatically
|
43
|
+
toggle_content = ToggleBlock(
|
44
|
+
rich_text=rich_text, color=BlockColor.DEFAULT, children=[]
|
100
45
|
)
|
101
46
|
|
102
|
-
|
103
|
-
def extract_pipe_content(cls, line: str) -> Optional[str]:
|
104
|
-
"""
|
105
|
-
Extracts content from a line with pipe prefix.
|
106
|
-
|
107
|
-
Returns:
|
108
|
-
The content without the pipe, or None if not a pipe-prefixed line.
|
109
|
-
"""
|
110
|
-
pipe_match = ToggleElement.PIPE_CONTENT_PATTERN.match(line)
|
111
|
-
if pipe_match:
|
112
|
-
return pipe_match.group(1)
|
113
|
-
return None
|
47
|
+
return CreateToggleBlock(toggle=toggle_content)
|
114
48
|
|
115
49
|
@classmethod
|
116
|
-
def notion_to_markdown(cls, block:
|
50
|
+
async def notion_to_markdown(cls, block: Block) -> Optional[str]:
|
117
51
|
"""
|
118
|
-
Converts a Notion toggle block into markdown using
|
52
|
+
Converts a Notion toggle block into markdown using the ultra-simplified +++ syntax.
|
119
53
|
"""
|
120
|
-
if block.
|
54
|
+
if block.type != BlockType.TOGGLE:
|
55
|
+
return None
|
56
|
+
|
57
|
+
if not block.toggle:
|
121
58
|
return None
|
122
59
|
|
123
|
-
toggle_data = block.
|
60
|
+
toggle_data = block.toggle
|
124
61
|
|
125
62
|
# Extract title from rich_text
|
126
|
-
title =
|
63
|
+
title = cls._extract_text_content(toggle_data.rich_text or [])
|
127
64
|
|
128
|
-
# Create toggle line
|
65
|
+
# Create toggle line with ultra-simplified syntax (no quotes!)
|
129
66
|
toggle_line = f"+++ {title}"
|
130
67
|
|
131
68
|
# Process children if available
|
132
|
-
children = toggle_data.
|
69
|
+
children = toggle_data.children or []
|
133
70
|
if not children:
|
134
|
-
return toggle_line
|
135
|
-
|
136
|
-
# Add a placeholder line for each child using pipe syntax
|
137
|
-
child_lines = ["| [Nested content]" for _ in children]
|
71
|
+
return toggle_line + "\n+++"
|
138
72
|
|
139
|
-
|
73
|
+
# Add a placeholder line for each child
|
74
|
+
child_lines = ["[Nested content]" for _ in children]
|
140
75
|
|
141
|
-
|
142
|
-
def is_multiline(cls) -> bool:
|
143
|
-
"""Toggle blocks can span multiple lines due to nested content."""
|
144
|
-
return True
|
76
|
+
return toggle_line + "\n" + "\n".join(child_lines) + "\n+++"
|
145
77
|
|
146
78
|
@classmethod
|
147
|
-
def _extract_text_content(cls, rich_text:
|
79
|
+
def _extract_text_content(cls, rich_text: list[RichTextObject]) -> str:
|
148
80
|
"""Extracts plain text content from Notion rich_text blocks."""
|
149
81
|
result = ""
|
150
82
|
for text_obj in rich_text:
|
151
|
-
if text_obj
|
152
|
-
result += text_obj.
|
153
|
-
elif
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
@classmethod
|
158
|
-
def find_matches(
|
159
|
-
cls,
|
160
|
-
text: str,
|
161
|
-
process_nested_content: Callable = None,
|
162
|
-
context_aware: bool = True,
|
163
|
-
) -> List[Tuple[int, int, Dict[str, Any]]]:
|
164
|
-
"""
|
165
|
-
Finds all toggle elements in markdown using pipe syntax for nested content.
|
166
|
-
|
167
|
-
Args:
|
168
|
-
text: The markdown input.
|
169
|
-
process_nested_content: Optional function to parse nested content into blocks.
|
170
|
-
context_aware: Whether to skip contextually irrelevant transcript toggles.
|
171
|
-
|
172
|
-
Returns:
|
173
|
-
List of (start_pos, end_pos, block) tuples.
|
174
|
-
"""
|
175
|
-
if not text:
|
176
|
-
return []
|
177
|
-
|
178
|
-
toggle_blocks = []
|
179
|
-
lines = text.split("\n")
|
180
|
-
current_line_index = 0
|
181
|
-
|
182
|
-
while current_line_index < len(lines):
|
183
|
-
current_line = lines[current_line_index]
|
184
|
-
|
185
|
-
# Check if the current line is a toggle
|
186
|
-
if not cls._is_toggle_line(current_line):
|
187
|
-
current_line_index += 1
|
188
|
-
continue
|
189
|
-
|
190
|
-
# Skip transcript toggles if required by context
|
191
|
-
if cls._should_skip_transcript_toggle(
|
192
|
-
current_line, lines, current_line_index, context_aware
|
83
|
+
if hasattr(text_obj, "plain_text"):
|
84
|
+
result += text_obj.plain_text or ""
|
85
|
+
elif (
|
86
|
+
hasattr(text_obj, "type")
|
87
|
+
and text_obj.type == "text"
|
88
|
+
and hasattr(text_obj, "text")
|
193
89
|
):
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
current_line_index += 1
|
203
|
-
continue
|
204
|
-
|
205
|
-
# Extract nested content
|
206
|
-
nested_content, next_line_index = cls.extract_nested_content(
|
207
|
-
lines, current_line_index + 1
|
208
|
-
)
|
209
|
-
end_position = cls._calculate_end_position(
|
210
|
-
start_position, current_line, nested_content
|
211
|
-
)
|
212
|
-
|
213
|
-
# Process nested content if needed
|
214
|
-
cls._process_nested_content_if_needed(
|
215
|
-
nested_content, process_nested_content, toggle_block
|
216
|
-
)
|
217
|
-
|
218
|
-
# Save result
|
219
|
-
toggle_blocks.append((start_position, end_position, toggle_block))
|
220
|
-
current_line_index = next_line_index
|
221
|
-
|
222
|
-
return toggle_blocks
|
223
|
-
|
224
|
-
@classmethod
|
225
|
-
def _is_toggle_line(cls, line: str) -> bool:
|
226
|
-
"""Checks whether the given line is a markdown toggle."""
|
227
|
-
return bool(ToggleElement.TOGGLE_PATTERN.match(line.strip()))
|
228
|
-
|
229
|
-
@classmethod
|
230
|
-
def _should_skip_transcript_toggle(
|
231
|
-
cls, line: str, lines: List[str], current_index: int, context_aware: bool
|
232
|
-
) -> bool:
|
233
|
-
"""Determines if a transcript toggle should be skipped based on the surrounding context."""
|
234
|
-
is_transcript_toggle = cls.TRANSCRIPT_TOGGLE_PATTERN.match(line.strip())
|
235
|
-
|
236
|
-
if not (context_aware and is_transcript_toggle):
|
237
|
-
return False
|
238
|
-
|
239
|
-
# Only keep transcript toggles that follow a list item
|
240
|
-
has_list_item_before = current_index > 0 and lines[
|
241
|
-
current_index - 1
|
242
|
-
].strip().startswith("- ")
|
243
|
-
return not has_list_item_before
|
244
|
-
|
245
|
-
@classmethod
|
246
|
-
def _calculate_start_position(cls, lines: List[str], current_index: int) -> int:
|
247
|
-
"""Calculates the character start position of a line within the full text."""
|
248
|
-
start_pos = 0
|
249
|
-
for index in range(current_index):
|
250
|
-
start_pos += len(lines[index]) + 1 # +1 for line break
|
251
|
-
return start_pos
|
252
|
-
|
253
|
-
@classmethod
|
254
|
-
def _calculate_end_position(
|
255
|
-
cls, start_pos: int, current_line: str, nested_content: List[str]
|
256
|
-
) -> int:
|
257
|
-
"""Calculates the end position of a toggle block including nested lines."""
|
258
|
-
line_length = len(current_line)
|
259
|
-
nested_content_length = sum(
|
260
|
-
len(line) + 1 for line in nested_content
|
261
|
-
) # +1 for each line break
|
262
|
-
return start_pos + line_length + nested_content_length
|
90
|
+
result += text_obj.text.content or ""
|
91
|
+
# Fallback for dict-style access (backward compatibility)
|
92
|
+
elif isinstance(text_obj, dict):
|
93
|
+
if text_obj.get("type") == "text":
|
94
|
+
result += text_obj.get("text", {}).get("content", "")
|
95
|
+
elif "plain_text" in text_obj:
|
96
|
+
result += text_obj.get("plain_text", "")
|
97
|
+
return result
|
263
98
|
|
264
99
|
@classmethod
|
265
|
-
def _process_nested_content_if_needed(
|
266
|
-
cls,
|
267
|
-
nested_content: List[str],
|
268
|
-
process_function: Optional[Callable],
|
269
|
-
toggle_block: Dict[str, Any],
|
270
|
-
) -> None:
|
271
|
-
"""Processes nested content using the provided function if applicable."""
|
272
|
-
if not (nested_content and process_function):
|
273
|
-
return
|
274
|
-
|
275
|
-
nested_text = "\n".join(nested_content)
|
276
|
-
nested_blocks = process_function(nested_text)
|
277
|
-
|
278
|
-
if nested_blocks:
|
279
|
-
toggle_block["toggle"]["children"] = nested_blocks
|
280
|
-
|
281
100
|
@classmethod
|
282
|
-
def
|
283
|
-
"""
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
"
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
"such as details, examples, or technical information."
|
294
|
-
)
|
295
|
-
.with_syntax("+++ Toggle Title\n| Toggle content with pipe prefix")
|
296
|
-
.with_examples(
|
297
|
-
[
|
298
|
-
"+++ Key Findings\n| The research demonstrates **three main conclusions**:\n| 1. First important point\n| 2. Second important point",
|
299
|
-
"+++ FAQ\n| **Q: When should I use toggles?**\n| *A: Use toggles for supplementary information.*",
|
300
|
-
]
|
301
|
-
)
|
302
|
-
.build()
|
101
|
+
def get_system_prompt_information(cls) -> Optional[BlockElementMarkdownInformation]:
|
102
|
+
"""Get system prompt information for toggle blocks."""
|
103
|
+
return BlockElementMarkdownInformation(
|
104
|
+
block_type=cls.__name__,
|
105
|
+
description="Toggle blocks create collapsible sections with expandable content",
|
106
|
+
syntax_examples=[
|
107
|
+
"+++Title\nContent goes here\n+++",
|
108
|
+
"+++Details\nMore information\nAdditional content\n+++",
|
109
|
+
"+++FAQ\nFrequently asked questions\n+++",
|
110
|
+
],
|
111
|
+
usage_guidelines="Use for collapsible content sections. Start with +++Title, add content, end with +++. Great for FAQs, details, or organizing long content.",
|
303
112
|
)
|
@@ -1,35 +1,45 @@
|
|
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 ToggleMarkdownBlockParams(BaseModel):
|
9
9
|
title: str
|
10
|
-
|
10
|
+
children: list[MarkdownNode]
|
11
|
+
model_config = {"arbitrary_types_allowed": True}
|
11
12
|
|
12
13
|
|
13
14
|
class ToggleMarkdownNode(MarkdownNode):
|
14
15
|
"""
|
15
|
-
|
16
|
-
with
|
16
|
+
Clean programmatic interface for creating Notion-style Markdown toggle blocks
|
17
|
+
with the simplified +++ "Title" syntax.
|
18
|
+
|
17
19
|
Example:
|
18
|
-
+++ Details
|
19
|
-
|
20
|
-
|
20
|
+
+++ "Advanced Details"
|
21
|
+
Content here
|
22
|
+
More content
|
23
|
+
+++
|
21
24
|
"""
|
22
25
|
|
23
|
-
def __init__(self, title: str,
|
26
|
+
def __init__(self, title: str, children: list[MarkdownNode]):
|
24
27
|
self.title = title
|
25
|
-
self.
|
28
|
+
self.children = children
|
26
29
|
|
27
30
|
@classmethod
|
28
31
|
def from_params(cls, params: ToggleMarkdownBlockParams) -> ToggleMarkdownNode:
|
29
|
-
return cls(title=params.title,
|
32
|
+
return cls(title=params.title, children=params.children)
|
30
33
|
|
31
34
|
def to_markdown(self) -> str:
|
32
|
-
result = f"+++
|
33
|
-
|
34
|
-
|
35
|
-
|
35
|
+
result = f"+++{self.title}"
|
36
|
+
|
37
|
+
if not self.children:
|
38
|
+
result += "\n+++"
|
39
|
+
return result
|
40
|
+
|
41
|
+
# Convert children to markdown
|
42
|
+
content_parts = [child.to_markdown() for child in self.children]
|
43
|
+
content_text = "\n\n".join(content_parts)
|
44
|
+
|
45
|
+
return result + "\n" + content_text + "\n+++"
|
@@ -0,0 +1,17 @@
|
|
1
|
+
from pydantic import BaseModel, Field
|
2
|
+
from typing_extensions import Literal
|
3
|
+
|
4
|
+
from notionary.blocks.models import Block
|
5
|
+
from notionary.blocks.rich_text.rich_text_models import RichTextObject
|
6
|
+
from notionary.blocks.types import BlockColor
|
7
|
+
|
8
|
+
|
9
|
+
class ToggleBlock(BaseModel):
|
10
|
+
rich_text: list[RichTextObject]
|
11
|
+
color: BlockColor = BlockColor.DEFAULT
|
12
|
+
children: list[Block] = Field(default_factory=list)
|
13
|
+
|
14
|
+
|
15
|
+
class CreateToggleBlock(BaseModel):
|
16
|
+
type: Literal["toggle"] = "toggle"
|
17
|
+
toggle: ToggleBlock
|
@@ -1,9 +1,13 @@
|
|
1
|
-
from .toggleable_heading_element import
|
2
|
-
|
1
|
+
from notionary.blocks.toggleable_heading.toggleable_heading_element import (
|
2
|
+
ToggleableHeadingElement,
|
3
|
+
)
|
4
|
+
from notionary.blocks.toggleable_heading.toggleable_heading_markdown_node import (
|
5
|
+
ToggleableHeadingMarkdownBlockParams,
|
3
6
|
ToggleableHeadingMarkdownNode,
|
4
7
|
)
|
5
8
|
|
6
9
|
__all__ = [
|
7
10
|
"ToggleableHeadingElement",
|
8
11
|
"ToggleableHeadingMarkdownNode",
|
12
|
+
"ToggleableHeadingMarkdownBlockParams",
|
9
13
|
]
|