notionary 0.2.26__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.26.dist-info → notionary-0.2.28.dist-info}/METADATA +54 -49
- notionary-0.2.28.dist-info/RECORD +200 -0
- {notionary-0.2.26.dist-info → notionary-0.2.28.dist-info}/WHEEL +1 -1
- {notionary-0.2.26.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 -674
- 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.26.dist-info/RECORD +0 -202
@@ -0,0 +1,40 @@
|
|
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 FileRenderer(CaptionedBlockRenderer):
|
8
|
+
@override
|
9
|
+
def _can_handle(self, block: Block) -> bool:
|
10
|
+
return block.type == BlockType.FILE
|
11
|
+
|
12
|
+
@override
|
13
|
+
async def _render_main_content(self, block: Block) -> str:
|
14
|
+
url = self._extract_file_url(block)
|
15
|
+
|
16
|
+
if not url:
|
17
|
+
return ""
|
18
|
+
|
19
|
+
syntax = self._syntax_registry.get_file_syntax()
|
20
|
+
return f"{syntax.start_delimiter}{url}{syntax.end_delimiter}"
|
21
|
+
|
22
|
+
def _extract_file_url(self, block: Block) -> str:
|
23
|
+
if not block.file:
|
24
|
+
return ""
|
25
|
+
|
26
|
+
if block.file.external:
|
27
|
+
return block.file.external.url or ""
|
28
|
+
elif block.file.file:
|
29
|
+
return block.file.file.url or ""
|
30
|
+
|
31
|
+
return ""
|
32
|
+
|
33
|
+
def _extract_file_name(self, block: Block) -> str:
|
34
|
+
if not block.file:
|
35
|
+
return ""
|
36
|
+
|
37
|
+
if block.file.name:
|
38
|
+
return block.file.name or ""
|
39
|
+
|
40
|
+
return ""
|
@@ -0,0 +1,69 @@
|
|
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.service import SyntaxRegistry
|
8
|
+
|
9
|
+
|
10
|
+
class HeadingRenderer(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._syntax = self._syntax_registry.get_heading_syntax()
|
18
|
+
self._rich_text_markdown_converter = rich_text_markdown_converter or RichTextToMarkdownConverter()
|
19
|
+
|
20
|
+
@override
|
21
|
+
def _can_handle(self, block: Block) -> bool:
|
22
|
+
if block.type == BlockType.HEADING_1:
|
23
|
+
return not block.heading_1.is_toggleable
|
24
|
+
if block.type == BlockType.HEADING_2:
|
25
|
+
return not block.heading_2.is_toggleable
|
26
|
+
if block.type == BlockType.HEADING_3:
|
27
|
+
return not block.heading_3.is_toggleable
|
28
|
+
|
29
|
+
return False
|
30
|
+
|
31
|
+
@override
|
32
|
+
async def _process(self, context: MarkdownRenderingContext) -> None:
|
33
|
+
level = self._get_heading_level(context.block)
|
34
|
+
title = await self._get_heading_title(context.block)
|
35
|
+
|
36
|
+
if not title or level == 0:
|
37
|
+
return
|
38
|
+
|
39
|
+
heading_markdown = f"{self._syntax.start_delimiter * level} {title}"
|
40
|
+
|
41
|
+
if context.indent_level > 0:
|
42
|
+
heading_markdown = context.indent_text(heading_markdown)
|
43
|
+
|
44
|
+
context.markdown_result = heading_markdown
|
45
|
+
|
46
|
+
def _get_heading_level(self, block: Block) -> int:
|
47
|
+
if block.type == BlockType.HEADING_1:
|
48
|
+
return 1
|
49
|
+
elif block.type == BlockType.HEADING_2:
|
50
|
+
return 2
|
51
|
+
elif block.type == BlockType.HEADING_3:
|
52
|
+
return 3
|
53
|
+
else:
|
54
|
+
return 0
|
55
|
+
|
56
|
+
async def _get_heading_title(self, block: Block) -> str:
|
57
|
+
if block.type == BlockType.HEADING_1:
|
58
|
+
heading_content = block.heading_1
|
59
|
+
elif block.type == BlockType.HEADING_2:
|
60
|
+
heading_content = block.heading_2
|
61
|
+
elif block.type == BlockType.HEADING_3:
|
62
|
+
heading_content = block.heading_3
|
63
|
+
else:
|
64
|
+
return ""
|
65
|
+
|
66
|
+
if not heading_content or not heading_content.rich_text:
|
67
|
+
return ""
|
68
|
+
|
69
|
+
return await self._rich_text_markdown_converter.to_markdown(heading_content.rich_text)
|
@@ -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 ImageRenderer(CaptionedBlockRenderer):
|
8
|
+
@override
|
9
|
+
def _can_handle(self, block: Block) -> bool:
|
10
|
+
return block.type == BlockType.IMAGE
|
11
|
+
|
12
|
+
@override
|
13
|
+
async def _render_main_content(self, block: Block) -> str:
|
14
|
+
url = self._extract_image_url(block)
|
15
|
+
|
16
|
+
if not url:
|
17
|
+
return ""
|
18
|
+
|
19
|
+
syntax = self._syntax_registry.get_image_syntax()
|
20
|
+
return f"{syntax.start_delimiter}{url}{syntax.end_delimiter}"
|
21
|
+
|
22
|
+
def _extract_image_url(self, block: Block) -> str:
|
23
|
+
if not block.image:
|
24
|
+
return ""
|
25
|
+
|
26
|
+
if block.image.external:
|
27
|
+
return block.image.external.url or ""
|
28
|
+
elif block.image.file:
|
29
|
+
return block.image.file.url or ""
|
30
|
+
|
31
|
+
return ""
|
@@ -0,0 +1,41 @@
|
|
1
|
+
from typing import override
|
2
|
+
|
3
|
+
from notionary.blocks.rich_text.rich_text_markdown_converter import (
|
4
|
+
RichTextToMarkdownConverter,
|
5
|
+
)
|
6
|
+
from notionary.blocks.schemas import Block, BlockType
|
7
|
+
from notionary.page.content.renderer.context import MarkdownRenderingContext
|
8
|
+
from notionary.page.content.renderer.renderers.base import BlockRenderer
|
9
|
+
from notionary.page.content.syntax.service import SyntaxRegistry
|
10
|
+
|
11
|
+
|
12
|
+
class NumberedListRenderer(BlockRenderer):
|
13
|
+
# Placeholder for numbered list fixer (post processing)
|
14
|
+
NUMBERED_LIST_PLACEHOLDER = "__NUM__"
|
15
|
+
|
16
|
+
def __init__(
|
17
|
+
self,
|
18
|
+
syntax_registry: SyntaxRegistry | None = None,
|
19
|
+
rich_text_markdown_converter: RichTextToMarkdownConverter | None = None,
|
20
|
+
) -> None:
|
21
|
+
super().__init__(syntax_registry=syntax_registry)
|
22
|
+
self._rich_text_markdown_converter = rich_text_markdown_converter or RichTextToMarkdownConverter()
|
23
|
+
|
24
|
+
@override
|
25
|
+
def _can_handle(self, block: Block) -> bool:
|
26
|
+
return block.type == BlockType.NUMBERED_LIST_ITEM
|
27
|
+
|
28
|
+
@override
|
29
|
+
async def _process(self, context: MarkdownRenderingContext) -> None:
|
30
|
+
list_item_data = context.block.numbered_list_item
|
31
|
+
rich_text = list_item_data.rich_text if list_item_data else []
|
32
|
+
content = await self._rich_text_markdown_converter.to_markdown(rich_text)
|
33
|
+
|
34
|
+
item_line = context.indent_text(f"{self.NUMBERED_LIST_PLACEHOLDER}. {content}")
|
35
|
+
|
36
|
+
children_markdown = await context.render_children_with_additional_indent(1)
|
37
|
+
|
38
|
+
if children_markdown:
|
39
|
+
context.markdown_result = f"{item_line}\n{children_markdown}"
|
40
|
+
else:
|
41
|
+
context.markdown_result = item_line
|
@@ -0,0 +1,40 @@
|
|
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 ParagraphRenderer(BlockRenderer):
|
10
|
+
def __init__(self, rich_text_markdown_converter: RichTextToMarkdownConverter | None = None) -> None:
|
11
|
+
super().__init__()
|
12
|
+
self._rich_text_markdown_converter = rich_text_markdown_converter or RichTextToMarkdownConverter()
|
13
|
+
|
14
|
+
@override
|
15
|
+
def _can_handle(self, block: Block) -> bool:
|
16
|
+
return block.type == BlockType.PARAGRAPH
|
17
|
+
|
18
|
+
@override
|
19
|
+
async def _process(self, context: MarkdownRenderingContext) -> None:
|
20
|
+
markdown = await self._convert_paragraph_to_markdown(context.block)
|
21
|
+
|
22
|
+
if not markdown:
|
23
|
+
context.markdown_result = ""
|
24
|
+
return
|
25
|
+
|
26
|
+
if context.indent_level > 0:
|
27
|
+
markdown = context.indent_text(markdown)
|
28
|
+
|
29
|
+
children_markdown = await context.render_children_with_additional_indent(1)
|
30
|
+
|
31
|
+
if children_markdown:
|
32
|
+
context.markdown_result = f"{markdown}\n{children_markdown}"
|
33
|
+
else:
|
34
|
+
context.markdown_result = markdown
|
35
|
+
|
36
|
+
async def _convert_paragraph_to_markdown(self, block: Block) -> str | None:
|
37
|
+
if not block.paragraph or not block.paragraph.rich_text:
|
38
|
+
return None
|
39
|
+
|
40
|
+
return await self._rich_text_markdown_converter.to_markdown(block.paragraph.rich_text)
|
@@ -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 PdfRenderer(CaptionedBlockRenderer):
|
8
|
+
@override
|
9
|
+
def _can_handle(self, block: Block) -> bool:
|
10
|
+
return block.type == BlockType.PDF
|
11
|
+
|
12
|
+
@override
|
13
|
+
async def _render_main_content(self, block: Block) -> str:
|
14
|
+
url = self._extract_pdf_url(block)
|
15
|
+
|
16
|
+
if not url:
|
17
|
+
return ""
|
18
|
+
|
19
|
+
syntax = self._syntax_registry.get_pdf_syntax()
|
20
|
+
return f"{syntax.start_delimiter}{url}{syntax.end_delimiter}"
|
21
|
+
|
22
|
+
def _extract_pdf_url(self, block: Block) -> str:
|
23
|
+
if not block.pdf:
|
24
|
+
return ""
|
25
|
+
|
26
|
+
if block.pdf.external:
|
27
|
+
return block.pdf.external.url or ""
|
28
|
+
elif block.pdf.file:
|
29
|
+
return block.pdf.file.url or ""
|
30
|
+
|
31
|
+
return ""
|
@@ -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.service 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.service 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,53 @@
|
|
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.service 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
|
+
children_markdown = await context.render_children()
|
38
|
+
|
39
|
+
toggle_end = syntax.end_delimiter
|
40
|
+
if context.indent_level > 0:
|
41
|
+
toggle_end = context.indent_text(toggle_end)
|
42
|
+
|
43
|
+
if children_markdown:
|
44
|
+
context.markdown_result = f"{toggle_start}\n{children_markdown}\n{toggle_end}"
|
45
|
+
else:
|
46
|
+
context.markdown_result = f"{toggle_start}\n{toggle_end}"
|
47
|
+
|
48
|
+
async def _extract_toggle_title(self, block: Block) -> str:
|
49
|
+
if not block.toggle or not block.toggle.rich_text:
|
50
|
+
return ""
|
51
|
+
|
52
|
+
rich_text_title = block.toggle.rich_text
|
53
|
+
return await self._rich_text_markdown_converter.to_markdown(rich_text_title)
|
@@ -0,0 +1,78 @@
|
|
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.service import SyntaxRegistry
|
8
|
+
|
9
|
+
|
10
|
+
class ToggleableHeadingRenderer(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._heading_syntax = self._syntax_registry.get_heading_syntax()
|
18
|
+
self._rich_text_markdown_converter = rich_text_markdown_converter or RichTextToMarkdownConverter()
|
19
|
+
|
20
|
+
@override
|
21
|
+
def _can_handle(self, block: Block) -> bool:
|
22
|
+
if block.type == BlockType.HEADING_1:
|
23
|
+
return block.heading_1.is_toggleable
|
24
|
+
if block.type == BlockType.HEADING_2:
|
25
|
+
return block.heading_2.is_toggleable
|
26
|
+
if block.type == BlockType.HEADING_3:
|
27
|
+
return block.heading_3.is_toggleable
|
28
|
+
|
29
|
+
@override
|
30
|
+
async def _process(self, context: MarkdownRenderingContext) -> None:
|
31
|
+
level = self._get_heading_level(context.block)
|
32
|
+
title = await self._get_heading_title(context.block)
|
33
|
+
|
34
|
+
if not title or level == 0:
|
35
|
+
return
|
36
|
+
|
37
|
+
syntax = self._syntax_registry.get_toggleable_heading_syntax()
|
38
|
+
prefix = self._syntax_registry.TOGGLE_DELIMITER + " " + (self._heading_syntax.start_delimiter * level)
|
39
|
+
heading_start = f"{prefix} {title}"
|
40
|
+
|
41
|
+
if context.indent_level > 0:
|
42
|
+
heading_start = context.indent_text(heading_start)
|
43
|
+
|
44
|
+
children_markdown = await context.render_children()
|
45
|
+
|
46
|
+
heading_end = syntax.end_delimiter
|
47
|
+
if context.indent_level > 0:
|
48
|
+
heading_end = context.indent_text(heading_end)
|
49
|
+
|
50
|
+
if children_markdown:
|
51
|
+
context.markdown_result = f"{heading_start}\n{children_markdown}\n{heading_end}"
|
52
|
+
else:
|
53
|
+
context.markdown_result = f"{heading_start}\n{heading_end}"
|
54
|
+
|
55
|
+
def _get_heading_level(self, block: Block) -> int:
|
56
|
+
if block.type == BlockType.HEADING_1:
|
57
|
+
return 1
|
58
|
+
elif block.type == BlockType.HEADING_2:
|
59
|
+
return 2
|
60
|
+
elif block.type == BlockType.HEADING_3:
|
61
|
+
return 3
|
62
|
+
else:
|
63
|
+
return 0
|
64
|
+
|
65
|
+
async def _get_heading_title(self, block: Block) -> str:
|
66
|
+
if block.type == BlockType.HEADING_1:
|
67
|
+
heading_content = block.heading_1
|
68
|
+
elif block.type == BlockType.HEADING_2:
|
69
|
+
heading_content = block.heading_2
|
70
|
+
elif block.type == BlockType.HEADING_3:
|
71
|
+
heading_content = block.heading_3
|
72
|
+
else:
|
73
|
+
return ""
|
74
|
+
|
75
|
+
if not heading_content or not heading_content.rich_text:
|
76
|
+
return ""
|
77
|
+
|
78
|
+
return await self._rich_text_markdown_converter.to_markdown(heading_content.rich_text)
|