notionary 0.1.18__py3-none-any.whl → 0.1.20__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.
Files changed (41) hide show
  1. notionary/__init__.py +2 -2
  2. notionary/database/notion_database.py +3 -1
  3. notionary/elements/audio_element.py +7 -11
  4. notionary/elements/bookmark_element.py +17 -29
  5. notionary/elements/bulleted_list_element.py +69 -0
  6. notionary/elements/callout_element.py +18 -80
  7. notionary/elements/code_block_element.py +15 -10
  8. notionary/elements/column_element.py +39 -26
  9. notionary/elements/divider_element.py +8 -25
  10. notionary/elements/embed_element.py +10 -18
  11. notionary/elements/heading_element.py +10 -12
  12. notionary/elements/image_element.py +9 -15
  13. notionary/elements/mention_element.py +6 -15
  14. notionary/elements/notion_block_element.py +12 -11
  15. notionary/elements/numbered_list_element.py +68 -0
  16. notionary/elements/paragraph_element.py +11 -7
  17. notionary/elements/prompts/element_prompt_content.py +20 -0
  18. notionary/elements/prompts/synthax_prompt_builder.py +92 -0
  19. notionary/elements/qoute_element.py +20 -89
  20. notionary/elements/registry/block_element_registry.py +90 -0
  21. notionary/elements/{block_element_registry_builder.py → registry/block_element_registry_builder.py} +15 -124
  22. notionary/elements/table_element.py +4 -16
  23. notionary/elements/text_inline_formatter.py +15 -78
  24. notionary/elements/todo_lists.py +14 -18
  25. notionary/elements/toggle_element.py +19 -1
  26. notionary/elements/video_element.py +10 -19
  27. notionary/notion_client.py +2 -2
  28. notionary/page/content/page_content_manager.py +2 -3
  29. notionary/page/markdown_to_notion_converter.py +3 -3
  30. notionary/page/notion_page.py +3 -3
  31. notionary/page/notion_to_markdown_converter.py +3 -3
  32. notionary/page/relations/notion_page_relation_manager.py +24 -24
  33. notionary/page/relations/notion_page_title_resolver.py +1 -2
  34. {notionary-0.1.18.dist-info → notionary-0.1.20.dist-info}/METADATA +3 -8
  35. notionary-0.1.20.dist-info/RECORD +59 -0
  36. {notionary-0.1.18.dist-info → notionary-0.1.20.dist-info}/WHEEL +1 -1
  37. notionary/elements/block_element_registry.py +0 -233
  38. notionary/elements/list_element.py +0 -130
  39. notionary-0.1.18.dist-info/RECORD +0 -56
  40. {notionary-0.1.18.dist-info → notionary-0.1.20.dist-info}/licenses/LICENSE +0 -0
  41. {notionary-0.1.18.dist-info → notionary-0.1.20.dist-info}/top_level.txt +0 -0
notionary/__init__.py CHANGED
@@ -7,8 +7,8 @@ from .database.database_discovery import DatabaseDiscovery
7
7
  from .page.notion_page import NotionPage
8
8
  from .page.notion_page_factory import NotionPageFactory
9
9
 
10
- from .elements.block_element_registry import BlockElementRegistry
11
- from .elements.block_element_registry_builder import (
10
+ from .elements.registry.block_element_registry import BlockElementRegistry
11
+ from .elements.registry.block_element_registry_builder import (
12
12
  BlockElementRegistryBuilder,
13
13
  )
14
14
 
@@ -230,7 +230,9 @@ class NotionDatabase(LoggingMixin):
230
230
  if response and "last_edited_time" in response:
231
231
  return response["last_edited_time"]
232
232
 
233
- self.logger.warning("Could not retrieve last_edited_time for database %s", self.database_id)
233
+ self.logger.warning(
234
+ "Could not retrieve last_edited_time for database %s", self.database_id
235
+ )
234
236
  return None
235
237
 
236
238
  except Exception as e:
@@ -1,6 +1,7 @@
1
1
  import re
2
2
  from typing import Dict, Any, Optional, List
3
3
  from notionary.elements.notion_block_element import NotionBlockElement
4
+ from notionary.elements.prompts.element_prompt_content import ElementPromptContent
4
5
 
5
6
 
6
7
  class AudioElement(NotionBlockElement):
@@ -121,23 +122,18 @@ class AudioElement(NotionBlockElement):
121
122
  return result
122
123
 
123
124
  @classmethod
124
- def get_llm_prompt_content(cls) -> dict:
125
+ def get_llm_prompt_content(cls) -> ElementPromptContent:
125
126
  """Returns information for LLM prompts about this element."""
126
127
  return {
127
128
  "description": "Embeds audio content from external sources like CDNs or direct audio URLs.",
128
- "when_to_use": "Use audio embeds when you want to include audio content directly in your document. Audio embeds are useful for podcasts, music, voice recordings, or any content that benefits from audio explanation.",
129
- "syntax": [
130
- "$[](https://example.com/audio.mp3) - Audio without caption",
131
- "$[Caption text](https://example.com/audio.mp3) - Audio with caption",
132
- ],
133
- "supported_sources": [
134
- "Direct links to audio files (.mp3, .wav, .ogg, etc.)",
135
- "Google Cloud Storage links (storage.googleapis.com)",
136
- "Other audio hosting platforms supported by Notion",
137
- ],
129
+ "syntax": "$[Caption](https://example.com/audio.mp3)",
138
130
  "examples": [
139
131
  "$[Podcast Episode](https://storage.googleapis.com/audio_summaries/ep_ai_summary_127d02ec-ca12-4312-a5ed-cb14b185480c.mp3)",
140
132
  "$[Voice recording](https://example.com/audio/recording.mp3)",
141
133
  "$[](https://storage.googleapis.com/audio_summaries/example.mp3)",
142
134
  ],
135
+ "when_to_use": (
136
+ "Use audio embeds when you want to include audio content directly in your document. "
137
+ "Audio embeds are useful for podcasts, music, voice recordings, or any content that benefits from audio explanation."
138
+ ),
143
139
  }
@@ -1,8 +1,8 @@
1
1
  import re
2
2
  from typing import Dict, Any, Optional, List, Tuple
3
- from typing_extensions import override
4
3
 
5
4
  from notionary.elements.notion_block_element import NotionBlockElement
5
+ from notionary.elements.prompts.element_prompt_content import ElementPromptContent
6
6
 
7
7
 
8
8
  class BookmarkElement(NotionBlockElement):
@@ -29,7 +29,6 @@ class BookmarkElement(NotionBlockElement):
29
29
  + r"\)$" # closing parenthesis
30
30
  )
31
31
 
32
- @override
33
32
  @staticmethod
34
33
  def match_markdown(text: str) -> bool:
35
34
  """Check if text is a markdown bookmark."""
@@ -37,14 +36,11 @@ class BookmarkElement(NotionBlockElement):
37
36
  BookmarkElement.PATTERN.match(text.strip())
38
37
  )
39
38
 
40
- @override
41
39
  @staticmethod
42
40
  def match_notion(block: Dict[str, Any]) -> bool:
43
41
  """Check if block is a Notion bookmark."""
44
- # Handle both standard "bookmark" type and "external-bookmark" type
45
42
  return block.get("type") in ["bookmark", "external-bookmark"]
46
43
 
47
- @override
48
44
  @staticmethod
49
45
  def markdown_to_notion(text: str) -> Optional[Dict[str, Any]]:
50
46
  """Convert markdown bookmark to Notion bookmark block."""
@@ -124,7 +120,6 @@ class BookmarkElement(NotionBlockElement):
124
120
 
125
121
  return {"type": "bookmark", "bookmark": bookmark_data}
126
122
 
127
- @override
128
123
  @staticmethod
129
124
  def notion_to_markdown(block: Dict[str, Any]) -> Optional[str]:
130
125
  """Convert Notion bookmark block to markdown bookmark."""
@@ -133,13 +128,10 @@ class BookmarkElement(NotionBlockElement):
133
128
  if block_type == "bookmark":
134
129
  bookmark_data = block.get("bookmark", {})
135
130
  elif block_type == "external-bookmark":
136
- # Handle external-bookmark type
137
- # Extract URL from the external-bookmark structure
138
131
  url = block.get("url", "")
139
132
  if not url:
140
133
  return None
141
134
 
142
- # For external bookmarks, create a simple bookmark format
143
135
  return f"[bookmark]({url})"
144
136
  else:
145
137
  return None
@@ -160,12 +152,12 @@ class BookmarkElement(NotionBlockElement):
160
152
 
161
153
  if title and description:
162
154
  return f'[bookmark]({url} "{title}" "{description}")'
163
- elif title:
155
+
156
+ if title:
164
157
  return f'[bookmark]({url} "{title}")'
165
- else:
166
- return f"[bookmark]({url})"
167
158
 
168
- @override
159
+ return f"[bookmark]({url})"
160
+
169
161
  @staticmethod
170
162
  def is_multiline() -> bool:
171
163
  """Bookmarks are single-line elements."""
@@ -191,34 +183,30 @@ class BookmarkElement(NotionBlockElement):
191
183
  if not caption:
192
184
  return "", ""
193
185
 
194
- # Extract the full text content from caption
195
186
  full_text = BookmarkElement._extract_text_content(caption)
196
187
 
197
- # Check if the text contains a separator
198
188
  if " - " in full_text:
199
189
  parts = full_text.split(" - ", 1)
200
190
  return parts[0].strip(), parts[1].strip()
201
- else:
202
- # If no separator, assume the whole content is the title
203
- return full_text.strip(), ""
204
191
 
205
- @override
192
+ return full_text.strip(), ""
193
+
206
194
  @classmethod
207
- def get_llm_prompt_content(cls) -> dict:
195
+ def get_llm_prompt_content(cls) -> ElementPromptContent:
208
196
  """
209
- Returns a dictionary with all information needed for LLM prompts about this element.
210
- Includes description, usage guidance, syntax options, and examples.
197
+ Returns structured LLM prompt metadata for the bookmark element.
211
198
  """
212
199
  return {
213
200
  "description": "Creates a bookmark that links to an external website.",
214
- "when_to_use": "Use bookmarks when you want to reference external content while keeping the page clean and organized. Bookmarks display a preview card for the linked content.",
215
- "syntax": [
216
- "[bookmark](https://example.com) - Simple bookmark with URL only",
217
- '[bookmark](https://example.com "Title") - Bookmark with URL and title',
218
- '[bookmark](https://example.com "Title" "Description") - Bookmark with URL, title, and description',
219
- ],
201
+ "when_to_use": (
202
+ "Use bookmarks when you want to reference external content while keeping the page clean and organized. "
203
+ "Bookmarks display a preview card for the linked content."
204
+ ),
205
+ "syntax": '[bookmark](https://example.com "Optional Title" "Optional Description")',
220
206
  "examples": [
221
- '[bookmark](https://notion.so "Notion Homepage" "Your connected workspace")',
207
+ "[bookmark](https://example.com)",
208
+ '[bookmark](https://example.com "Example Title")',
209
+ '[bookmark](https://example.com "Example Title" "Example description of the site")',
222
210
  '[bookmark](https://github.com "GitHub" "Where the world builds software")',
223
211
  ],
224
212
  }
@@ -0,0 +1,69 @@
1
+ import re
2
+ from typing import Dict, Any, Optional
3
+ from typing_extensions import override
4
+ from notionary.elements.notion_block_element import NotionBlockElement
5
+ from notionary.elements.prompts.element_prompt_content import ElementPromptContent
6
+ from notionary.elements.text_inline_formatter import TextInlineFormatter
7
+
8
+
9
+ class BulletedListElement(NotionBlockElement):
10
+ """Class for converting between Markdown bullet lists and Notion bulleted list items."""
11
+
12
+ @override
13
+ @staticmethod
14
+ def markdown_to_notion(text: str) -> Optional[Dict[str, Any]]:
15
+ """Convert markdown bulleted list item to Notion block."""
16
+ pattern = re.compile(
17
+ r"^(\s*)[*\-+]\s+(?!\[[ x]\])(.+)$"
18
+ ) # Avoid matching todo items
19
+ list_match = pattern.match(text)
20
+ if not list_match:
21
+ return None
22
+
23
+ content = list_match.group(2)
24
+
25
+ # Use parse_inline_formatting to handle rich text
26
+ rich_text = TextInlineFormatter.parse_inline_formatting(content)
27
+
28
+ return {
29
+ "type": "bulleted_list_item",
30
+ "bulleted_list_item": {"rich_text": rich_text, "color": "default"},
31
+ }
32
+
33
+ @staticmethod
34
+ def notion_to_markdown(block: Dict[str, Any]) -> Optional[str]:
35
+ """Convert Notion bulleted list item block to markdown."""
36
+ if block.get("type") != "bulleted_list_item":
37
+ return None
38
+
39
+ rich_text = block.get("bulleted_list_item", {}).get("rich_text", [])
40
+ content = TextInlineFormatter.extract_text_with_formatting(rich_text)
41
+
42
+ return f"- {content}"
43
+
44
+ @staticmethod
45
+ def match_markdown(text: str) -> bool:
46
+ """Check if this element can handle the given markdown text."""
47
+ pattern = re.compile(r"^(\s*)[*\-+]\s+(?!\[[ x]\])(.+)$")
48
+ return bool(pattern.match(text))
49
+
50
+ @staticmethod
51
+ def match_notion(block: Dict[str, Any]) -> bool:
52
+ """Check if this element can handle the given Notion block."""
53
+ return block.get("type") == "bulleted_list_item"
54
+
55
+ @classmethod
56
+ def get_llm_prompt_content(cls) -> ElementPromptContent:
57
+ """
58
+ Returns structured LLM prompt metadata for the bulleted list element.
59
+ """
60
+ return {
61
+ "description": "Creates bulleted list items for unordered lists.",
62
+ "when_to_use": "Use for lists where order doesn't matter, such as features, options, or items without hierarchy.",
63
+ "syntax": "- Item text",
64
+ "examples": [
65
+ "- First item\n- Second item\n- Third item",
66
+ "* Apple\n* Banana\n* Cherry",
67
+ "+ Task A\n+ Task B",
68
+ ],
69
+ }
@@ -1,7 +1,8 @@
1
+ import re
1
2
  from typing import Dict, Any, Optional
2
3
  from typing_extensions import override
3
- import re
4
4
 
5
+ from notionary.elements.prompts.element_prompt_content import ElementPromptContent
5
6
  from notionary.elements.text_inline_formatter import TextInlineFormatter
6
7
  from notionary.elements.notion_block_element import NotionBlockElement
7
8
 
@@ -12,53 +13,21 @@ class CalloutElement(NotionBlockElement):
12
13
 
13
14
  Markdown callout syntax:
14
15
  - !> [emoji] Text - Callout with custom emoji
15
- - !> {color} [emoji] Text - Callout with custom color and emoji
16
- - !> Text - Simple callout with default emoji and color
16
+ - !> Text - Simple callout with default emoji
17
17
 
18
18
  Where:
19
- - {color} can be one of Notion's color options (e.g., "blue_background")
20
19
  - [emoji] is any emoji character
21
20
  - Text is the callout content with optional inline formatting
22
21
  """
23
22
 
24
- COLOR_PATTERN = r"(?:(?:{([a-z_]+)})?\s*)?"
25
23
  EMOJI_PATTERN = r"(?:\[([^\]]+)\])?\s*"
26
24
  TEXT_PATTERN = r"(.+)"
27
25
 
28
- # Combine the patterns
29
- PATTERN = re.compile(
30
- r"^!>\s+" # Callout prefix
31
- + COLOR_PATTERN
32
- + EMOJI_PATTERN
33
- + TEXT_PATTERN
34
- + r"$" # End of line
35
- )
26
+ PATTERN = re.compile(r"^!>\s+" + EMOJI_PATTERN + TEXT_PATTERN + r"$")
36
27
 
37
28
  DEFAULT_EMOJI = "💡"
38
29
  DEFAULT_COLOR = "gray_background"
39
30
 
40
- VALID_COLORS = [
41
- "default",
42
- "gray",
43
- "brown",
44
- "orange",
45
- "yellow",
46
- "green",
47
- "blue",
48
- "purple",
49
- "pink",
50
- "red",
51
- "gray_background",
52
- "brown_background",
53
- "orange_background",
54
- "yellow_background",
55
- "green_background",
56
- "blue_background",
57
- "purple_background",
58
- "pink_background",
59
- "red_background",
60
- ]
61
-
62
31
  @override
63
32
  @staticmethod
64
33
  def match_markdown(text: str) -> bool:
@@ -81,22 +50,18 @@ class CalloutElement(NotionBlockElement):
81
50
  if not callout_match:
82
51
  return None
83
52
 
84
- color = callout_match.group(1)
85
- emoji = callout_match.group(2)
86
- content = callout_match.group(3)
53
+ emoji = callout_match.group(1)
54
+ content = callout_match.group(2)
87
55
 
88
56
  if not emoji:
89
57
  emoji = CalloutElement.DEFAULT_EMOJI
90
58
 
91
- if not color or color not in CalloutElement.VALID_COLORS:
92
- color = CalloutElement.DEFAULT_COLOR
93
-
94
59
  return {
95
60
  "type": "callout",
96
61
  "callout": {
97
62
  "rich_text": TextInlineFormatter.parse_inline_formatting(content),
98
63
  "icon": {"type": "emoji", "emoji": emoji},
99
- "color": color,
64
+ "color": CalloutElement.DEFAULT_COLOR,
100
65
  },
101
66
  }
102
67
 
@@ -110,7 +75,6 @@ class CalloutElement(NotionBlockElement):
110
75
  callout_data = block.get("callout", {})
111
76
  rich_text = callout_data.get("rich_text", [])
112
77
  icon = callout_data.get("icon", {})
113
- color = callout_data.get("color", CalloutElement.DEFAULT_COLOR)
114
78
 
115
79
  text = TextInlineFormatter.extract_text_with_formatting(rich_text)
116
80
  if not text:
@@ -120,15 +84,11 @@ class CalloutElement(NotionBlockElement):
120
84
  if icon and icon.get("type") == "emoji":
121
85
  emoji = icon.get("emoji", "")
122
86
 
123
- color_str = ""
124
- if color and color != CalloutElement.DEFAULT_COLOR:
125
- color_str = f"{{{color}}} "
126
-
127
87
  emoji_str = ""
128
- if emoji:
88
+ if emoji and emoji != CalloutElement.DEFAULT_EMOJI:
129
89
  emoji_str = f"[{emoji}] "
130
90
 
131
- return f"!> {color_str}{emoji_str}{text}"
91
+ return f"!> {emoji_str}{text}"
132
92
 
133
93
  @override
134
94
  @staticmethod
@@ -136,44 +96,22 @@ class CalloutElement(NotionBlockElement):
136
96
  return False
137
97
 
138
98
  @classmethod
139
- def get_llm_prompt_content(cls) -> dict:
99
+ def get_llm_prompt_content(cls) -> ElementPromptContent:
140
100
  """
141
101
  Returns a dictionary with all information needed for LLM prompts about this element.
142
102
  Includes description, usage guidance, syntax options, and examples.
143
103
  """
144
104
  return {
145
- "description": "Creates a callout block to highlight important information with an icon and background color.",
146
- "when_to_use": "Use callouts when you want to draw attention to important information, tips, warnings, or notes that stand out from the main content.",
147
- "syntax": [
148
- "!> Text - Simple callout with default emoji (💡) and color (gray background)",
149
- "!> [emoji] Text - Callout with custom emoji",
150
- "!> {color} [emoji] Text - Callout with custom color and emoji",
151
- ],
152
- "color_options": [
153
- "default",
154
- "gray",
155
- "brown",
156
- "orange",
157
- "yellow",
158
- "green",
159
- "blue",
160
- "purple",
161
- "pink",
162
- "red",
163
- "gray_background",
164
- "brown_background",
165
- "orange_background",
166
- "yellow_background",
167
- "green_background",
168
- "blue_background",
169
- "purple_background",
170
- "pink_background",
171
- "red_background",
172
- ],
105
+ "description": "Creates a callout block to highlight important information with an icon.",
106
+ "when_to_use": (
107
+ "Use callouts when you want to draw attention to important information, "
108
+ "tips, warnings, or notes that stand out from the main content."
109
+ ),
110
+ "syntax": "!> [emoji] Text",
173
111
  "examples": [
174
112
  "!> This is a default callout with the light bulb emoji",
175
113
  "!> [🔔] This is a callout with a bell emoji",
176
- "!> {blue_background} [💧] This is a blue callout with a water drop emoji",
177
- "!> {yellow_background} [⚠️] Warning: This is an important note to pay attention to",
114
+ "!> [⚠️] Warning: This is an important note to pay attention to",
115
+ "!> [💡] Tip: Add emoji that matches your content's purpose",
178
116
  ],
179
117
  }
@@ -1,7 +1,7 @@
1
- from typing import Dict, Any, Optional, List, Tuple
2
- from typing_extensions import override
3
1
  import re
2
+ from typing import Dict, Any, Optional, List, Tuple
4
3
  from notionary.elements.notion_block_element import NotionBlockElement
4
+ from notionary.elements.prompts.element_prompt_content import ElementPromptContent
5
5
 
6
6
 
7
7
  class CodeBlockElement(NotionBlockElement):
@@ -20,19 +20,16 @@ class CodeBlockElement(NotionBlockElement):
20
20
 
21
21
  PATTERN = re.compile(r"```(\w*)\n([\s\S]+?)```", re.MULTILINE)
22
22
 
23
- @override
24
23
  @staticmethod
25
24
  def match_markdown(text: str) -> bool:
26
25
  """Check if text contains a markdown code block."""
27
26
  return bool(CodeBlockElement.PATTERN.search(text))
28
27
 
29
- @override
30
28
  @staticmethod
31
29
  def match_notion(block: Dict[str, Any]) -> bool:
32
30
  """Check if block is a Notion code block."""
33
31
  return block.get("type") == "code"
34
32
 
35
- @override
36
33
  @staticmethod
37
34
  def markdown_to_notion(text: str) -> Optional[Dict[str, Any]]:
38
35
  """Convert markdown code block to Notion code block."""
@@ -68,7 +65,6 @@ class CodeBlockElement(NotionBlockElement):
68
65
  },
69
66
  }
70
67
 
71
- @override
72
68
  @staticmethod
73
69
  def notion_to_markdown(block: Dict[str, Any]) -> Optional[str]:
74
70
  """Convert Notion code block to markdown code block."""
@@ -134,20 +130,29 @@ class CodeBlockElement(NotionBlockElement):
134
130
 
135
131
  return matches
136
132
 
137
- @override
138
133
  @staticmethod
139
134
  def is_multiline() -> bool:
140
135
  return True
141
136
 
142
137
  @classmethod
143
- def get_llm_prompt_content(cls) -> dict:
138
+ def get_llm_prompt_content(cls) -> ElementPromptContent:
144
139
  """
145
- Returns a dictionary with all information needed for LLM prompts about this element.
140
+ Returns structured LLM prompt metadata for the code block element.
146
141
  """
147
142
  return {
148
- "description": "Use fenced code blocks to format content as code. Supports language annotations like 'python', 'json', or 'mermaid'. Use when you want to display code, configurations, command-line examples, or diagram syntax. Also useful when breaking down or visualizing a system or architecture for complex problems (e.g. using mermaid).",
143
+ "description": (
144
+ "Use fenced code blocks to format content as code. Supports language annotations like "
145
+ "'python', 'json', or 'mermaid'. Useful for displaying code, configurations, command-line "
146
+ "examples, or diagram syntax. Also suitable for explaining or visualizing systems with diagram languages."
147
+ ),
148
+ "when_to_use": (
149
+ "Use code blocks when you want to present technical content like code snippets, terminal commands, "
150
+ "JSON structures, or system diagrams. Especially helpful when structure and formatting are essential."
151
+ ),
152
+ "syntax": "```language\ncode content\n```",
149
153
  "examples": [
150
154
  "```python\nprint('Hello, world!')\n```",
155
+ '```json\n{"name": "Alice", "age": 30}\n```',
151
156
  "```mermaid\nflowchart TD\n A --> B\n```",
152
157
  ],
153
158
  }
@@ -1,8 +1,8 @@
1
1
  import re
2
2
  from typing import Dict, Any, Optional, List, Tuple, Callable
3
- from typing_extensions import override
4
3
 
5
4
  from notionary.elements.notion_block_element import NotionBlockElement
5
+ from notionary.elements.prompts.element_prompt_content import ElementPromptContent
6
6
 
7
7
 
8
8
  class ColumnElement(NotionBlockElement):
@@ -40,19 +40,16 @@ class ColumnElement(NotionBlockElement):
40
40
  """
41
41
  cls._converter_callback = callback
42
42
 
43
- @override
44
43
  @staticmethod
45
44
  def match_markdown(text: str) -> bool:
46
45
  """Check if text starts a columns block."""
47
46
  return bool(ColumnElement.COLUMNS_START.match(text.strip()))
48
47
 
49
- @override
50
48
  @staticmethod
51
49
  def match_notion(block: Dict[str, Any]) -> bool:
52
50
  """Check if block is a Notion column_list."""
53
51
  return block.get("type") == "column_list"
54
52
 
55
- @override
56
53
  @staticmethod
57
54
  def markdown_to_notion(text: str) -> Optional[Dict[str, Any]]:
58
55
  """
@@ -68,7 +65,6 @@ class ColumnElement(NotionBlockElement):
68
65
  # Child columns will be added by the column processor
69
66
  return {"type": "column_list", "column_list": {"children": []}}
70
67
 
71
- @override
72
68
  @staticmethod
73
69
  def notion_to_markdown(block: Dict[str, Any]) -> Optional[str]:
74
70
  """Convert Notion column_list block to markdown column syntax."""
@@ -95,7 +91,6 @@ class ColumnElement(NotionBlockElement):
95
91
 
96
92
  return "\n".join(result)
97
93
 
98
- @override
99
94
  @staticmethod
100
95
  def is_multiline() -> bool:
101
96
  """Column blocks span multiple lines."""
@@ -264,31 +259,49 @@ class ColumnElement(NotionBlockElement):
264
259
  columns_children.append(column_block)
265
260
 
266
261
  @classmethod
267
- def get_llm_prompt_content(cls) -> dict:
262
+ def get_llm_prompt_content(cls) -> ElementPromptContent:
268
263
  """
269
- Returns a dictionary with all information needed for LLM prompts about this element.
270
- Includes description, usage guidance, syntax options, and examples.
264
+ Returns structured LLM prompt metadata for the column layout element.
271
265
  """
272
266
  return {
273
267
  "description": "Creates a multi-column layout that displays content side by side.",
274
- "when_to_use": "Use columns sparingly, only for direct comparisons or when parallel presentation significantly improves readability. Best for pros/cons lists, feature comparisons, or pairing images with descriptions. Avoid overusing as it can complicate document structure.",
275
- "syntax": [
276
- "::: columns",
277
- "::: column",
278
- "Content for first column",
279
- ":::",
280
- "::: column",
281
- "Content for second column",
268
+ "when_to_use": (
269
+ "Use columns sparingly, only for direct comparisons or when parallel presentation significantly improves readability. "
270
+ "Best for pros/cons lists, feature comparisons, or pairing images with descriptions. "
271
+ "Avoid overusing as it can complicate document structure."
272
+ ),
273
+ "syntax": (
274
+ "::: columns\n"
275
+ "::: column\n"
276
+ "Content for first column\n"
277
+ ":::\n"
278
+ "::: column\n"
279
+ "Content for second column\n"
280
+ ":::\n"
281
+ ":::"
282
+ ),
283
+ "examples": [
284
+ "::: columns\n"
285
+ "::: column\n"
286
+ "## Features\n"
287
+ "- Fast response time\n"
288
+ "- Intuitive interface\n"
289
+ "- Regular updates\n"
290
+ ":::\n"
291
+ "::: column\n"
292
+ "## Benefits\n"
293
+ "- Increased productivity\n"
294
+ "- Better collaboration\n"
295
+ "- Simplified workflows\n"
296
+ ":::\n"
282
297
  ":::",
298
+ "::: columns\n"
299
+ "::: column\n"
300
+ "![Image placeholder](/api/placeholder/400/320)\n"
301
+ ":::\n"
302
+ "::: column\n"
303
+ "This text appears next to the image, creating a media-with-caption style layout that's perfect for documentation or articles.\n"
304
+ ":::\n"
283
305
  ":::",
284
306
  ],
285
- "notes": [
286
- "Any Notion block can be placed within columns",
287
- "Add more columns with additional '::: column' sections",
288
- "Each column must close with ':::' and the entire columns section with another ':::'",
289
- ],
290
- "examples": [
291
- "::: columns\n::: column\n## Features\n- Fast response time\n- Intuitive interface\n- Regular updates\n:::\n::: column\n## Benefits\n- Increased productivity\n- Better collaboration\n- Simplified workflows\n:::\n:::",
292
- "::: columns\n::: column\n![Image placeholder](/api/placeholder/400/320)\n:::\n::: column\nThis text appears next to the image, creating a media-with-caption style layout that's perfect for documentation or articles.\n:::\n:::",
293
- ],
294
307
  }