notionary 0.2.22__py3-none-any.whl → 0.2.24__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.
- notionary/__init__.py +1 -1
- notionary/blocks/__init__.py +3 -1
- notionary/blocks/audio/__init__.py +0 -2
- notionary/blocks/audio/audio_element.py +92 -49
- notionary/blocks/audio/audio_markdown_node.py +4 -17
- notionary/blocks/bookmark/__init__.py +0 -2
- notionary/blocks/bookmark/bookmark_markdown_node.py +5 -21
- notionary/blocks/breadcrumbs/__init__.py +0 -2
- notionary/blocks/breadcrumbs/breadcrumb_markdown_node.py +2 -21
- notionary/blocks/bulleted_list/__init__.py +0 -2
- notionary/blocks/bulleted_list/bulleted_list_markdown_node.py +3 -17
- notionary/blocks/bulleted_list/bulleted_list_models.py +0 -1
- notionary/blocks/callout/__init__.py +0 -2
- notionary/blocks/callout/callout_markdown_node.py +4 -18
- notionary/blocks/callout/callout_models.py +3 -4
- notionary/blocks/child_database/child_database_element.py +2 -4
- notionary/blocks/code/code_markdown_node.py +5 -19
- notionary/blocks/column/__init__.py +0 -4
- notionary/blocks/column/column_list_markdown_node.py +3 -19
- notionary/blocks/column/column_markdown_node.py +4 -21
- notionary/blocks/divider/__init__.py +0 -2
- notionary/blocks/divider/divider_markdown_node.py +2 -16
- notionary/blocks/embed/__init__.py +0 -2
- notionary/blocks/embed/embed_markdown_node.py +4 -17
- notionary/blocks/equation/__init__.py +0 -1
- notionary/blocks/equation/equation_element_markdown_node.py +3 -15
- notionary/blocks/file/__init__.py +0 -2
- notionary/blocks/file/file_element.py +67 -46
- notionary/blocks/file/file_element_markdown_node.py +4 -17
- notionary/blocks/heading/__init__.py +0 -2
- notionary/blocks/heading/heading_markdown_node.py +5 -19
- notionary/blocks/heading/heading_models.py +3 -3
- notionary/blocks/image_block/__init__.py +0 -2
- notionary/blocks/image_block/image_element.py +66 -25
- notionary/blocks/image_block/image_markdown_node.py +5 -20
- notionary/{markdown → blocks/markdown}/markdown_builder.py +29 -233
- notionary/blocks/markdown/markdown_node.py +25 -0
- notionary/blocks/mixins/file_upload/__init__.py +3 -0
- notionary/blocks/mixins/file_upload/file_upload_mixin.py +320 -0
- notionary/blocks/numbered_list/__init__.py +0 -1
- notionary/blocks/numbered_list/numbered_list_markdown_node.py +3 -17
- notionary/blocks/numbered_list/numbered_list_models.py +3 -3
- notionary/blocks/paragraph/__init__.py +0 -2
- notionary/blocks/paragraph/paragraph_markdown_node.py +3 -13
- notionary/blocks/pdf/__init__.py +0 -2
- notionary/blocks/pdf/pdf_element.py +81 -32
- notionary/blocks/pdf/pdf_markdown_node.py +5 -18
- notionary/blocks/quote/__init__.py +0 -2
- notionary/blocks/quote/quote_markdown_node.py +3 -13
- notionary/blocks/registry/__init__.py +1 -2
- notionary/blocks/registry/block_registry.py +116 -61
- notionary/blocks/rich_text/text_inline_formatter.py +1 -1
- notionary/blocks/table/__init__.py +0 -2
- notionary/blocks/table/table_markdown_node.py +17 -16
- notionary/blocks/table_of_contents/__init__.py +0 -2
- notionary/blocks/table_of_contents/table_of_contents_element.py +27 -15
- notionary/blocks/table_of_contents/table_of_contents_markdown_node.py +3 -17
- notionary/blocks/table_of_contents/table_of_contents_models.py +2 -2
- notionary/blocks/todo/__init__.py +0 -2
- notionary/blocks/todo/todo_markdown_node.py +9 -20
- notionary/blocks/todo/todo_models.py +2 -3
- notionary/blocks/toggle/__init__.py +0 -2
- notionary/blocks/toggle/toggle_markdown_node.py +5 -19
- notionary/blocks/toggleable_heading/__init__.py +0 -2
- notionary/blocks/toggleable_heading/toggleable_heading_markdown_node.py +6 -23
- notionary/blocks/video/__init__.py +0 -2
- notionary/blocks/video/video_element.py +110 -34
- notionary/blocks/video/video_markdown_node.py +4 -15
- notionary/comments/__init__.py +26 -0
- notionary/comments/client.py +211 -0
- notionary/comments/models.py +129 -0
- notionary/file_upload/client.py +3 -2
- notionary/file_upload/models.py +10 -1
- notionary/file_upload/notion_file_upload.py +5 -5
- notionary/page/client.py +1 -6
- notionary/page/markdown_whitespace_processor.py +129 -0
- notionary/page/notion_page.py +87 -48
- notionary/page/page_content_deleting_service.py +1 -1
- notionary/page/page_content_writer.py +32 -129
- notionary/page/page_context.py +0 -6
- notionary/page/reader/handler/column_list_renderer.py +2 -2
- notionary/page/reader/handler/column_renderer.py +2 -2
- notionary/page/reader/handler/line_renderer.py +2 -2
- notionary/page/reader/handler/toggle_renderer.py +2 -2
- notionary/page/reader/handler/toggleable_heading_renderer.py +2 -2
- notionary/page/writer/handler/toggle_handler.py +8 -4
- notionary/page/writer/handler/toggleable_heading_handler.py +3 -2
- notionary/page/writer/markdown_to_notion_converter.py +74 -30
- notionary/schemas/__init__.py +3 -0
- notionary/schemas/base.py +73 -0
- notionary/shared/__init__.py +3 -0
- notionary/{blocks/rich_text → shared}/name_to_id_resolver.py +0 -2
- {notionary-0.2.22.dist-info → notionary-0.2.24.dist-info}/METADATA +15 -2
- {notionary-0.2.22.dist-info → notionary-0.2.24.dist-info}/RECORD +97 -95
- notionary/blocks/guards.py +0 -22
- notionary/blocks/registry/block_registry_builder.py +0 -264
- notionary/markdown/makdown_document_model.py +0 -0
- notionary/markdown/markdown_document_model.py +0 -228
- notionary/markdown/markdown_node.py +0 -30
- notionary/models/notion_database_response.py +0 -0
- notionary/page/writer/markdown_to_notion_formatting_post_processor.py +0 -73
- notionary/page/writer/markdown_to_notion_post_processor.py +0 -0
- /notionary/{markdown/___init__.py → blocks/markdown/markdown_document_model.py} +0 -0
- {notionary-0.2.22.dist-info → notionary-0.2.24.dist-info}/LICENSE +0 -0
- {notionary-0.2.22.dist-info → notionary-0.2.24.dist-info}/WHEEL +0 -0
notionary/page/notion_page.py
CHANGED
@@ -5,16 +5,20 @@ import random
|
|
5
5
|
from typing import TYPE_CHECKING, Any, Callable, Optional, Union
|
6
6
|
|
7
7
|
from notionary.blocks.client import NotionBlockClient
|
8
|
+
from notionary.comments import CommentClient, Comment
|
8
9
|
from notionary.blocks.syntax_prompt_builder import SyntaxPromptBuilder
|
9
10
|
from notionary.blocks.models import DatabaseParent
|
10
11
|
from notionary.blocks.registry.block_registry import BlockRegistry
|
11
|
-
from notionary.blocks.registry.block_registry_builder import BlockRegistryBuilder
|
12
12
|
from notionary.database.client import NotionDatabaseClient
|
13
|
-
from notionary.
|
13
|
+
from notionary.file_upload.client import NotionFileUploadClient
|
14
|
+
from notionary.blocks.markdown.markdown_builder import MarkdownBuilder
|
15
|
+
from notionary.schemas import NotionContentSchema
|
16
|
+
from notionary.page import page_context
|
14
17
|
from notionary.page.client import NotionPageClient
|
15
18
|
from notionary.page.models import NotionPageResponse
|
16
19
|
from notionary.page.page_content_deleting_service import PageContentDeletingService
|
17
20
|
from notionary.page.page_content_writer import PageContentWriter
|
21
|
+
from notionary.page.page_context import PageContextProvider, page_context
|
18
22
|
from notionary.page.property_formatter import NotionPropertyFormatter
|
19
23
|
from notionary.page.reader.page_content_retriever import PageContentRetriever
|
20
24
|
from notionary.page.utils import extract_property_value
|
@@ -24,6 +28,7 @@ from notionary.util.fuzzy import find_best_match
|
|
24
28
|
if TYPE_CHECKING:
|
25
29
|
from notionary import NotionDatabase
|
26
30
|
|
31
|
+
|
27
32
|
class NotionPage(LoggingMixin):
|
28
33
|
"""
|
29
34
|
Managing content and metadata of a Notion page.
|
@@ -56,8 +61,9 @@ class NotionPage(LoggingMixin):
|
|
56
61
|
|
57
62
|
self._client = NotionPageClient(token=token)
|
58
63
|
self._block_client = NotionBlockClient(token=token)
|
64
|
+
self._comment_client = CommentClient(token=token)
|
59
65
|
self._page_data = None
|
60
|
-
|
66
|
+
|
61
67
|
self.block_element_registry = BlockRegistry.create_registry()
|
62
68
|
|
63
69
|
self._page_content_writer = PageContentWriter(
|
@@ -74,6 +80,8 @@ class NotionPage(LoggingMixin):
|
|
74
80
|
block_registry=self.block_element_registry,
|
75
81
|
)
|
76
82
|
|
83
|
+
self.page_context_provider = self._setup_page_context_provider()
|
84
|
+
|
77
85
|
@classmethod
|
78
86
|
async def from_page_id(
|
79
87
|
cls, page_id: str, token: Optional[str] = None
|
@@ -202,17 +210,49 @@ class NotionPage(LoggingMixin):
|
|
202
210
|
def is_in_trash(self) -> bool:
|
203
211
|
return self._is_in_trash
|
204
212
|
|
205
|
-
@property
|
206
|
-
def block_registry_builder(self) -> BlockRegistryBuilder:
|
207
|
-
"""
|
208
|
-
Returns the block registry builder for this page.
|
209
|
-
"""
|
210
|
-
return self.block_element_registry.builder
|
211
|
-
|
212
213
|
def get_prompt_information(self) -> str:
|
213
214
|
markdown_syntax_builder = SyntaxPromptBuilder()
|
214
215
|
return markdown_syntax_builder.build_concise_reference()
|
215
216
|
|
217
|
+
async def get_comments(self) -> list[Comment]:
|
218
|
+
return await self._comment_client.list_all_comments_for_page(
|
219
|
+
page_id=self._page_id
|
220
|
+
)
|
221
|
+
|
222
|
+
async def post_comment(
|
223
|
+
self,
|
224
|
+
content: str,
|
225
|
+
*,
|
226
|
+
discussion_id: Optional[str] = None,
|
227
|
+
rich_text: Optional[list[dict[str, Any]]] = None,
|
228
|
+
) -> Optional[Comment]:
|
229
|
+
"""
|
230
|
+
Post a comment on this page.
|
231
|
+
|
232
|
+
Args:
|
233
|
+
content: The plain text content of the comment
|
234
|
+
discussion_id: Optional discussion ID to reply to an existing discussion
|
235
|
+
rich_text: Optional rich text formatting for the comment content
|
236
|
+
|
237
|
+
Returns:
|
238
|
+
Comment: The created comment object, or None if creation failed
|
239
|
+
"""
|
240
|
+
try:
|
241
|
+
# Use the comment client to create the comment
|
242
|
+
comment = await self._comment_client.create_comment(
|
243
|
+
page_id=self._page_id,
|
244
|
+
content=content,
|
245
|
+
discussion_id=discussion_id,
|
246
|
+
rich_text=rich_text,
|
247
|
+
)
|
248
|
+
self.logger.info(f"Successfully posted comment on page '{self._title}'")
|
249
|
+
return comment
|
250
|
+
except Exception as e:
|
251
|
+
self.logger.error(
|
252
|
+
f"Failed to post comment on page '{self._title}': {str(e)}"
|
253
|
+
)
|
254
|
+
return None
|
255
|
+
|
216
256
|
async def set_title(self, title: str) -> str:
|
217
257
|
"""
|
218
258
|
Set the title of the page.
|
@@ -234,43 +274,31 @@ class NotionPage(LoggingMixin):
|
|
234
274
|
|
235
275
|
async def append_markdown(
|
236
276
|
self,
|
237
|
-
content: Union[
|
238
|
-
|
239
|
-
|
240
|
-
append_divider: bool = False,
|
277
|
+
content: Union[
|
278
|
+
str, Callable[[MarkdownBuilder], MarkdownBuilder], NotionContentSchema
|
279
|
+
],
|
241
280
|
) -> bool:
|
242
281
|
"""
|
243
|
-
Append markdown content to the page.
|
244
|
-
|
245
|
-
Args:
|
246
|
-
content: Either raw markdown text OR a callback function that receives a MarkdownBuilder
|
247
|
-
prepend_table_of_contents: Whether to prepend table of contents
|
248
|
-
append_divider: Whether to append a divider
|
249
|
-
|
250
|
-
Returns:
|
251
|
-
bool: True if successful, False otherwise
|
282
|
+
Append markdown content to the page using text, builder callback, MarkdownDocumentModel, or NotionContentSchema.
|
252
283
|
"""
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
return result is not None
|
284
|
+
async with page_context(self.page_context_provider):
|
285
|
+
result = await self._page_content_writer.append_markdown(
|
286
|
+
content=content,
|
287
|
+
)
|
288
|
+
return result is not None
|
259
289
|
|
260
290
|
async def replace_content(
|
261
291
|
self,
|
262
|
-
content: Union[
|
263
|
-
|
264
|
-
|
265
|
-
append_divider: bool = False,
|
292
|
+
content: Union[
|
293
|
+
str, Callable[[MarkdownBuilder], MarkdownBuilder], NotionContentSchema
|
294
|
+
],
|
266
295
|
) -> bool:
|
267
296
|
"""
|
268
297
|
Replace the entire page content with new markdown content.
|
269
298
|
|
270
299
|
Args:
|
271
|
-
content: Either raw markdown text
|
272
|
-
|
273
|
-
append_divider: Whether to append a divider
|
300
|
+
content: Either raw markdown text, a callback function that receives a MarkdownBuilder,
|
301
|
+
a MarkdownDocumentModel, or a NotionContentSchema
|
274
302
|
|
275
303
|
Returns:
|
276
304
|
bool: True if successful, False otherwise
|
@@ -281,8 +309,6 @@ class NotionPage(LoggingMixin):
|
|
281
309
|
|
282
310
|
result = await self._page_content_writer.append_markdown(
|
283
311
|
content=content,
|
284
|
-
prepend_table_of_contents=prepend_table_of_contents,
|
285
|
-
append_divider=append_divider,
|
286
312
|
)
|
287
313
|
return result is not None
|
288
314
|
|
@@ -320,27 +346,33 @@ class NotionPage(LoggingMixin):
|
|
320
346
|
|
321
347
|
self.logger.error(f"Error updating page emoji: {str(e)}")
|
322
348
|
return None
|
323
|
-
|
349
|
+
|
324
350
|
async def create_child_database(self, title: str) -> NotionDatabase:
|
325
351
|
from notionary import NotionDatabase
|
352
|
+
|
326
353
|
database_client = NotionDatabaseClient(token=self._client.token)
|
327
|
-
|
328
|
-
create_database_response =
|
354
|
+
|
355
|
+
create_database_response = await database_client.create_database(
|
329
356
|
title=title,
|
330
357
|
parent_page_id=self._page_id,
|
331
358
|
)
|
332
|
-
|
333
|
-
return await NotionDatabase.from_database_id(
|
334
|
-
|
359
|
+
|
360
|
+
return await NotionDatabase.from_database_id(
|
361
|
+
id=create_database_response.id, token=self._client.token
|
362
|
+
)
|
363
|
+
|
335
364
|
async def create_child_page(self, title: str) -> NotionPage:
|
336
365
|
from notionary import NotionPage
|
366
|
+
|
337
367
|
child_page_response = await self._client.create_page(
|
338
368
|
parent_page_id=self._page_id,
|
339
369
|
title=title,
|
340
370
|
)
|
341
|
-
|
342
|
-
return await NotionPage.from_page_id(
|
343
|
-
|
371
|
+
|
372
|
+
return await NotionPage.from_page_id(
|
373
|
+
page_id=child_page_response.id, token=self._client.token
|
374
|
+
)
|
375
|
+
|
344
376
|
async def set_external_icon(self, url: str) -> Optional[str]:
|
345
377
|
"""
|
346
378
|
Sets the page icon to an external image.
|
@@ -636,4 +668,11 @@ class NotionPage(LoggingMixin):
|
|
636
668
|
"""Extract parent database ID from page response."""
|
637
669
|
parent = page_response.parent
|
638
670
|
if isinstance(parent, DatabaseParent):
|
639
|
-
return parent.database_id
|
671
|
+
return parent.database_id
|
672
|
+
|
673
|
+
def _setup_page_context_provider(self) -> PageContextProvider:
|
674
|
+
return PageContextProvider(
|
675
|
+
page_id=self._page_id,
|
676
|
+
database_client=NotionDatabaseClient(token=self._client.token),
|
677
|
+
file_upload_client=NotionFileUploadClient(),
|
678
|
+
)
|
@@ -27,7 +27,7 @@ class PageContentDeletingService(LoggingMixin):
|
|
27
27
|
return None
|
28
28
|
|
29
29
|
# Use PageContentRetriever for sophisticated markdown conversion
|
30
|
-
deleted_content = self._content_retriever._convert_blocks_to_markdown(
|
30
|
+
deleted_content = await self._content_retriever._convert_blocks_to_markdown(
|
31
31
|
children_response.results, indent_level=0
|
32
32
|
)
|
33
33
|
|
@@ -1,10 +1,10 @@
|
|
1
1
|
from typing import Callable, Optional, Union
|
2
2
|
|
3
3
|
from notionary.blocks.client import NotionBlockClient
|
4
|
-
from notionary.blocks.divider import DividerElement
|
5
4
|
from notionary.blocks.registry.block_registry import BlockRegistry
|
6
|
-
from notionary.blocks.
|
7
|
-
from notionary.
|
5
|
+
from notionary.blocks.markdown.markdown_builder import MarkdownBuilder
|
6
|
+
from notionary.schemas.base import NotionContentSchema
|
7
|
+
from notionary.page.markdown_whitespace_processor import MarkdownWhitespaceProcessor
|
8
8
|
from notionary.page.writer.markdown_to_notion_converter import MarkdownToNotionConverter
|
9
9
|
from notionary.util import LoggingMixin
|
10
10
|
|
@@ -21,36 +21,18 @@ class PageContentWriter(LoggingMixin):
|
|
21
21
|
|
22
22
|
async def append_markdown(
|
23
23
|
self,
|
24
|
-
content: Union[
|
25
|
-
|
26
|
-
|
27
|
-
prepend_table_of_contents: bool = False,
|
24
|
+
content: Union[
|
25
|
+
str, Callable[[MarkdownBuilder], MarkdownBuilder], NotionContentSchema
|
26
|
+
],
|
28
27
|
) -> Optional[str]:
|
29
28
|
"""
|
30
|
-
Append markdown content to a Notion page using
|
29
|
+
Append markdown content to a Notion page using text, builder callback, MarkdownDocumentModel, or NotionContentSchema.
|
31
30
|
"""
|
31
|
+
markdown = self._extract_markdown_from_param(content)
|
32
32
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
builder = MarkdownBuilder()
|
37
|
-
content(builder)
|
38
|
-
final_markdown = builder.build()
|
39
|
-
else:
|
40
|
-
raise ValueError(
|
41
|
-
"content must be either a string or a callable that takes a MarkdownBuilder"
|
42
|
-
)
|
43
|
-
|
44
|
-
# Add optional components
|
45
|
-
if prepend_table_of_contents:
|
46
|
-
self._ensure_table_of_contents_exists_in_registry()
|
47
|
-
final_markdown = "[toc]\n\n" + final_markdown
|
48
|
-
|
49
|
-
if append_divider:
|
50
|
-
self._ensure_divider_exists_in_registry()
|
51
|
-
final_markdown = final_markdown + "\n\n---\n"
|
52
|
-
|
53
|
-
processed_markdown = self._process_markdown_whitespace(final_markdown)
|
33
|
+
processed_markdown = MarkdownWhitespaceProcessor.process_markdown_whitespace(
|
34
|
+
markdown
|
35
|
+
)
|
54
36
|
|
55
37
|
try:
|
56
38
|
blocks = await self._markdown_to_notion_converter.convert(
|
@@ -72,106 +54,27 @@ class PageContentWriter(LoggingMixin):
|
|
72
54
|
self.logger.error("Error appending markdown: %s", str(e), exc_info=True)
|
73
55
|
return None
|
74
56
|
|
75
|
-
def
|
76
|
-
"""Process markdown text to normalize whitespace while preserving code blocks."""
|
77
|
-
lines = markdown_text.split("\n")
|
78
|
-
if not lines:
|
79
|
-
return ""
|
80
|
-
|
81
|
-
return self._process_whitespace_lines(lines)
|
82
|
-
|
83
|
-
def _process_whitespace_lines(self, lines: list[str]) -> str:
|
84
|
-
"""Process all lines and return the processed markdown."""
|
85
|
-
processed_lines = []
|
86
|
-
in_code_block = False
|
87
|
-
current_code_block = []
|
88
|
-
|
89
|
-
for line in lines:
|
90
|
-
processed_lines, in_code_block, current_code_block = (
|
91
|
-
self._process_single_line(
|
92
|
-
line, processed_lines, in_code_block, current_code_block
|
93
|
-
)
|
94
|
-
)
|
95
|
-
|
96
|
-
return "\n".join(processed_lines)
|
97
|
-
|
98
|
-
def _process_single_line(
|
57
|
+
def _extract_markdown_from_param(
|
99
58
|
self,
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
return
|
113
|
-
else:
|
114
|
-
processed_lines.append(line.lstrip())
|
115
|
-
return processed_lines, in_code_block, current_code_block
|
59
|
+
content: Union[
|
60
|
+
str, Callable[[MarkdownBuilder], MarkdownBuilder], NotionContentSchema
|
61
|
+
],
|
62
|
+
) -> str:
|
63
|
+
"""
|
64
|
+
Prepare markdown content from string, builder callback, MarkdownDocumentModel, or NotionContentSchema.
|
65
|
+
"""
|
66
|
+
if isinstance(content, str):
|
67
|
+
return content
|
68
|
+
elif isinstance(content, NotionContentSchema):
|
69
|
+
# Use new injection-based API
|
70
|
+
builder = MarkdownBuilder()
|
71
|
+
return content.to_notion_content(builder)
|
116
72
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
in_code_block: bool,
|
122
|
-
current_code_block: list[str],
|
123
|
-
) -> tuple[list[str], bool, list[str]]:
|
124
|
-
"""Handle code block start/end markers."""
|
125
|
-
if not in_code_block:
|
126
|
-
return self._start_code_block(line, processed_lines)
|
73
|
+
elif callable(content):
|
74
|
+
builder = MarkdownBuilder()
|
75
|
+
content(builder)
|
76
|
+
return builder.build()
|
127
77
|
else:
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
self, line: str, processed_lines: list[str]
|
132
|
-
) -> tuple[list[str], bool, list[str]]:
|
133
|
-
"""Start a new code block."""
|
134
|
-
processed_lines.append(self._normalize_code_block_start(line))
|
135
|
-
return processed_lines, True, []
|
136
|
-
|
137
|
-
def _end_code_block(
|
138
|
-
self, processed_lines: list[str], current_code_block: list[str]
|
139
|
-
) -> tuple[list[str], bool, list[str]]:
|
140
|
-
"""End the current code block."""
|
141
|
-
processed_lines.extend(self._normalize_code_block_content(current_code_block))
|
142
|
-
processed_lines.append("```")
|
143
|
-
return processed_lines, False, []
|
144
|
-
|
145
|
-
def _is_code_block_marker(self, line: str) -> bool:
|
146
|
-
"""Check if line is a code block marker."""
|
147
|
-
return line.lstrip().startswith("```")
|
148
|
-
|
149
|
-
def _normalize_code_block_start(self, line: str) -> str:
|
150
|
-
"""Normalize code block opening marker."""
|
151
|
-
language = line.lstrip().replace("```", "", 1).strip()
|
152
|
-
return "```" + language
|
153
|
-
|
154
|
-
def _normalize_code_block_content(self, code_lines: list[str]) -> list[str]:
|
155
|
-
"""Normalize code block indentation."""
|
156
|
-
if not code_lines:
|
157
|
-
return []
|
158
|
-
|
159
|
-
# Find minimum indentation from non-empty lines
|
160
|
-
non_empty_lines = [line for line in code_lines if line.strip()]
|
161
|
-
if not non_empty_lines:
|
162
|
-
return [""] * len(code_lines)
|
163
|
-
|
164
|
-
min_indent = min(len(line) - len(line.lstrip()) for line in non_empty_lines)
|
165
|
-
if min_indent == 0:
|
166
|
-
return code_lines
|
167
|
-
|
168
|
-
# Remove common indentation
|
169
|
-
return ["" if not line.strip() else line[min_indent:] for line in code_lines]
|
170
|
-
|
171
|
-
def _ensure_table_of_contents_exists_in_registry(self) -> None:
|
172
|
-
"""Ensure TableOfContents is registered in the block registry."""
|
173
|
-
self.block_registry.register(TableOfContentsElement)
|
174
|
-
|
175
|
-
def _ensure_divider_exists_in_registry(self) -> None:
|
176
|
-
"""Ensure DividerBlock is registered in the block registry."""
|
177
|
-
self.block_registry.register(DividerElement)
|
78
|
+
raise ValueError(
|
79
|
+
"content must be either a string, a NotionContentSchema, a MarkdownDocumentModel, or a callable that takes a MarkdownBuilder"
|
80
|
+
)
|
notionary/page/page_context.py
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
# notionary/blocks/context/page_context.py
|
2
1
|
from __future__ import annotations
|
3
2
|
|
4
3
|
from typing import TYPE_CHECKING, Optional
|
@@ -35,11 +34,6 @@ def get_page_context() -> PageContextProvider:
|
|
35
34
|
return context
|
36
35
|
|
37
36
|
|
38
|
-
def get_page_context_optional() -> Optional[PageContextProvider]:
|
39
|
-
"""Get current page context or None if not available."""
|
40
|
-
return _page_context.get()
|
41
|
-
|
42
|
-
|
43
37
|
class page_context:
|
44
38
|
"""Async-only context manager for page operations."""
|
45
39
|
|
@@ -8,7 +8,7 @@ class ColumnListRenderer(BlockHandler):
|
|
8
8
|
def _can_handle(self, context: BlockRenderingContext) -> bool:
|
9
9
|
return ColumnListElement.match_notion(context.block)
|
10
10
|
|
11
|
-
def _process(self, context: BlockRenderingContext) -> None:
|
11
|
+
async def _process(self, context: BlockRenderingContext) -> None:
|
12
12
|
# Create column list start line
|
13
13
|
column_list_start = "::: columns"
|
14
14
|
|
@@ -28,7 +28,7 @@ class ColumnListRenderer(BlockHandler):
|
|
28
28
|
|
29
29
|
# Create a temporary retriever to process children
|
30
30
|
retriever = PageContentRetriever(context.block_registry)
|
31
|
-
children_markdown = retriever._convert_blocks_to_markdown(
|
31
|
+
children_markdown = await retriever._convert_blocks_to_markdown(
|
32
32
|
context.get_children_blocks(),
|
33
33
|
indent_level=0, # No indentation for content inside column lists
|
34
34
|
)
|
@@ -8,7 +8,7 @@ class ColumnRenderer(BlockHandler):
|
|
8
8
|
def _can_handle(self, context: BlockRenderingContext) -> bool:
|
9
9
|
return ColumnElement.match_notion(context.block)
|
10
10
|
|
11
|
-
def _process(self, context: BlockRenderingContext) -> None:
|
11
|
+
async def _process(self, context: BlockRenderingContext) -> None:
|
12
12
|
# Get the column start line with potential width ratio
|
13
13
|
column_start = self._extract_column_start(context.block)
|
14
14
|
|
@@ -28,7 +28,7 @@ class ColumnRenderer(BlockHandler):
|
|
28
28
|
|
29
29
|
# Create a temporary retriever to process children
|
30
30
|
retriever = PageContentRetriever(context.block_registry)
|
31
|
-
children_markdown = retriever._convert_blocks_to_markdown(
|
31
|
+
children_markdown = await retriever._convert_blocks_to_markdown(
|
32
32
|
context.get_children_blocks(),
|
33
33
|
indent_level=0, # No indentation for content inside columns
|
34
34
|
)
|
@@ -29,7 +29,7 @@ class LineRenderer(BlockHandler):
|
|
29
29
|
)
|
30
30
|
|
31
31
|
retriever = PageContentRetriever(context.block_registry)
|
32
|
-
children_markdown = retriever._convert_blocks_to_markdown(
|
32
|
+
children_markdown = await retriever._convert_blocks_to_markdown(
|
33
33
|
context.get_children_blocks(), indent_level=context.indent_level + 1
|
34
34
|
)
|
35
35
|
context.markdown_result = children_markdown
|
@@ -52,7 +52,7 @@ class LineRenderer(BlockHandler):
|
|
52
52
|
from notionary.page.reader.page_content_retriever import PageContentRetriever
|
53
53
|
|
54
54
|
retriever = PageContentRetriever(context.block_registry)
|
55
|
-
children_markdown = retriever._convert_blocks_to_markdown(
|
55
|
+
children_markdown = await retriever._convert_blocks_to_markdown(
|
56
56
|
context.get_children_blocks(), indent_level=context.indent_level + 1
|
57
57
|
)
|
58
58
|
|
@@ -8,7 +8,7 @@ class ToggleRenderer(BlockHandler):
|
|
8
8
|
def _can_handle(self, context: BlockRenderingContext) -> bool:
|
9
9
|
return ToggleElement.match_notion(context.block)
|
10
10
|
|
11
|
-
def _process(self, context: BlockRenderingContext) -> None:
|
11
|
+
async def _process(self, context: BlockRenderingContext) -> None:
|
12
12
|
# Get the toggle title from the block
|
13
13
|
toggle_title = self._extract_toggle_title(context.block)
|
14
14
|
|
@@ -34,7 +34,7 @@ class ToggleRenderer(BlockHandler):
|
|
34
34
|
|
35
35
|
# Create a temporary retriever to process children
|
36
36
|
retriever = PageContentRetriever(context.block_registry)
|
37
|
-
children_markdown = retriever._convert_blocks_to_markdown(
|
37
|
+
children_markdown = await retriever._convert_blocks_to_markdown(
|
38
38
|
context.get_children_blocks(),
|
39
39
|
indent_level=0, # No indentation for content inside toggles
|
40
40
|
)
|
@@ -11,7 +11,7 @@ class ToggleableHeadingRenderer(BlockHandler):
|
|
11
11
|
def _can_handle(self, context: BlockRenderingContext) -> bool:
|
12
12
|
return ToggleableHeadingElement.match_notion(context.block)
|
13
13
|
|
14
|
-
def _process(self, context: BlockRenderingContext) -> None:
|
14
|
+
async def _process(self, context: BlockRenderingContext) -> None:
|
15
15
|
# Get the heading level and title
|
16
16
|
level, title = self._extract_heading_info(context.block)
|
17
17
|
|
@@ -38,7 +38,7 @@ class ToggleableHeadingRenderer(BlockHandler):
|
|
38
38
|
|
39
39
|
# Create a temporary retriever to process children
|
40
40
|
retriever = PageContentRetriever(context.block_registry)
|
41
|
-
children_markdown = retriever._convert_blocks_to_markdown(
|
41
|
+
children_markdown = await retriever._convert_blocks_to_markdown(
|
42
42
|
context.get_children_blocks(),
|
43
43
|
indent_level=0, # No indentation for content inside toggleable headings
|
44
44
|
)
|
@@ -15,7 +15,8 @@ class ToggleHandler(LineHandler):
|
|
15
15
|
|
16
16
|
def __init__(self):
|
17
17
|
super().__init__()
|
18
|
-
|
18
|
+
# Updated: Support both "+++title" and "+++ title"
|
19
|
+
self._start_pattern = re.compile(r"^[+]{3}\s*(.+)$", re.IGNORECASE)
|
19
20
|
self._end_pattern = re.compile(r"^[+]{3}\s*$")
|
20
21
|
|
21
22
|
def _can_handle(self, context: LineProcessingContext) -> bool:
|
@@ -43,15 +44,18 @@ class ToggleHandler(LineHandler):
|
|
43
44
|
context.should_continue = True
|
44
45
|
|
45
46
|
def _is_toggle_start(self, context: LineProcessingContext) -> bool:
|
46
|
-
"""Check if line starts a toggle (+++ Title)."""
|
47
|
+
"""Check if line starts a toggle (+++ Title or +++Title)."""
|
47
48
|
line = context.line.strip()
|
48
49
|
|
49
|
-
# Must match our pattern
|
50
|
+
# Must match our pattern (now allows optional space)
|
50
51
|
if not self._start_pattern.match(line):
|
51
52
|
return False
|
52
53
|
|
53
54
|
# But NOT match toggleable heading pattern (has # after +++)
|
54
|
-
|
55
|
+
# Updated: Support both "+++#title" and "+++ # title"
|
56
|
+
toggleable_heading_pattern = re.compile(
|
57
|
+
r"^[+]{3}\s*#{1,3}\s+.+$", re.IGNORECASE
|
58
|
+
)
|
55
59
|
if toggleable_heading_pattern.match(line):
|
56
60
|
return False
|
57
61
|
|
@@ -19,8 +19,9 @@ class ToggleableHeadingHandler(LineHandler):
|
|
19
19
|
|
20
20
|
def __init__(self):
|
21
21
|
super().__init__()
|
22
|
+
# Updated: Support both "+++# title" and "+++#title"
|
22
23
|
self._start_pattern = re.compile(
|
23
|
-
r"^[+]{3}(?P<level>#{1,3})\s
|
24
|
+
r"^[+]{3}\s*(?P<level>#{1,3})\s*(.+)$", re.IGNORECASE
|
24
25
|
)
|
25
26
|
# +++
|
26
27
|
self._end_pattern = re.compile(r"^[+]{3}\s*$")
|
@@ -49,7 +50,7 @@ class ToggleableHeadingHandler(LineHandler):
|
|
49
50
|
return await _handle(self._add_toggleable_heading_content)
|
50
51
|
|
51
52
|
def _is_toggleable_heading_start(self, context: LineProcessingContext) -> bool:
|
52
|
-
"""Check if line starts a toggleable heading (+++# "Title")."""
|
53
|
+
"""Check if line starts a toggleable heading (+++# "Title" or +++#"Title")."""
|
53
54
|
return self._start_pattern.match(context.line.strip()) is not None
|
54
55
|
|
55
56
|
def _is_toggleable_heading_end(self, context: LineProcessingContext) -> bool:
|