notionary 0.2.18__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.18.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 -710
- 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/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 -52
- 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.18.dist-info/RECORD +0 -149
- /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.18.dist-info → notionary-0.2.21.dist-info}/LICENSE +0 -0
- {notionary-0.2.18.dist-info → notionary-0.2.21.dist-info}/WHEEL +0 -0
@@ -1,303 +1,96 @@
|
|
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.models import Block, BlockCreateResult, BlockType
|
8
|
+
from notionary.blocks.rich_text.rich_text_models import RichTextObject
|
9
|
+
from notionary.blocks.rich_text.text_inline_formatter import TextInlineFormatter
|
10
|
+
from notionary.blocks.toggle.toggle_models import CreateToggleBlock, ToggleBlock
|
11
|
+
from notionary.blocks.types import BlockColor
|
10
12
|
|
11
13
|
|
12
|
-
class ToggleElement(
|
14
|
+
class ToggleElement(BaseBlockElement):
|
13
15
|
"""
|
14
|
-
|
16
|
+
Simplified ToggleElement class that works with the stack-based converter.
|
17
|
+
Children are automatically handled by the StackBasedMarkdownConverter.
|
15
18
|
"""
|
16
19
|
|
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()))
|
20
|
+
# Updated pattern for ultra-simplified +++ Title syntax (no quotes!)
|
21
|
+
TOGGLE_PATTERN = re.compile(r"^[+]{3}\s+(.+)$", re.IGNORECASE)
|
22
|
+
TRANSCRIPT_TOGGLE_PATTERN = re.compile(r"^[+]{3}\s+Transcript$", re.IGNORECASE)
|
26
23
|
|
27
24
|
@classmethod
|
28
|
-
def match_notion(cls, block:
|
25
|
+
def match_notion(cls, block: Block) -> bool:
|
29
26
|
"""Check if the block is a Notion toggle block."""
|
30
|
-
return block.
|
31
|
-
|
32
|
-
@classmethod
|
33
|
-
def markdown_to_notion(cls, text: str) -> NotionBlockResult:
|
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
|
-
}
|
27
|
+
return block.type == BlockType.TOGGLE
|
50
28
|
|
51
29
|
@classmethod
|
52
|
-
def
|
53
|
-
cls, lines: List[str], start_index: int
|
54
|
-
) -> Tuple[List[str], int]:
|
30
|
+
def markdown_to_notion(cls, text: str) -> BlockCreateResult:
|
55
31
|
"""
|
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)
|
32
|
+
Convert markdown toggle line to Notion ToggleBlock.
|
33
|
+
Children are automatically handled by the StackBasedMarkdownConverter.
|
64
34
|
"""
|
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
|
35
|
+
if not (match := cls.TOGGLE_PATTERN.match(text.strip())):
|
36
|
+
return None
|
90
37
|
|
91
|
-
|
38
|
+
title = match.group(1).strip()
|
39
|
+
rich_text = TextInlineFormatter.parse_inline_formatting(title)
|
92
40
|
|
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
|
41
|
+
# Create toggle block with empty children - they will be populated automatically
|
42
|
+
toggle_content = ToggleBlock(
|
43
|
+
rich_text=rich_text, color=BlockColor.DEFAULT, children=[]
|
100
44
|
)
|
101
45
|
|
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
|
46
|
+
return CreateToggleBlock(toggle=toggle_content)
|
114
47
|
|
115
48
|
@classmethod
|
116
|
-
def notion_to_markdown(cls, block:
|
49
|
+
def notion_to_markdown(cls, block: Block) -> Optional[str]:
|
117
50
|
"""
|
118
|
-
Converts a Notion toggle block into markdown using
|
51
|
+
Converts a Notion toggle block into markdown using the ultra-simplified +++ syntax.
|
119
52
|
"""
|
120
|
-
if block.
|
53
|
+
if block.type != BlockType.TOGGLE:
|
54
|
+
return None
|
55
|
+
|
56
|
+
if not block.toggle:
|
121
57
|
return None
|
122
58
|
|
123
|
-
toggle_data = block.
|
59
|
+
toggle_data = block.toggle
|
124
60
|
|
125
61
|
# Extract title from rich_text
|
126
|
-
title =
|
62
|
+
title = cls._extract_text_content(toggle_data.rich_text or [])
|
127
63
|
|
128
|
-
# Create toggle line
|
64
|
+
# Create toggle line with ultra-simplified syntax (no quotes!)
|
129
65
|
toggle_line = f"+++ {title}"
|
130
66
|
|
131
67
|
# Process children if available
|
132
|
-
children = toggle_data.
|
68
|
+
children = toggle_data.children or []
|
133
69
|
if not children:
|
134
|
-
return toggle_line
|
70
|
+
return toggle_line + "\n+++"
|
135
71
|
|
136
|
-
# Add a placeholder line for each child
|
137
|
-
child_lines = ["
|
72
|
+
# Add a placeholder line for each child
|
73
|
+
child_lines = ["[Nested content]" for _ in children]
|
138
74
|
|
139
|
-
return toggle_line + "\n" + "\n".join(child_lines)
|
75
|
+
return toggle_line + "\n" + "\n".join(child_lines) + "\n+++"
|
140
76
|
|
141
77
|
@classmethod
|
142
|
-
def
|
143
|
-
"""Toggle blocks can span multiple lines due to nested content."""
|
144
|
-
return True
|
145
|
-
|
146
|
-
@classmethod
|
147
|
-
def _extract_text_content(cls, rich_text: List[Dict[str, Any]]) -> str:
|
78
|
+
def _extract_text_content(cls, rich_text: list[RichTextObject]) -> str:
|
148
79
|
"""Extracts plain text content from Notion rich_text blocks."""
|
149
80
|
result = ""
|
150
81
|
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
|
82
|
+
if hasattr(text_obj, "plain_text"):
|
83
|
+
result += text_obj.plain_text or ""
|
84
|
+
elif (
|
85
|
+
hasattr(text_obj, "type")
|
86
|
+
and text_obj.type == "text"
|
87
|
+
and hasattr(text_obj, "text")
|
193
88
|
):
|
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
|
263
|
-
|
264
|
-
@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
|
-
@classmethod
|
282
|
-
def get_llm_prompt_content(cls) -> ElementPromptContent:
|
283
|
-
"""
|
284
|
-
Returns structured LLM prompt metadata for the toggle element with pipe syntax examples.
|
285
|
-
"""
|
286
|
-
return (
|
287
|
-
ElementPromptBuilder()
|
288
|
-
.with_description(
|
289
|
-
"Toggle elements are collapsible sections that help organize and hide detailed information."
|
290
|
-
)
|
291
|
-
.with_usage_guidelines(
|
292
|
-
"Use toggles for supplementary information that's not essential for the first reading, "
|
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()
|
303
|
-
)
|
89
|
+
result += text_obj.text.content or ""
|
90
|
+
# Fallback for dict-style access (backward compatibility)
|
91
|
+
elif isinstance(text_obj, dict):
|
92
|
+
if text_obj.get("type") == "text":
|
93
|
+
result += text_obj.get("text", {}).get("content", "")
|
94
|
+
elif "plain_text" in text_obj:
|
95
|
+
result += text_obj.get("plain_text", "")
|
96
|
+
return result
|
@@ -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
35
|
result = f"+++ {self.title}"
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
]
|