notionary 0.2.27__py3-none-any.whl → 0.3.0__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/__init__.py +4 -4
- notionary/blocks/client.py +90 -216
- notionary/blocks/enums.py +167 -0
- notionary/blocks/rich_text/markdown_rich_text_converter.py +280 -0
- notionary/blocks/rich_text/models.py +178 -0
- notionary/blocks/rich_text/name_id_resolver/__init__.py +13 -0
- notionary/blocks/rich_text/name_id_resolver/data_source.py +32 -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 +144 -0
- notionary/blocks/rich_text/rich_text_patterns.py +42 -0
- notionary/blocks/schemas.py +778 -0
- notionary/comments/__init__.py +1 -22
- notionary/comments/client.py +52 -187
- notionary/comments/factory.py +38 -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 +104 -0
- notionary/data_source/properties/schemas.py +402 -0
- notionary/data_source/query/builder.py +448 -0
- notionary/data_source/query/resolver.py +114 -0
- notionary/data_source/query/schema.py +302 -0
- notionary/data_source/query/validator.py +73 -0
- notionary/data_source/schema/registry.py +104 -0
- notionary/data_source/schema/service.py +136 -0
- notionary/data_source/schemas.py +27 -0
- notionary/data_source/service.py +377 -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 +168 -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 +57 -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 +204 -0
- notionary/http/models.py +50 -0
- notionary/page/blocks/client.py +1 -0
- notionary/page/content/factory.py +73 -0
- notionary/page/content/markdown/__init__.py +5 -0
- notionary/page/content/markdown/builder.py +226 -0
- notionary/page/content/markdown/nodes/__init__.py +52 -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 +41 -0
- notionary/page/content/markdown/nodes/callout.py +34 -0
- notionary/page/content/markdown/nodes/code.py +28 -0
- notionary/page/content/markdown/nodes/columns.py +69 -0
- notionary/page/content/markdown/nodes/container.py +64 -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 +36 -0
- notionary/page/content/markdown/nodes/image.py +23 -0
- notionary/page/content/markdown/nodes/mixins/__init__.py +5 -0
- notionary/page/content/markdown/nodes/mixins/caption.py +12 -0
- notionary/page/content/markdown/nodes/numbered_list.py +38 -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 +27 -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 +38 -0
- notionary/page/content/markdown/nodes/toggle.py +27 -0
- notionary/page/content/markdown/nodes/video.py +23 -0
- notionary/page/content/parser/context.py +126 -0
- notionary/page/content/parser/factory.py +210 -0
- notionary/page/content/parser/parsers/__init__.py +58 -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 +85 -0
- notionary/page/content/parser/parsers/callout.py +100 -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 +76 -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 +115 -0
- notionary/page/content/parser/parsers/image.py +42 -0
- notionary/page/content/parser/parsers/numbered_list.py +89 -0
- notionary/page/content/parser/parsers/paragraph.py +37 -0
- notionary/page/content/parser/parsers/pdf.py +42 -0
- notionary/page/content/parser/parsers/quote.py +125 -0
- notionary/page/content/parser/parsers/space.py +41 -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 +96 -0
- notionary/page/content/parser/parsers/toggle.py +70 -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 +95 -0
- notionary/page/content/parser/post_processing/handlers/rich_text_length_truncation.py +114 -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 +11 -0
- notionary/page/content/parser/pre_processsing/handlers/column_syntax.py +130 -0
- notionary/page/content/parser/pre_processsing/handlers/indentation.py +84 -0
- notionary/page/content/parser/pre_processsing/handlers/port.py +7 -0
- notionary/page/content/parser/pre_processsing/handlers/whitespace.py +73 -0
- notionary/page/content/parser/pre_processsing/service.py +15 -0
- notionary/page/content/parser/service.py +78 -0
- notionary/page/content/renderer/context.py +51 -0
- notionary/page/content/renderer/factory.py +231 -0
- notionary/page/content/renderer/post_processing/handlers/__init__.py +5 -0
- notionary/page/content/renderer/post_processing/handlers/numbered_list.py +156 -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 +55 -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 +50 -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 +53 -0
- notionary/page/content/renderer/renderers/column_list.py +44 -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 +95 -0
- notionary/page/content/renderer/renderers/image.py +31 -0
- notionary/page/content/renderer/renderers/numbered_list.py +42 -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 +52 -0
- notionary/page/content/renderer/renderers/video.py +31 -0
- notionary/page/content/renderer/service.py +50 -0
- notionary/page/content/service.py +68 -0
- notionary/page/content/syntax/__init__.py +4 -0
- notionary/page/content/syntax/grammar.py +10 -0
- notionary/page/content/syntax/models.py +66 -0
- notionary/page/content/syntax/registry.py +393 -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 +308 -0
- notionary/page/properties/service.py +261 -0
- notionary/page/schemas.py +13 -0
- notionary/page/service.py +225 -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/shared/typings.py +3 -0
- notionary/user/__init__.py +4 -8
- notionary/user/base.py +138 -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/date.py +51 -0
- notionary/utils/decorators.py +122 -0
- notionary/utils/fuzzy.py +68 -0
- notionary/utils/mixins/logging.py +58 -0
- notionary/utils/pagination.py +100 -0
- notionary/utils/uuid_utils.py +20 -0
- notionary/workspace/__init__.py +4 -0
- notionary/workspace/client.py +62 -0
- notionary/workspace/query/__init__.py +3 -0
- notionary/workspace/query/builder.py +60 -0
- notionary/workspace/query/models.py +61 -0
- notionary/workspace/query/service.py +100 -0
- notionary/workspace/schemas.py +21 -0
- notionary/workspace/service.py +116 -0
- notionary-0.3.0.dist-info/METADATA +201 -0
- notionary-0.3.0.dist-info/RECORD +209 -0
- {notionary-0.2.27.dist-info → notionary-0.3.0.dist-info}/WHEEL +1 -1
- {notionary-0.2.27.dist-info → notionary-0.3.0.dist-info/licenses}/LICENSE +9 -9
- notionary/base_notion_client.py +0 -219
- 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/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/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/logging_mixin.py +0 -59
- 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/METADATA +0 -270
- notionary-0.2.27.dist-info/RECORD +0 -202
- /notionary/{database → user}/factory.py +0 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from typing import override
|
|
2
|
+
|
|
3
|
+
from notionary.blocks.rich_text.rich_text_markdown_converter import RichTextToMarkdownConverter
|
|
4
|
+
from notionary.blocks.schemas import Block, BlockType
|
|
5
|
+
from notionary.page.content.renderer.context import MarkdownRenderingContext
|
|
6
|
+
from notionary.page.content.renderer.renderers.base import BlockRenderer
|
|
7
|
+
from notionary.page.content.syntax import SyntaxRegistry
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class QuoteRenderer(BlockRenderer):
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
syntax_registry: SyntaxRegistry | None = None,
|
|
14
|
+
rich_text_markdown_converter: RichTextToMarkdownConverter | None = None,
|
|
15
|
+
) -> None:
|
|
16
|
+
super().__init__(syntax_registry=syntax_registry)
|
|
17
|
+
self._rich_text_markdown_converter = rich_text_markdown_converter or RichTextToMarkdownConverter()
|
|
18
|
+
|
|
19
|
+
@override
|
|
20
|
+
def _can_handle(self, block: Block) -> bool:
|
|
21
|
+
return block.type == BlockType.QUOTE
|
|
22
|
+
|
|
23
|
+
@override
|
|
24
|
+
async def _process(self, context: MarkdownRenderingContext) -> None:
|
|
25
|
+
markdown = await self._convert_quote_to_markdown(context.block)
|
|
26
|
+
|
|
27
|
+
if not markdown:
|
|
28
|
+
context.markdown_result = ""
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
syntax = self._syntax_registry.get_quote_syntax()
|
|
32
|
+
quote_lines = markdown.split("\n")
|
|
33
|
+
quote_markdown = "\n".join(f"{syntax.start_delimiter}{line}" for line in quote_lines)
|
|
34
|
+
|
|
35
|
+
if context.indent_level > 0:
|
|
36
|
+
quote_markdown = context.indent_text(quote_markdown)
|
|
37
|
+
|
|
38
|
+
children_markdown = await context.render_children_with_additional_indent(1)
|
|
39
|
+
|
|
40
|
+
if children_markdown:
|
|
41
|
+
context.markdown_result = f"{quote_markdown}\n{children_markdown}"
|
|
42
|
+
else:
|
|
43
|
+
context.markdown_result = quote_markdown
|
|
44
|
+
|
|
45
|
+
async def _convert_quote_to_markdown(self, block: Block) -> str | None:
|
|
46
|
+
if not block.quote or not block.quote.rich_text:
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
return await self._rich_text_markdown_converter.to_markdown(block.quote.rich_text)
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
from typing import override
|
|
2
|
+
|
|
3
|
+
from notionary.blocks.rich_text.rich_text_markdown_converter import RichTextToMarkdownConverter
|
|
4
|
+
from notionary.blocks.schemas import Block, BlockType
|
|
5
|
+
from notionary.page.content.renderer.context import MarkdownRenderingContext
|
|
6
|
+
from notionary.page.content.renderer.renderers.base import BlockRenderer
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TableRenderer(BlockRenderer):
|
|
10
|
+
MINIMUM_COLUMN_WIDTH = 3
|
|
11
|
+
|
|
12
|
+
def __init__(self, rich_text_markdown_converter: RichTextToMarkdownConverter | None = None) -> None:
|
|
13
|
+
super().__init__()
|
|
14
|
+
self._rich_text_markdown_converter = rich_text_markdown_converter or RichTextToMarkdownConverter()
|
|
15
|
+
self._table_syntax = self._syntax_registry.get_table_syntax()
|
|
16
|
+
|
|
17
|
+
@override
|
|
18
|
+
def _can_handle(self, block: Block) -> bool:
|
|
19
|
+
return block.type == BlockType.TABLE
|
|
20
|
+
|
|
21
|
+
@override
|
|
22
|
+
async def _process(self, context: MarkdownRenderingContext) -> None:
|
|
23
|
+
table_markdown = await self._build_table_markdown(context.block)
|
|
24
|
+
|
|
25
|
+
if not table_markdown:
|
|
26
|
+
context.markdown_result = ""
|
|
27
|
+
return
|
|
28
|
+
|
|
29
|
+
if context.indent_level > 0:
|
|
30
|
+
table_markdown = context.indent_text(table_markdown)
|
|
31
|
+
|
|
32
|
+
children_markdown = await context.render_children_with_additional_indent(1)
|
|
33
|
+
|
|
34
|
+
if children_markdown:
|
|
35
|
+
context.markdown_result = f"{table_markdown}\n{children_markdown}"
|
|
36
|
+
else:
|
|
37
|
+
context.markdown_result = table_markdown
|
|
38
|
+
|
|
39
|
+
async def _build_table_markdown(self, block: Block) -> str:
|
|
40
|
+
if not block.table or not block.has_children or not block.children:
|
|
41
|
+
return ""
|
|
42
|
+
|
|
43
|
+
rows = []
|
|
44
|
+
for row_block in block.children:
|
|
45
|
+
if row_block.type != BlockType.TABLE_ROW or not row_block.table_row:
|
|
46
|
+
continue
|
|
47
|
+
|
|
48
|
+
row_cells = await self._extract_row_cells(row_block)
|
|
49
|
+
rows.append(row_cells)
|
|
50
|
+
|
|
51
|
+
if not rows:
|
|
52
|
+
return ""
|
|
53
|
+
|
|
54
|
+
max_columns = max(len(row) for row in rows)
|
|
55
|
+
normalized_rows = self._normalize_row_lengths(rows, max_columns)
|
|
56
|
+
column_widths = self._calculate_column_widths(normalized_rows, max_columns)
|
|
57
|
+
|
|
58
|
+
markdown_lines = []
|
|
59
|
+
|
|
60
|
+
first_row = normalized_rows[0]
|
|
61
|
+
formatted_first_row = self._format_row(first_row, column_widths)
|
|
62
|
+
markdown_lines.append(formatted_first_row)
|
|
63
|
+
|
|
64
|
+
separator_line = self._create_separator_line(column_widths)
|
|
65
|
+
markdown_lines.append(separator_line)
|
|
66
|
+
|
|
67
|
+
remaining_rows = normalized_rows[1:]
|
|
68
|
+
for row in remaining_rows:
|
|
69
|
+
formatted_row = self._format_row(row, column_widths)
|
|
70
|
+
markdown_lines.append(formatted_row)
|
|
71
|
+
|
|
72
|
+
return "\n".join(markdown_lines)
|
|
73
|
+
|
|
74
|
+
def _normalize_row_lengths(self, rows: list[list[str]], target_length: int) -> list[list[str]]:
|
|
75
|
+
return [row + [""] * (target_length - len(row)) for row in rows]
|
|
76
|
+
|
|
77
|
+
def _calculate_column_widths(self, rows: list[list[str]], num_columns: int) -> list[int]:
|
|
78
|
+
widths = [max(len(row[i]) for row in rows) for i in range(num_columns)]
|
|
79
|
+
return [max(width, self.MINIMUM_COLUMN_WIDTH) for width in widths]
|
|
80
|
+
|
|
81
|
+
def _format_row(self, cells: list[str], column_widths: list[int]) -> str:
|
|
82
|
+
centered_cells = [cell.center(column_widths[i]) for i, cell in enumerate(cells)]
|
|
83
|
+
delimiter = self._table_syntax.start_delimiter
|
|
84
|
+
return f"{delimiter} {f' {delimiter} '.join(centered_cells)} {delimiter}"
|
|
85
|
+
|
|
86
|
+
def _create_separator_line(self, column_widths: list[int]) -> str:
|
|
87
|
+
separators = ["-" * width for width in column_widths]
|
|
88
|
+
delimiter = self._table_syntax.start_delimiter
|
|
89
|
+
return f"{delimiter} {f' {delimiter} '.join(separators)} {delimiter}"
|
|
90
|
+
|
|
91
|
+
def _has_column_header(self, block: Block) -> bool:
|
|
92
|
+
if not block.table:
|
|
93
|
+
return False
|
|
94
|
+
return block.table.has_column_header or False
|
|
95
|
+
|
|
96
|
+
def _has_row_header(self, block: Block) -> bool:
|
|
97
|
+
if not block.table:
|
|
98
|
+
return False
|
|
99
|
+
return block.table.has_row_header or False
|
|
100
|
+
|
|
101
|
+
def _get_table_width(self, block: Block) -> int:
|
|
102
|
+
if not block.table:
|
|
103
|
+
return 0
|
|
104
|
+
return block.table.table_width or 0
|
|
105
|
+
|
|
106
|
+
async def _extract_row_cells(self, row_block: Block) -> list[str]:
|
|
107
|
+
if not row_block.table_row or not row_block.table_row.cells:
|
|
108
|
+
return []
|
|
109
|
+
|
|
110
|
+
cells = []
|
|
111
|
+
for cell in row_block.table_row.cells:
|
|
112
|
+
cell_text = await self._rich_text_markdown_converter.to_markdown(cell)
|
|
113
|
+
cells.append(cell_text or "")
|
|
114
|
+
|
|
115
|
+
return cells
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from typing import override
|
|
2
|
+
|
|
3
|
+
from notionary.blocks.schemas import Block, BlockType
|
|
4
|
+
from notionary.page.content.renderer.context import MarkdownRenderingContext
|
|
5
|
+
from notionary.page.content.renderer.renderers.base import BlockRenderer
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TableOfContentsRenderer(BlockRenderer):
|
|
9
|
+
@override
|
|
10
|
+
def _can_handle(self, block: Block) -> bool:
|
|
11
|
+
return block.type == BlockType.TABLE_OF_CONTENTS
|
|
12
|
+
|
|
13
|
+
@override
|
|
14
|
+
async def _process(self, context: MarkdownRenderingContext) -> None:
|
|
15
|
+
syntax = self._syntax_registry.get_table_of_contents_syntax()
|
|
16
|
+
toc_markdown = syntax.start_delimiter
|
|
17
|
+
|
|
18
|
+
if context.indent_level > 0:
|
|
19
|
+
toc_markdown = context.indent_text(toc_markdown)
|
|
20
|
+
|
|
21
|
+
children_markdown = await context.render_children_with_additional_indent(1)
|
|
22
|
+
|
|
23
|
+
if children_markdown:
|
|
24
|
+
context.markdown_result = f"{toc_markdown}\n{children_markdown}"
|
|
25
|
+
else:
|
|
26
|
+
context.markdown_result = toc_markdown
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from typing import override
|
|
2
|
+
|
|
3
|
+
from notionary.blocks.enums import BlockType
|
|
4
|
+
from notionary.blocks.schemas import Block
|
|
5
|
+
from notionary.page.content.renderer.context import MarkdownRenderingContext
|
|
6
|
+
from notionary.page.content.renderer.renderers.base import BlockRenderer
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TableRowHandler(BlockRenderer):
|
|
10
|
+
@override
|
|
11
|
+
def _can_handle(self, block: Block) -> bool:
|
|
12
|
+
return block.type == BlockType.TABLE_ROW
|
|
13
|
+
|
|
14
|
+
@override
|
|
15
|
+
async def _process(self, context: MarkdownRenderingContext) -> None:
|
|
16
|
+
"""Table rows are internally handled by table as the structure supports it"""
|
|
17
|
+
pass
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from typing import override
|
|
2
|
+
|
|
3
|
+
from notionary.blocks.enums import BlockType
|
|
4
|
+
from notionary.blocks.rich_text.rich_text_markdown_converter import RichTextToMarkdownConverter
|
|
5
|
+
from notionary.blocks.schemas import Block
|
|
6
|
+
from notionary.page.content.renderer.context import MarkdownRenderingContext
|
|
7
|
+
from notionary.page.content.renderer.renderers.base import BlockRenderer
|
|
8
|
+
from notionary.page.content.syntax import SyntaxRegistry
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TodoRenderer(BlockRenderer):
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
syntax_registry: SyntaxRegistry | None = None,
|
|
15
|
+
rich_text_markdown_converter: RichTextToMarkdownConverter | None = None,
|
|
16
|
+
) -> None:
|
|
17
|
+
super().__init__(syntax_registry=syntax_registry)
|
|
18
|
+
self._rich_text_markdown_converter = rich_text_markdown_converter or RichTextToMarkdownConverter()
|
|
19
|
+
|
|
20
|
+
@override
|
|
21
|
+
def _can_handle(self, block: Block) -> bool:
|
|
22
|
+
return block.type == BlockType.TO_DO
|
|
23
|
+
|
|
24
|
+
@override
|
|
25
|
+
async def _process(self, context: MarkdownRenderingContext) -> None:
|
|
26
|
+
is_checked, content = await self._extract_todo_info(context.block)
|
|
27
|
+
|
|
28
|
+
if not content:
|
|
29
|
+
context.markdown_result = ""
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
syntax = self._syntax_registry.get_todo_done_syntax() if is_checked else self._syntax_registry.get_todo_syntax()
|
|
33
|
+
|
|
34
|
+
todo_markdown = f"{syntax.start_delimiter} {content}"
|
|
35
|
+
|
|
36
|
+
if context.indent_level > 0:
|
|
37
|
+
todo_markdown = context.indent_text(todo_markdown)
|
|
38
|
+
|
|
39
|
+
children_markdown = await context.render_children_with_additional_indent(1)
|
|
40
|
+
|
|
41
|
+
if children_markdown:
|
|
42
|
+
context.markdown_result = f"{todo_markdown}\n{children_markdown}"
|
|
43
|
+
else:
|
|
44
|
+
context.markdown_result = todo_markdown
|
|
45
|
+
|
|
46
|
+
async def _extract_todo_info(self, block: Block) -> tuple[bool, str]:
|
|
47
|
+
if not block.to_do:
|
|
48
|
+
return False, ""
|
|
49
|
+
|
|
50
|
+
is_checked = block.to_do.checked or False
|
|
51
|
+
|
|
52
|
+
content = ""
|
|
53
|
+
if block.to_do.rich_text:
|
|
54
|
+
content = await self._rich_text_markdown_converter.to_markdown(block.to_do.rich_text)
|
|
55
|
+
|
|
56
|
+
return is_checked, content
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from typing import override
|
|
2
|
+
|
|
3
|
+
from notionary.blocks.enums import BlockType
|
|
4
|
+
from notionary.blocks.rich_text.rich_text_markdown_converter import RichTextToMarkdownConverter
|
|
5
|
+
from notionary.blocks.schemas import Block
|
|
6
|
+
from notionary.page.content.renderer.context import MarkdownRenderingContext
|
|
7
|
+
from notionary.page.content.renderer.renderers.base import BlockRenderer
|
|
8
|
+
from notionary.page.content.syntax import SyntaxRegistry
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ToggleRenderer(BlockRenderer):
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
syntax_registry: SyntaxRegistry | None = None,
|
|
15
|
+
rich_text_markdown_converter: RichTextToMarkdownConverter | None = None,
|
|
16
|
+
) -> None:
|
|
17
|
+
super().__init__(syntax_registry=syntax_registry)
|
|
18
|
+
self._rich_text_markdown_converter = rich_text_markdown_converter or RichTextToMarkdownConverter()
|
|
19
|
+
|
|
20
|
+
@override
|
|
21
|
+
def _can_handle(self, block: Block) -> bool:
|
|
22
|
+
return block.type == BlockType.TOGGLE
|
|
23
|
+
|
|
24
|
+
@override
|
|
25
|
+
async def _process(self, context: MarkdownRenderingContext) -> None:
|
|
26
|
+
toggle_title = await self._extract_toggle_title(context.block)
|
|
27
|
+
|
|
28
|
+
if not toggle_title:
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
syntax = self._syntax_registry.get_toggle_syntax()
|
|
32
|
+
toggle_start = f"{syntax.start_delimiter} {toggle_title}"
|
|
33
|
+
|
|
34
|
+
if context.indent_level > 0:
|
|
35
|
+
toggle_start = context.indent_text(toggle_start)
|
|
36
|
+
|
|
37
|
+
original_indent = context.indent_level
|
|
38
|
+
context.indent_level += 1
|
|
39
|
+
children_markdown = await context.render_children()
|
|
40
|
+
context.indent_level = original_indent
|
|
41
|
+
|
|
42
|
+
if children_markdown:
|
|
43
|
+
context.markdown_result = f"{toggle_start}\n{children_markdown}"
|
|
44
|
+
else:
|
|
45
|
+
context.markdown_result = toggle_start
|
|
46
|
+
|
|
47
|
+
async def _extract_toggle_title(self, block: Block) -> str:
|
|
48
|
+
if not block.toggle or not block.toggle.rich_text:
|
|
49
|
+
return ""
|
|
50
|
+
|
|
51
|
+
rich_text_title = block.toggle.rich_text
|
|
52
|
+
return await self._rich_text_markdown_converter.to_markdown(rich_text_title)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from typing import override
|
|
2
|
+
|
|
3
|
+
from notionary.blocks.schemas import Block, BlockType
|
|
4
|
+
from notionary.page.content.renderer.renderers.captioned_block import CaptionedBlockRenderer
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class VideoRenderer(CaptionedBlockRenderer):
|
|
8
|
+
@override
|
|
9
|
+
def _can_handle(self, block: Block) -> bool:
|
|
10
|
+
return block.type == BlockType.VIDEO
|
|
11
|
+
|
|
12
|
+
@override
|
|
13
|
+
async def _render_main_content(self, block: Block) -> str:
|
|
14
|
+
url = self._extract_video_url(block)
|
|
15
|
+
|
|
16
|
+
if not url:
|
|
17
|
+
return ""
|
|
18
|
+
|
|
19
|
+
syntax = self._syntax_registry.get_video_syntax()
|
|
20
|
+
return f"{syntax.start_delimiter}{url}{syntax.end_delimiter}"
|
|
21
|
+
|
|
22
|
+
def _extract_video_url(self, block: Block) -> str:
|
|
23
|
+
if not block.video:
|
|
24
|
+
return ""
|
|
25
|
+
|
|
26
|
+
if block.video.external:
|
|
27
|
+
return block.video.external.url or ""
|
|
28
|
+
elif block.video.file:
|
|
29
|
+
return block.video.file.url or ""
|
|
30
|
+
|
|
31
|
+
return ""
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from notionary.blocks.schemas import Block
|
|
2
|
+
from notionary.page.content.renderer.context import MarkdownRenderingContext
|
|
3
|
+
from notionary.page.content.renderer.post_processing.service import MarkdownRenderingPostProcessor
|
|
4
|
+
from notionary.page.content.renderer.renderers import BlockRenderer
|
|
5
|
+
from notionary.utils.mixins.logging import LoggingMixin
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class NotionToMarkdownConverter(LoggingMixin):
|
|
9
|
+
def __init__(
|
|
10
|
+
self,
|
|
11
|
+
renderer_chain: BlockRenderer,
|
|
12
|
+
post_processor: MarkdownRenderingPostProcessor,
|
|
13
|
+
) -> None:
|
|
14
|
+
self._renderer_chain = renderer_chain
|
|
15
|
+
self._post_processor = post_processor
|
|
16
|
+
|
|
17
|
+
async def convert(self, blocks: list[Block], indent_level: int = 0) -> str:
|
|
18
|
+
if not blocks:
|
|
19
|
+
return ""
|
|
20
|
+
|
|
21
|
+
rendered_block_parts = []
|
|
22
|
+
current_block_index = 0
|
|
23
|
+
|
|
24
|
+
while current_block_index < len(blocks):
|
|
25
|
+
context = self._create_rendering_context(blocks, current_block_index, indent_level)
|
|
26
|
+
await self._renderer_chain.handle(context)
|
|
27
|
+
|
|
28
|
+
if context.markdown_result:
|
|
29
|
+
rendered_block_parts.append(context.markdown_result)
|
|
30
|
+
|
|
31
|
+
current_block_index += 1
|
|
32
|
+
|
|
33
|
+
result = self._join_rendered_blocks(rendered_block_parts, indent_level)
|
|
34
|
+
result = self._post_processor.process(result)
|
|
35
|
+
|
|
36
|
+
return result
|
|
37
|
+
|
|
38
|
+
def _create_rendering_context(
|
|
39
|
+
self, blocks: list[Block], block_index: int, indent_level: int
|
|
40
|
+
) -> MarkdownRenderingContext:
|
|
41
|
+
block = blocks[block_index]
|
|
42
|
+
return MarkdownRenderingContext(
|
|
43
|
+
block=block,
|
|
44
|
+
indent_level=indent_level,
|
|
45
|
+
convert_children_callback=self.convert,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
def _join_rendered_blocks(self, rendered_parts: list[str], indent_level: int) -> str:
|
|
49
|
+
separator = "\n\n" if indent_level == 0 else "\n"
|
|
50
|
+
return separator.join(rendered_parts)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from collections.abc import Callable
|
|
3
|
+
|
|
4
|
+
from notionary.blocks.client import NotionBlockHttpClient
|
|
5
|
+
from notionary.blocks.schemas import Block
|
|
6
|
+
from notionary.page.content.markdown.builder import MarkdownBuilder
|
|
7
|
+
from notionary.page.content.parser.service import MarkdownToNotionConverter
|
|
8
|
+
from notionary.page.content.renderer.service import NotionToMarkdownConverter
|
|
9
|
+
from notionary.utils.decorators import async_retry, time_execution_async
|
|
10
|
+
from notionary.utils.mixins.logging import LoggingMixin
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class PageContentService(LoggingMixin):
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
page_id: str,
|
|
17
|
+
block_client: NotionBlockHttpClient,
|
|
18
|
+
markdown_converter: MarkdownToNotionConverter,
|
|
19
|
+
notion_to_markdown_converter: NotionToMarkdownConverter,
|
|
20
|
+
) -> None:
|
|
21
|
+
self._page_id = page_id
|
|
22
|
+
self._block_client = block_client
|
|
23
|
+
self._markdown_converter = markdown_converter
|
|
24
|
+
self._notion_to_markdown_converter = notion_to_markdown_converter
|
|
25
|
+
|
|
26
|
+
@time_execution_async()
|
|
27
|
+
async def get_as_markdown(self) -> str:
|
|
28
|
+
blocks = await self._block_client.get_block_tree(parent_block_id=self._page_id)
|
|
29
|
+
return await self._notion_to_markdown_converter.convert(blocks=blocks)
|
|
30
|
+
|
|
31
|
+
@time_execution_async()
|
|
32
|
+
async def clear(self) -> None:
|
|
33
|
+
children_response = await self._block_client.get_block_children(block_id=self._page_id)
|
|
34
|
+
|
|
35
|
+
if not children_response or not children_response.results:
|
|
36
|
+
self.logger.debug("No blocks to delete for page: %s", self._page_id)
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
await asyncio.gather(*[self._delete_single_block(block) for block in children_response.results])
|
|
40
|
+
|
|
41
|
+
@async_retry(max_retries=10, initial_delay=0.2, backoff_factor=1.5)
|
|
42
|
+
async def _delete_single_block(self, block: Block) -> None:
|
|
43
|
+
self.logger.debug("Deleting block: %s", block.id)
|
|
44
|
+
await self._block_client.delete_block(block.id)
|
|
45
|
+
|
|
46
|
+
@time_execution_async()
|
|
47
|
+
async def append_markdown(self, content: str | Callable[[MarkdownBuilder], MarkdownBuilder]) -> None:
|
|
48
|
+
markdown = self._extract_markdown(content)
|
|
49
|
+
if not markdown:
|
|
50
|
+
self.logger.debug("No markdown content to append for page: %s", self._page_id)
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
blocks = await self._markdown_converter.convert(markdown)
|
|
54
|
+
await self._append_blocks(blocks)
|
|
55
|
+
|
|
56
|
+
def _extract_markdown(self, content: str | Callable[[MarkdownBuilder], MarkdownBuilder]) -> str:
|
|
57
|
+
if isinstance(content, str):
|
|
58
|
+
return content
|
|
59
|
+
|
|
60
|
+
if callable(content):
|
|
61
|
+
builder = MarkdownBuilder()
|
|
62
|
+
content(builder)
|
|
63
|
+
return builder.build()
|
|
64
|
+
|
|
65
|
+
raise ValueError("content must be either a string or a callable that takes a MarkdownBuilder")
|
|
66
|
+
|
|
67
|
+
async def _append_blocks(self, blocks: list[Block]) -> None:
|
|
68
|
+
await self._block_client.append_block_children(block_id=self._page_id, children=blocks)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from enum import StrEnum
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SyntaxRegistryKey(StrEnum):
|
|
7
|
+
AUDIO = "audio"
|
|
8
|
+
BOOKMARK = "bookmark"
|
|
9
|
+
IMAGE = "image"
|
|
10
|
+
VIDEO = "video"
|
|
11
|
+
FILE = "file"
|
|
12
|
+
PDF = "pdf"
|
|
13
|
+
|
|
14
|
+
# List blocks
|
|
15
|
+
BULLETED_LIST = "bulleted_list"
|
|
16
|
+
NUMBERED_LIST = "numbered_list"
|
|
17
|
+
TO_DO = "todo"
|
|
18
|
+
TO_DO_DONE = "todo_done"
|
|
19
|
+
|
|
20
|
+
TOGGLE = "toggle"
|
|
21
|
+
TOGGLEABLE_HEADING = "toggleable_heading"
|
|
22
|
+
CALLOUT = "callout"
|
|
23
|
+
QUOTE = "quote"
|
|
24
|
+
CODE = "code"
|
|
25
|
+
|
|
26
|
+
COLUMN_LIST = "column_list"
|
|
27
|
+
COLUMN = "column"
|
|
28
|
+
|
|
29
|
+
# Heading blocks
|
|
30
|
+
HEADING_1 = "heading_1"
|
|
31
|
+
HEADING_2 = "heading_2"
|
|
32
|
+
HEADING_3 = "heading_3"
|
|
33
|
+
HEADING = "heading" # Shared pattern for regular headings
|
|
34
|
+
|
|
35
|
+
DIVIDER = "divider"
|
|
36
|
+
BREADCRUMB = "breadcrumb"
|
|
37
|
+
TABLE_OF_CONTENTS = "table_of_contents"
|
|
38
|
+
EQUATION = "equation"
|
|
39
|
+
EMBED = "embed"
|
|
40
|
+
TABLE = "table"
|
|
41
|
+
TABLE_ROW = "table_row"
|
|
42
|
+
|
|
43
|
+
CAPTION = "caption"
|
|
44
|
+
SPACE = "space"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# some elemente need closing delimiters, others not
|
|
48
|
+
# either use union type or validate config in service
|
|
49
|
+
@dataclass(frozen=True)
|
|
50
|
+
class SyntaxDefinition:
|
|
51
|
+
"""
|
|
52
|
+
Defines the syntax pattern for a block type.
|
|
53
|
+
|
|
54
|
+
Attributes:
|
|
55
|
+
start_delimiter: The opening delimiter (e.g., "```", "+++", ">")
|
|
56
|
+
end_delimiter: The optional closing delimiter (empty string if none)
|
|
57
|
+
regex_pattern: The compiled regex pattern to match this syntax
|
|
58
|
+
end_regex_pattern: Optional compiled regex pattern for end delimiter
|
|
59
|
+
is_multiline_block: Whether this block can contain child blocks
|
|
60
|
+
is_inline: Whether this is an inline syntax (like [audio](url))
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
start_delimiter: str
|
|
64
|
+
end_delimiter: str
|
|
65
|
+
regex_pattern: re.Pattern
|
|
66
|
+
end_regex_pattern: re.Pattern | None = None
|