notionary 0.1.10__py3-none-any.whl → 0.1.12__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 (33) hide show
  1. notionary/__init__.py +13 -2
  2. notionary/core/converters/elements/audio_element.py +143 -0
  3. notionary/core/converters/elements/embed_element.py +2 -4
  4. notionary/core/converters/elements/toggle_element.py +28 -20
  5. notionary/core/converters/markdown_to_notion_converter.py +70 -109
  6. notionary/core/converters/registry/block_element_registry.py +2 -6
  7. notionary/core/converters/registry/block_element_registry_builder.py +2 -0
  8. notionary/core/database/database_discovery.py +140 -0
  9. notionary/core/database/notion_database_manager.py +26 -49
  10. notionary/core/database/notion_database_manager_factory.py +10 -4
  11. notionary/core/notion_client.py +4 -2
  12. notionary/core/page/content/notion_page_content_chunker.py +84 -0
  13. notionary/core/page/content/page_content_manager.py +26 -8
  14. notionary/core/page/metadata/metadata_editor.py +57 -44
  15. notionary/core/page/metadata/notion_icon_manager.py +9 -11
  16. notionary/core/page/metadata/notion_page_cover_manager.py +15 -20
  17. notionary/core/page/notion_page_manager.py +137 -156
  18. notionary/core/page/properites/database_property_service.py +114 -98
  19. notionary/core/page/properites/page_property_manager.py +78 -49
  20. notionary/core/page/properites/property_formatter.py +1 -1
  21. notionary/core/page/properites/property_operation_result.py +43 -30
  22. notionary/core/page/properites/property_value_extractor.py +26 -8
  23. notionary/core/page/relations/notion_page_relation_manager.py +71 -52
  24. notionary/core/page/relations/notion_page_title_resolver.py +11 -11
  25. notionary/core/page/relations/page_database_relation.py +14 -14
  26. notionary/core/page/relations/relation_operation_result.py +50 -41
  27. notionary/util/page_id_utils.py +11 -7
  28. {notionary-0.1.10.dist-info → notionary-0.1.12.dist-info}/METADATA +1 -1
  29. {notionary-0.1.10.dist-info → notionary-0.1.12.dist-info}/RECORD +32 -30
  30. notionary/core/database/notion_database_schema.py +0 -104
  31. {notionary-0.1.10.dist-info → notionary-0.1.12.dist-info}/WHEEL +0 -0
  32. {notionary-0.1.10.dist-info → notionary-0.1.12.dist-info}/licenses/LICENSE +0 -0
  33. {notionary-0.1.10.dist-info → notionary-0.1.12.dist-info}/top_level.txt +0 -0
notionary/__init__.py CHANGED
@@ -1,9 +1,20 @@
1
- from .core.page.notion_page_manager import NotionPageManager
1
+ from .core.notion_client import NotionClient
2
+
2
3
  from .core.database.notion_database_manager import NotionDatabaseManager
3
4
  from .core.database.notion_database_manager_factory import NotionDatabaseFactory
5
+ from .core.database.database_discovery import DatabaseDiscovery
6
+
7
+ from .core.page.notion_page_manager import NotionPageManager
8
+
9
+ from .core.converters.registry.block_element_registry import BlockElementRegistry
10
+ from .core.converters.registry.block_element_registry_builder import BlockElementRegistryBuilder
4
11
 
5
12
  __all__ = [
6
- "NotionPageManager",
13
+ "NotionClient",
7
14
  "NotionDatabaseManager",
8
15
  "NotionDatabaseFactory",
16
+ "DatabaseDiscovery",
17
+ "NotionPageManager",
18
+ "BlockElementRegistry",
19
+ "BlockElementRegistryBuilder",
9
20
  ]
@@ -0,0 +1,143 @@
1
+ import re
2
+ from typing import Dict, Any, Optional, List
3
+ from notionary.core.converters.elements.notion_block_element import NotionBlockElement
4
+
5
+
6
+ class AudioElement(NotionBlockElement):
7
+ """
8
+ Handles conversion between Markdown audio embeds and Notion audio blocks.
9
+
10
+ Markdown audio syntax (custom format since standard Markdown doesn't support audio):
11
+ - $[Caption](https://example.com/audio.mp3) - Basic audio with caption
12
+ - $[](https://example.com/audio.mp3) - Audio without caption
13
+ - $[Caption](https://storage.googleapis.com/audio_summaries/example.mp3) - CDN hosted audio
14
+
15
+ Supports various audio URLs including direct audio file links from CDNs and other sources.
16
+ """
17
+
18
+ # Regex pattern for audio syntax
19
+ PATTERN = re.compile(
20
+ r"^\$\[(.*?)\]" # $[Caption] part
21
+ + r'\((https?://[^\s"]+)' # (URL part
22
+ + r"\)$" # closing parenthesis
23
+ )
24
+
25
+ # Audio file extensions
26
+ AUDIO_EXTENSIONS = [".mp3", ".wav", ".ogg", ".m4a", ".flac", ".aac"]
27
+
28
+ @staticmethod
29
+ def match_markdown(text: str) -> bool:
30
+ """Check if text is a markdown audio embed."""
31
+ text = text.strip()
32
+ return text.startswith("$[") and bool(AudioElement.PATTERN.match(text))
33
+
34
+ @staticmethod
35
+ def match_notion(block: Dict[str, Any]) -> bool:
36
+ """Check if block is a Notion audio."""
37
+ return block.get("type") == "audio"
38
+
39
+ @staticmethod
40
+ def is_audio_url(url: str) -> bool:
41
+ """Check if URL points to an audio file."""
42
+ return (
43
+ any(url.lower().endswith(ext) for ext in AudioElement.AUDIO_EXTENSIONS)
44
+ or "audio" in url.lower()
45
+ or "storage.googleapis.com/audio_summaries" in url.lower()
46
+ )
47
+
48
+ @staticmethod
49
+ def markdown_to_notion(text: str) -> Optional[Dict[str, Any]]:
50
+ """Convert markdown audio embed to Notion audio block."""
51
+ audio_match = AudioElement.PATTERN.match(text.strip())
52
+ if not audio_match:
53
+ return None
54
+
55
+ caption = audio_match.group(1)
56
+ url = audio_match.group(2)
57
+
58
+ if not url:
59
+ return None
60
+
61
+ # Make sure this is an audio URL
62
+ if not AudioElement.is_audio_url(url):
63
+ # If not obviously an audio URL, we'll still accept it as the user might know better
64
+ pass
65
+
66
+ # Prepare the audio block
67
+ audio_block = {
68
+ "type": "audio",
69
+ "audio": {"type": "external", "external": {"url": url}},
70
+ }
71
+
72
+ # Add caption if provided
73
+ if caption:
74
+ audio_block["audio"]["caption"] = [
75
+ {"type": "text", "text": {"content": caption}}
76
+ ]
77
+
78
+ return audio_block
79
+
80
+ @staticmethod
81
+ def notion_to_markdown(block: Dict[str, Any]) -> Optional[str]:
82
+ """Convert Notion audio block to markdown audio embed."""
83
+ if block.get("type") != "audio":
84
+ return None
85
+
86
+ audio_data = block.get("audio", {})
87
+
88
+ # Handle both external and file (uploaded) audios
89
+ if audio_data.get("type") == "external":
90
+ url = audio_data.get("external", {}).get("url", "")
91
+ elif audio_data.get("type") == "file":
92
+ url = audio_data.get("file", {}).get("url", "")
93
+ else:
94
+ return None
95
+
96
+ if not url:
97
+ return None
98
+
99
+ # Extract caption if available
100
+ caption = ""
101
+ caption_rich_text = audio_data.get("caption", [])
102
+ if caption_rich_text:
103
+ caption = AudioElement._extract_text_content(caption_rich_text)
104
+
105
+ return f"$[{caption}]({url})"
106
+
107
+ @staticmethod
108
+ def is_multiline() -> bool:
109
+ """Audio embeds are single-line elements."""
110
+ return False
111
+
112
+ @staticmethod
113
+ def _extract_text_content(rich_text: List[Dict[str, Any]]) -> str:
114
+ """Extract plain text content from Notion rich_text elements."""
115
+ result = ""
116
+ for text_obj in rich_text:
117
+ if text_obj.get("type") == "text":
118
+ result += text_obj.get("text", {}).get("content", "")
119
+ elif "plain_text" in text_obj:
120
+ result += text_obj.get("plain_text", "")
121
+ return result
122
+
123
+ @classmethod
124
+ def get_llm_prompt_content(cls) -> dict:
125
+ """Returns information for LLM prompts about this element."""
126
+ return {
127
+ "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
+ ],
138
+ "examples": [
139
+ "$[Podcast Episode](https://storage.googleapis.com/audio_summaries/ep_ai_summary_127d02ec-ca12-4312-a5ed-cb14b185480c.mp3)",
140
+ "$[Voice recording](https://example.com/audio/recording.mp3)",
141
+ "$[](https://storage.googleapis.com/audio_summaries/example.mp3)",
142
+ ],
143
+ }
@@ -16,9 +16,7 @@ class EmbedElement(NotionBlockElement):
16
16
  """
17
17
 
18
18
  PATTERN = re.compile(
19
- r"^<embed(?:\:(.*?))?>(?:\s*)"
20
- + r'\((https?://[^\s"]+)'
21
- + r"\)$"
19
+ r"^<embed(?:\:(.*?))?>(?:\s*)" + r'\((https?://[^\s"]+)' + r"\)$"
22
20
  )
23
21
 
24
22
  @staticmethod
@@ -126,4 +124,4 @@ class EmbedElement(NotionBlockElement):
126
124
  "<embed:Project documentation>(https://github.com/username/repo)",
127
125
  "<embed>(https://example.com/important-reference.pdf)",
128
126
  ],
129
- }
127
+ }
@@ -6,20 +6,15 @@ from notionary.core.converters.elements.notion_block_element import NotionBlockE
6
6
 
7
7
  class ToggleElement(NotionBlockElement):
8
8
  """
9
- Handles conversion between Markdown toggle blocks and Notion toggle blocks.
10
-
11
- Markdown toggle syntax:
12
- +++ Toggle title
13
- Indented content that belongs to the toggle
14
- More indented content
15
-
16
- Non-indented content marks the end of the toggle block.
9
+ Verbesserte ToggleElement-Klasse, die Kontext berücksichtigt.
17
10
  """
18
11
 
19
12
  TOGGLE_PATTERN = re.compile(r"^[+]{3}\s+(.+)$")
20
-
21
13
  INDENT_PATTERN = re.compile(r"^(\s{2,}|\t+)(.+)$")
22
14
 
15
+ # Ein neues Pattern, um spezifisch nach der "Transcript" Überschrift zu suchen
16
+ TRANSCRIPT_TOGGLE_PATTERN = re.compile(r"^[+]{3}\s+Transcript$")
17
+
23
18
  @staticmethod
24
19
  def match_markdown(text: str) -> bool:
25
20
  """Check if text is a markdown toggle."""
@@ -32,11 +27,7 @@ class ToggleElement(NotionBlockElement):
32
27
 
33
28
  @staticmethod
34
29
  def markdown_to_notion(text: str) -> Optional[Dict[str, Any]]:
35
- """Convert markdown toggle to Notion toggle block.
36
-
37
- Note: This method only converts the toggle title line.
38
- The nested content needs to be processed separately.
39
- """
30
+ """Convert markdown toggle to Notion toggle block."""
40
31
  toggle_match = ToggleElement.TOGGLE_PATTERN.match(text.strip())
41
32
  if not toggle_match:
42
33
  return None
@@ -149,18 +140,21 @@ class ToggleElement(NotionBlockElement):
149
140
 
150
141
  @classmethod
151
142
  def find_matches(
152
- cls, text: str, process_nested_content: Callable = None
143
+ cls,
144
+ text: str,
145
+ process_nested_content: Callable = None,
146
+ context_aware: bool = True,
153
147
  ) -> List[Tuple[int, int, Dict[str, Any]]]:
154
148
  """
155
- Find all toggle elements in the text and process them.
149
+ Verbesserte find_matches-Methode, die Kontext beim Finden von Toggles berücksichtigt.
156
150
 
157
151
  Args:
158
- text: The text to search in
159
- process_nested_content: Optional callback function to process nested content
160
- It should accept a string and return a list of Notion blocks
152
+ text: Der zu durchsuchende Text
153
+ process_nested_content: Optionale Callback-Funktion zur Verarbeitung verschachtelter Inhalte
154
+ context_aware: Ob der Kontext (vorhergehende Zeilen) beim Finden von Toggles berücksichtigt werden soll
161
155
 
162
156
  Returns:
163
- List of (start_pos, end_pos, block) tuples
157
+ Liste von (start_pos, end_pos, block) Tupeln
164
158
  """
165
159
  if not text:
166
160
  return []
@@ -177,6 +171,20 @@ class ToggleElement(NotionBlockElement):
177
171
  i += 1
178
172
  continue
179
173
 
174
+ # Wenn context_aware aktiviert ist, prüfen wir für "Transcript"-Toggles
175
+ # ob sie direkt nach einem Bullet Point kommen
176
+ is_transcript_toggle = cls.TRANSCRIPT_TOGGLE_PATTERN.match(line.strip())
177
+
178
+ if context_aware and is_transcript_toggle:
179
+ # Prüfen, ob der Toggle in einem gültigen Kontext ist (nach Bullet Point)
180
+ if i > 0 and lines[i - 1].strip().startswith("- "):
181
+ # Gültiger Kontext, fahre fort
182
+ pass
183
+ else:
184
+ # Ungültiger Kontext für Transcript-Toggle, überspringe ihn
185
+ i += 1
186
+ continue
187
+
180
188
  start_pos = 0
181
189
  for j in range(i):
182
190
  start_pos += len(lines[j]) + 1
@@ -12,6 +12,8 @@ class MarkdownToNotionConverter:
12
12
  SPACER_MARKER = "<!-- spacer -->"
13
13
  MULTILINE_CONTENT_MARKER = "<!-- REMOVED_MULTILINE_CONTENT -->"
14
14
  TOGGLE_MARKER = "<!-- toggle_content -->"
15
+ TOGGLE_MARKER_PREFIX = "<!-- toggle_"
16
+ TOGGLE_MARKER_SUFFIX = " -->"
15
17
 
16
18
  def __init__(self, block_registry: Optional[BlockElementRegistry] = None):
17
19
  """
@@ -46,38 +48,48 @@ class MarkdownToNotionConverter:
46
48
  if not markdown_text:
47
49
  return []
48
50
 
49
- # Process toggles first
50
- processed_text, toggle_blocks = self._extract_toggle_elements(markdown_text)
51
+ # We'll process all blocks in order, preserving their original positions
52
+ all_blocks = []
53
+
54
+ # First, identify all toggle blocks
55
+ toggle_blocks = self._identify_toggle_blocks(markdown_text)
56
+
57
+ # If we have toggles, process them and extract positions
58
+ if toggle_blocks:
59
+ all_blocks.extend(toggle_blocks)
51
60
 
52
61
  # Process other multiline elements
53
- processed_text, multiline_blocks = self._extract_multiline_elements(
54
- processed_text
55
- )
62
+ multiline_blocks = self._identify_multiline_blocks(markdown_text, toggle_blocks)
63
+ if multiline_blocks:
64
+ all_blocks.extend(multiline_blocks)
56
65
 
57
66
  # Process remaining text line by line
58
- line_blocks = self._process_text_lines(processed_text)
67
+ line_blocks = self._process_text_lines(
68
+ markdown_text, toggle_blocks + multiline_blocks
69
+ )
70
+ if line_blocks:
71
+ all_blocks.extend(line_blocks)
59
72
 
60
- # Combine and sort all blocks
61
- all_blocks = toggle_blocks + multiline_blocks + line_blocks
73
+ # Sort all blocks by their position in the text
62
74
  all_blocks.sort(key=lambda x: x[0])
63
75
 
64
- # Extract just the blocks from position tuples
76
+ # Extract just the blocks without position information
65
77
  blocks = [block for _, _, block in all_blocks]
66
78
 
67
79
  # Process spacing between blocks
68
80
  return self._process_block_spacing(blocks)
69
81
 
70
- def _extract_toggle_elements(
82
+ def _identify_toggle_blocks(
71
83
  self, text: str
72
- ) -> Tuple[str, List[Tuple[int, int, Dict[str, Any]]]]:
84
+ ) -> List[Tuple[int, int, Dict[str, Any]]]:
73
85
  """
74
- Extract toggle elements and their nested content using the ToggleElement class.
86
+ Identify all toggle blocks in the text without replacing them.
75
87
 
76
88
  Args:
77
89
  text: The text to process
78
90
 
79
91
  Returns:
80
- Tuple of (processed text, list of (start_pos, end_pos, block) tuples)
92
+ List of (start_pos, end_pos, block) tuples
81
93
  """
82
94
  # Find toggle element in registry
83
95
  toggle_element = None
@@ -91,67 +103,28 @@ class MarkdownToNotionConverter:
91
103
  break
92
104
 
93
105
  if not toggle_element:
94
- # No toggle element found, return text as is
95
- return text, []
106
+ return []
96
107
 
97
- # Use the find_matches method of ToggleElement to find and process all toggles
108
+ # Use the find_matches method with context awareness
98
109
  # Pass the converter's convert method as a callback to process nested content
99
- toggle_blocks = toggle_element.find_matches(text, self.convert)
100
-
101
- if not toggle_blocks:
102
- return text, []
103
-
104
- # Create a processed text with toggle markers
105
- lines = text.split("\n")
106
- processed_lines = lines.copy()
107
-
108
- # Replace toggle content with markers
109
- for start_pos, end_pos, _ in reversed(toggle_blocks):
110
- # Calculate line indices for this toggle
111
- start_line_index = 0
112
- current_pos = 0
113
- for i, line in enumerate(lines):
114
- line_length = len(line) + 1 # +1 for newline
115
- if current_pos <= start_pos < current_pos + line_length:
116
- start_line_index = i
117
- break
118
- current_pos += line_length
119
-
120
- end_line_index = start_line_index
121
- current_pos = 0
122
- for i, line in enumerate(lines):
123
- line_length = len(line) + 1 # +1 for newline
124
- if current_pos <= end_pos < current_pos + line_length:
125
- end_line_index = i
126
- break
127
- current_pos += line_length
128
-
129
- # Replace toggle content with markers
130
- num_lines = end_line_index - start_line_index + 1
131
- for i in range(start_line_index, start_line_index + num_lines):
132
- processed_lines[i] = self.TOGGLE_MARKER
133
-
134
- processed_text = "\n".join(processed_lines)
135
- return processed_text, toggle_blocks
110
+ toggle_blocks = toggle_element.find_matches(
111
+ text, self.convert, context_aware=True
112
+ )
113
+ return toggle_blocks
136
114
 
137
- def _extract_multiline_elements(
138
- self, text: str
139
- ) -> Tuple[str, List[Tuple[int, int, Dict[str, Any]]]]:
115
+ def _identify_multiline_blocks(
116
+ self, text: str, exclude_blocks: List[Tuple[int, int, Dict[str, Any]]]
117
+ ) -> List[Tuple[int, int, Dict[str, Any]]]:
140
118
  """
141
- Extract multiline elements and remove them from the text.
119
+ Identify all multiline blocks (except toggle blocks) without altering the text.
142
120
 
143
121
  Args:
144
122
  text: The text to process
123
+ exclude_blocks: Blocks to exclude (e.g., already identified toggle blocks)
145
124
 
146
125
  Returns:
147
- Tuple of (processed text, list of (start_pos, end_pos, block) tuples)
126
+ List of (start_pos, end_pos, block) tuples
148
127
  """
149
- if not text:
150
- return text, []
151
-
152
- multiline_blocks = []
153
- processed_text = text
154
-
155
128
  # Get all multiline elements except ToggleElement
156
129
  multiline_elements = [
157
130
  element
@@ -160,51 +133,45 @@ class MarkdownToNotionConverter:
160
133
  ]
161
134
 
162
135
  if not multiline_elements:
163
- return text, []
136
+ return []
164
137
 
138
+ # Create a set of ranges to exclude
139
+ exclude_ranges = set()
140
+ for start, end, _ in exclude_blocks:
141
+ exclude_ranges.update(range(start, end + 1))
142
+
143
+ multiline_blocks = []
165
144
  for element in multiline_elements:
166
145
  if not hasattr(element, "find_matches"):
167
146
  continue
168
147
 
169
- # Find all matches for this element (pass the convert method as callback if needed)
148
+ # Find all matches for this element
170
149
  if hasattr(element, "set_converter_callback"):
171
- matches = element.find_matches(processed_text, self.convert)
150
+ matches = element.find_matches(text, self.convert)
172
151
  else:
173
- matches = element.find_matches(processed_text)
152
+ matches = element.find_matches(text)
174
153
 
175
154
  if not matches:
176
155
  continue
177
156
 
178
- multiline_blocks.extend(matches)
157
+ # Add only blocks that don't overlap with excluded ranges
158
+ for start, end, block in matches:
159
+ # Check if this block overlaps with any excluded range
160
+ if any(start <= i <= end for i in exclude_ranges):
161
+ continue
162
+ multiline_blocks.append((start, end, block))
179
163
 
180
- # Remove matched content from the text to avoid processing it again
181
- processed_text = self._replace_matched_content_with_markers(
182
- processed_text, matches
183
- )
184
-
185
- return processed_text, multiline_blocks
186
-
187
- def _replace_matched_content_with_markers(
188
- self, text: str, matches: List[Tuple[int, int, Dict[str, Any]]]
189
- ) -> str:
190
- """Replace matched content with marker placeholders to preserve line structure."""
191
- for start, end, _ in reversed(matches):
192
- num_newlines = text[start:end].count("\n")
193
- text = (
194
- text[:start]
195
- + "\n"
196
- + self.MULTILINE_CONTENT_MARKER
197
- + "\n" * num_newlines
198
- + text[end:]
199
- )
200
- return text
164
+ return multiline_blocks
201
165
 
202
- def _process_text_lines(self, text: str) -> List[Tuple[int, int, Dict[str, Any]]]:
166
+ def _process_text_lines(
167
+ self, text: str, exclude_blocks: List[Tuple[int, int, Dict[str, Any]]]
168
+ ) -> List[Tuple[int, int, Dict[str, Any]]]:
203
169
  """
204
- Process text line by line for single-line elements.
170
+ Process text line by line, excluding ranges already processed.
205
171
 
206
172
  Args:
207
173
  text: The text to process
174
+ exclude_blocks: Blocks to exclude (e.g., already identified toggle and multiline blocks)
208
175
 
209
176
  Returns:
210
177
  List of (start_pos, end_pos, block) tuples
@@ -212,6 +179,11 @@ class MarkdownToNotionConverter:
212
179
  if not text:
213
180
  return []
214
181
 
182
+ # Create a set of excluded positions
183
+ exclude_positions = set()
184
+ for start, end, _ in exclude_blocks:
185
+ exclude_positions.update(range(start, end + 1))
186
+
215
187
  line_blocks = []
216
188
  lines = text.split("\n")
217
189
 
@@ -222,9 +194,10 @@ class MarkdownToNotionConverter:
222
194
 
223
195
  for line in lines:
224
196
  line_length = len(line) + 1 # +1 for newline
197
+ line_end = current_pos + line_length - 1
225
198
 
226
- # Skip marker lines
227
- if self._is_marker_line(line):
199
+ # Skip lines that are part of excluded blocks
200
+ if any(current_pos <= pos <= line_end for pos in exclude_positions):
228
201
  current_pos += line_length
229
202
  continue
230
203
 
@@ -233,7 +206,7 @@ class MarkdownToNotionConverter:
233
206
  line_blocks.append(
234
207
  (
235
208
  current_pos,
236
- current_pos + line_length,
209
+ current_pos + line_length - 1,
237
210
  self._create_empty_paragraph(),
238
211
  )
239
212
  )
@@ -273,7 +246,7 @@ class MarkdownToNotionConverter:
273
246
  current_paragraph, paragraph_start, current_pos, line_blocks
274
247
  )
275
248
  line_blocks.append(
276
- (current_pos, current_pos + line_length, special_block)
249
+ (current_pos, current_pos + line_length - 1, special_block)
277
250
  )
278
251
  current_paragraph = []
279
252
  current_pos += line_length
@@ -292,18 +265,6 @@ class MarkdownToNotionConverter:
292
265
 
293
266
  return line_blocks
294
267
 
295
- def _is_marker_line(self, line: str) -> bool:
296
- """Check if a line is any kind of marker line that should be skipped."""
297
- return self._is_multiline_marker(line) or self._is_toggle_marker(line)
298
-
299
- def _is_multiline_marker(self, line: str) -> bool:
300
- """Check if a line is a multiline content marker."""
301
- return line.strip() == self.MULTILINE_CONTENT_MARKER
302
-
303
- def _is_toggle_marker(self, line: str) -> bool:
304
- """Check if a line is a toggle content marker."""
305
- return line.strip() == self.TOGGLE_MARKER
306
-
307
268
  def _is_spacer_marker(self, line: str) -> bool:
308
269
  """Check if a line is a spacer marker."""
309
270
  return line.strip() == self.SPACER_MARKER
@@ -343,7 +304,7 @@ class MarkdownToNotionConverter:
343
304
  )
344
305
  current_paragraph.clear()
345
306
 
346
- line_blocks.append((current_pos, current_pos + line_length, todo_block))
307
+ line_blocks.append((current_pos, current_pos + line_length - 1, todo_block))
347
308
 
348
309
  def _extract_special_block(self, line: str) -> Optional[Dict[str, Any]]:
349
310
  """
@@ -95,8 +95,8 @@ class BlockElementRegistry:
95
95
  """
96
96
  # Create a copy of registered elements
97
97
  element_classes = self._elements.copy()
98
-
99
- # TODO: Das hier besser formattieren und über debug level lösen . )
98
+
99
+ # TODO: Das hier besser formattieren und über debug level lösen . )
100
100
  print("Elements in registry:", element_classes)
101
101
 
102
102
  formatter_names = [e.__name__ for e in element_classes]
@@ -234,7 +234,3 @@ paragraphs, lists, quotes, etc.
234
234
  element_docs = cls.generate_element_docs(element_classes)
235
235
 
236
236
  return cls.SYSTEM_PROMPT_TEMPLATE.format(element_docs=element_docs)
237
-
238
-
239
-
240
- # TODO: Testen ob der Inline Formatter hier überhaupt funktionieren tut:
@@ -1,6 +1,7 @@
1
1
  from typing import List, Type
2
2
  from collections import OrderedDict
3
3
 
4
+ from notionary.core.converters.elements.audio_element import AudioElement
4
5
  from notionary.core.converters.elements.embed_element import EmbedElement
5
6
  from notionary.core.converters.elements.notion_block_element import NotionBlockElement
6
7
  from notionary.core.converters.registry.block_element_registry import (
@@ -92,6 +93,7 @@ class BlockElementRegistryBuilder:
92
93
  .add_element(ImageElement)
93
94
  .add_element(VideoElement)
94
95
  .add_element(EmbedElement)
96
+ .add_element(AudioElement)
95
97
  .add_element(ParagraphElement)
96
98
  ) # Add paragraph last as fallback
97
99