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.
Files changed (204) hide show
  1. notionary/__init__.py +8 -4
  2. notionary/base_notion_client.py +3 -1
  3. notionary/blocks/__init__.py +2 -91
  4. notionary/blocks/_bootstrap.py +263 -0
  5. notionary/blocks/audio/__init__.py +8 -2
  6. notionary/blocks/audio/audio_element.py +42 -104
  7. notionary/blocks/audio/audio_markdown_node.py +3 -1
  8. notionary/blocks/audio/audio_models.py +6 -55
  9. notionary/blocks/base_block_element.py +30 -0
  10. notionary/blocks/bookmark/__init__.py +9 -2
  11. notionary/blocks/bookmark/bookmark_element.py +46 -139
  12. notionary/blocks/bookmark/bookmark_markdown_node.py +3 -1
  13. notionary/blocks/bookmark/bookmark_models.py +15 -0
  14. notionary/blocks/breadcrumbs/__init__.py +17 -0
  15. notionary/blocks/breadcrumbs/breadcrumb_element.py +39 -0
  16. notionary/blocks/breadcrumbs/breadcrumb_markdown_node.py +32 -0
  17. notionary/blocks/breadcrumbs/breadcrumb_models.py +12 -0
  18. notionary/blocks/bulleted_list/__init__.py +12 -2
  19. notionary/blocks/bulleted_list/bulleted_list_element.py +40 -55
  20. notionary/blocks/bulleted_list/bulleted_list_markdown_node.py +2 -1
  21. notionary/blocks/bulleted_list/bulleted_list_models.py +18 -0
  22. notionary/blocks/callout/__init__.py +9 -2
  23. notionary/blocks/callout/callout_element.py +40 -89
  24. notionary/blocks/callout/callout_markdown_node.py +3 -1
  25. notionary/blocks/callout/callout_models.py +33 -0
  26. notionary/blocks/child_database/__init__.py +7 -0
  27. notionary/blocks/child_database/child_database_models.py +19 -0
  28. notionary/blocks/child_page/__init__.py +9 -0
  29. notionary/blocks/child_page/child_page_models.py +12 -0
  30. notionary/blocks/{shared/block_client.py → client.py} +55 -54
  31. notionary/blocks/code/__init__.py +6 -2
  32. notionary/blocks/code/code_element.py +53 -187
  33. notionary/blocks/code/code_markdown_node.py +13 -13
  34. notionary/blocks/code/code_models.py +94 -0
  35. notionary/blocks/column/__init__.py +25 -1
  36. notionary/blocks/column/column_element.py +40 -314
  37. notionary/blocks/column/column_list_element.py +37 -0
  38. notionary/blocks/column/column_list_markdown_node.py +50 -0
  39. notionary/blocks/column/column_markdown_node.py +59 -0
  40. notionary/blocks/column/column_models.py +26 -0
  41. notionary/blocks/divider/__init__.py +9 -2
  42. notionary/blocks/divider/divider_element.py +26 -49
  43. notionary/blocks/divider/divider_markdown_node.py +2 -1
  44. notionary/blocks/divider/divider_models.py +12 -0
  45. notionary/blocks/embed/__init__.py +9 -2
  46. notionary/blocks/embed/embed_element.py +47 -114
  47. notionary/blocks/embed/embed_markdown_node.py +3 -1
  48. notionary/blocks/embed/embed_models.py +14 -0
  49. notionary/blocks/equation/__init__.py +14 -0
  50. notionary/blocks/equation/equation_element.py +80 -0
  51. notionary/blocks/equation/equation_element_markdown_node.py +36 -0
  52. notionary/blocks/equation/equation_models.py +11 -0
  53. notionary/blocks/file/__init__.py +25 -0
  54. notionary/blocks/file/file_element.py +93 -0
  55. notionary/blocks/file/file_element_markdown_node.py +35 -0
  56. notionary/blocks/file/file_element_models.py +39 -0
  57. notionary/blocks/heading/__init__.py +16 -2
  58. notionary/blocks/heading/heading_element.py +67 -72
  59. notionary/blocks/heading/heading_markdown_node.py +2 -1
  60. notionary/blocks/heading/heading_models.py +29 -0
  61. notionary/blocks/image_block/__init__.py +13 -0
  62. notionary/blocks/image_block/image_element.py +84 -0
  63. notionary/blocks/{image → image_block}/image_markdown_node.py +3 -1
  64. notionary/blocks/image_block/image_models.py +10 -0
  65. notionary/blocks/models.py +172 -0
  66. notionary/blocks/numbered_list/__init__.py +12 -2
  67. notionary/blocks/numbered_list/numbered_list_element.py +33 -58
  68. notionary/blocks/numbered_list/numbered_list_markdown_node.py +3 -1
  69. notionary/blocks/numbered_list/numbered_list_models.py +17 -0
  70. notionary/blocks/paragraph/__init__.py +12 -2
  71. notionary/blocks/paragraph/paragraph_element.py +27 -69
  72. notionary/blocks/paragraph/paragraph_markdown_node.py +2 -1
  73. notionary/blocks/paragraph/paragraph_models.py +16 -0
  74. notionary/blocks/pdf/__init__.py +13 -0
  75. notionary/blocks/pdf/pdf_element.py +91 -0
  76. notionary/blocks/pdf/pdf_markdown_node.py +35 -0
  77. notionary/blocks/pdf/pdf_models.py +11 -0
  78. notionary/blocks/quote/__init__.py +11 -2
  79. notionary/blocks/quote/quote_element.py +31 -65
  80. notionary/blocks/quote/quote_markdown_node.py +4 -1
  81. notionary/blocks/quote/quote_models.py +18 -0
  82. notionary/blocks/registry/__init__.py +4 -0
  83. notionary/blocks/registry/block_registry.py +75 -91
  84. notionary/blocks/registry/block_registry_builder.py +107 -59
  85. notionary/blocks/rich_text/__init__.py +33 -0
  86. notionary/blocks/rich_text/rich_text_models.py +188 -0
  87. notionary/blocks/rich_text/text_inline_formatter.py +125 -0
  88. notionary/blocks/table/__init__.py +16 -2
  89. notionary/blocks/table/table_element.py +48 -241
  90. notionary/blocks/table/table_markdown_node.py +2 -1
  91. notionary/blocks/table/table_models.py +28 -0
  92. notionary/blocks/table_of_contents/__init__.py +19 -0
  93. notionary/blocks/table_of_contents/table_of_contents_element.py +51 -0
  94. notionary/blocks/table_of_contents/table_of_contents_markdown_node.py +35 -0
  95. notionary/blocks/table_of_contents/table_of_contents_models.py +18 -0
  96. notionary/blocks/todo/__init__.py +9 -2
  97. notionary/blocks/todo/todo_element.py +38 -95
  98. notionary/blocks/todo/todo_markdown_node.py +2 -1
  99. notionary/blocks/todo/todo_models.py +19 -0
  100. notionary/blocks/toggle/__init__.py +13 -3
  101. notionary/blocks/toggle/toggle_element.py +57 -264
  102. notionary/blocks/toggle/toggle_markdown_node.py +24 -14
  103. notionary/blocks/toggle/toggle_models.py +17 -0
  104. notionary/blocks/toggleable_heading/__init__.py +6 -2
  105. notionary/blocks/toggleable_heading/toggleable_heading_element.py +74 -244
  106. notionary/blocks/toggleable_heading/toggleable_heading_markdown_node.py +26 -18
  107. notionary/blocks/types.py +61 -0
  108. notionary/blocks/video/__init__.py +8 -2
  109. notionary/blocks/video/video_element.py +67 -143
  110. notionary/blocks/video/video_element_models.py +10 -0
  111. notionary/blocks/video/video_markdown_node.py +3 -1
  112. notionary/database/client.py +3 -8
  113. notionary/database/database.py +13 -14
  114. notionary/database/database_filter_builder.py +2 -2
  115. notionary/database/database_provider.py +5 -4
  116. notionary/database/models.py +337 -0
  117. notionary/database/notion_database.py +6 -7
  118. notionary/file_upload/client.py +5 -7
  119. notionary/file_upload/models.py +2 -1
  120. notionary/file_upload/notion_file_upload.py +2 -3
  121. notionary/markdown/markdown_builder.py +722 -0
  122. notionary/markdown/markdown_document_model.py +228 -0
  123. notionary/{blocks → markdown}/markdown_node.py +1 -0
  124. notionary/models/notion_database_response.py +0 -338
  125. notionary/page/client.py +9 -10
  126. notionary/page/models.py +327 -0
  127. notionary/page/notion_page.py +99 -52
  128. notionary/page/notion_text_length_utils.py +119 -0
  129. notionary/page/{content/page_content_writer.py → page_content_writer.py} +88 -38
  130. notionary/page/reader/handler/__init__.py +17 -0
  131. notionary/page/reader/handler/base_block_renderer.py +44 -0
  132. notionary/page/reader/handler/block_processing_context.py +35 -0
  133. notionary/page/reader/handler/block_rendering_context.py +43 -0
  134. notionary/page/reader/handler/column_list_renderer.py +51 -0
  135. notionary/page/reader/handler/column_renderer.py +60 -0
  136. notionary/page/reader/handler/line_renderer.py +60 -0
  137. notionary/page/reader/handler/toggle_renderer.py +69 -0
  138. notionary/page/reader/handler/toggleable_heading_renderer.py +89 -0
  139. notionary/page/reader/page_content_retriever.py +69 -0
  140. notionary/page/search_filter_builder.py +2 -1
  141. notionary/page/writer/handler/__init__.py +22 -0
  142. notionary/page/writer/handler/code_handler.py +100 -0
  143. notionary/page/writer/handler/column_handler.py +141 -0
  144. notionary/page/writer/handler/column_list_handler.py +139 -0
  145. notionary/page/writer/handler/line_handler.py +35 -0
  146. notionary/page/writer/handler/line_processing_context.py +54 -0
  147. notionary/page/writer/handler/regular_line_handler.py +92 -0
  148. notionary/page/writer/handler/table_handler.py +130 -0
  149. notionary/page/writer/handler/toggle_handler.py +153 -0
  150. notionary/page/writer/handler/toggleable_heading_handler.py +167 -0
  151. notionary/page/writer/markdown_to_notion_converter.py +76 -0
  152. notionary/telemetry/__init__.py +2 -2
  153. notionary/telemetry/service.py +4 -3
  154. notionary/user/__init__.py +2 -2
  155. notionary/user/base_notion_user.py +2 -1
  156. notionary/user/client.py +2 -3
  157. notionary/user/models.py +1 -0
  158. notionary/user/notion_bot_user.py +4 -5
  159. notionary/user/notion_user.py +3 -4
  160. notionary/user/notion_user_manager.py +3 -2
  161. notionary/user/notion_user_provider.py +1 -1
  162. notionary/util/__init__.py +3 -2
  163. notionary/util/fuzzy.py +2 -1
  164. notionary/util/logging_mixin.py +2 -2
  165. notionary/util/singleton_metaclass.py +1 -1
  166. notionary/workspace.py +3 -2
  167. {notionary-0.2.18.dist-info → notionary-0.2.21.dist-info}/METADATA +12 -8
  168. notionary-0.2.21.dist-info/RECORD +185 -0
  169. notionary/blocks/document/__init__.py +0 -7
  170. notionary/blocks/document/document_element.py +0 -102
  171. notionary/blocks/document/document_markdown_node.py +0 -31
  172. notionary/blocks/image/__init__.py +0 -7
  173. notionary/blocks/image/image_element.py +0 -151
  174. notionary/blocks/markdown_builder.py +0 -356
  175. notionary/blocks/mention/__init__.py +0 -7
  176. notionary/blocks/mention/mention_element.py +0 -229
  177. notionary/blocks/mention/mention_markdown_node.py +0 -38
  178. notionary/blocks/prompts/element_prompt_builder.py +0 -83
  179. notionary/blocks/prompts/element_prompt_content.py +0 -41
  180. notionary/blocks/shared/__init__.py +0 -0
  181. notionary/blocks/shared/models.py +0 -710
  182. notionary/blocks/shared/notion_block_element.py +0 -37
  183. notionary/blocks/shared/text_inline_formatter.py +0 -262
  184. notionary/blocks/shared/text_inline_formatter_new.py +0 -139
  185. notionary/blocks/toggleable_heading/toggleable_heading_models.py +0 -0
  186. notionary/database/models/page_result.py +0 -10
  187. notionary/models/notion_block_response.py +0 -264
  188. notionary/models/notion_page_response.py +0 -78
  189. notionary/models/search_response.py +0 -0
  190. notionary/page/__init__.py +0 -0
  191. notionary/page/content/notion_text_length_utils.py +0 -87
  192. notionary/page/content/page_content_retriever.py +0 -52
  193. notionary/page/formatting/line_processor.py +0 -153
  194. notionary/page/formatting/markdown_to_notion_converter.py +0 -153
  195. notionary/page/markdown_syntax_prompt_generator.py +0 -114
  196. notionary/page/notion_to_markdown_converter.py +0 -179
  197. notionary/page/properites/property_value_extractor.py +0 -0
  198. notionary-0.2.18.dist-info/RECORD +0 -149
  199. /notionary/{blocks/document/document_models.py → markdown/___init__.py} +0 -0
  200. /notionary/{blocks/image/image_models.py → markdown/makdown_document_model.py} +0 -0
  201. /notionary/page/{content/markdown_whitespace_processor.py → markdown_whitespace_processor.py} +0 -0
  202. /notionary/{blocks/mention/mention_models.py → page/reader/handler/context.py} +0 -0
  203. {notionary-0.2.18.dist-info → notionary-0.2.21.dist-info}/LICENSE +0 -0
  204. {notionary-0.2.18.dist-info → notionary-0.2.21.dist-info}/WHEEL +0 -0
@@ -0,0 +1,30 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC
4
+ from typing import Optional
5
+
6
+ from notionary.blocks.models import Block, BlockCreateResult
7
+
8
+
9
+ class BaseBlockElement(ABC):
10
+ """Base class for elements that can be converted between Markdown and Notion."""
11
+
12
+ @classmethod
13
+ def markdown_to_notion(cls, text: str) -> BlockCreateResult:
14
+ """
15
+ Convert markdown to Notion block content.
16
+
17
+ Returns:
18
+ - BlockContent: Single block content (e.g., ToDoBlock, ParagraphBlock)
19
+ - list[BlockContent]: Multiple block contents
20
+ - None: Cannot convert this markdown
21
+ """
22
+
23
+ @classmethod
24
+ def notion_to_markdown(cls, block: Block) -> Optional[str]:
25
+ """Convert Notion block to markdown."""
26
+
27
+ @classmethod
28
+ def match_notion(cls, block: Block) -> bool:
29
+ """Check if this element can handle the given Notion block."""
30
+ return bool(cls.notion_to_markdown(block)) # Now calls the class's version
@@ -1,7 +1,14 @@
1
- from .bookmark_element import BookmarkElement
2
- from .bookmark_markdown_node import BookmarkMarkdownNode
1
+ from notionary.blocks.bookmark.bookmark_element import BookmarkElement
2
+ from notionary.blocks.bookmark.bookmark_markdown_node import (
3
+ BookmarkMarkdownBlockParams,
4
+ BookmarkMarkdownNode,
5
+ )
6
+ from notionary.blocks.bookmark.bookmark_models import BookmarkBlock, CreateBookmarkBlock
3
7
 
4
8
  __all__ = [
5
9
  "BookmarkElement",
10
+ "BookmarkBlock",
11
+ "CreateBookmarkBlock",
6
12
  "BookmarkMarkdownNode",
13
+ "BookmarkMarkdownBlockParams",
7
14
  ]
@@ -1,173 +1,80 @@
1
+ from __future__ import annotations
2
+
1
3
  import re
2
- from typing import Dict, Any, Optional, List, Tuple
4
+ from typing import Optional
3
5
 
4
- from notionary.blocks import NotionBlockElement
5
- from notionary.blocks import (
6
- ElementPromptContent,
7
- ElementPromptBuilder,
8
- NotionBlockResult,
9
- )
10
- from notionary.blocks.shared.models import RichTextObject
6
+ from notionary.blocks.base_block_element import BaseBlockElement
7
+ from notionary.blocks.bookmark.bookmark_models import BookmarkBlock, CreateBookmarkBlock
8
+ from notionary.blocks.models import Block, BlockCreateResult, BlockType
9
+ from notionary.blocks.rich_text.text_inline_formatter import TextInlineFormatter
11
10
 
12
11
 
13
- class BookmarkElement(NotionBlockElement):
12
+ # BookmarkElement implementation using BlockType enum and TextInlineFormatter
13
+ class BookmarkElement(BaseBlockElement):
14
14
  """
15
15
  Handles conversion between Markdown bookmarks and Notion bookmark blocks.
16
16
 
17
17
  Markdown bookmark syntax:
18
- - [bookmark](https://example.com) - Simple bookmark with URL only
19
- - [bookmark](https://example.com "Title") - Bookmark with URL and title
20
- - [bookmark](https://example.com "Title" "Description") - Bookmark with URL, title, and description
21
-
22
- Where:
23
- - URL is the required bookmark URL
24
- - Title is an optional title (enclosed in quotes)
25
- - Description is an optional description (enclosed in quotes)
18
+ - [bookmark](https://example.com) - URL only
19
+ - [bookmark](https://example.com "Title") - URL + title
20
+ - [bookmark](https://example.com "Title" "Description") - URL + title + description
26
21
  """
27
22
 
28
- # Regex pattern for bookmark syntax with optional title and description
29
23
  PATTERN = re.compile(
30
- r"^\[bookmark\]\(" # [bookmark]( prefix
31
- + r'(https?://[^\s"]+)' # URL (required)
32
- + r'(?:\s+"([^"]+)")?' # Optional title in quotes
33
- + r'(?:\s+"([^"]+)")?' # Optional description in quotes
34
- + r"\)$" # closing parenthesis
24
+ r"^\[bookmark\]\(" # prefix
25
+ r"(https?://[^\s\"]+)" # URL
26
+ r"(?:\s+\"([^\"]+)\")?" # optional Title
27
+ r"(?:\s+\"([^\"]+)\")?" # optional Description
28
+ r"\)$"
35
29
  )
36
30
 
37
31
  @classmethod
38
- def match_markdown(cls, text: str) -> bool:
39
- """Check if text is a markdown bookmark."""
40
- return text.strip().startswith("[bookmark]") and bool(
41
- cls.PATTERN.match(text.strip())
42
- )
43
-
44
- @classmethod
45
- def match_notion(cls, block: Dict[str, Any]) -> bool:
46
- """Check if block is a Notion bookmark."""
47
- return block.get("type") in ["bookmark", "external-bookmark"]
32
+ def match_notion(cls, block: Block) -> bool:
33
+ return block.type == BlockType.BOOKMARK and block.bookmark
48
34
 
49
35
  @classmethod
50
- def markdown_to_notion(cls, text: str) -> NotionBlockResult:
51
- """Convert markdown bookmark to Notion bookmark block."""
52
- bookmark_match = BookmarkElement.PATTERN.match(text.strip())
53
- if not bookmark_match:
36
+ def markdown_to_notion(cls, text: str) -> BlockCreateResult:
37
+ """
38
+ Convert a markdown bookmark into a Notion BookmarkBlock.
39
+ """
40
+ if not (m := cls.PATTERN.match(text.strip())):
54
41
  return None
55
42
 
56
- url = bookmark_match.group(1)
57
- title = bookmark_match.group(2)
58
- description = bookmark_match.group(3)
59
-
60
- bookmark_data = {"url": url}
43
+ url, title, description = m.group(1), m.group(2), m.group(3)
61
44
 
62
- # Build caption string
63
- caption_parts = []
45
+ # Build caption texts
46
+ parts: list[str] = []
64
47
  if title:
65
- caption_parts.append(title)
48
+ parts.append(title)
66
49
  if description:
67
- caption_parts.append(description)
50
+ parts.append(description)
68
51
 
69
- if caption_parts:
70
- caption_text = " - ".join(caption_parts)
71
- caption_rich_text = RichTextObject.from_plain_text(caption_text)
72
- bookmark_data["caption"] = [caption_rich_text.model_dump()]
73
- else:
74
- bookmark_data["caption"] = []
52
+ caption = []
53
+ if parts:
54
+ joined = " – ".join(parts)
55
+ caption = TextInlineFormatter.parse_inline_formatting(joined)
75
56
 
76
- return [{"type": "bookmark", "bookmark": bookmark_data}]
57
+ bookmark_data = BookmarkBlock(url=url, caption=caption)
58
+ return CreateBookmarkBlock(bookmark=bookmark_data)
77
59
 
78
60
  @classmethod
79
- def notion_to_markdown(cls, block: Dict[str, Any]) -> Optional[str]:
80
- """Convert Notion bookmark block to markdown bookmark."""
81
- block_type = block.get("type", "")
82
-
83
- if block_type == "bookmark":
84
- bookmark_data = block.get("bookmark", {})
85
- elif block_type == "external-bookmark":
86
- url = block.get("url", "")
87
- if not url:
88
- return None
89
-
90
- return f"[bookmark]({url})"
91
- else:
61
+ def notion_to_markdown(cls, block: Block) -> Optional[str]:
62
+ if block.type != BlockType.BOOKMARK or block.bookmark is None:
92
63
  return None
93
64
 
94
- url = bookmark_data.get("url", "")
95
-
65
+ bm = block.bookmark
66
+ url = bm.url
96
67
  if not url:
97
68
  return None
98
69
 
99
- caption = bookmark_data.get("caption", [])
100
-
101
- if not caption:
102
- # Simple bookmark with URL only
70
+ captions = bm.caption or []
71
+ if not captions:
103
72
  return f"[bookmark]({url})"
104
73
 
105
- # Extract title and description from caption
106
- title, description = BookmarkElement._parse_caption(caption)
107
-
108
- if title and description:
109
- return f'[bookmark]({url} "{title}" "{description}")'
110
-
111
- if title:
112
- return f'[bookmark]({url} "{title}")'
113
-
114
- return f"[bookmark]({url})"
115
-
116
- @classmethod
117
- def is_multiline(cls) -> bool:
118
- """Bookmarks are single-line elements."""
119
- return False
120
-
121
- @classmethod
122
- def _extract_text_content(cls, rich_text: List[Dict[str, Any]]) -> str:
123
- """Extract plain text content from Notion rich_text elements."""
124
- result = ""
125
- for text_obj in rich_text:
126
- if text_obj.get("type") == "text":
127
- result += text_obj.get("text", {}).get("content", "")
128
- elif "plain_text" in text_obj:
129
- result += text_obj.get("plain_text", "")
130
- return result
131
-
132
- @classmethod
133
- def _parse_caption(cls, caption: List[Dict[str, Any]]) -> Tuple[str, str]:
134
- """
135
- Parse Notion caption into title and description components.
136
- Returns a tuple of (title, description).
137
- """
138
- if not caption:
139
- return "", ""
140
-
141
- full_text = BookmarkElement._extract_text_content(caption)
142
-
143
- if " - " in full_text:
144
- parts = full_text.split(" - ", 1)
145
- return parts[0].strip(), parts[1].strip()
74
+ text = TextInlineFormatter.extract_text_with_formatting(captions)
146
75
 
147
- return full_text.strip(), ""
76
+ if " - " in text:
77
+ title, desc = map(str.strip, text.split(" - ", 1))
78
+ return f'[bookmark]({url} "{title}" "{desc}")'
148
79
 
149
- @classmethod
150
- def get_llm_prompt_content(cls) -> ElementPromptContent:
151
- """
152
- Returns structured LLM prompt metadata for the bookmark element.
153
- """
154
- return (
155
- ElementPromptBuilder()
156
- .with_description("Creates a bookmark that links to an external website.")
157
- .with_usage_guidelines(
158
- "Use bookmarks when you want to reference external content while keeping the page clean and organized. "
159
- "Bookmarks display a preview card for the linked content."
160
- )
161
- .with_syntax(
162
- '[bookmark](https://example.com "Optional Title" "Optional Description")'
163
- )
164
- .with_examples(
165
- [
166
- "[bookmark](https://example.com)",
167
- '[bookmark](https://example.com "Example Title")',
168
- '[bookmark](https://example.com "Example Title" "Example description of the site")',
169
- '[bookmark](https://github.com "GitHub" "Where the world builds software")',
170
- ]
171
- )
172
- .build()
173
- )
80
+ return f'[bookmark]({url} "{text}")'
@@ -1,8 +1,10 @@
1
1
  from __future__ import annotations
2
+
2
3
  from typing import Optional
4
+
3
5
  from pydantic import BaseModel
4
6
 
5
- from notionary.blocks.markdown_node import MarkdownNode
7
+ from notionary.markdown.markdown_node import MarkdownNode
6
8
 
7
9
 
8
10
  class BookmarkMarkdownBlockParams(BaseModel):
@@ -0,0 +1,15 @@
1
+ from typing import Literal
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+ from notionary.blocks.rich_text.rich_text_models import RichTextObject
6
+
7
+
8
+ class BookmarkBlock(BaseModel):
9
+ caption: list[RichTextObject] = Field(default_factory=list)
10
+ url: str
11
+
12
+
13
+ class CreateBookmarkBlock(BaseModel):
14
+ type: Literal["bookmark"] = "bookmark"
15
+ bookmark: BookmarkBlock
@@ -0,0 +1,17 @@
1
+ from notionary.blocks.breadcrumbs.breadcrumb_element import BreadcrumbElement
2
+ from notionary.blocks.breadcrumbs.breadcrumb_markdown_node import (
3
+ BreadcrumbMarkdownBlockParams,
4
+ BreadcrumbMarkdownNode,
5
+ )
6
+ from notionary.blocks.breadcrumbs.breadcrumb_models import (
7
+ BreadcrumbBlock,
8
+ CreateBreadcrumbBlock,
9
+ )
10
+
11
+ __all__ = [
12
+ "BreadcrumbElement",
13
+ "BreadcrumbBlock",
14
+ "CreateBreadcrumbBlock",
15
+ "BreadcrumbMarkdownNode",
16
+ "BreadcrumbMarkdownBlockParams",
17
+ ]
@@ -0,0 +1,39 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from typing import Optional
5
+
6
+ from notionary.blocks.base_block_element import BaseBlockElement
7
+ from notionary.blocks.breadcrumbs.breadcrumb_models import (
8
+ BreadcrumbBlock,
9
+ CreateBreadcrumbBlock,
10
+ )
11
+ from notionary.blocks.models import Block, BlockCreateResult, BlockType
12
+
13
+
14
+ class BreadcrumbElement(BaseBlockElement):
15
+ """
16
+ Handles conversion between Markdown breadcrumb marker and Notion breadcrumb blocks.
17
+
18
+ Markdown syntax:
19
+ [breadcrumb]
20
+ """
21
+
22
+ BREADCRUMB_MARKER = "[breadcrumb]"
23
+ PATTERN = re.compile(r"^\[breadcrumb\]\s*$", re.IGNORECASE)
24
+
25
+ @classmethod
26
+ def match_notion(cls, block: Block) -> bool:
27
+ # Kein extra Payload – nur Typ prüfen
28
+ return block.type == BlockType.BREADCRUMB and block.breadcrumb
29
+
30
+ @classmethod
31
+ def markdown_to_notion(cls, text: str) -> BlockCreateResult:
32
+ if not cls.PATTERN.match(text.strip()):
33
+ return None
34
+ return CreateBreadcrumbBlock(breadcrumb=BreadcrumbBlock())
35
+
36
+ @classmethod
37
+ def notion_to_markdown(cls, block: Block) -> Optional[str]:
38
+ if block.type == BlockType.BREADCRUMB and block.breadcrumb:
39
+ return cls.BREADCRUMB_MARKER
@@ -0,0 +1,32 @@
1
+ from __future__ import annotations
2
+
3
+ from pydantic import BaseModel
4
+
5
+ from notionary.markdown.markdown_node import MarkdownNode
6
+
7
+
8
+ class BreadcrumbMarkdownBlockParams(BaseModel):
9
+ """Parameters for breadcrumb markdown block. No parameters needed."""
10
+
11
+ pass
12
+
13
+
14
+ class BreadcrumbMarkdownNode(MarkdownNode):
15
+ """
16
+ Programmatic interface for creating Markdown breadcrumb blocks.
17
+ Example:
18
+ [breadcrumb]
19
+ """
20
+
21
+ def __init__(self):
22
+ # No parameters needed for breadcrumb
23
+ pass
24
+
25
+ @classmethod
26
+ def from_params(
27
+ cls, params: BreadcrumbMarkdownBlockParams
28
+ ) -> BreadcrumbMarkdownNode:
29
+ return cls()
30
+
31
+ def to_markdown(self) -> str:
32
+ return "[breadcrumb]"
@@ -0,0 +1,12 @@
1
+ from typing import Literal
2
+
3
+ from pydantic import BaseModel
4
+
5
+
6
+ class BreadcrumbBlock(BaseModel):
7
+ pass
8
+
9
+
10
+ class CreateBreadcrumbBlock(BaseModel):
11
+ type: Literal["breadcrumb"] = "breadcrumb"
12
+ breadcrumb: BreadcrumbBlock
@@ -1,7 +1,17 @@
1
- from .bulleted_list_element import BulletedListElement
2
- from .bulleted_list_markdown_node import BulletedListMarkdownNode
1
+ from notionary.blocks.bulleted_list.bulleted_list_element import BulletedListElement
2
+ from notionary.blocks.bulleted_list.bulleted_list_markdown_node import (
3
+ BulletedListMarkdownBlockParams,
4
+ BulletedListMarkdownNode,
5
+ )
6
+ from notionary.blocks.bulleted_list.bulleted_list_models import (
7
+ BulletedListItemBlock,
8
+ CreateBulletedListItemBlock,
9
+ )
3
10
 
4
11
  __all__ = [
5
12
  "BulletedListElement",
13
+ "BulletedListItemBlock",
14
+ "CreateBulletedListItemBlock",
6
15
  "BulletedListMarkdownNode",
16
+ "BulletedListMarkdownBlockParams",
7
17
  ]
@@ -1,72 +1,57 @@
1
+ from __future__ import annotations
2
+
1
3
  import re
2
- from typing import Dict, Any, Optional
3
- from notionary.blocks import NotionBlockElement
4
- from notionary.blocks import (
5
- ElementPromptContent,
6
- ElementPromptBuilder,
7
- NotionBlockResult,
8
- )
4
+ from typing import Optional
9
5
 
10
- from notionary.blocks.shared.text_inline_formatter import TextInlineFormatter
6
+ from notionary.blocks.base_block_element import BaseBlockElement
7
+ from notionary.blocks.bulleted_list.bulleted_list_models import (
8
+ BulletedListItemBlock,
9
+ CreateBulletedListItemBlock,
10
+ )
11
+ from notionary.blocks.models import Block, BlockCreateResult, BlockType
12
+ from notionary.blocks.rich_text.text_inline_formatter import TextInlineFormatter
11
13
 
12
14
 
13
- class BulletedListElement(NotionBlockElement):
15
+ class BulletedListElement(BaseBlockElement):
14
16
  """Class for converting between Markdown bullet lists and Notion bulleted list items."""
15
17
 
18
+ # Regex for markdown bullets (excluding todo items [ ] or [x])
19
+ PATTERN = re.compile(r"^(\s*)[*\-+]\s+(?!\[[ x]\])(.+)$")
20
+
21
+ @classmethod
22
+ def match_notion(cls, block: Block) -> bool:
23
+ """Check if this element can handle the given Notion block."""
24
+ return block.type == BlockType.BULLETED_LIST_ITEM and block.bulleted_list_item
25
+
16
26
  @classmethod
17
- def markdown_to_notion(cls, text: str) -> NotionBlockResult:
18
- """Convert markdown bulleted list item to Notion block."""
19
- pattern = re.compile(
20
- r"^(\s*)[*\-+]\s+(?!\[[ x]\])(.+)$"
21
- ) # Avoid matching todo items
22
- list_match = pattern.match(text)
23
- if not list_match:
27
+ def markdown_to_notion(cls, text: str) -> BlockCreateResult:
28
+ """
29
+ Convert a markdown bulleted list item into a Notion BulletedListItemBlock.
30
+ """
31
+ if not (match := cls.PATTERN.match(text.strip())):
24
32
  return None
25
33
 
26
- content = list_match.group(2)
34
+ # Extract the content part (second capture group)
35
+ content = match.group(2)
27
36
 
28
- # Use parse_inline_formatting to handle rich text
37
+ # Parse inline markdown formatting into RichTextObject list
29
38
  rich_text = TextInlineFormatter.parse_inline_formatting(content)
30
39
 
31
- return {
32
- "type": "bulleted_list_item",
33
- "bulleted_list_item": {"rich_text": rich_text, "color": "default"},
34
- }
40
+ # Return a properly typed Notion block
41
+ bulleted_list_content = BulletedListItemBlock(
42
+ rich_text=rich_text, color="default"
43
+ )
44
+ return CreateBulletedListItemBlock(bulleted_list_item=bulleted_list_content)
35
45
 
36
46
  @classmethod
37
- def notion_to_markdown(cls, block: Dict[str, Any]) -> Optional[str]:
38
- """Convert Notion bulleted list item block to markdown."""
39
- if block.get("type") != "bulleted_list_item":
47
+ def notion_to_markdown(cls, block: Block) -> Optional[str]:
48
+ """Convert Notion bulleted_list_item block to Markdown."""
49
+ if block.type != BlockType.BULLETED_LIST_ITEM or not block.bulleted_list_item:
40
50
  return None
41
51
 
42
- rich_text = block.get("bulleted_list_item", {}).get("rich_text", [])
43
- content = TextInlineFormatter.extract_text_with_formatting(rich_text)
52
+ rich_list = block.bulleted_list_item.rich_text
53
+ if not rich_list:
54
+ return "-"
44
55
 
45
- return f"- {content}"
46
-
47
- @classmethod
48
- def match_markdown(cls, text: str) -> bool:
49
- """Check if this element can handle the given markdown text."""
50
- pattern = re.compile(r"^(\s*)[*\-+]\s+(?!\[[ x]\])(.+)$")
51
- return bool(pattern.match(text))
52
-
53
- @classmethod
54
- def match_notion(cls, block: Dict[str, Any]) -> bool:
55
- """Check if this element can handle the given Notion block."""
56
- return block.get("type") == "bulleted_list_item"
57
-
58
- @classmethod
59
- def get_llm_prompt_content(cls) -> ElementPromptContent:
60
- """
61
- Returns structured LLM prompt metadata for the bulleted list element.
62
- """
63
- return (
64
- ElementPromptBuilder()
65
- .with_description("Creates bulleted list items for unordered lists.")
66
- .with_usage_guidelines(
67
- "Use for lists where order doesn't matter, such as features, options, or items without hierarchy."
68
- )
69
- .with_syntax("- Item text")
70
- .with_standard_markdown()
71
- .build()
72
- )
56
+ text = TextInlineFormatter.extract_text_with_formatting(rich_list)
57
+ return f"- {text}"
@@ -1,7 +1,8 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from pydantic import BaseModel
4
- from notionary.blocks.markdown_node import MarkdownNode
4
+
5
+ from notionary.markdown.markdown_node import MarkdownNode
5
6
 
6
7
 
7
8
  class BulletedListMarkdownBlockParams(BaseModel):
@@ -0,0 +1,18 @@
1
+ from typing import Literal
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+ from notionary.blocks.models import Block
6
+ from notionary.blocks.rich_text import RichTextObject
7
+ from notionary.blocks.types import BlockColor
8
+
9
+
10
+ class BulletedListItemBlock(BaseModel):
11
+ rich_text: list[RichTextObject]
12
+ color: BlockColor = BlockColor.DEFAULT
13
+ children: list[Block] = Field(default_factory=list)
14
+
15
+
16
+ class CreateBulletedListItemBlock(BaseModel):
17
+ type: Literal["bulleted_list_item"] = "bulleted_list_item"
18
+ bulleted_list_item: BulletedListItemBlock
@@ -1,7 +1,14 @@
1
- from .callout_element import CalloutElement
2
- from .callout_markdown_node import CalloutMarkdownNode
1
+ from notionary.blocks.callout.callout_element import CalloutElement
2
+ from notionary.blocks.callout.callout_markdown_node import (
3
+ CalloutMarkdownBlockParams,
4
+ CalloutMarkdownNode,
5
+ )
6
+ from notionary.blocks.callout.callout_models import CalloutBlock, CreateCalloutBlock
3
7
 
4
8
  __all__ = [
5
9
  "CalloutElement",
10
+ "CalloutBlock",
11
+ "CreateCalloutBlock",
6
12
  "CalloutMarkdownNode",
13
+ "CalloutMarkdownBlockParams",
7
14
  ]