notionary 0.2.27__py3-none-any.whl → 0.2.28__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 +5 -20
- notionary/blocks/client.py +87 -215
- notionary/blocks/enums.py +167 -0
- notionary/blocks/rich_text/markdown_rich_text_converter.py +266 -0
- notionary/blocks/rich_text/models.py +164 -0
- notionary/blocks/rich_text/name_id_resolver/__init__.py +11 -0
- notionary/blocks/rich_text/name_id_resolver/database.py +31 -0
- notionary/blocks/rich_text/name_id_resolver/page.py +34 -0
- notionary/blocks/rich_text/name_id_resolver/person.py +37 -0
- notionary/blocks/rich_text/name_id_resolver/port.py +11 -0
- notionary/blocks/rich_text/rich_text_markdown_converter.py +132 -0
- notionary/blocks/rich_text/rich_text_patterns.py +39 -0
- notionary/blocks/schemas.py +746 -0
- notionary/comments/client.py +52 -187
- notionary/comments/factory.py +40 -0
- notionary/comments/models.py +5 -127
- notionary/comments/schemas.py +240 -0
- notionary/comments/service.py +34 -0
- notionary/data_source/http/client.py +11 -0
- notionary/data_source/http/data_source_instance_client.py +94 -0
- notionary/data_source/properties/models.py +406 -0
- notionary/data_source/query/builder.py +429 -0
- notionary/data_source/query/resolver.py +114 -0
- notionary/data_source/query/schema.py +304 -0
- notionary/data_source/query/validator.py +73 -0
- notionary/data_source/schemas.py +27 -0
- notionary/data_source/service.py +353 -0
- notionary/database/client.py +30 -135
- notionary/database/database_metadata_update_client.py +19 -0
- notionary/database/schemas.py +29 -0
- notionary/database/service.py +169 -0
- notionary/exceptions/__init__.py +33 -0
- notionary/exceptions/api.py +41 -0
- notionary/exceptions/base.py +2 -0
- notionary/exceptions/block_parsing.py +16 -0
- notionary/exceptions/data_source/__init__.py +6 -0
- notionary/exceptions/data_source/builder.py +182 -0
- notionary/exceptions/data_source/properties.py +34 -0
- notionary/exceptions/properties.py +58 -0
- notionary/exceptions/search.py +33 -0
- notionary/file_upload/client.py +18 -30
- notionary/file_upload/models.py +7 -8
- notionary/file_upload/{notion_file_upload.py → service.py} +29 -64
- notionary/http/client.py +205 -0
- notionary/http/models.py +49 -0
- notionary/page/blocks/client.py +1 -0
- notionary/page/content/factory.py +68 -0
- notionary/page/content/markdown/__init__.py +5 -0
- notionary/page/content/markdown/builder.py +304 -0
- notionary/page/content/markdown/nodes/__init__.py +54 -0
- notionary/page/content/markdown/nodes/audio.py +23 -0
- notionary/page/content/markdown/nodes/base.py +12 -0
- notionary/page/content/markdown/nodes/bookmark.py +25 -0
- notionary/page/content/markdown/nodes/breadcrumb.py +14 -0
- notionary/page/content/markdown/nodes/bulleted_list.py +18 -0
- notionary/page/content/markdown/nodes/callout.py +32 -0
- notionary/page/content/markdown/nodes/code.py +30 -0
- notionary/page/content/markdown/nodes/columns.py +51 -0
- notionary/page/content/markdown/nodes/divider.py +14 -0
- notionary/page/content/markdown/nodes/embed.py +23 -0
- notionary/page/content/markdown/nodes/equation.py +19 -0
- notionary/page/content/markdown/nodes/file.py +23 -0
- notionary/page/content/markdown/nodes/heading.py +16 -0
- notionary/page/content/markdown/nodes/image.py +23 -0
- notionary/page/content/markdown/nodes/mixins/caption.py +12 -0
- notionary/page/content/markdown/nodes/numbered_list.py +15 -0
- notionary/page/content/markdown/nodes/paragraph.py +14 -0
- notionary/page/content/markdown/nodes/pdf.py +23 -0
- notionary/page/content/markdown/nodes/quote.py +15 -0
- notionary/page/content/markdown/nodes/space.py +14 -0
- notionary/page/content/markdown/nodes/table.py +45 -0
- notionary/page/content/markdown/nodes/table_of_contents.py +14 -0
- notionary/page/content/markdown/nodes/todo.py +22 -0
- notionary/page/content/markdown/nodes/toggle.py +28 -0
- notionary/page/content/markdown/nodes/toggleable_heading.py +35 -0
- notionary/page/content/markdown/nodes/video.py +23 -0
- notionary/page/content/parser/context.py +49 -0
- notionary/page/content/parser/factory.py +219 -0
- notionary/page/content/parser/parsers/__init__.py +60 -0
- notionary/page/content/parser/parsers/audio.py +40 -0
- notionary/page/content/parser/parsers/base.py +30 -0
- notionary/page/content/parser/parsers/bookmark.py +33 -0
- notionary/page/content/parser/parsers/breadcrumb.py +33 -0
- notionary/page/content/parser/parsers/bulleted_list.py +41 -0
- notionary/page/content/parser/parsers/callout.py +129 -0
- notionary/page/content/parser/parsers/caption.py +55 -0
- notionary/page/content/parser/parsers/code.py +81 -0
- notionary/page/content/parser/parsers/column.py +117 -0
- notionary/page/content/parser/parsers/column_list.py +81 -0
- notionary/page/content/parser/parsers/divider.py +33 -0
- notionary/page/content/parser/parsers/embed.py +33 -0
- notionary/page/content/parser/parsers/equation.py +65 -0
- notionary/page/content/parser/parsers/file.py +42 -0
- notionary/page/content/parser/parsers/heading.py +58 -0
- notionary/page/content/parser/parsers/image.py +42 -0
- notionary/page/content/parser/parsers/numbered_list.py +45 -0
- notionary/page/content/parser/parsers/paragraph.py +36 -0
- notionary/page/content/parser/parsers/pdf.py +42 -0
- notionary/page/content/parser/parsers/quote.py +65 -0
- notionary/page/content/parser/parsers/space.py +35 -0
- notionary/page/content/parser/parsers/table.py +144 -0
- notionary/page/content/parser/parsers/table_of_contents.py +32 -0
- notionary/page/content/parser/parsers/todo.py +58 -0
- notionary/page/content/parser/parsers/toggle.py +127 -0
- notionary/page/content/parser/parsers/toggleable_heading.py +150 -0
- notionary/page/content/parser/parsers/video.py +42 -0
- notionary/page/content/parser/post_processing/handlers/__init__.py +5 -0
- notionary/page/content/parser/post_processing/handlers/rich_text_length.py +93 -0
- notionary/page/content/parser/post_processing/handlers/rich_text_length_truncation.py +93 -0
- notionary/page/content/parser/post_processing/port.py +9 -0
- notionary/page/content/parser/post_processing/service.py +16 -0
- notionary/page/content/parser/pre_processsing/handlers/__init__.py +9 -0
- notionary/page/content/parser/pre_processsing/handlers/column_syntax.py +80 -0
- notionary/page/content/parser/pre_processsing/handlers/port.py +7 -0
- notionary/page/content/parser/pre_processsing/handlers/whitespace.py +68 -0
- notionary/page/content/parser/pre_processsing/service.py +15 -0
- notionary/page/content/parser/service.py +69 -0
- notionary/page/content/renderer/context.py +48 -0
- notionary/page/content/renderer/factory.py +240 -0
- notionary/page/content/renderer/post_processing/handlers/__init__.py +5 -0
- notionary/page/content/renderer/post_processing/handlers/numbered_list_placeholdere.py +62 -0
- notionary/page/content/renderer/post_processing/port.py +7 -0
- notionary/page/content/renderer/post_processing/service.py +15 -0
- notionary/page/content/renderer/renderers/__init__.py +57 -0
- notionary/page/content/renderer/renderers/audio.py +31 -0
- notionary/page/content/renderer/renderers/base.py +31 -0
- notionary/page/content/renderer/renderers/bookmark.py +25 -0
- notionary/page/content/renderer/renderers/breadcrumb.py +21 -0
- notionary/page/content/renderer/renderers/bulleted_list.py +48 -0
- notionary/page/content/renderer/renderers/callout.py +65 -0
- notionary/page/content/renderer/renderers/captioned_block.py +58 -0
- notionary/page/content/renderer/renderers/code.py +34 -0
- notionary/page/content/renderer/renderers/column.py +44 -0
- notionary/page/content/renderer/renderers/column_list.py +31 -0
- notionary/page/content/renderer/renderers/divider.py +22 -0
- notionary/page/content/renderer/renderers/embed.py +25 -0
- notionary/page/content/renderer/renderers/equation.py +37 -0
- notionary/page/content/renderer/renderers/fallback.py +24 -0
- notionary/page/content/renderer/renderers/file.py +40 -0
- notionary/page/content/renderer/renderers/heading.py +69 -0
- notionary/page/content/renderer/renderers/image.py +31 -0
- notionary/page/content/renderer/renderers/numbered_list.py +41 -0
- notionary/page/content/renderer/renderers/paragraph.py +40 -0
- notionary/page/content/renderer/renderers/pdf.py +31 -0
- notionary/page/content/renderer/renderers/quote.py +49 -0
- notionary/page/content/renderer/renderers/table.py +115 -0
- notionary/page/content/renderer/renderers/table_of_contents.py +26 -0
- notionary/page/content/renderer/renderers/table_row.py +17 -0
- notionary/page/content/renderer/renderers/todo.py +56 -0
- notionary/page/content/renderer/renderers/toggle.py +53 -0
- notionary/page/content/renderer/renderers/toggleable_heading.py +78 -0
- notionary/page/content/renderer/renderers/video.py +31 -0
- notionary/page/content/renderer/service.py +50 -0
- notionary/page/content/service.py +65 -0
- notionary/page/content/syntax/models.py +68 -0
- notionary/page/content/syntax/service.py +453 -0
- notionary/page/page_context.py +7 -16
- notionary/page/page_http_client.py +15 -0
- notionary/page/page_metadata_update_client.py +19 -0
- notionary/page/properties/client.py +144 -0
- notionary/page/properties/factory.py +26 -0
- notionary/page/properties/models.py +307 -0
- notionary/page/properties/service.py +257 -0
- notionary/page/schemas.py +13 -0
- notionary/page/service.py +222 -0
- notionary/shared/entity/client.py +29 -0
- notionary/shared/entity/dto_parsers.py +53 -0
- notionary/shared/entity/entity_metadata_update_client.py +41 -0
- notionary/shared/entity/schemas.py +45 -0
- notionary/shared/entity/service.py +171 -0
- notionary/shared/models/cover.py +20 -0
- notionary/shared/models/file.py +21 -0
- notionary/shared/models/icon.py +28 -0
- notionary/shared/models/parent.py +41 -0
- notionary/shared/properties/type.py +30 -0
- notionary/user/__init__.py +4 -8
- notionary/user/base.py +89 -0
- notionary/user/bot.py +70 -0
- notionary/user/client.py +22 -111
- notionary/user/person.py +41 -0
- notionary/user/schemas.py +67 -0
- notionary/user/service.py +65 -0
- notionary/utils/async_retry.py +39 -0
- notionary/utils/date.py +51 -0
- notionary/utils/fuzzy.py +56 -0
- notionary/{util/logging_mixin.py → utils/mixins/logging.py} +4 -16
- notionary/utils/pagination.py +50 -0
- notionary/utils/singleton.py +13 -0
- notionary/utils/uuid_utils.py +20 -0
- notionary/workspace/__init__.py +3 -0
- notionary/workspace/client.py +62 -0
- notionary/workspace/query/builder.py +60 -0
- notionary/workspace/query/models.py +60 -0
- notionary/workspace/query/service.py +93 -0
- notionary/workspace/schemas.py +21 -0
- notionary/workspace/service.py +116 -0
- {notionary-0.2.27.dist-info → notionary-0.2.28.dist-info}/METADATA +54 -49
- notionary-0.2.28.dist-info/RECORD +200 -0
- {notionary-0.2.27.dist-info → notionary-0.2.28.dist-info}/WHEEL +1 -1
- {notionary-0.2.27.dist-info → notionary-0.2.28.dist-info/licenses}/LICENSE +9 -9
- notionary/base_notion_client.py +0 -219
- notionary/blocks/__init__.py +0 -5
- notionary/blocks/_bootstrap.py +0 -271
- notionary/blocks/audio/__init__.py +0 -11
- notionary/blocks/audio/audio_element.py +0 -158
- notionary/blocks/audio/audio_markdown_node.py +0 -24
- notionary/blocks/audio/audio_models.py +0 -10
- notionary/blocks/base_block_element.py +0 -42
- notionary/blocks/bookmark/__init__.py +0 -12
- notionary/blocks/bookmark/bookmark_element.py +0 -83
- notionary/blocks/bookmark/bookmark_markdown_node.py +0 -28
- notionary/blocks/bookmark/bookmark_models.py +0 -15
- notionary/blocks/breadcrumbs/__init__.py +0 -15
- notionary/blocks/breadcrumbs/breadcrumb_element.py +0 -39
- notionary/blocks/breadcrumbs/breadcrumb_markdown_node.py +0 -13
- notionary/blocks/breadcrumbs/breadcrumb_models.py +0 -12
- notionary/blocks/bulleted_list/__init__.py +0 -15
- notionary/blocks/bulleted_list/bulleted_list_element.py +0 -74
- notionary/blocks/bulleted_list/bulleted_list_markdown_node.py +0 -20
- notionary/blocks/bulleted_list/bulleted_list_models.py +0 -17
- notionary/blocks/callout/__init__.py +0 -12
- notionary/blocks/callout/callout_element.py +0 -99
- notionary/blocks/callout/callout_markdown_node.py +0 -19
- notionary/blocks/callout/callout_models.py +0 -33
- notionary/blocks/child_database/__init__.py +0 -14
- notionary/blocks/child_database/child_database_element.py +0 -59
- notionary/blocks/child_database/child_database_models.py +0 -12
- notionary/blocks/child_page/__init__.py +0 -9
- notionary/blocks/child_page/child_page_element.py +0 -94
- notionary/blocks/child_page/child_page_models.py +0 -12
- notionary/blocks/code/__init__.py +0 -11
- notionary/blocks/code/code_element.py +0 -149
- notionary/blocks/code/code_markdown_node.py +0 -80
- notionary/blocks/code/code_models.py +0 -94
- notionary/blocks/column/__init__.py +0 -25
- notionary/blocks/column/column_element.py +0 -65
- notionary/blocks/column/column_list_element.py +0 -52
- notionary/blocks/column/column_list_markdown_node.py +0 -34
- notionary/blocks/column/column_markdown_node.py +0 -42
- notionary/blocks/column/column_models.py +0 -26
- notionary/blocks/divider/__init__.py +0 -12
- notionary/blocks/divider/divider_element.py +0 -41
- notionary/blocks/divider/divider_markdown_node.py +0 -11
- notionary/blocks/divider/divider_models.py +0 -12
- notionary/blocks/embed/__init__.py +0 -12
- notionary/blocks/embed/embed_element.py +0 -98
- notionary/blocks/embed/embed_markdown_node.py +0 -19
- notionary/blocks/embed/embed_models.py +0 -14
- notionary/blocks/equation/__init__.py +0 -13
- notionary/blocks/equation/equation_element.py +0 -133
- notionary/blocks/equation/equation_element_markdown_node.py +0 -23
- notionary/blocks/equation/equation_models.py +0 -11
- notionary/blocks/file/__init__.py +0 -23
- notionary/blocks/file/file_element.py +0 -133
- notionary/blocks/file/file_element_markdown_node.py +0 -24
- notionary/blocks/file/file_element_models.py +0 -39
- notionary/blocks/heading/__init__.py +0 -19
- notionary/blocks/heading/heading_element.py +0 -112
- notionary/blocks/heading/heading_markdown_node.py +0 -16
- notionary/blocks/heading/heading_models.py +0 -29
- notionary/blocks/image_block/__init__.py +0 -11
- notionary/blocks/image_block/image_element.py +0 -130
- notionary/blocks/image_block/image_markdown_node.py +0 -25
- notionary/blocks/image_block/image_models.py +0 -10
- notionary/blocks/markdown/markdown_builder.py +0 -525
- notionary/blocks/markdown/markdown_document_model.py +0 -0
- notionary/blocks/markdown/markdown_node.py +0 -25
- notionary/blocks/mixins/captions/__init__.py +0 -4
- notionary/blocks/mixins/captions/caption_markdown_node_mixin.py +0 -31
- notionary/blocks/mixins/captions/caption_mixin.py +0 -92
- notionary/blocks/mixins/file_upload/__init__.py +0 -3
- notionary/blocks/mixins/file_upload/file_upload_mixin.py +0 -320
- notionary/blocks/models.py +0 -174
- notionary/blocks/numbered_list/__init__.py +0 -16
- notionary/blocks/numbered_list/numbered_list_element.py +0 -65
- notionary/blocks/numbered_list/numbered_list_markdown_node.py +0 -17
- notionary/blocks/numbered_list/numbered_list_models.py +0 -17
- notionary/blocks/paragraph/__init__.py +0 -15
- notionary/blocks/paragraph/paragraph_element.py +0 -58
- notionary/blocks/paragraph/paragraph_markdown_node.py +0 -16
- notionary/blocks/paragraph/paragraph_models.py +0 -16
- notionary/blocks/pdf/__init__.py +0 -11
- notionary/blocks/pdf/pdf_element.py +0 -146
- notionary/blocks/pdf/pdf_markdown_node.py +0 -24
- notionary/blocks/pdf/pdf_models.py +0 -11
- notionary/blocks/quote/__init__.py +0 -14
- notionary/blocks/quote/quote_element.py +0 -75
- notionary/blocks/quote/quote_markdown_node.py +0 -16
- notionary/blocks/quote/quote_models.py +0 -18
- notionary/blocks/registry/__init__.py +0 -3
- notionary/blocks/registry/block_registry.py +0 -150
- notionary/blocks/rich_text/__init__.py +0 -33
- notionary/blocks/rich_text/rich_text_models.py +0 -221
- notionary/blocks/rich_text/text_inline_formatter.py +0 -456
- notionary/blocks/syntax_prompt_builder.py +0 -137
- notionary/blocks/table/__init__.py +0 -19
- notionary/blocks/table/table_element.py +0 -225
- notionary/blocks/table/table_markdown_node.py +0 -42
- notionary/blocks/table/table_models.py +0 -28
- notionary/blocks/table_of_contents/__init__.py +0 -17
- notionary/blocks/table_of_contents/table_of_contents_element.py +0 -80
- notionary/blocks/table_of_contents/table_of_contents_markdown_node.py +0 -21
- notionary/blocks/table_of_contents/table_of_contents_models.py +0 -18
- notionary/blocks/todo/__init__.py +0 -12
- notionary/blocks/todo/todo_element.py +0 -81
- notionary/blocks/todo/todo_markdown_node.py +0 -21
- notionary/blocks/todo/todo_models.py +0 -18
- notionary/blocks/toggle/__init__.py +0 -12
- notionary/blocks/toggle/toggle_element.py +0 -112
- notionary/blocks/toggle/toggle_markdown_node.py +0 -31
- notionary/blocks/toggle/toggle_models.py +0 -17
- notionary/blocks/toggleable_heading/__init__.py +0 -11
- notionary/blocks/toggleable_heading/toggleable_heading_element.py +0 -115
- notionary/blocks/toggleable_heading/toggleable_heading_markdown_node.py +0 -34
- notionary/blocks/types.py +0 -130
- notionary/blocks/video/__init__.py +0 -11
- notionary/blocks/video/video_element.py +0 -187
- notionary/blocks/video/video_element_models.py +0 -10
- notionary/blocks/video/video_markdown_node.py +0 -26
- notionary/comments/__init__.py +0 -26
- notionary/database/__init__.py +0 -4
- notionary/database/database.py +0 -480
- notionary/database/database_filter_builder.py +0 -173
- notionary/database/database_provider.py +0 -227
- notionary/database/exceptions.py +0 -13
- notionary/database/factory.py +0 -0
- notionary/database/models.py +0 -337
- notionary/database/notion_database.py +0 -487
- notionary/file_upload/__init__.py +0 -7
- notionary/page/client.py +0 -124
- notionary/page/markdown_whitespace_processor.py +0 -129
- notionary/page/models.py +0 -322
- notionary/page/notion_page.py +0 -712
- notionary/page/page_content_deleting_service.py +0 -117
- notionary/page/page_content_writer.py +0 -80
- notionary/page/property_formatter.py +0 -99
- notionary/page/reader/handler/__init__.py +0 -19
- notionary/page/reader/handler/base_block_renderer.py +0 -44
- notionary/page/reader/handler/block_processing_context.py +0 -35
- notionary/page/reader/handler/block_rendering_context.py +0 -48
- notionary/page/reader/handler/column_list_renderer.py +0 -51
- notionary/page/reader/handler/column_renderer.py +0 -60
- notionary/page/reader/handler/equation_renderer.py +0 -0
- notionary/page/reader/handler/line_renderer.py +0 -73
- notionary/page/reader/handler/numbered_list_renderer.py +0 -85
- notionary/page/reader/handler/toggle_renderer.py +0 -69
- notionary/page/reader/handler/toggleable_heading_renderer.py +0 -89
- notionary/page/reader/page_content_retriever.py +0 -81
- notionary/page/search_filter_builder.py +0 -132
- notionary/page/utils.py +0 -60
- notionary/page/writer/handler/__init__.py +0 -24
- notionary/page/writer/handler/code_handler.py +0 -72
- notionary/page/writer/handler/column_handler.py +0 -141
- notionary/page/writer/handler/column_list_handler.py +0 -139
- notionary/page/writer/handler/equation_handler.py +0 -74
- notionary/page/writer/handler/line_handler.py +0 -35
- notionary/page/writer/handler/line_processing_context.py +0 -54
- notionary/page/writer/handler/regular_line_handler.py +0 -86
- notionary/page/writer/handler/table_handler.py +0 -66
- notionary/page/writer/handler/toggle_handler.py +0 -159
- notionary/page/writer/handler/toggleable_heading_handler.py +0 -174
- notionary/page/writer/markdown_to_notion_converter.py +0 -139
- notionary/page/writer/markdown_to_notion_converter_context.py +0 -30
- notionary/page/writer/markdown_to_notion_text_length_post_processor.py +0 -0
- notionary/page/writer/notion_text_length_processor.py +0 -150
- notionary/schemas/__init__.py +0 -3
- notionary/schemas/base.py +0 -73
- notionary/shared/__init__.py +0 -3
- notionary/shared/name_to_id_resolver.py +0 -203
- notionary/telemetry/__init__.py +0 -19
- notionary/telemetry/service.py +0 -136
- notionary/telemetry/views.py +0 -73
- notionary/user/base_notion_user.py +0 -53
- notionary/user/models.py +0 -84
- notionary/user/notion_bot_user.py +0 -226
- notionary/user/notion_user.py +0 -255
- notionary/user/notion_user_manager.py +0 -101
- notionary/util/__init__.py +0 -15
- notionary/util/concurrency_limiter.py +0 -0
- notionary/util/factory_decorator.py +0 -0
- notionary/util/factory_only.py +0 -37
- notionary/util/fuzzy.py +0 -75
- notionary/util/page_id_utils.py +0 -27
- notionary/util/singleton.py +0 -18
- notionary/util/singleton_metaclass.py +0 -22
- notionary/workspace.py +0 -105
- notionary-0.2.27.dist-info/RECORD +0 -202
@@ -1,85 +0,0 @@
|
|
1
|
-
from notionary.blocks.models import Block, BlockType
|
2
|
-
from notionary.blocks.registry.block_registry import BlockRegistry
|
3
|
-
from notionary.page.reader.handler.base_block_renderer import BlockHandler
|
4
|
-
from notionary.page.reader.handler.block_rendering_context import BlockRenderingContext
|
5
|
-
|
6
|
-
|
7
|
-
class NumberedListRenderer(BlockHandler):
|
8
|
-
"""Handles numbered list items with sequential numbering."""
|
9
|
-
|
10
|
-
def _can_handle(self, context: BlockRenderingContext) -> bool:
|
11
|
-
"""Check if this is a numbered list item."""
|
12
|
-
return (
|
13
|
-
context.block.type == BlockType.NUMBERED_LIST_ITEM
|
14
|
-
and context.block.numbered_list_item is not None
|
15
|
-
)
|
16
|
-
|
17
|
-
async def _process(self, context: BlockRenderingContext) -> None:
|
18
|
-
"""Process numbered list item with sequential numbering."""
|
19
|
-
if context.all_blocks is None or context.current_block_index is None:
|
20
|
-
await self._process_single_item(context, 1)
|
21
|
-
return
|
22
|
-
|
23
|
-
items, blocks_to_skip = self._collect_numbered_list_items(context)
|
24
|
-
|
25
|
-
markdown_parts = []
|
26
|
-
for i, item_context in enumerate(items, 1):
|
27
|
-
item_markdown = await self._process_single_item(item_context, i)
|
28
|
-
if item_markdown:
|
29
|
-
markdown_parts.append(item_markdown)
|
30
|
-
|
31
|
-
# Set result and mark how many blocks to skip
|
32
|
-
if markdown_parts:
|
33
|
-
context.markdown_result = "\n".join(markdown_parts)
|
34
|
-
context.was_processed = True
|
35
|
-
context.blocks_consumed = blocks_to_skip
|
36
|
-
|
37
|
-
def _collect_numbered_list_items(
|
38
|
-
self, context: BlockRenderingContext
|
39
|
-
) -> tuple[list[BlockRenderingContext], int]:
|
40
|
-
"""Collect all consecutive numbered list items starting from current position."""
|
41
|
-
items = []
|
42
|
-
current_index = context.current_block_index
|
43
|
-
all_blocks = context.all_blocks
|
44
|
-
|
45
|
-
# Start with current block
|
46
|
-
items.append(context)
|
47
|
-
blocks_processed = 1
|
48
|
-
|
49
|
-
# Look ahead for more numbered list items
|
50
|
-
for i in range(current_index + 1, len(all_blocks)):
|
51
|
-
block = all_blocks[i]
|
52
|
-
|
53
|
-
# Check if it's a numbered list item
|
54
|
-
if (
|
55
|
-
block.type == BlockType.NUMBERED_LIST_ITEM
|
56
|
-
and block.numbered_list_item is not None
|
57
|
-
):
|
58
|
-
|
59
|
-
# Create context for this item
|
60
|
-
item_context = BlockRenderingContext(
|
61
|
-
block=block,
|
62
|
-
indent_level=context.indent_level,
|
63
|
-
block_registry=context.block_registry,
|
64
|
-
convert_children_callback=context.convert_children_callback,
|
65
|
-
)
|
66
|
-
items.append(item_context)
|
67
|
-
blocks_processed += 1
|
68
|
-
else:
|
69
|
-
# Not a numbered list item - stop collecting
|
70
|
-
break
|
71
|
-
|
72
|
-
return items, blocks_processed
|
73
|
-
|
74
|
-
async def _process_single_item(
|
75
|
-
self, context: BlockRenderingContext, number: int
|
76
|
-
) -> str:
|
77
|
-
"""Process a single numbered list item with the given number."""
|
78
|
-
from notionary.blocks.rich_text.text_inline_formatter import TextInlineFormatter
|
79
|
-
|
80
|
-
rich_text = context.block.numbered_list_item.rich_text
|
81
|
-
content = await TextInlineFormatter.extract_text_with_formatting(rich_text)
|
82
|
-
|
83
|
-
# Apply indentation
|
84
|
-
indent = " " * context.indent_level
|
85
|
-
return f"{indent}{number}. {content}"
|
@@ -1,69 +0,0 @@
|
|
1
|
-
from notionary.blocks.toggle.toggle_element import ToggleElement
|
2
|
-
from notionary.page.reader.handler import BlockHandler, BlockRenderingContext
|
3
|
-
|
4
|
-
|
5
|
-
class ToggleRenderer(BlockHandler):
|
6
|
-
"""Handles toggle blocks with their children content."""
|
7
|
-
|
8
|
-
def _can_handle(self, context: BlockRenderingContext) -> bool:
|
9
|
-
return ToggleElement.match_notion(context.block)
|
10
|
-
|
11
|
-
async def _process(self, context: BlockRenderingContext) -> None:
|
12
|
-
# Get the toggle title from the block
|
13
|
-
toggle_title = self._extract_toggle_title(context.block)
|
14
|
-
|
15
|
-
if not toggle_title:
|
16
|
-
return
|
17
|
-
|
18
|
-
# Create toggle start line
|
19
|
-
toggle_start = f"+++ {toggle_title}"
|
20
|
-
|
21
|
-
# Apply indentation if needed
|
22
|
-
if context.indent_level > 0:
|
23
|
-
toggle_start = self._indent_text(
|
24
|
-
toggle_start, spaces=context.indent_level * 4
|
25
|
-
)
|
26
|
-
|
27
|
-
# Process children if they exist
|
28
|
-
children_markdown = ""
|
29
|
-
if context.has_children():
|
30
|
-
# Import here to avoid circular dependency
|
31
|
-
from notionary.page.reader.page_content_retriever import (
|
32
|
-
PageContentRetriever,
|
33
|
-
)
|
34
|
-
|
35
|
-
# Create a temporary retriever to process children
|
36
|
-
retriever = PageContentRetriever(context.block_registry)
|
37
|
-
children_markdown = await retriever._convert_blocks_to_markdown(
|
38
|
-
context.get_children_blocks(),
|
39
|
-
indent_level=0, # No indentation for content inside toggles
|
40
|
-
)
|
41
|
-
|
42
|
-
# Create toggle end line
|
43
|
-
toggle_end = "+++"
|
44
|
-
if context.indent_level > 0:
|
45
|
-
toggle_end = self._indent_text(toggle_end, spaces=context.indent_level * 4)
|
46
|
-
|
47
|
-
# Combine toggle with children content
|
48
|
-
if children_markdown:
|
49
|
-
context.markdown_result = (
|
50
|
-
f"{toggle_start}\n{children_markdown}\n{toggle_end}"
|
51
|
-
)
|
52
|
-
else:
|
53
|
-
context.markdown_result = f"{toggle_start}\n{toggle_end}"
|
54
|
-
|
55
|
-
context.was_processed = True
|
56
|
-
|
57
|
-
def _extract_toggle_title(self, block) -> str:
|
58
|
-
"""Extract toggle title from the block."""
|
59
|
-
if not block.toggle or not block.toggle.rich_text:
|
60
|
-
return ""
|
61
|
-
|
62
|
-
title = ""
|
63
|
-
for text_obj in block.toggle.rich_text:
|
64
|
-
if hasattr(text_obj, "plain_text"):
|
65
|
-
title += text_obj.plain_text or ""
|
66
|
-
elif hasattr(text_obj, "text") and hasattr(text_obj.text, "content"):
|
67
|
-
title += text_obj.text.content or ""
|
68
|
-
|
69
|
-
return title.strip()
|
@@ -1,89 +0,0 @@
|
|
1
|
-
from notionary.blocks.toggleable_heading.toggleable_heading_element import (
|
2
|
-
ToggleableHeadingElement,
|
3
|
-
)
|
4
|
-
from notionary.blocks.types import BlockType
|
5
|
-
from notionary.page.reader.handler import BlockHandler, BlockRenderingContext
|
6
|
-
|
7
|
-
|
8
|
-
class ToggleableHeadingRenderer(BlockHandler):
|
9
|
-
"""Handles toggleable heading blocks with their children content."""
|
10
|
-
|
11
|
-
def _can_handle(self, context: BlockRenderingContext) -> bool:
|
12
|
-
return ToggleableHeadingElement.match_notion(context.block)
|
13
|
-
|
14
|
-
async def _process(self, context: BlockRenderingContext) -> None:
|
15
|
-
# Get the heading level and title
|
16
|
-
level, title = self._extract_heading_info(context.block)
|
17
|
-
|
18
|
-
if not title or level == 0:
|
19
|
-
return
|
20
|
-
|
21
|
-
# Create toggleable heading start line
|
22
|
-
prefix = "+++" + ("#" * level)
|
23
|
-
heading_start = f"{prefix} {title}"
|
24
|
-
|
25
|
-
# Apply indentation if needed
|
26
|
-
if context.indent_level > 0:
|
27
|
-
heading_start = self._indent_text(
|
28
|
-
heading_start, spaces=context.indent_level * 4
|
29
|
-
)
|
30
|
-
|
31
|
-
# Process children if they exist
|
32
|
-
children_markdown = ""
|
33
|
-
if context.has_children():
|
34
|
-
# Import here to avoid circular dependency
|
35
|
-
from notionary.page.reader.page_content_retriever import (
|
36
|
-
PageContentRetriever,
|
37
|
-
)
|
38
|
-
|
39
|
-
# Create a temporary retriever to process children
|
40
|
-
retriever = PageContentRetriever(context.block_registry)
|
41
|
-
children_markdown = await retriever._convert_blocks_to_markdown(
|
42
|
-
context.get_children_blocks(),
|
43
|
-
indent_level=0, # No indentation for content inside toggleable headings
|
44
|
-
)
|
45
|
-
|
46
|
-
# Create toggleable heading end line
|
47
|
-
heading_end = "+++"
|
48
|
-
if context.indent_level > 0:
|
49
|
-
heading_end = self._indent_text(
|
50
|
-
heading_end, spaces=context.indent_level * 4
|
51
|
-
)
|
52
|
-
|
53
|
-
# Combine heading with children content
|
54
|
-
if children_markdown:
|
55
|
-
context.markdown_result = (
|
56
|
-
f"{heading_start}\n{children_markdown}\n{heading_end}"
|
57
|
-
)
|
58
|
-
else:
|
59
|
-
context.markdown_result = f"{heading_start}\n{heading_end}"
|
60
|
-
|
61
|
-
context.was_processed = True
|
62
|
-
|
63
|
-
def _extract_heading_info(self, block) -> tuple[int, str]:
|
64
|
-
"""Extract heading level and title from the block."""
|
65
|
-
# Determine heading level from block type
|
66
|
-
if block.type == BlockType.HEADING_1:
|
67
|
-
level = 1
|
68
|
-
heading_content = block.heading_1
|
69
|
-
elif block.type == BlockType.HEADING_2:
|
70
|
-
level = 2
|
71
|
-
heading_content = block.heading_2
|
72
|
-
elif block.type == BlockType.HEADING_3:
|
73
|
-
level = 3
|
74
|
-
heading_content = block.heading_3
|
75
|
-
else:
|
76
|
-
return 0, ""
|
77
|
-
|
78
|
-
if not heading_content or not heading_content.rich_text:
|
79
|
-
return level, ""
|
80
|
-
|
81
|
-
# Extract title from rich_text
|
82
|
-
title = ""
|
83
|
-
for text_obj in heading_content.rich_text:
|
84
|
-
if hasattr(text_obj, "plain_text"):
|
85
|
-
title += text_obj.plain_text or ""
|
86
|
-
elif hasattr(text_obj, "text") and hasattr(text_obj.text, "content"):
|
87
|
-
title += text_obj.text.content or ""
|
88
|
-
|
89
|
-
return level, title.strip()
|
@@ -1,81 +0,0 @@
|
|
1
|
-
from notionary.blocks.models import Block
|
2
|
-
from notionary.blocks.registry.block_registry import BlockRegistry
|
3
|
-
from notionary.page.reader.handler import (
|
4
|
-
BlockRenderingContext,
|
5
|
-
ColumnListRenderer,
|
6
|
-
ColumnRenderer,
|
7
|
-
LineRenderer,
|
8
|
-
NumberedListRenderer,
|
9
|
-
ToggleableHeadingRenderer,
|
10
|
-
ToggleRenderer,
|
11
|
-
)
|
12
|
-
from notionary.util import LoggingMixin
|
13
|
-
|
14
|
-
|
15
|
-
class PageContentRetriever(LoggingMixin):
|
16
|
-
"""Retrieves Notion page content and converts it to Markdown using chain of responsibility."""
|
17
|
-
|
18
|
-
def __init__(
|
19
|
-
self,
|
20
|
-
block_registry: BlockRegistry,
|
21
|
-
):
|
22
|
-
self._block_registry = block_registry
|
23
|
-
|
24
|
-
self._setup_handler_chain()
|
25
|
-
|
26
|
-
async def convert_to_markdown(self, blocks: list[Block]) -> str:
|
27
|
-
"""
|
28
|
-
Retrieve page content and convert it to Markdown.
|
29
|
-
Uses the chain of responsibility pattern for scalable block processing.
|
30
|
-
"""
|
31
|
-
return await self._convert_blocks_to_markdown(blocks, indent_level=0)
|
32
|
-
|
33
|
-
def _setup_handler_chain(self) -> None:
|
34
|
-
"""Setup the chain of handlers in priority order."""
|
35
|
-
toggle_handler = ToggleRenderer()
|
36
|
-
toggleable_heading_handler = ToggleableHeadingRenderer()
|
37
|
-
column_list_handler = ColumnListRenderer()
|
38
|
-
column_handler = ColumnRenderer()
|
39
|
-
numbered_list_handler = NumberedListRenderer()
|
40
|
-
regular_handler = LineRenderer()
|
41
|
-
|
42
|
-
# Chain handlers - most specific first
|
43
|
-
toggle_handler.set_next(toggleable_heading_handler).set_next(
|
44
|
-
column_list_handler
|
45
|
-
).set_next(column_handler).set_next(numbered_list_handler).set_next(
|
46
|
-
regular_handler
|
47
|
-
)
|
48
|
-
|
49
|
-
self._handler_chain = toggle_handler
|
50
|
-
|
51
|
-
async def _convert_blocks_to_markdown(
|
52
|
-
self, blocks: list[Block], indent_level: int = 0
|
53
|
-
) -> str:
|
54
|
-
"""Convert blocks to Markdown using the handler chain."""
|
55
|
-
if not blocks:
|
56
|
-
return ""
|
57
|
-
|
58
|
-
markdown_parts = []
|
59
|
-
i = 0
|
60
|
-
|
61
|
-
while i < len(blocks):
|
62
|
-
block = blocks[i]
|
63
|
-
context = BlockRenderingContext(
|
64
|
-
block=block,
|
65
|
-
indent_level=indent_level,
|
66
|
-
block_registry=self._block_registry,
|
67
|
-
all_blocks=blocks,
|
68
|
-
current_block_index=i,
|
69
|
-
convert_children_callback=self._convert_blocks_to_markdown,
|
70
|
-
)
|
71
|
-
|
72
|
-
await self._handler_chain.handle(context)
|
73
|
-
|
74
|
-
if context.was_processed and context.markdown_result:
|
75
|
-
markdown_parts.append(context.markdown_result)
|
76
|
-
|
77
|
-
# Skip additional blocks if they were consumed by batch processing
|
78
|
-
i += max(1, context.blocks_consumed)
|
79
|
-
|
80
|
-
separator = "\n\n" if indent_level == 0 else "\n"
|
81
|
-
return separator.join(markdown_parts)
|
@@ -1,132 +0,0 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
from dataclasses import dataclass
|
4
|
-
from typing import Any, Dict, Literal, Optional
|
5
|
-
|
6
|
-
|
7
|
-
@dataclass
|
8
|
-
class SearchConfig:
|
9
|
-
"""Konfiguration für Notion Search API Filter."""
|
10
|
-
|
11
|
-
query: Optional[str] = None
|
12
|
-
object_type: Optional[Literal["page", "database"]] = None
|
13
|
-
sort_direction: Literal["ascending", "descending"] = "descending"
|
14
|
-
sort_timestamp: Literal["last_edited_time", "created_time"] = "last_edited_time"
|
15
|
-
page_size: int = 100
|
16
|
-
start_cursor: Optional[str] = None
|
17
|
-
|
18
|
-
def to_search_dict(self) -> Dict[str, Any]:
|
19
|
-
"""Konvertiert zu einem Notion Search API Dictionary."""
|
20
|
-
search_dict = {}
|
21
|
-
|
22
|
-
if self.query:
|
23
|
-
search_dict["query"] = self.query
|
24
|
-
|
25
|
-
if self.object_type:
|
26
|
-
search_dict["filter"] = {"property": "object", "value": self.object_type}
|
27
|
-
|
28
|
-
search_dict["sort"] = {
|
29
|
-
"direction": self.sort_direction,
|
30
|
-
"timestamp": self.sort_timestamp,
|
31
|
-
}
|
32
|
-
|
33
|
-
search_dict["page_size"] = min(self.page_size, 100)
|
34
|
-
|
35
|
-
if self.start_cursor:
|
36
|
-
search_dict["start_cursor"] = self.start_cursor
|
37
|
-
|
38
|
-
return search_dict
|
39
|
-
|
40
|
-
|
41
|
-
class SearchFilterBuilder:
|
42
|
-
"""
|
43
|
-
Builder class for creating Notion Search API filters with comprehensive options.
|
44
|
-
"""
|
45
|
-
|
46
|
-
def __init__(self, config: SearchConfig = None):
|
47
|
-
self.config = config or SearchConfig()
|
48
|
-
|
49
|
-
def with_query(self, query: str) -> SearchFilterBuilder:
|
50
|
-
"""Set the search query string."""
|
51
|
-
self.config.query = query
|
52
|
-
return self
|
53
|
-
|
54
|
-
def with_pages_only(self) -> SearchFilterBuilder:
|
55
|
-
"""Filter to only return pages."""
|
56
|
-
self.config.object_type = "page"
|
57
|
-
return self
|
58
|
-
|
59
|
-
def with_databases_only(self) -> SearchFilterBuilder:
|
60
|
-
"""Filter to only return databases."""
|
61
|
-
self.config.object_type = "database"
|
62
|
-
return self
|
63
|
-
|
64
|
-
def with_sort_direction(
|
65
|
-
self, direction: Literal["ascending", "descending"]
|
66
|
-
) -> SearchFilterBuilder:
|
67
|
-
"""Set sort direction (ascending or descending)."""
|
68
|
-
self.config.sort_direction = direction
|
69
|
-
return self
|
70
|
-
|
71
|
-
def with_sort_ascending(self) -> SearchFilterBuilder:
|
72
|
-
"""Sort results in ascending order."""
|
73
|
-
return self.with_sort_direction("ascending")
|
74
|
-
|
75
|
-
def with_sort_descending(self) -> SearchFilterBuilder:
|
76
|
-
"""Sort results in descending order."""
|
77
|
-
return self.with_sort_direction("descending")
|
78
|
-
|
79
|
-
def with_sort_timestamp(
|
80
|
-
self, timestamp: Literal["last_edited_time", "created_time"]
|
81
|
-
) -> SearchFilterBuilder:
|
82
|
-
"""Set the timestamp field to sort by."""
|
83
|
-
self.config.sort_timestamp = timestamp
|
84
|
-
return self
|
85
|
-
|
86
|
-
def with_sort_by_created_time(self) -> SearchFilterBuilder:
|
87
|
-
"""Sort by creation time."""
|
88
|
-
return self.with_sort_timestamp("created_time")
|
89
|
-
|
90
|
-
def with_sort_by_last_edited(self) -> SearchFilterBuilder:
|
91
|
-
"""Sort by last edited time."""
|
92
|
-
return self.with_sort_timestamp("last_edited_time")
|
93
|
-
|
94
|
-
def with_page_size(self, size: int) -> SearchFilterBuilder:
|
95
|
-
"""Set page size for pagination (max 100)."""
|
96
|
-
self.config.page_size = min(size, 100)
|
97
|
-
return self
|
98
|
-
|
99
|
-
def with_cursor(self, cursor: Optional[str]) -> SearchFilterBuilder:
|
100
|
-
"""Set start cursor for pagination."""
|
101
|
-
self.config.start_cursor = cursor
|
102
|
-
return self
|
103
|
-
|
104
|
-
def without_cursor(self) -> SearchFilterBuilder:
|
105
|
-
"""Remove start cursor (for first page)."""
|
106
|
-
self.config.start_cursor = None
|
107
|
-
return self
|
108
|
-
|
109
|
-
def build(self) -> Dict[str, Any]:
|
110
|
-
"""Build the final search filter dictionary. Builder bleibt wiederverwendbar!"""
|
111
|
-
return self.config.to_search_dict()
|
112
|
-
|
113
|
-
def get_config(self) -> SearchConfig:
|
114
|
-
"""Get the underlying SearchConfig."""
|
115
|
-
return self.config
|
116
|
-
|
117
|
-
def copy(self) -> SearchFilterBuilder:
|
118
|
-
"""Create a copy of the builder."""
|
119
|
-
new_config = SearchConfig(
|
120
|
-
query=self.config.query,
|
121
|
-
object_type=self.config.object_type,
|
122
|
-
sort_direction=self.config.sort_direction,
|
123
|
-
sort_timestamp=self.config.sort_timestamp,
|
124
|
-
page_size=self.config.page_size,
|
125
|
-
start_cursor=self.config.start_cursor,
|
126
|
-
)
|
127
|
-
return SearchFilterBuilder(new_config)
|
128
|
-
|
129
|
-
def reset(self) -> SearchFilterBuilder:
|
130
|
-
"""Reset all configurations to defaults."""
|
131
|
-
self.config = SearchConfig()
|
132
|
-
return self
|
notionary/page/utils.py
DELETED
@@ -1,60 +0,0 @@
|
|
1
|
-
from typing import Any
|
2
|
-
|
3
|
-
|
4
|
-
def extract_property_value(prop_data: dict) -> Any:
|
5
|
-
"""
|
6
|
-
Extract the value of a Notion property from its data dict.
|
7
|
-
Supports all common Notion property types.
|
8
|
-
|
9
|
-
Args:
|
10
|
-
prop_data: The property data dictionary from Notion API
|
11
|
-
|
12
|
-
Returns:
|
13
|
-
The extracted value based on property type
|
14
|
-
"""
|
15
|
-
prop_type = prop_data.get("type")
|
16
|
-
if not prop_type:
|
17
|
-
return None
|
18
|
-
|
19
|
-
# Handler dictionary for different property types
|
20
|
-
handlers = {
|
21
|
-
"title": lambda: "".join(
|
22
|
-
t.get("plain_text", "") for t in prop_data.get("title", [])
|
23
|
-
),
|
24
|
-
"rich_text": lambda: "".join(
|
25
|
-
t.get("plain_text", "") for t in prop_data.get("rich_text", [])
|
26
|
-
),
|
27
|
-
"number": lambda: prop_data.get("number"),
|
28
|
-
"select": lambda: (
|
29
|
-
prop_data.get("select", {}).get("name") if prop_data.get("select") else None
|
30
|
-
),
|
31
|
-
"multi_select": lambda: [
|
32
|
-
o.get("name") for o in prop_data.get("multi_select", [])
|
33
|
-
],
|
34
|
-
"status": lambda: (
|
35
|
-
prop_data.get("status", {}).get("name") if prop_data.get("status") else None
|
36
|
-
),
|
37
|
-
"date": lambda: prop_data.get("date"),
|
38
|
-
"checkbox": lambda: prop_data.get("checkbox"),
|
39
|
-
"url": lambda: prop_data.get("url"),
|
40
|
-
"email": lambda: prop_data.get("email"),
|
41
|
-
"phone_number": lambda: prop_data.get("phone_number"),
|
42
|
-
"people": lambda: [p.get("id") for p in prop_data.get("people", [])],
|
43
|
-
"files": lambda: [
|
44
|
-
(
|
45
|
-
f.get("external", {}).get("url")
|
46
|
-
if f.get("type") == "external"
|
47
|
-
else f.get("name")
|
48
|
-
)
|
49
|
-
for f in prop_data.get("files", [])
|
50
|
-
],
|
51
|
-
}
|
52
|
-
|
53
|
-
handler = handlers.get(prop_type)
|
54
|
-
if handler is None:
|
55
|
-
return prop_data # Return raw data if type unknown
|
56
|
-
|
57
|
-
try:
|
58
|
-
return handler()
|
59
|
-
except Exception:
|
60
|
-
return None # Return None if extraction fails
|
@@ -1,24 +0,0 @@
|
|
1
|
-
from .code_handler import CodeHandler
|
2
|
-
from .column_handler import ColumnHandler
|
3
|
-
from .column_list_handler import ColumnListHandler
|
4
|
-
from .equation_handler import EquationHandler
|
5
|
-
from .line_handler import LineHandler
|
6
|
-
from .line_processing_context import LineProcessingContext, ParentBlockContext
|
7
|
-
from .regular_line_handler import RegularLineHandler
|
8
|
-
from .table_handler import TableHandler
|
9
|
-
from .toggle_handler import ToggleHandler
|
10
|
-
from .toggleable_heading_handler import ToggleableHeadingHandler
|
11
|
-
|
12
|
-
__all__ = [
|
13
|
-
"LineProcessingContext",
|
14
|
-
"ParentBlockContext",
|
15
|
-
"LineHandler",
|
16
|
-
"ColumnListHandler",
|
17
|
-
"ColumnHandler",
|
18
|
-
"ToggleableHeadingHandler",
|
19
|
-
"ToggleHandler",
|
20
|
-
"TableHandler",
|
21
|
-
"RegularLineHandler",
|
22
|
-
"CodeHandler",
|
23
|
-
"EquationHandler",
|
24
|
-
]
|
@@ -1,72 +0,0 @@
|
|
1
|
-
import re
|
2
|
-
|
3
|
-
from notionary.blocks.code.code_element import CodeElement
|
4
|
-
from notionary.page.writer.handler.line_handler import (
|
5
|
-
LineHandler,
|
6
|
-
LineProcessingContext,
|
7
|
-
)
|
8
|
-
|
9
|
-
|
10
|
-
class CodeHandler(LineHandler):
|
11
|
-
"""Handles code block specific logic with batching.
|
12
|
-
|
13
|
-
Markdown syntax:
|
14
|
-
```language "optional caption"
|
15
|
-
code lines...
|
16
|
-
```
|
17
|
-
"""
|
18
|
-
|
19
|
-
def __init__(self):
|
20
|
-
super().__init__()
|
21
|
-
self._code_start_pattern = re.compile(r"^```(\w*)\s*(?:\"([^\"]*)\")?\s*$")
|
22
|
-
self._code_end_pattern = re.compile(r"^```\s*$")
|
23
|
-
|
24
|
-
def _can_handle(self, context: LineProcessingContext) -> bool:
|
25
|
-
if self._is_inside_parent_context(context):
|
26
|
-
return False
|
27
|
-
return self._is_code_start(context)
|
28
|
-
|
29
|
-
async def _process(self, context: LineProcessingContext) -> None:
|
30
|
-
if self._is_code_start(context):
|
31
|
-
await self._process_complete_code_block(context)
|
32
|
-
self._mark_processed(context)
|
33
|
-
|
34
|
-
def _is_code_start(self, context: LineProcessingContext) -> bool:
|
35
|
-
"""Check if this line starts a code block."""
|
36
|
-
return self._code_start_pattern.match(context.line.strip()) is not None
|
37
|
-
|
38
|
-
def _is_inside_parent_context(self, context: LineProcessingContext) -> bool:
|
39
|
-
"""Check if we're currently inside any parent context (toggle, heading, etc.)."""
|
40
|
-
return len(context.parent_stack) > 0
|
41
|
-
|
42
|
-
async def _process_complete_code_block(
|
43
|
-
self, context: LineProcessingContext
|
44
|
-
) -> None:
|
45
|
-
"""Process the entire code block in one go using CodeElement."""
|
46
|
-
code_lines, lines_to_consume = self._collect_code_lines(context)
|
47
|
-
|
48
|
-
block = CodeElement.create_from_markdown_block(
|
49
|
-
opening_line=context.line, code_lines=code_lines
|
50
|
-
)
|
51
|
-
|
52
|
-
if block:
|
53
|
-
context.lines_consumed = lines_to_consume
|
54
|
-
context.result_blocks.append(block)
|
55
|
-
|
56
|
-
def _collect_code_lines(
|
57
|
-
self, context: LineProcessingContext
|
58
|
-
) -> tuple[list[str], int]:
|
59
|
-
"""Collect lines until closing fence and return (lines, count_to_consume)."""
|
60
|
-
lines = []
|
61
|
-
for idx, ln in enumerate(context.get_remaining_lines()):
|
62
|
-
if self._code_end_pattern.match(ln.strip()):
|
63
|
-
return lines, idx + 1
|
64
|
-
lines.append(ln)
|
65
|
-
# No closing fence: consume all remaining
|
66
|
-
rem = context.get_remaining_lines()
|
67
|
-
return rem, len(rem)
|
68
|
-
|
69
|
-
def _mark_processed(self, context: LineProcessingContext) -> None:
|
70
|
-
"""Mark context as processed and continue."""
|
71
|
-
context.was_processed = True
|
72
|
-
context.should_continue = True
|