notionary 0.1.11__py3-none-any.whl → 0.1.13__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 (55) hide show
  1. notionary/__init__.py +21 -6
  2. notionary/{core/converters → converters}/elements/audio_element.py +7 -5
  3. notionary/{core/converters → converters}/elements/bookmark_element.py +1 -1
  4. notionary/{core/converters → converters}/elements/callout_element.py +2 -2
  5. notionary/{core/converters → converters}/elements/code_block_element.py +1 -1
  6. notionary/{core/converters → converters}/elements/column_element.py +1 -1
  7. notionary/{core/converters → converters}/elements/divider_element.py +1 -1
  8. notionary/{core/converters → converters}/elements/embed_element.py +3 -5
  9. notionary/{core/converters → converters}/elements/heading_element.py +2 -2
  10. notionary/{core/converters → converters}/elements/image_element.py +1 -1
  11. notionary/{core/converters → converters}/elements/list_element.py +2 -2
  12. notionary/{core/converters → converters}/elements/paragraph_element.py +2 -2
  13. notionary/{core/converters → converters}/elements/qoute_element.py +1 -1
  14. notionary/{core/converters → converters}/elements/table_element.py +2 -2
  15. notionary/{core/converters → converters}/elements/todo_lists.py +2 -2
  16. notionary/{core/converters → converters}/elements/toggle_element.py +24 -21
  17. notionary/{core/converters → converters}/elements/video_element.py +1 -1
  18. notionary/{core/converters → converters}/markdown_to_notion_converter.py +72 -111
  19. notionary/{core/converters → converters}/notion_to_markdown_converter.py +2 -2
  20. notionary/{core/converters → converters}/registry/block_element_registry.py +5 -5
  21. notionary/{core/converters → converters}/registry/block_element_registry_builder.py +18 -18
  22. notionary/database/database_discovery.py +142 -0
  23. notionary/{core/database → database}/database_info_service.py +1 -1
  24. notionary/{core/database/notion_database_manager.py → database/notion_database.py} +33 -57
  25. notionary/{core/database/notion_database_manager_factory.py → database/notion_database_factory.py} +18 -16
  26. notionary/{core/notion_client.py → notion_client.py} +4 -2
  27. notionary/page/content/notion_page_content_chunker.py +84 -0
  28. notionary/{core/page → page}/content/page_content_manager.py +29 -13
  29. notionary/{core/page → page}/metadata/metadata_editor.py +59 -46
  30. notionary/{core/page → page}/metadata/notion_icon_manager.py +10 -12
  31. notionary/{core/page → page}/metadata/notion_page_cover_manager.py +16 -21
  32. notionary/page/notion_page.py +504 -0
  33. notionary/page/notion_page_factory.py +256 -0
  34. notionary/{core/page → page}/properites/database_property_service.py +115 -99
  35. notionary/{core/page → page}/properites/page_property_manager.py +81 -52
  36. notionary/{core/page → page}/properites/property_formatter.py +1 -1
  37. notionary/{core/page → page}/properites/property_operation_result.py +43 -30
  38. notionary/{core/page → page}/properites/property_value_extractor.py +26 -8
  39. notionary/{core/page → page}/relations/notion_page_relation_manager.py +72 -53
  40. notionary/{core/page → page}/relations/notion_page_title_resolver.py +12 -12
  41. notionary/{core/page → page}/relations/page_database_relation.py +15 -15
  42. notionary/{core/page → page}/relations/relation_operation_result.py +50 -41
  43. notionary/util/page_id_utils.py +14 -8
  44. {notionary-0.1.11.dist-info → notionary-0.1.13.dist-info}/METADATA +1 -1
  45. notionary-0.1.13.dist-info/RECORD +56 -0
  46. notionary/core/database/notion_database_schema.py +0 -104
  47. notionary/core/page/notion_page_manager.py +0 -322
  48. notionary-0.1.11.dist-info/RECORD +0 -54
  49. /notionary/{core/converters → converters}/__init__.py +0 -0
  50. /notionary/{core/converters → converters}/elements/notion_block_element.py +0 -0
  51. /notionary/{core/converters → converters}/elements/text_inline_formatter.py +0 -0
  52. /notionary/{core/database → database}/models/page_result.py +0 -0
  53. {notionary-0.1.11.dist-info → notionary-0.1.13.dist-info}/WHEEL +0 -0
  54. {notionary-0.1.11.dist-info → notionary-0.1.13.dist-info}/licenses/LICENSE +0 -0
  55. {notionary-0.1.11.dist-info → notionary-0.1.13.dist-info}/top_level.txt +0 -0
notionary/__init__.py CHANGED
@@ -1,9 +1,24 @@
1
- from .core.page.notion_page_manager import NotionPageManager
2
- from .core.database.notion_database_manager import NotionDatabaseManager
3
- from .core.database.notion_database_manager_factory import NotionDatabaseFactory
1
+ from .notion_client import NotionClient
2
+
3
+ from .database.notion_database import NotionDatabase
4
+ from .database.notion_database_factory import NotionDatabaseFactory
5
+ from .database.database_discovery import DatabaseDiscovery
6
+
7
+ from .page.notion_page import NotionPage
8
+ from .page.notion_page_factory import NotionPageFactory
9
+
10
+ from .converters.registry.block_element_registry import BlockElementRegistry
11
+ from .converters.registry.block_element_registry_builder import (
12
+ BlockElementRegistryBuilder,
13
+ )
4
14
 
5
15
  __all__ = [
6
- "NotionPageManager",
7
- "NotionDatabaseManager",
16
+ "NotionClient",
17
+ "NotionDatabase",
8
18
  "NotionDatabaseFactory",
9
- ]
19
+ "DatabaseDiscovery",
20
+ "NotionPage",
21
+ "NotionPageFactory",
22
+ "BlockElementRegistry",
23
+ "BlockElementRegistryBuilder",
24
+ ]
@@ -1,6 +1,6 @@
1
1
  import re
2
2
  from typing import Dict, Any, Optional, List
3
- from notionary.core.converters.elements.notion_block_element import NotionBlockElement
3
+ from notionary.converters.elements.notion_block_element import NotionBlockElement
4
4
 
5
5
 
6
6
  class AudioElement(NotionBlockElement):
@@ -39,9 +39,11 @@ class AudioElement(NotionBlockElement):
39
39
  @staticmethod
40
40
  def is_audio_url(url: str) -> bool:
41
41
  """Check if URL points to an audio file."""
42
- return any(url.lower().endswith(ext) for ext in AudioElement.AUDIO_EXTENSIONS) or \
43
- "audio" in url.lower() or \
44
- "storage.googleapis.com/audio_summaries" in url.lower()
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
+ )
45
47
 
46
48
  @staticmethod
47
49
  def markdown_to_notion(text: str) -> Optional[Dict[str, Any]]:
@@ -138,4 +140,4 @@ class AudioElement(NotionBlockElement):
138
140
  "$[Voice recording](https://example.com/audio/recording.mp3)",
139
141
  "$[](https://storage.googleapis.com/audio_summaries/example.mp3)",
140
142
  ],
141
- }
143
+ }
@@ -2,7 +2,7 @@ import re
2
2
  from typing import Dict, Any, Optional, List, Tuple
3
3
  from typing_extensions import override
4
4
 
5
- from notionary.core.converters.elements.notion_block_element import NotionBlockElement
5
+ from notionary.converters.elements.notion_block_element import NotionBlockElement
6
6
 
7
7
 
8
8
  class BookmarkElement(NotionBlockElement):
@@ -2,8 +2,8 @@ from typing import Dict, Any, Optional
2
2
  from typing_extensions import override
3
3
  import re
4
4
 
5
- from notionary.core.converters.elements.text_inline_formatter import TextInlineFormatter
6
- from notionary.core.converters.elements.notion_block_element import NotionBlockElement
5
+ from notionary.converters.elements.text_inline_formatter import TextInlineFormatter
6
+ from notionary.converters.elements.notion_block_element import NotionBlockElement
7
7
 
8
8
 
9
9
  class CalloutElement(NotionBlockElement):
@@ -1,7 +1,7 @@
1
1
  from typing import Dict, Any, Optional, List, Tuple
2
2
  from typing_extensions import override
3
3
  import re
4
- from notionary.core.converters.elements.notion_block_element import NotionBlockElement
4
+ from notionary.converters.elements.notion_block_element import NotionBlockElement
5
5
 
6
6
 
7
7
  class CodeBlockElement(NotionBlockElement):
@@ -2,7 +2,7 @@ import re
2
2
  from typing import Dict, Any, Optional, List, Tuple, Callable
3
3
  from typing_extensions import override
4
4
 
5
- from notionary.core.converters.elements.notion_block_element import NotionBlockElement
5
+ from notionary.converters.elements.notion_block_element import NotionBlockElement
6
6
 
7
7
 
8
8
  class ColumnElement(NotionBlockElement):
@@ -4,7 +4,7 @@ from typing import Dict, Any, Optional
4
4
  from typing_extensions import override
5
5
  import re
6
6
 
7
- from notionary.core.converters.elements.notion_block_element import NotionBlockElement
7
+ from notionary.converters.elements.notion_block_element import NotionBlockElement
8
8
 
9
9
 
10
10
  class DividerElement(NotionBlockElement):
@@ -1,6 +1,6 @@
1
1
  import re
2
2
  from typing import Dict, Any, Optional, List
3
- from notionary.core.converters.elements.notion_block_element import NotionBlockElement
3
+ from notionary.converters.elements.notion_block_element import NotionBlockElement
4
4
 
5
5
 
6
6
  class EmbedElement(NotionBlockElement):
@@ -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
+ }
@@ -2,8 +2,8 @@ from typing import Dict, Any, Optional
2
2
  from typing_extensions import override
3
3
  import re
4
4
 
5
- from notionary.core.converters.elements.notion_block_element import NotionBlockElement
6
- from notionary.core.converters.elements.text_inline_formatter import TextInlineFormatter
5
+ from notionary.converters.elements.notion_block_element import NotionBlockElement
6
+ from notionary.converters.elements.text_inline_formatter import TextInlineFormatter
7
7
 
8
8
 
9
9
  class HeadingElement(NotionBlockElement):
@@ -1,7 +1,7 @@
1
1
  import re
2
2
  from typing import Dict, Any, Optional, List
3
3
  from typing_extensions import override
4
- from notionary.core.converters.elements.notion_block_element import NotionBlockElement
4
+ from notionary.converters.elements.notion_block_element import NotionBlockElement
5
5
 
6
6
 
7
7
  class ImageElement(NotionBlockElement):
@@ -1,8 +1,8 @@
1
1
  import re
2
2
  from typing import Dict, Any, Optional
3
3
  from typing_extensions import override
4
- from notionary.core.converters.elements.notion_block_element import NotionBlockElement
5
- from notionary.core.converters.elements.text_inline_formatter import TextInlineFormatter
4
+ from notionary.converters.elements.notion_block_element import NotionBlockElement
5
+ from notionary.converters.elements.text_inline_formatter import TextInlineFormatter
6
6
 
7
7
 
8
8
  class BulletedListElement(NotionBlockElement):
@@ -1,8 +1,8 @@
1
1
  from typing import Dict, Any, Optional
2
2
  from typing_extensions import override
3
3
 
4
- from notionary.core.converters.elements.notion_block_element import NotionBlockElement
5
- from notionary.core.converters.elements.text_inline_formatter import TextInlineFormatter
4
+ from notionary.converters.elements.notion_block_element import NotionBlockElement
5
+ from notionary.converters.elements.text_inline_formatter import TextInlineFormatter
6
6
 
7
7
 
8
8
  class ParagraphElement(NotionBlockElement):
@@ -2,7 +2,7 @@ import re
2
2
  from typing import Dict, Any, Optional, List, Tuple
3
3
  from typing_extensions import override
4
4
 
5
- from notionary.core.converters.elements.notion_block_element import NotionBlockElement
5
+ from notionary.converters.elements.notion_block_element import NotionBlockElement
6
6
 
7
7
 
8
8
  class QuoteElement(NotionBlockElement):
@@ -3,8 +3,8 @@
3
3
  from typing import Dict, Any, Optional, List, Tuple
4
4
  from typing_extensions import override
5
5
  import re
6
- from notionary.core.converters.elements.notion_block_element import NotionBlockElement
7
- from notionary.core.converters.elements.text_inline_formatter import TextInlineFormatter
6
+ from notionary.converters.elements.notion_block_element import NotionBlockElement
7
+ from notionary.converters.elements.text_inline_formatter import TextInlineFormatter
8
8
 
9
9
 
10
10
  class TableElement(NotionBlockElement):
@@ -1,8 +1,8 @@
1
1
  import re
2
2
  from typing import Dict, Any, Optional
3
3
  from typing_extensions import override
4
- from notionary.core.converters.elements.notion_block_element import NotionBlockElement
5
- from notionary.core.converters.elements.text_inline_formatter import TextInlineFormatter
4
+ from notionary.converters.elements.notion_block_element import NotionBlockElement
5
+ from notionary.converters.elements.text_inline_formatter import TextInlineFormatter
6
6
 
7
7
 
8
8
  class TodoElement(NotionBlockElement):
@@ -1,25 +1,20 @@
1
1
  import re
2
2
  from typing import Dict, Any, Optional, List, Tuple, Callable
3
3
 
4
- from notionary.core.converters.elements.notion_block_element import NotionBlockElement
4
+ from notionary.converters.elements.notion_block_element import NotionBlockElement
5
5
 
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,15 @@ class ToggleElement(NotionBlockElement):
177
171
  i += 1
178
172
  continue
179
173
 
174
+ is_transcript_toggle = cls.TRANSCRIPT_TOGGLE_PATTERN.match(line.strip())
175
+
176
+ if context_aware and is_transcript_toggle:
177
+ if i > 0 and lines[i - 1].strip().startswith("- "):
178
+ pass
179
+ else:
180
+ i += 1
181
+ continue
182
+
180
183
  start_pos = 0
181
184
  for j in range(i):
182
185
  start_pos += len(lines[j]) + 1
@@ -1,6 +1,6 @@
1
1
  import re
2
2
  from typing import Dict, Any, Optional, List
3
- from notionary.core.converters.elements.notion_block_element import NotionBlockElement
3
+ from notionary.converters.elements.notion_block_element import NotionBlockElement
4
4
 
5
5
 
6
6
  class VideoElement(NotionBlockElement):
@@ -1,9 +1,9 @@
1
1
  from typing import Dict, Any, List, Optional, Tuple
2
2
 
3
- from notionary.core.converters.registry.block_element_registry import (
3
+ from notionary.converters.registry.block_element_registry import (
4
4
  BlockElementRegistry,
5
5
  )
6
- from notionary.core.converters.registry.block_element_registry_builder import (
6
+ from notionary.converters.registry.block_element_registry_builder import (
7
7
  BlockElementRegistryBuilder,
8
8
  )
9
9
 
@@ -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
  """
@@ -1,9 +1,9 @@
1
1
  from typing import Dict, Any, List, Optional
2
2
 
3
- from notionary.core.converters.registry.block_element_registry import (
3
+ from notionary.converters.registry.block_element_registry import (
4
4
  BlockElementRegistry,
5
5
  )
6
- from notionary.core.converters.registry.block_element_registry_builder import (
6
+ from notionary.converters.registry.block_element_registry_builder import (
7
7
  BlockElementRegistryBuilder,
8
8
  )
9
9
 
@@ -1,7 +1,7 @@
1
1
  from typing import Dict, Any, Optional, List, Type
2
2
 
3
- from notionary.core.converters.elements.notion_block_element import NotionBlockElement
4
- from notionary.core.converters.elements.text_inline_formatter import TextInlineFormatter
3
+ from notionary.converters.elements.notion_block_element import NotionBlockElement
4
+ from notionary.converters.elements.text_inline_formatter import TextInlineFormatter
5
5
 
6
6
 
7
7
  class BlockElementRegistry:
@@ -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]
@@ -233,4 +233,4 @@ paragraphs, lists, quotes, etc.
233
233
  """
234
234
  element_docs = cls.generate_element_docs(element_classes)
235
235
 
236
- return cls.SYSTEM_PROMPT_TEMPLATE.format(element_docs=element_docs)
236
+ return cls.SYSTEM_PROMPT_TEMPLATE.format(element_docs=element_docs)