notionary 0.2.21__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/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 +61 -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/name_to_id_resolver.py +205 -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/database/client.py +23 -0
- notionary/file_upload/models.py +2 -2
- notionary/markdown/markdown_builder.py +34 -27
- notionary/page/client.py +26 -6
- notionary/page/notion_page.py +37 -6
- notionary/page/page_content_deleting_service.py +117 -0
- notionary/page/page_content_writer.py +89 -113
- notionary/page/page_context.py +65 -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/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.22.dist-info/METADATA +237 -0
- {notionary-0.2.21.dist-info → notionary-0.2.22.dist-info}/RECORD +92 -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.22.dist-info}/LICENSE +0 -0
- {notionary-0.2.21.dist-info → notionary-0.2.22.dist-info}/WHEEL +0 -0
@@ -0,0 +1,137 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from dataclasses import dataclass
|
4
|
+
from textwrap import dedent
|
5
|
+
from typing import TYPE_CHECKING, Optional
|
6
|
+
|
7
|
+
|
8
|
+
if TYPE_CHECKING:
|
9
|
+
from notionary.blocks.registry.block_registry import BlockRegistry
|
10
|
+
|
11
|
+
|
12
|
+
@dataclass
|
13
|
+
class BlockElementMarkdownInformation:
|
14
|
+
"""Metadata describing how a Notion block maps to Markdown syntax."""
|
15
|
+
|
16
|
+
block_type: str
|
17
|
+
description: str
|
18
|
+
syntax_examples: list[str]
|
19
|
+
usage_guidelines: str
|
20
|
+
|
21
|
+
|
22
|
+
class SyntaxPromptBuilder:
|
23
|
+
"""
|
24
|
+
Builds a comprehensive markdown syntax reference from a block registry.
|
25
|
+
Iterates over all registered elements and collects their system prompt information.
|
26
|
+
"""
|
27
|
+
|
28
|
+
def __init__(self, block_registry: BlockRegistry):
|
29
|
+
self.block_registry = block_registry
|
30
|
+
|
31
|
+
def build_markdown_reference(self) -> str:
|
32
|
+
"""
|
33
|
+
Build a complete markdown syntax reference string.
|
34
|
+
"""
|
35
|
+
sections = [
|
36
|
+
self._build_header(),
|
37
|
+
*self._build_element_sections(),
|
38
|
+
]
|
39
|
+
|
40
|
+
return "\n\n".join(sections)
|
41
|
+
|
42
|
+
def build_concise_reference(self) -> str:
|
43
|
+
"""
|
44
|
+
Build a more concise reference suitable for system prompts.
|
45
|
+
"""
|
46
|
+
lines = ["# Notionary Markdown Syntax"]
|
47
|
+
|
48
|
+
for element_class in self.block_registry.get_elements():
|
49
|
+
info: Optional[BlockElementMarkdownInformation] = (
|
50
|
+
element_class.get_system_prompt_information()
|
51
|
+
)
|
52
|
+
if info and info.syntax_examples:
|
53
|
+
# Just show the first example for conciseness
|
54
|
+
example = info.syntax_examples[0]
|
55
|
+
lines.append(f"- {info.block_type}: `{example}`")
|
56
|
+
|
57
|
+
return "\n".join(lines)
|
58
|
+
|
59
|
+
def get_blocks_with_information(self) -> list[str]:
|
60
|
+
"""Get list of block names that provide system prompt information."""
|
61
|
+
blocks = []
|
62
|
+
|
63
|
+
for element_class in self.block_registry.get_elements():
|
64
|
+
info: Optional[BlockElementMarkdownInformation] = (
|
65
|
+
element_class.get_system_prompt_information()
|
66
|
+
)
|
67
|
+
if info:
|
68
|
+
blocks.append(info.block_type)
|
69
|
+
|
70
|
+
return blocks
|
71
|
+
|
72
|
+
def _build_header(self) -> str:
|
73
|
+
"""Build the header section of the reference."""
|
74
|
+
return dedent(
|
75
|
+
"""
|
76
|
+
# Notionary Markdown Syntax Reference
|
77
|
+
|
78
|
+
This comprehensive reference documents all supported markdown syntax for converting between Markdown and Notion blocks.
|
79
|
+
|
80
|
+
Each block type includes:
|
81
|
+
- **Description:** What the block does
|
82
|
+
- **When to use:** Guidelines for appropriate usage
|
83
|
+
- **Syntax:** Complete syntax examples with variations
|
84
|
+
"""
|
85
|
+
).strip()
|
86
|
+
|
87
|
+
def _build_element_sections(self) -> list[str]:
|
88
|
+
"""Build sections for all registered elements."""
|
89
|
+
sections = []
|
90
|
+
|
91
|
+
for element_class in self.block_registry.get_elements():
|
92
|
+
info = element_class.get_system_prompt_information()
|
93
|
+
if info:
|
94
|
+
sections.append(self._build_element_section(info))
|
95
|
+
|
96
|
+
return sections
|
97
|
+
|
98
|
+
def _build_element_section(self, info: BlockElementMarkdownInformation) -> str:
|
99
|
+
"""Build a well-structured section for a single block element."""
|
100
|
+
section_parts = [
|
101
|
+
f"## {info.block_type}",
|
102
|
+
"",
|
103
|
+
f"**Description:** {info.description}",
|
104
|
+
"",
|
105
|
+
]
|
106
|
+
|
107
|
+
if info.usage_guidelines:
|
108
|
+
section_parts.extend(["**When to use:**", info.usage_guidelines, ""])
|
109
|
+
|
110
|
+
if info.syntax_examples:
|
111
|
+
section_parts.extend(
|
112
|
+
[
|
113
|
+
"**Syntax:**",
|
114
|
+
"",
|
115
|
+
*self._format_syntax_examples(info.syntax_examples),
|
116
|
+
"",
|
117
|
+
]
|
118
|
+
)
|
119
|
+
|
120
|
+
return "\n".join(section_parts).rstrip()
|
121
|
+
|
122
|
+
def _format_syntax_examples(self, examples: list[str]) -> list[str]:
|
123
|
+
"""Format syntax examples with proper markdown and clear structure."""
|
124
|
+
formatted = []
|
125
|
+
|
126
|
+
for i, example in enumerate(examples, 1):
|
127
|
+
if len(examples) > 1:
|
128
|
+
formatted.append(f"**Example {i}:**")
|
129
|
+
|
130
|
+
if "\n" in example:
|
131
|
+
# Multi-line example - use code block
|
132
|
+
formatted.extend(["```", example, "```", ""])
|
133
|
+
else:
|
134
|
+
# Single line - use inline code with description
|
135
|
+
formatted.extend([f"`{example}`", ""])
|
136
|
+
|
137
|
+
return formatted
|
@@ -4,9 +4,16 @@ 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
|
9
|
+
from notionary.blocks.rich_text.rich_text_models import RichTextObject
|
8
10
|
from notionary.blocks.rich_text.text_inline_formatter import TextInlineFormatter
|
9
|
-
from notionary.blocks.table.table_models import
|
11
|
+
from notionary.blocks.table.table_models import (
|
12
|
+
CreateTableBlock,
|
13
|
+
TableBlock,
|
14
|
+
CreateTableRowBlock,
|
15
|
+
TableRowBlock,
|
16
|
+
)
|
10
17
|
from notionary.blocks.types import BlockType
|
11
18
|
|
12
19
|
|
@@ -17,12 +24,11 @@ class TableElement(BaseBlockElement):
|
|
17
24
|
|
18
25
|
Markdown table syntax:
|
19
26
|
| Header 1 | Header 2 | Header 3 |
|
20
|
-
|
27
|
+
| -------- | -------- | -------- |
|
28
|
+
| Cell 1 | Cell 2 | Cell 3 |
|
21
29
|
"""
|
22
30
|
|
23
|
-
# Pattern für Table-Zeilen (jede Zeile die mit | startet und endet)
|
24
31
|
ROW_PATTERN = re.compile(r"^\s*\|(.+)\|\s*$")
|
25
|
-
# Pattern für Separator-Zeilen
|
26
32
|
SEPARATOR_PATTERN = re.compile(r"^\s*\|([\s\-:|]+)\|\s*$")
|
27
33
|
|
28
34
|
@classmethod
|
@@ -31,7 +37,7 @@ class TableElement(BaseBlockElement):
|
|
31
37
|
return block.type == BlockType.TABLE and block.table
|
32
38
|
|
33
39
|
@classmethod
|
34
|
-
def markdown_to_notion(cls, text: str) -> BlockCreateResult:
|
40
|
+
async def markdown_to_notion(cls, text: str) -> BlockCreateResult:
|
35
41
|
"""Convert opening table row to Notion table block."""
|
36
42
|
if not cls.ROW_PATTERN.match(text.strip()):
|
37
43
|
return None
|
@@ -40,7 +46,6 @@ class TableElement(BaseBlockElement):
|
|
40
46
|
header_cells = cls._parse_table_row(text)
|
41
47
|
col_count = len(header_cells)
|
42
48
|
|
43
|
-
# Create empty TableBlock - content will be added by stack processor
|
44
49
|
table_block = TableBlock(
|
45
50
|
table_width=col_count,
|
46
51
|
has_column_header=True,
|
@@ -51,7 +56,91 @@ class TableElement(BaseBlockElement):
|
|
51
56
|
return CreateTableBlock(table=table_block)
|
52
57
|
|
53
58
|
@classmethod
|
54
|
-
def
|
59
|
+
async def create_from_markdown_table(
|
60
|
+
cls, table_lines: list[str]
|
61
|
+
) -> BlockCreateResult:
|
62
|
+
"""
|
63
|
+
Create a complete table block from markdown table lines.
|
64
|
+
"""
|
65
|
+
if not table_lines:
|
66
|
+
return None
|
67
|
+
|
68
|
+
first_row = None
|
69
|
+
for line in table_lines:
|
70
|
+
line = line.strip()
|
71
|
+
if line and cls.ROW_PATTERN.match(line):
|
72
|
+
first_row = line
|
73
|
+
break
|
74
|
+
|
75
|
+
if not first_row:
|
76
|
+
return None
|
77
|
+
|
78
|
+
# Parse header row to determine column count
|
79
|
+
header_cells = cls._parse_table_row(first_row)
|
80
|
+
col_count = len(header_cells)
|
81
|
+
|
82
|
+
# Process all table lines
|
83
|
+
table_rows, separator_found = await cls._process_table_lines(table_lines)
|
84
|
+
|
85
|
+
# Create complete TableBlock
|
86
|
+
table_block = TableBlock(
|
87
|
+
table_width=col_count,
|
88
|
+
has_column_header=separator_found,
|
89
|
+
has_row_header=False,
|
90
|
+
children=table_rows,
|
91
|
+
)
|
92
|
+
|
93
|
+
return CreateTableBlock(table=table_block)
|
94
|
+
|
95
|
+
@classmethod
|
96
|
+
async def _process_table_lines(
|
97
|
+
cls, table_lines: list[str]
|
98
|
+
) -> tuple[list[CreateTableRowBlock], bool]:
|
99
|
+
"""Process all table lines and return rows and separator status."""
|
100
|
+
table_rows = []
|
101
|
+
separator_found = False
|
102
|
+
|
103
|
+
for line in table_lines:
|
104
|
+
line = line.strip()
|
105
|
+
if not line:
|
106
|
+
continue
|
107
|
+
|
108
|
+
if cls._is_separator_line(line):
|
109
|
+
separator_found = True
|
110
|
+
continue
|
111
|
+
|
112
|
+
if cls.ROW_PATTERN.match(line):
|
113
|
+
table_row = await cls._create_table_row_from_line(line)
|
114
|
+
table_rows.append(table_row)
|
115
|
+
|
116
|
+
return table_rows, separator_found
|
117
|
+
|
118
|
+
@classmethod
|
119
|
+
def _is_separator_line(cls, line: str) -> bool:
|
120
|
+
"""Check if line is a table separator (|---|---|)."""
|
121
|
+
return cls.SEPARATOR_PATTERN.match(line) is not None
|
122
|
+
|
123
|
+
@classmethod
|
124
|
+
async def _create_table_row_from_line(cls, line: str) -> CreateTableRowBlock:
|
125
|
+
"""Create a table row block from a markdown line."""
|
126
|
+
cells = cls._parse_table_row(line)
|
127
|
+
rich_text_cells = []
|
128
|
+
for cell in cells:
|
129
|
+
rich_text_cell = await cls._convert_cell_to_rich_text(cell)
|
130
|
+
rich_text_cells.append(rich_text_cell)
|
131
|
+
table_row = TableRowBlock(cells=rich_text_cells)
|
132
|
+
return CreateTableRowBlock(table_row=table_row)
|
133
|
+
|
134
|
+
@classmethod
|
135
|
+
async def _convert_cell_to_rich_text(cls, cell: str) -> list[RichTextObject]:
|
136
|
+
"""Convert cell text to rich text objects."""
|
137
|
+
rich_text = await TextInlineFormatter.parse_inline_formatting(cell)
|
138
|
+
if not rich_text:
|
139
|
+
rich_text = [RichTextObject.from_plain_text(cell)]
|
140
|
+
return rich_text
|
141
|
+
|
142
|
+
@classmethod
|
143
|
+
async def notion_to_markdown(cls, block: Block) -> Optional[str]:
|
55
144
|
"""Convert Notion table block to markdown table."""
|
56
145
|
if block.type != BlockType.TABLE:
|
57
146
|
return None
|
@@ -80,7 +169,7 @@ class TableElement(BaseBlockElement):
|
|
80
169
|
header_processed = False
|
81
170
|
|
82
171
|
for child in children:
|
83
|
-
if child.type !=
|
172
|
+
if child.type != BlockType.TABLE_ROW:
|
84
173
|
continue
|
85
174
|
|
86
175
|
if not child.table_row:
|
@@ -91,7 +180,7 @@ class TableElement(BaseBlockElement):
|
|
91
180
|
|
92
181
|
row_cells = []
|
93
182
|
for cell in cells:
|
94
|
-
cell_text = TextInlineFormatter.extract_text_with_formatting(cell)
|
183
|
+
cell_text = await TextInlineFormatter.extract_text_with_formatting(cell)
|
95
184
|
row_cells.append(cell_text or "")
|
96
185
|
|
97
186
|
row = "| " + " | ".join(row_cells) + " |"
|
@@ -122,3 +211,15 @@ class TableElement(BaseBlockElement):
|
|
122
211
|
def is_table_row(cls, line: str) -> bool:
|
123
212
|
"""Check if a line is a valid table row."""
|
124
213
|
return bool(cls.ROW_PATTERN.match(line.strip()))
|
214
|
+
|
215
|
+
@classmethod
|
216
|
+
def get_system_prompt_information(cls) -> Optional[BlockElementMarkdownInformation]:
|
217
|
+
"""Get system prompt information for table blocks."""
|
218
|
+
return BlockElementMarkdownInformation(
|
219
|
+
block_type=cls.__name__,
|
220
|
+
description="Table blocks create structured data in rows and columns with headers",
|
221
|
+
syntax_examples=[
|
222
|
+
"| Name | Age | City |\n| -------- | -------- | -------- |\n| Alice | 25 | Berlin |\n| Bob | 30 | Munich |"
|
223
|
+
],
|
224
|
+
usage_guidelines="Use for structured data presentation. First row is header, second row is separator with dashes, following rows are data. Cells are separated by | characters.",
|
225
|
+
)
|
@@ -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
|
8
9
|
from notionary.blocks.table_of_contents.table_of_contents_models import (
|
9
10
|
CreateTableOfContentsBlock,
|
@@ -29,7 +30,7 @@ class TableOfContentsElement(BaseBlockElement):
|
|
29
30
|
return block.type == BlockType.TABLE_OF_CONTENTS and block.table_of_contents
|
30
31
|
|
31
32
|
@classmethod
|
32
|
-
def markdown_to_notion(cls, text: str) -> BlockCreateResult:
|
33
|
+
async def markdown_to_notion(cls, text: str) -> BlockCreateResult:
|
33
34
|
if not (input_match := cls.PATTERN.match(text.strip())):
|
34
35
|
return None
|
35
36
|
|
@@ -39,7 +40,7 @@ class TableOfContentsElement(BaseBlockElement):
|
|
39
40
|
)
|
40
41
|
|
41
42
|
@classmethod
|
42
|
-
def notion_to_markdown(cls, block: Block) -> Optional[str]:
|
43
|
+
async def notion_to_markdown(cls, block: Block) -> Optional[str]:
|
43
44
|
# Fix: Use 'or' instead of 'and'
|
44
45
|
if block.type != BlockType.TABLE_OF_CONTENTS or not block.table_of_contents:
|
45
46
|
return None
|
@@ -49,3 +50,19 @@ class TableOfContentsElement(BaseBlockElement):
|
|
49
50
|
if color == "default":
|
50
51
|
return "[toc]"
|
51
52
|
return f"[toc]({color})"
|
53
|
+
|
54
|
+
@classmethod
|
55
|
+
@classmethod
|
56
|
+
def get_system_prompt_information(cls) -> Optional[BlockElementMarkdownInformation]:
|
57
|
+
"""Get system prompt information for table of contents blocks."""
|
58
|
+
return BlockElementMarkdownInformation(
|
59
|
+
block_type=cls.__name__,
|
60
|
+
description="Table of contents blocks automatically generate navigation for page headings",
|
61
|
+
syntax_examples=[
|
62
|
+
"[toc]",
|
63
|
+
"[toc](blue)",
|
64
|
+
"[toc](blue_background)",
|
65
|
+
"[toc](gray_background)",
|
66
|
+
],
|
67
|
+
usage_guidelines="Use to automatically generate a clickable table of contents from page headings. Optional color parameter changes the appearance. Default color is gray.",
|
68
|
+
)
|
@@ -4,6 +4,7 @@ import re
|
|
4
4
|
from typing import TYPE_CHECKING, 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.rich_text.text_inline_formatter import TextInlineFormatter
|
9
10
|
from notionary.blocks.todo.todo_models import CreateToDoBlock, ToDoBlock
|
@@ -28,7 +29,7 @@ class TodoElement(BaseBlockElement):
|
|
28
29
|
return block.type == BlockType.TO_DO and block.to_do
|
29
30
|
|
30
31
|
@classmethod
|
31
|
-
def markdown_to_notion(cls, text: str) -> BlockCreateResult:
|
32
|
+
async def markdown_to_notion(cls, text: str) -> BlockCreateResult:
|
32
33
|
"""Convert markdown todo or done item to Notion to_do block."""
|
33
34
|
m_done = cls.DONE_PATTERN.match(text)
|
34
35
|
m_todo = None if m_done else cls.PATTERN.match(text)
|
@@ -43,7 +44,7 @@ class TodoElement(BaseBlockElement):
|
|
43
44
|
return None
|
44
45
|
|
45
46
|
# build rich text
|
46
|
-
rich = TextInlineFormatter.parse_inline_formatting(content)
|
47
|
+
rich = await TextInlineFormatter.parse_inline_formatting(content)
|
47
48
|
|
48
49
|
todo_content = ToDoBlock(
|
49
50
|
rich_text=rich,
|
@@ -53,12 +54,28 @@ class TodoElement(BaseBlockElement):
|
|
53
54
|
return CreateToDoBlock(to_do=todo_content)
|
54
55
|
|
55
56
|
@classmethod
|
56
|
-
def notion_to_markdown(cls, block: Block) -> Optional[str]:
|
57
|
+
async def notion_to_markdown(cls, block: Block) -> Optional[str]:
|
57
58
|
"""Convert Notion to_do block to markdown todo item."""
|
58
59
|
if block.type != BlockType.TO_DO or not block.to_do:
|
59
60
|
return None
|
60
61
|
|
61
62
|
td = block.to_do
|
62
|
-
content = TextInlineFormatter.extract_text_with_formatting(td.rich_text)
|
63
|
+
content = await TextInlineFormatter.extract_text_with_formatting(td.rich_text)
|
63
64
|
checkbox = "[x]" if td.checked else "[ ]"
|
64
65
|
return f"- {checkbox} {content}"
|
66
|
+
|
67
|
+
@classmethod
|
68
|
+
def get_system_prompt_information(cls) -> Optional[BlockElementMarkdownInformation]:
|
69
|
+
"""Get system prompt information for todo blocks."""
|
70
|
+
return BlockElementMarkdownInformation(
|
71
|
+
block_type=cls.__name__,
|
72
|
+
description="Todo blocks create interactive checkboxes for task management",
|
73
|
+
syntax_examples=[
|
74
|
+
"- [ ] Unchecked todo item",
|
75
|
+
"- [x] Checked todo item",
|
76
|
+
"* [ ] Todo with asterisk",
|
77
|
+
"+ [ ] Todo with plus sign",
|
78
|
+
"- [x] Completed task",
|
79
|
+
],
|
80
|
+
usage_guidelines="Use for task lists and checkboxes. [ ] for unchecked, [x] for checked items. Supports -, *, or + as bullet markers. Interactive in Notion interface.",
|
81
|
+
)
|
@@ -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.rich_text.rich_text_models import RichTextObject
|
9
10
|
from notionary.blocks.rich_text.text_inline_formatter import TextInlineFormatter
|
@@ -27,7 +28,7 @@ class ToggleElement(BaseBlockElement):
|
|
27
28
|
return block.type == BlockType.TOGGLE
|
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
|
"""
|
32
33
|
Convert markdown toggle line to Notion ToggleBlock.
|
33
34
|
Children are automatically handled by the StackBasedMarkdownConverter.
|
@@ -36,7 +37,7 @@ class ToggleElement(BaseBlockElement):
|
|
36
37
|
return None
|
37
38
|
|
38
39
|
title = match.group(1).strip()
|
39
|
-
rich_text = TextInlineFormatter.parse_inline_formatting(title)
|
40
|
+
rich_text = await TextInlineFormatter.parse_inline_formatting(title)
|
40
41
|
|
41
42
|
# Create toggle block with empty children - they will be populated automatically
|
42
43
|
toggle_content = ToggleBlock(
|
@@ -46,7 +47,7 @@ class ToggleElement(BaseBlockElement):
|
|
46
47
|
return CreateToggleBlock(toggle=toggle_content)
|
47
48
|
|
48
49
|
@classmethod
|
49
|
-
def notion_to_markdown(cls, block: Block) -> Optional[str]:
|
50
|
+
async def notion_to_markdown(cls, block: Block) -> Optional[str]:
|
50
51
|
"""
|
51
52
|
Converts a Notion toggle block into markdown using the ultra-simplified +++ syntax.
|
52
53
|
"""
|
@@ -94,3 +95,18 @@ class ToggleElement(BaseBlockElement):
|
|
94
95
|
elif "plain_text" in text_obj:
|
95
96
|
result += text_obj.get("plain_text", "")
|
96
97
|
return result
|
98
|
+
|
99
|
+
@classmethod
|
100
|
+
@classmethod
|
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.",
|
112
|
+
)
|
@@ -10,6 +10,7 @@ from notionary.blocks.heading.heading_models import (
|
|
10
10
|
CreateHeading3Block,
|
11
11
|
HeadingBlock,
|
12
12
|
)
|
13
|
+
from notionary.blocks.syntax_prompt_builder import BlockElementMarkdownInformation
|
13
14
|
from notionary.blocks.models import Block, BlockCreateResult, BlockType
|
14
15
|
from notionary.blocks.rich_text.text_inline_formatter import TextInlineFormatter
|
15
16
|
|
@@ -42,7 +43,7 @@ class ToggleableHeadingElement(BaseBlockElement):
|
|
42
43
|
return True
|
43
44
|
|
44
45
|
@classmethod
|
45
|
-
def markdown_to_notion(cls, text: str) -> BlockCreateResult:
|
46
|
+
async def markdown_to_notion(cls, text: str) -> BlockCreateResult:
|
46
47
|
"""
|
47
48
|
Convert markdown collapsible heading to a toggleable Notion HeadingBlock.
|
48
49
|
Children are automatically handled by the StackBasedMarkdownConverter.
|
@@ -56,7 +57,7 @@ class ToggleableHeadingElement(BaseBlockElement):
|
|
56
57
|
if level < 1 or level > 3 or not content:
|
57
58
|
return None
|
58
59
|
|
59
|
-
rich_text = TextInlineFormatter.parse_inline_formatting(content)
|
60
|
+
rich_text = await TextInlineFormatter.parse_inline_formatting(content)
|
60
61
|
|
61
62
|
heading_content = HeadingBlock(
|
62
63
|
rich_text=rich_text, color="default", is_toggleable=True, children=[]
|
@@ -70,7 +71,7 @@ class ToggleableHeadingElement(BaseBlockElement):
|
|
70
71
|
return CreateHeading3Block(heading_3=heading_content)
|
71
72
|
|
72
73
|
@staticmethod
|
73
|
-
def notion_to_markdown(block: Block) -> Optional[str]:
|
74
|
+
async def notion_to_markdown(block: Block) -> Optional[str]:
|
74
75
|
"""Convert Notion toggleable heading block to markdown collapsible heading."""
|
75
76
|
# Only handle heading blocks via BlockType enum
|
76
77
|
if block.type not in (
|
@@ -92,9 +93,23 @@ class ToggleableHeadingElement(BaseBlockElement):
|
|
92
93
|
if not isinstance(heading_content, HeadingBlock):
|
93
94
|
return None
|
94
95
|
|
95
|
-
text = TextInlineFormatter.extract_text_with_formatting(
|
96
|
+
text = await TextInlineFormatter.extract_text_with_formatting(
|
96
97
|
heading_content.rich_text
|
97
98
|
)
|
98
99
|
prefix = "#" * level
|
99
100
|
|
100
101
|
return f'+++{prefix} {text or ""}'
|
102
|
+
|
103
|
+
@classmethod
|
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.",
|
115
|
+
)
|
notionary/blocks/types.py
CHANGED
@@ -2,6 +2,12 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
from enum import Enum
|
4
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
|
+
|
5
11
|
|
6
12
|
class BlockColor(str, Enum):
|
7
13
|
BLUE = "blue"
|
@@ -59,3 +65,66 @@ class BlockType(str, Enum):
|
|
59
65
|
UNSUPPORTED = "unsupported"
|
60
66
|
VIDEO = "video"
|
61
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]
|