notionary 0.2.21__py3-none-any.whl → 0.2.22__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 (96) hide show
  1. notionary/blocks/_bootstrap.py +9 -1
  2. notionary/blocks/audio/audio_element.py +53 -28
  3. notionary/blocks/audio/audio_markdown_node.py +10 -4
  4. notionary/blocks/base_block_element.py +15 -3
  5. notionary/blocks/bookmark/bookmark_element.py +39 -36
  6. notionary/blocks/bookmark/bookmark_markdown_node.py +16 -17
  7. notionary/blocks/breadcrumbs/breadcrumb_element.py +2 -2
  8. notionary/blocks/bulleted_list/bulleted_list_element.py +21 -4
  9. notionary/blocks/callout/callout_element.py +20 -4
  10. notionary/blocks/child_database/__init__.py +11 -4
  11. notionary/blocks/child_database/child_database_element.py +61 -0
  12. notionary/blocks/child_database/child_database_models.py +7 -14
  13. notionary/blocks/child_page/child_page_element.py +94 -0
  14. notionary/blocks/client.py +0 -1
  15. notionary/blocks/code/code_element.py +51 -2
  16. notionary/blocks/code/code_markdown_node.py +52 -1
  17. notionary/blocks/column/column_element.py +9 -3
  18. notionary/blocks/column/column_list_element.py +18 -3
  19. notionary/blocks/divider/divider_element.py +3 -11
  20. notionary/blocks/embed/embed_element.py +27 -6
  21. notionary/blocks/equation/equation_element.py +94 -41
  22. notionary/blocks/equation/equation_element_markdown_node.py +8 -9
  23. notionary/blocks/file/file_element.py +56 -37
  24. notionary/blocks/file/file_element_markdown_node.py +9 -7
  25. notionary/blocks/guards.py +22 -0
  26. notionary/blocks/heading/heading_element.py +23 -4
  27. notionary/blocks/image_block/image_element.py +43 -38
  28. notionary/blocks/image_block/image_markdown_node.py +10 -5
  29. notionary/blocks/mixins/captions/__init__.py +4 -0
  30. notionary/blocks/mixins/captions/caption_markdown_node_mixin.py +31 -0
  31. notionary/blocks/mixins/captions/caption_mixin.py +92 -0
  32. notionary/blocks/models.py +3 -1
  33. notionary/blocks/numbered_list/numbered_list_element.py +21 -4
  34. notionary/blocks/paragraph/paragraph_element.py +21 -5
  35. notionary/blocks/pdf/pdf_element.py +47 -41
  36. notionary/blocks/pdf/pdf_markdown_node.py +9 -7
  37. notionary/blocks/quote/quote_element.py +26 -9
  38. notionary/blocks/quote/quote_markdown_node.py +2 -2
  39. notionary/blocks/registry/block_registry.py +1 -46
  40. notionary/blocks/registry/block_registry_builder.py +8 -0
  41. notionary/blocks/rich_text/name_to_id_resolver.py +205 -0
  42. notionary/blocks/rich_text/rich_text_models.py +62 -29
  43. notionary/blocks/rich_text/text_inline_formatter.py +432 -101
  44. notionary/blocks/syntax_prompt_builder.py +137 -0
  45. notionary/blocks/table/table_element.py +110 -9
  46. notionary/blocks/table_of_contents/table_of_contents_element.py +19 -2
  47. notionary/blocks/todo/todo_element.py +21 -4
  48. notionary/blocks/toggle/toggle_element.py +19 -3
  49. notionary/blocks/toggle/toggle_markdown_node.py +1 -1
  50. notionary/blocks/toggleable_heading/toggleable_heading_element.py +19 -4
  51. notionary/blocks/types.py +69 -0
  52. notionary/blocks/video/video_element.py +44 -39
  53. notionary/blocks/video/video_markdown_node.py +10 -5
  54. notionary/database/client.py +23 -0
  55. notionary/file_upload/models.py +2 -2
  56. notionary/markdown/markdown_builder.py +34 -27
  57. notionary/page/client.py +26 -6
  58. notionary/page/notion_page.py +37 -6
  59. notionary/page/page_content_deleting_service.py +117 -0
  60. notionary/page/page_content_writer.py +89 -113
  61. notionary/page/page_context.py +65 -0
  62. notionary/page/reader/handler/__init__.py +2 -0
  63. notionary/page/reader/handler/base_block_renderer.py +4 -4
  64. notionary/page/reader/handler/block_rendering_context.py +5 -0
  65. notionary/page/reader/handler/line_renderer.py +16 -3
  66. notionary/page/reader/handler/numbered_list_renderer.py +85 -0
  67. notionary/page/reader/page_content_retriever.py +17 -5
  68. notionary/page/writer/handler/__init__.py +2 -0
  69. notionary/page/writer/handler/code_handler.py +12 -40
  70. notionary/page/writer/handler/column_handler.py +12 -12
  71. notionary/page/writer/handler/column_list_handler.py +13 -13
  72. notionary/page/writer/handler/equation_handler.py +74 -0
  73. notionary/page/writer/handler/line_handler.py +4 -4
  74. notionary/page/writer/handler/regular_line_handler.py +31 -37
  75. notionary/page/writer/handler/table_handler.py +8 -72
  76. notionary/page/writer/handler/toggle_handler.py +14 -12
  77. notionary/page/writer/handler/toggleable_heading_handler.py +22 -16
  78. notionary/page/writer/markdown_to_notion_converter.py +28 -9
  79. notionary/page/writer/markdown_to_notion_converter_context.py +30 -0
  80. notionary/page/writer/markdown_to_notion_formatting_post_processor.py +73 -0
  81. notionary/page/writer/markdown_to_notion_post_processor.py +0 -0
  82. notionary/page/writer/markdown_to_notion_text_length_post_processor.py +0 -0
  83. notionary/page/writer/notion_text_length_processor.py +150 -0
  84. notionary/telemetry/service.py +0 -1
  85. notionary/user/notion_user_manager.py +22 -95
  86. notionary/util/concurrency_limiter.py +0 -0
  87. notionary/workspace.py +4 -4
  88. notionary-0.2.22.dist-info/METADATA +237 -0
  89. {notionary-0.2.21.dist-info → notionary-0.2.22.dist-info}/RECORD +92 -77
  90. notionary/page/markdown_whitespace_processor.py +0 -80
  91. notionary/page/notion_text_length_utils.py +0 -119
  92. notionary/user/notion_user_provider.py +0 -1
  93. notionary-0.2.21.dist-info/METADATA +0 -229
  94. /notionary/page/reader/handler/{context.py → equation_renderer.py} +0 -0
  95. {notionary-0.2.21.dist-info → notionary-0.2.22.dist-info}/LICENSE +0 -0
  96. {notionary-0.2.21.dist-info → notionary-0.2.22.dist-info}/WHEEL +0 -0
@@ -26,14 +26,14 @@ class ColumnHandler(LineHandler):
26
26
  def _can_handle(self, context: LineProcessingContext) -> bool:
27
27
  return self._is_column_start(context) or self._is_column_end(context)
28
28
 
29
- def _process(self, context: LineProcessingContext) -> None:
29
+ async def _process(self, context: LineProcessingContext) -> None:
30
30
  if self._is_column_start(context):
31
- self._start_column(context)
31
+ await self._start_column(context)
32
32
  self._mark_processed(context)
33
33
  return
34
34
 
35
35
  if self._is_column_end(context):
36
- self._finalize_column(context)
36
+ await self._finalize_column(context)
37
37
  self._mark_processed(context)
38
38
 
39
39
  def _is_column_start(self, context: LineProcessingContext) -> bool:
@@ -52,15 +52,15 @@ class ColumnHandler(LineHandler):
52
52
  current_parent = context.parent_stack[-1]
53
53
  return issubclass(current_parent.element_type, ColumnElement)
54
54
 
55
- def _start_column(self, context: LineProcessingContext) -> None:
55
+ async def _start_column(self, context: LineProcessingContext) -> None:
56
56
  """Start a new column."""
57
57
  # Create Column block directly - much more efficient!
58
58
  column_element = ColumnElement()
59
- result = column_element.markdown_to_notion(context.line)
59
+ result = await column_element.markdown_to_notion(context.line)
60
60
  if not result:
61
61
  return
62
62
 
63
- block = result if not isinstance(result, list) else result[0]
63
+ block = result
64
64
 
65
65
  # Push to parent stack
66
66
  parent_context = ParentBlockContext(
@@ -70,10 +70,10 @@ class ColumnHandler(LineHandler):
70
70
  )
71
71
  context.parent_stack.append(parent_context)
72
72
 
73
- def _finalize_column(self, context: LineProcessingContext) -> None:
73
+ async def _finalize_column(self, context: LineProcessingContext) -> None:
74
74
  """Finalize a single column and add it to the column list or result."""
75
75
  column_context = context.parent_stack.pop()
76
- self._assign_column_children_if_any(column_context, context)
76
+ await self._assign_column_children_if_any(column_context, context)
77
77
 
78
78
  if context.parent_stack:
79
79
  parent = context.parent_stack[-1]
@@ -87,7 +87,7 @@ class ColumnHandler(LineHandler):
87
87
  # Fallback: no parent or parent is not ColumnList
88
88
  context.result_blocks.append(column_context.block)
89
89
 
90
- def _assign_column_children_if_any(
90
+ async def _assign_column_children_if_any(
91
91
  self, column_context: ParentBlockContext, context: LineProcessingContext
92
92
  ) -> None:
93
93
  """Collect and assign any children blocks inside this column."""
@@ -96,7 +96,7 @@ class ColumnHandler(LineHandler):
96
96
  # Process text lines
97
97
  if column_context.child_lines:
98
98
  children_text = "\n".join(column_context.child_lines)
99
- text_blocks = self._convert_children_text(
99
+ text_blocks = await self._convert_children_text(
100
100
  children_text, context.block_registry
101
101
  )
102
102
  all_children.extend(text_blocks)
@@ -123,7 +123,7 @@ class ColumnHandler(LineHandler):
123
123
  parent.block.column_list.children.append(column_context.block)
124
124
  return True
125
125
 
126
- def _convert_children_text(self, text: str, block_registry) -> list:
126
+ async def _convert_children_text(self, text: str, block_registry) -> list:
127
127
  """Convert children text to blocks."""
128
128
  from notionary.page.writer.markdown_to_notion_converter import (
129
129
  MarkdownToNotionConverter,
@@ -133,7 +133,7 @@ class ColumnHandler(LineHandler):
133
133
  return []
134
134
 
135
135
  child_converter = MarkdownToNotionConverter(block_registry)
136
- return child_converter._process_lines(text)
136
+ return await child_converter.process_lines(text)
137
137
 
138
138
  def _mark_processed(self, context: LineProcessingContext) -> None:
139
139
  """Mark context as processed and signal to continue."""
@@ -7,9 +7,9 @@ from notionary.page.writer.handler.line_handler import (
7
7
  LineHandler,
8
8
  LineProcessingContext,
9
9
  )
10
-
11
10
  from notionary.page.writer.handler.line_processing_context import ParentBlockContext
12
11
 
12
+
13
13
  class ColumnListHandler(LineHandler):
14
14
  """Handles column list elements - both start and end.
15
15
  Syntax:
@@ -31,15 +31,15 @@ class ColumnListHandler(LineHandler):
31
31
  def _can_handle(self, context: LineProcessingContext) -> bool:
32
32
  return self._is_column_list_start(context) or self._is_column_list_end(context)
33
33
 
34
- def _process(self, context: LineProcessingContext) -> None:
34
+ async def _process(self, context: LineProcessingContext) -> None:
35
35
  if self._is_column_list_start(context):
36
- self._start_column_list(context)
36
+ await self._start_column_list(context)
37
37
  context.was_processed = True
38
38
  context.should_continue = True
39
39
  return
40
40
 
41
41
  if self._is_column_list_end(context):
42
- self._finalize_column_list(context)
42
+ await self._finalize_column_list(context)
43
43
  context.was_processed = True
44
44
  context.should_continue = True
45
45
 
@@ -59,7 +59,7 @@ class ColumnListHandler(LineHandler):
59
59
  current_parent = context.parent_stack[-1]
60
60
  return issubclass(current_parent.element_type, ColumnListElement)
61
61
 
62
- def _start_column_list(self, context: LineProcessingContext) -> None:
62
+ async def _start_column_list(self, context: LineProcessingContext) -> None:
63
63
  """Start a new column list."""
64
64
  # Create ColumnList block using the element from registry
65
65
  column_list_element = None
@@ -72,11 +72,11 @@ class ColumnListHandler(LineHandler):
72
72
  return
73
73
 
74
74
  # Create the block
75
- result = column_list_element.markdown_to_notion(context.line)
75
+ result = await column_list_element.markdown_to_notion(context.line)
76
76
  if not result:
77
77
  return
78
78
 
79
- block = result if not isinstance(result, list) else result[0]
79
+ block = result
80
80
 
81
81
  # Push to parent stack
82
82
  parent_context = ParentBlockContext(
@@ -86,10 +86,10 @@ class ColumnListHandler(LineHandler):
86
86
  )
87
87
  context.parent_stack.append(parent_context)
88
88
 
89
- def _finalize_column_list(self, context: LineProcessingContext) -> None:
89
+ async def _finalize_column_list(self, context: LineProcessingContext) -> None:
90
90
  """Finalize a column list and add it to result_blocks."""
91
91
  column_list_context = context.parent_stack.pop()
92
- self._assign_column_list_children_if_any(column_list_context, context)
92
+ await self._assign_column_list_children_if_any(column_list_context, context)
93
93
 
94
94
  # Check if we have a parent context to add this column_list to
95
95
  if context.parent_stack:
@@ -101,7 +101,7 @@ class ColumnListHandler(LineHandler):
101
101
  # No parent, add to top level
102
102
  context.result_blocks.append(column_list_context.block)
103
103
 
104
- def _assign_column_list_children_if_any(
104
+ async def _assign_column_list_children_if_any(
105
105
  self, column_list_context: ParentBlockContext, context: LineProcessingContext
106
106
  ) -> None:
107
107
  """Collect and assign any column children blocks inside this column list."""
@@ -110,7 +110,7 @@ class ColumnListHandler(LineHandler):
110
110
  # Process text lines
111
111
  if column_list_context.child_lines:
112
112
  children_text = "\n".join(column_list_context.child_lines)
113
- children_blocks = self._convert_children_text(
113
+ children_blocks = await self._convert_children_text(
114
114
  children_text, context.block_registry
115
115
  )
116
116
  all_children.extend(children_blocks)
@@ -126,7 +126,7 @@ class ColumnListHandler(LineHandler):
126
126
  ]
127
127
  column_list_context.block.column_list.children = column_children
128
128
 
129
- def _convert_children_text(self, text: str, block_registry) -> list:
129
+ async def _convert_children_text(self, text: str, block_registry) -> list:
130
130
  """Convert children text to blocks."""
131
131
  from notionary.page.writer.markdown_to_notion_converter import (
132
132
  MarkdownToNotionConverter,
@@ -136,4 +136,4 @@ class ColumnListHandler(LineHandler):
136
136
  return []
137
137
 
138
138
  child_converter = MarkdownToNotionConverter(block_registry)
139
- return child_converter._process_lines(text)
139
+ return await child_converter.process_lines(text)
@@ -0,0 +1,74 @@
1
+ import re
2
+
3
+ from notionary.blocks.equation.equation_element import EquationElement
4
+ from notionary.page.writer.handler.line_handler import (
5
+ LineHandler,
6
+ LineProcessingContext,
7
+ )
8
+
9
+
10
+ class EquationHandler(LineHandler):
11
+ """Handles equation block specific logic with batching.
12
+
13
+ Markdown syntax:
14
+ $$
15
+ \sum_{i=1}^n i = \frac{n(n+1)}{2} \\
16
+ \sum_{i=1}^n i^2 = \frac{n(n+1)(2n+1)}{6} \\
17
+ \sum_{i=1}^n i^3 = \left(\frac{n(n+1)}{2}\right)^2
18
+ $$
19
+ """
20
+
21
+ def __init__(self):
22
+ super().__init__()
23
+ self._equation_start_pattern = re.compile(r"^\$\$\s*$")
24
+ self._equation_end_pattern = re.compile(r"^\$\$\s*$")
25
+
26
+ def _can_handle(self, context: LineProcessingContext) -> bool:
27
+ if self._is_inside_parent_context(context):
28
+ return False
29
+ return self._is_equation_start(context)
30
+
31
+ async def _process(self, context: LineProcessingContext) -> None:
32
+ if self._is_equation_start(context):
33
+ await self._process_complete_equation_block(context)
34
+ self._mark_processed(context)
35
+
36
+ def _is_equation_start(self, context: LineProcessingContext) -> bool:
37
+ """Check if this line starts an equation block."""
38
+ return self._equation_start_pattern.match(context.line.strip()) is not None
39
+
40
+ def _is_inside_parent_context(self, context: LineProcessingContext) -> bool:
41
+ """Check if we're currently inside any parent context (toggle, heading, etc.)."""
42
+ return len(context.parent_stack) > 0
43
+
44
+ async def _process_complete_equation_block(
45
+ self, context: LineProcessingContext
46
+ ) -> None:
47
+ """Process the entire equation block in one go using EquationElement."""
48
+ equation_lines, lines_to_consume = self._collect_equation_lines(context)
49
+
50
+ block = EquationElement.create_from_markdown_block(
51
+ opening_line=context.line, equation_lines=equation_lines
52
+ )
53
+
54
+ if block:
55
+ context.lines_consumed = lines_to_consume
56
+ context.result_blocks.append(block)
57
+
58
+ def _collect_equation_lines(
59
+ self, context: LineProcessingContext
60
+ ) -> tuple[list[str], int]:
61
+ """Collect lines until closing $$ fence and return (lines, count_to_consume)."""
62
+ lines = []
63
+ for idx, ln in enumerate(context.get_remaining_lines()):
64
+ if self._equation_end_pattern.match(ln.strip()):
65
+ return lines, idx + 1
66
+ lines.append(ln)
67
+ # No closing fence: consume all remaining
68
+ rem = context.get_remaining_lines()
69
+ return rem, len(rem)
70
+
71
+ def _mark_processed(self, context: LineProcessingContext) -> None:
72
+ """Mark context as processed and continue."""
73
+ context.was_processed = True
74
+ context.should_continue = True
@@ -17,12 +17,12 @@ class LineHandler(ABC):
17
17
  self._next_handler = handler
18
18
  return handler
19
19
 
20
- def handle(self, context: LineProcessingContext) -> None:
20
+ async def handle(self, context: LineProcessingContext) -> None:
21
21
  """Handle the line or pass to next handler."""
22
22
  if self._can_handle(context):
23
- self._process(context)
23
+ await self._process(context)
24
24
  elif self._next_handler:
25
- self._next_handler.handle(context)
25
+ await self._next_handler.handle(context)
26
26
 
27
27
  @abstractmethod
28
28
  def _can_handle(self, context: LineProcessingContext) -> bool:
@@ -30,6 +30,6 @@ class LineHandler(ABC):
30
30
  pass
31
31
 
32
32
  @abstractmethod
33
- def _process(self, context: LineProcessingContext) -> None:
33
+ async def _process(self, context: LineProcessingContext) -> None:
34
34
  """Process the line and update context."""
35
35
  pass
@@ -1,6 +1,5 @@
1
1
  from notionary.blocks.column.column_element import ColumnElement
2
2
  from notionary.blocks.column.column_list_element import ColumnListElement
3
- from notionary.blocks.models import BlockCreateRequest, BlockCreateResult
4
3
  from notionary.page.writer.handler import LineHandler, LineProcessingContext
5
4
 
6
5
 
@@ -10,16 +9,16 @@ class RegularLineHandler(LineHandler):
10
9
  def _can_handle(self, context: LineProcessingContext) -> bool:
11
10
  return context.line.strip()
12
11
 
13
- def _process(self, context: LineProcessingContext) -> None:
12
+ async def _process(self, context: LineProcessingContext) -> None:
14
13
  if self._is_in_column_context(context):
15
14
  self._add_to_column_context(context)
16
15
  context.was_processed = True
17
16
  context.should_continue = True
18
17
  return
19
18
 
20
- block_created = self._process_single_line_content(context)
19
+ block_created = await self._process_single_line_content(context)
21
20
  if not block_created:
22
- self._process_as_paragraph(context)
21
+ await self._process_as_paragraph(context)
23
22
 
24
23
  context.was_processed = True
25
24
 
@@ -37,56 +36,51 @@ class RegularLineHandler(LineHandler):
37
36
  """Add line as child to the current Column context."""
38
37
  context.parent_stack[-1].add_child_line(context.line)
39
38
 
40
- def _process_single_line_content(self, context: LineProcessingContext) -> bool:
39
+ async def _process_single_line_content(
40
+ self, context: LineProcessingContext
41
+ ) -> bool:
41
42
  """Process a regular line for simple elements (lists, etc.)."""
43
+ specialized_elements = self._get_specialized_elements()
44
+
42
45
  for element in context.block_registry.get_elements():
43
- # Skip all elements that have specialized handlers
44
- from notionary.blocks.code import CodeElement
45
- from notionary.blocks.paragraph import ParagraphElement
46
- from notionary.blocks.table import TableElement
47
- from notionary.blocks.toggle import ToggleElement
48
- from notionary.blocks.toggleable_heading import ToggleableHeadingElement
49
-
50
- specialized_elements = (
51
- ColumnListElement,
52
- ColumnElement,
53
- ToggleElement,
54
- ToggleableHeadingElement,
55
- TableElement,
56
- CodeElement,
57
- ParagraphElement, # Skip paragraph to ensure equations are processed first
58
- )
59
46
 
60
47
  if issubclass(element, specialized_elements):
61
48
  continue
62
49
 
63
- result = element.markdown_to_notion(context.line)
50
+ result = await element.markdown_to_notion(context.line)
64
51
  if not result:
65
52
  continue
66
53
 
67
- blocks = self._normalize_to_list(result)
68
- for block in blocks:
69
- context.result_blocks.append(block)
54
+ context.result_blocks.append(result)
70
55
 
71
56
  return True
72
57
 
73
58
  return False
74
59
 
75
- def _process_as_paragraph(self, context: LineProcessingContext) -> None:
60
+ async def _process_as_paragraph(self, context: LineProcessingContext) -> None:
76
61
  """Process a line as a paragraph."""
77
62
  from notionary.blocks.paragraph.paragraph_element import ParagraphElement
78
63
 
79
64
  paragraph_element = ParagraphElement()
80
- result = paragraph_element.markdown_to_notion(context.line)
65
+ result = await paragraph_element.markdown_to_notion(context.line)
81
66
 
82
67
  if result:
83
- blocks = self._normalize_to_list(result)
84
- for block in blocks:
85
- context.result_blocks.append(block)
86
-
87
- @staticmethod
88
- def _normalize_to_list(result: BlockCreateResult) -> list[BlockCreateRequest]:
89
- """Normalize the result to a list."""
90
- if result is None:
91
- return []
92
- return result if isinstance(result, list) else [result]
68
+ context.result_blocks.append(result)
69
+
70
+ def _get_specialized_elements(self):
71
+ """Get tuple of elements that have specialized handlers."""
72
+ from notionary.blocks.code import CodeElement
73
+ from notionary.blocks.paragraph import ParagraphElement
74
+ from notionary.blocks.table import TableElement
75
+ from notionary.blocks.toggle import ToggleElement
76
+ from notionary.blocks.toggleable_heading import ToggleableHeadingElement
77
+
78
+ return (
79
+ ColumnListElement,
80
+ ColumnElement,
81
+ ToggleElement,
82
+ ToggleableHeadingElement,
83
+ TableElement,
84
+ CodeElement,
85
+ ParagraphElement,
86
+ )
@@ -1,9 +1,6 @@
1
1
  import re
2
2
 
3
- from notionary.blocks.rich_text.rich_text_models import RichTextObject
4
- from notionary.blocks.rich_text.text_inline_formatter import TextInlineFormatter
5
3
  from notionary.blocks.table.table_element import TableElement
6
- from notionary.blocks.table.table_models import CreateTableRowBlock, TableRowBlock
7
4
  from notionary.page.writer.handler import LineHandler, LineProcessingContext
8
5
 
9
6
 
@@ -20,11 +17,11 @@ class TableHandler(LineHandler):
20
17
  return False
21
18
  return self._is_table_start(context)
22
19
 
23
- def _process(self, context: LineProcessingContext) -> None:
20
+ async def _process(self, context: LineProcessingContext) -> None:
24
21
  if not self._is_table_start(context):
25
22
  return
26
23
 
27
- self._process_complete_table(context)
24
+ await self._process_complete_table(context)
28
25
  context.was_processed = True
29
26
  context.should_continue = True
30
27
 
@@ -36,16 +33,8 @@ class TableHandler(LineHandler):
36
33
  """Check if this line starts a table."""
37
34
  return self._table_row_pattern.match(context.line.strip()) is not None
38
35
 
39
- def _process_complete_table(self, context: LineProcessingContext) -> None:
40
- """Process the entire table in one go."""
41
- # Create table element
42
- table_element = TableElement()
43
- result = table_element.markdown_to_notion(context.line)
44
- if not result:
45
- return
46
-
47
- block = result if not isinstance(result, list) else result[0]
48
-
36
+ async def _process_complete_table(self, context: LineProcessingContext) -> None:
37
+ """Process the entire table in one go using TableElement."""
49
38
  # Collect all table lines (including the current one)
50
39
  table_lines = [context.line]
51
40
  remaining_lines = context.get_remaining_lines()
@@ -68,63 +57,10 @@ class TableHandler(LineHandler):
68
57
  lines_to_consume = i
69
58
  break
70
59
  else:
71
- # Consumed all remaining lines
72
60
  lines_to_consume = len(remaining_lines)
73
61
 
74
- # Process the table content
75
- table_rows, separator_found = self._process_table_lines(table_lines)
76
-
77
- table = block.table
78
- table.children = table_rows
79
- table.has_column_header = bool(separator_found)
80
-
81
- # Tell the main loop to skip the consumed lines
82
- context.lines_consumed = lines_to_consume
83
- context.result_blocks.append(block)
84
-
85
- def _process_table_lines(
86
- self, table_lines: list[str]
87
- ) -> tuple[list[CreateTableRowBlock], bool]:
88
- """Process all table lines and return rows and separator status."""
89
- table_rows = []
90
- separator_found = False
91
-
92
- for line in table_lines:
93
- line = line.strip()
94
- if not line:
95
- continue
96
-
97
- if self._is_separator_line(line):
98
- separator_found = True
99
- continue
100
-
101
- if self._table_row_pattern.match(line):
102
- table_row = self._create_table_row_from_line(line)
103
- table_rows.append(table_row)
104
-
105
- return table_rows, separator_found
106
-
107
- def _is_separator_line(self, line: str) -> bool:
108
- return self._separator_pattern.match(line) is not None
109
-
110
- def _create_table_row_from_line(self, line: str) -> CreateTableRowBlock:
111
- cells = self._parse_table_row(line)
112
- rich_text_cells = [self._convert_cell_to_rich_text(cell) for cell in cells]
113
- table_row = TableRowBlock(cells=rich_text_cells)
114
- return CreateTableRowBlock(table_row=table_row)
115
-
116
- def _convert_cell_to_rich_text(self, cell: str) -> list[RichTextObject]:
117
- rich_text = TextInlineFormatter.parse_inline_formatting(cell)
118
- if not rich_text:
119
- rich_text = [RichTextObject.from_plain_text(cell)]
120
- return rich_text
121
-
122
- def _parse_table_row(self, row_text: str) -> list[str]:
123
- row_content = row_text.strip()
124
-
125
- if row_content.startswith("|"):
126
- row_content = row_content[1:]
127
- if row_content.endswith("|"):
128
- row_content = row_content[:-1]
62
+ block = await TableElement.create_from_markdown_table(table_lines)
129
63
 
130
- return [cell.strip() for cell in row_content.split("|")]
64
+ if block:
65
+ context.lines_consumed = lines_to_consume
66
+ context.result_blocks.append(block)
@@ -25,15 +25,15 @@ class ToggleHandler(LineHandler):
25
25
  or self._is_toggle_content(context)
26
26
  )
27
27
 
28
- def _process(self, context: LineProcessingContext) -> None:
28
+ async def _process(self, context: LineProcessingContext) -> None:
29
29
  # Explicit, readable branches (small duplication is acceptable)
30
30
  if self._is_toggle_start(context):
31
- self._start_toggle(context)
31
+ await self._start_toggle(context)
32
32
  context.was_processed = True
33
33
  context.should_continue = True
34
34
 
35
35
  if self._is_toggle_end(context):
36
- self._finalize_toggle(context)
36
+ await self._finalize_toggle(context)
37
37
  context.was_processed = True
38
38
  context.should_continue = True
39
39
 
@@ -69,16 +69,16 @@ class ToggleHandler(LineHandler):
69
69
  current_parent = context.parent_stack[-1]
70
70
  return issubclass(current_parent.element_type, ToggleElement)
71
71
 
72
- def _start_toggle(self, context: LineProcessingContext) -> None:
72
+ async def _start_toggle(self, context: LineProcessingContext) -> None:
73
73
  """Start a new toggle block."""
74
74
  toggle_element = ToggleElement()
75
75
 
76
76
  # Create the block
77
- result = toggle_element.markdown_to_notion(context.line)
77
+ result = await toggle_element.markdown_to_notion(context.line)
78
78
  if not result:
79
79
  return
80
80
 
81
- block = result if not isinstance(result, list) else result[0]
81
+ block = result
82
82
 
83
83
  # Push to parent stack
84
84
  parent_context = ParentBlockContext(
@@ -88,12 +88,12 @@ class ToggleHandler(LineHandler):
88
88
  )
89
89
  context.parent_stack.append(parent_context)
90
90
 
91
- def _finalize_toggle(self, context: LineProcessingContext) -> None:
91
+ async def _finalize_toggle(self, context: LineProcessingContext) -> None:
92
92
  """Finalize a toggle block and add it to result_blocks."""
93
93
  toggle_context = context.parent_stack.pop()
94
94
 
95
95
  if toggle_context.has_children():
96
- all_children = self._get_all_children(
96
+ all_children = await self._get_all_children(
97
97
  toggle_context, context.block_registry
98
98
  )
99
99
  toggle_context.block.toggle.children = all_children
@@ -124,7 +124,7 @@ class ToggleHandler(LineHandler):
124
124
  """Add content to the current toggle context."""
125
125
  context.parent_stack[-1].add_child_line(context.line)
126
126
 
127
- def _convert_children_text(self, text: str, block_registry) -> list:
127
+ async def _convert_children_text(self, text: str, block_registry) -> list:
128
128
  """Convert children text to blocks."""
129
129
  from notionary.page.writer.markdown_to_notion_converter import (
130
130
  MarkdownToNotionConverter,
@@ -134,16 +134,18 @@ class ToggleHandler(LineHandler):
134
134
  return []
135
135
 
136
136
  child_converter = MarkdownToNotionConverter(block_registry)
137
- return child_converter._process_lines(text)
137
+ return await child_converter.process_lines(text)
138
138
 
139
- def _get_all_children(self, parent_context, block_registry) -> list:
139
+ async def _get_all_children(self, parent_context, block_registry) -> list:
140
140
  """Helper method to combine text-based and direct block children."""
141
141
  children_blocks = []
142
142
 
143
143
  # Process text lines
144
144
  if parent_context.child_lines:
145
145
  children_text = "\n".join(parent_context.child_lines)
146
- text_blocks = self._convert_children_text(children_text, block_registry)
146
+ text_blocks = await self._convert_children_text(
147
+ children_text, block_registry
148
+ )
147
149
  children_blocks.extend(text_blocks)
148
150
 
149
151
  # Add direct blocks (like processed columns)