notionary 0.1.11__tar.gz → 0.1.13__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {notionary-0.1.11 → notionary-0.1.13}/PKG-INFO +1 -1
- notionary-0.1.13/notionary/__init__.py +24 -0
- {notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/converters/elements/audio_element.py +7 -5
- {notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/converters/elements/bookmark_element.py +1 -1
- {notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/converters/elements/callout_element.py +2 -2
- {notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/converters/elements/code_block_element.py +1 -1
- {notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/converters/elements/column_element.py +1 -1
- {notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/converters/elements/divider_element.py +1 -1
- {notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/converters/elements/embed_element.py +3 -5
- {notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/converters/elements/heading_element.py +2 -2
- {notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/converters/elements/image_element.py +1 -1
- {notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/converters/elements/list_element.py +2 -2
- {notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/converters/elements/paragraph_element.py +2 -2
- {notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/converters/elements/qoute_element.py +1 -1
- {notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/converters/elements/table_element.py +2 -2
- {notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/converters/elements/todo_lists.py +2 -2
- {notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/converters/elements/toggle_element.py +24 -21
- {notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/converters/elements/video_element.py +1 -1
- {notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/converters/markdown_to_notion_converter.py +72 -111
- {notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/converters/notion_to_markdown_converter.py +2 -2
- {notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/converters/registry/block_element_registry.py +5 -5
- {notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/converters/registry/block_element_registry_builder.py +18 -18
- notionary-0.1.13/notionary/database/database_discovery.py +142 -0
- {notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/database/database_info_service.py +1 -1
- notionary-0.1.11/notionary/core/database/notion_database_manager.py → notionary-0.1.13/notionary/database/notion_database.py +33 -57
- notionary-0.1.11/notionary/core/database/notion_database_manager_factory.py → notionary-0.1.13/notionary/database/notion_database_factory.py +18 -16
- {notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/notion_client.py +4 -2
- notionary-0.1.13/notionary/page/content/notion_page_content_chunker.py +84 -0
- {notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/page/content/page_content_manager.py +29 -13
- {notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/page/metadata/metadata_editor.py +59 -46
- {notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/page/metadata/notion_icon_manager.py +10 -12
- {notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/page/metadata/notion_page_cover_manager.py +16 -21
- notionary-0.1.13/notionary/page/notion_page.py +504 -0
- notionary-0.1.13/notionary/page/notion_page_factory.py +256 -0
- {notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/page/properites/database_property_service.py +115 -99
- {notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/page/properites/page_property_manager.py +81 -52
- {notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/page/properites/property_formatter.py +1 -1
- {notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/page/properites/property_operation_result.py +43 -30
- {notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/page/properites/property_value_extractor.py +26 -8
- {notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/page/relations/notion_page_relation_manager.py +72 -53
- {notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/page/relations/notion_page_title_resolver.py +12 -12
- {notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/page/relations/page_database_relation.py +15 -15
- {notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/page/relations/relation_operation_result.py +50 -41
- {notionary-0.1.11 → notionary-0.1.13}/notionary/util/page_id_utils.py +14 -8
- {notionary-0.1.11 → notionary-0.1.13}/notionary.egg-info/PKG-INFO +1 -1
- notionary-0.1.13/notionary.egg-info/SOURCES.txt +59 -0
- {notionary-0.1.11 → notionary-0.1.13}/setup.py +2 -2
- notionary-0.1.11/notionary/__init__.py +0 -9
- notionary-0.1.11/notionary/core/database/notion_database_schema.py +0 -104
- notionary-0.1.11/notionary/core/page/notion_page_manager.py +0 -322
- notionary-0.1.11/notionary.egg-info/SOURCES.txt +0 -57
- {notionary-0.1.11 → notionary-0.1.13}/LICENSE +0 -0
- {notionary-0.1.11 → notionary-0.1.13}/README.md +0 -0
- {notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/converters/__init__.py +0 -0
- {notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/converters/elements/notion_block_element.py +0 -0
- {notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/converters/elements/text_inline_formatter.py +0 -0
- {notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/database/models/page_result.py +0 -0
- {notionary-0.1.11 → notionary-0.1.13}/notionary/exceptions/database_exceptions.py +0 -0
- {notionary-0.1.11 → notionary-0.1.13}/notionary/exceptions/page_creation_exception.py +0 -0
- {notionary-0.1.11 → notionary-0.1.13}/notionary/util/logging_mixin.py +0 -0
- {notionary-0.1.11 → notionary-0.1.13}/notionary/util/singleton_decorator.py +0 -0
- {notionary-0.1.11 → notionary-0.1.13}/notionary.egg-info/dependency_links.txt +0 -0
- {notionary-0.1.11 → notionary-0.1.13}/notionary.egg-info/requires.txt +0 -0
- {notionary-0.1.11 → notionary-0.1.13}/notionary.egg-info/top_level.txt +0 -0
- {notionary-0.1.11 → notionary-0.1.13}/setup.cfg +0 -0
@@ -0,0 +1,24 @@
|
|
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
|
+
)
|
14
|
+
|
15
|
+
__all__ = [
|
16
|
+
"NotionClient",
|
17
|
+
"NotionDatabase",
|
18
|
+
"NotionDatabaseFactory",
|
19
|
+
"DatabaseDiscovery",
|
20
|
+
"NotionPage",
|
21
|
+
"NotionPageFactory",
|
22
|
+
"BlockElementRegistry",
|
23
|
+
"BlockElementRegistryBuilder",
|
24
|
+
]
|
{notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/converters/elements/audio_element.py
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
import re
|
2
2
|
from typing import Dict, Any, Optional, List
|
3
|
-
from notionary.
|
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
|
43
|
-
|
44
|
-
|
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.
|
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.
|
6
|
-
from notionary.
|
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.
|
4
|
+
from notionary.converters.elements.notion_block_element import NotionBlockElement
|
5
5
|
|
6
6
|
|
7
7
|
class CodeBlockElement(NotionBlockElement):
|
{notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/converters/elements/column_element.py
RENAMED
@@ -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.
|
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.
|
7
|
+
from notionary.converters.elements.notion_block_element import NotionBlockElement
|
8
8
|
|
9
9
|
|
10
10
|
class DividerElement(NotionBlockElement):
|
{notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/converters/elements/embed_element.py
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
import re
|
2
2
|
from typing import Dict, Any, Optional, List
|
3
|
-
from notionary.
|
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.
|
6
|
-
from notionary.
|
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):
|
{notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/converters/elements/image_element.py
RENAMED
@@ -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.
|
4
|
+
from notionary.converters.elements.notion_block_element import NotionBlockElement
|
5
5
|
|
6
6
|
|
7
7
|
class ImageElement(NotionBlockElement):
|
{notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/converters/elements/list_element.py
RENAMED
@@ -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.
|
5
|
-
from notionary.
|
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.
|
5
|
-
from notionary.
|
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):
|
{notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/converters/elements/qoute_element.py
RENAMED
@@ -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.
|
5
|
+
from notionary.converters.elements.notion_block_element import NotionBlockElement
|
6
6
|
|
7
7
|
|
8
8
|
class QuoteElement(NotionBlockElement):
|
{notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/converters/elements/table_element.py
RENAMED
@@ -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.
|
7
|
-
from notionary.
|
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):
|
{notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/converters/elements/todo_lists.py
RENAMED
@@ -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.
|
5
|
-
from notionary.
|
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):
|
{notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/converters/elements/toggle_element.py
RENAMED
@@ -1,25 +1,20 @@
|
|
1
1
|
import re
|
2
2
|
from typing import Dict, Any, Optional, List, Tuple, Callable
|
3
3
|
|
4
|
-
from notionary.
|
4
|
+
from notionary.converters.elements.notion_block_element import NotionBlockElement
|
5
5
|
|
6
6
|
|
7
7
|
class ToggleElement(NotionBlockElement):
|
8
8
|
"""
|
9
|
-
|
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,
|
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
|
-
|
149
|
+
Verbesserte find_matches-Methode, die Kontext beim Finden von Toggles berücksichtigt.
|
156
150
|
|
157
151
|
Args:
|
158
|
-
text:
|
159
|
-
process_nested_content:
|
160
|
-
|
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
|
-
|
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
|
{notionary-0.1.11/notionary/core → notionary-0.1.13/notionary}/converters/elements/video_element.py
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
import re
|
2
2
|
from typing import Dict, Any, Optional, List
|
3
|
-
from notionary.
|
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.
|
3
|
+
from notionary.converters.registry.block_element_registry import (
|
4
4
|
BlockElementRegistry,
|
5
5
|
)
|
6
|
-
from notionary.
|
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
|
-
#
|
50
|
-
|
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
|
-
|
54
|
-
|
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(
|
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
|
-
#
|
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
|
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
|
82
|
+
def _identify_toggle_blocks(
|
71
83
|
self, text: str
|
72
|
-
) ->
|
84
|
+
) -> List[Tuple[int, int, Dict[str, Any]]]:
|
73
85
|
"""
|
74
|
-
|
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
|
-
|
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
|
-
|
95
|
-
return text, []
|
106
|
+
return []
|
96
107
|
|
97
|
-
# Use the find_matches method
|
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(
|
100
|
-
|
101
|
-
|
102
|
-
|
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
|
138
|
-
self, text: str
|
139
|
-
) ->
|
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
|
-
|
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
|
-
|
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
|
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
|
148
|
+
# Find all matches for this element
|
170
149
|
if hasattr(element, "set_converter_callback"):
|
171
|
-
matches = element.find_matches(
|
150
|
+
matches = element.find_matches(text, self.convert)
|
172
151
|
else:
|
173
|
-
matches = element.find_matches(
|
152
|
+
matches = element.find_matches(text)
|
174
153
|
|
175
154
|
if not matches:
|
176
155
|
continue
|
177
156
|
|
178
|
-
|
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
|
-
|
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(
|
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
|
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
|
227
|
-
if
|
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.
|
3
|
+
from notionary.converters.registry.block_element_registry import (
|
4
4
|
BlockElementRegistry,
|
5
5
|
)
|
6
|
-
from notionary.
|
6
|
+
from notionary.converters.registry.block_element_registry_builder import (
|
7
7
|
BlockElementRegistryBuilder,
|
8
8
|
)
|
9
9
|
|