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,525 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Clean Fluent Markdown Builder
|
3
|
-
============================
|
4
|
-
|
5
|
-
A direct, chainable builder for all MarkdownNode types without overengineering.
|
6
|
-
Maps 1:1 to the available blocks with clear, expressive method names.
|
7
|
-
"""
|
8
|
-
|
9
|
-
from __future__ import annotations
|
10
|
-
|
11
|
-
from typing import Callable, Optional, Self
|
12
|
-
|
13
|
-
from notionary.blocks.bookmark import BookmarkMarkdownNode
|
14
|
-
from notionary.blocks.breadcrumbs import BreadcrumbMarkdownNode
|
15
|
-
from notionary.blocks.bulleted_list import BulletedListMarkdownNode
|
16
|
-
from notionary.blocks.callout import CalloutMarkdownNode
|
17
|
-
from notionary.blocks.code import CodeLanguage, CodeMarkdownNode
|
18
|
-
from notionary.blocks.column import ColumnListMarkdownNode, ColumnMarkdownNode
|
19
|
-
from notionary.blocks.divider import DividerMarkdownNode
|
20
|
-
from notionary.blocks.embed import EmbedMarkdownNode
|
21
|
-
from notionary.blocks.equation import EquationMarkdownNode
|
22
|
-
from notionary.blocks.file import FileMarkdownNode
|
23
|
-
from notionary.blocks.heading import HeadingMarkdownNode
|
24
|
-
from notionary.blocks.image_block import ImageMarkdownNode
|
25
|
-
from notionary.blocks.numbered_list import NumberedListMarkdownNode
|
26
|
-
from notionary.blocks.paragraph import ParagraphMarkdownNode
|
27
|
-
from notionary.blocks.pdf import PdfMarkdownNode
|
28
|
-
from notionary.blocks.quote import QuoteMarkdownNode
|
29
|
-
from notionary.blocks.table import TableMarkdownNode
|
30
|
-
from notionary.blocks.table_of_contents import TableOfContentsMarkdownNode
|
31
|
-
from notionary.blocks.todo import TodoMarkdownNode
|
32
|
-
from notionary.blocks.toggle import ToggleMarkdownNode
|
33
|
-
from notionary.blocks.toggleable_heading import ToggleableHeadingMarkdownNode
|
34
|
-
from notionary.blocks.video import VideoMarkdownNode
|
35
|
-
from notionary.blocks.audio import AudioMarkdownNode
|
36
|
-
|
37
|
-
from notionary.blocks.markdown.markdown_node import MarkdownNode
|
38
|
-
|
39
|
-
|
40
|
-
class MarkdownBuilder:
|
41
|
-
"""
|
42
|
-
Fluent interface builder for creating Notion content with clean, direct methods.
|
43
|
-
|
44
|
-
Focuses on the developer API for programmatic content creation.
|
45
|
-
Model processing is handled by MarkdownModelProcessor.
|
46
|
-
"""
|
47
|
-
|
48
|
-
def __init__(self) -> None:
|
49
|
-
"""Initialize builder with empty children list."""
|
50
|
-
self.children: list[MarkdownNode] = []
|
51
|
-
|
52
|
-
def h1(self, text: str) -> Self:
|
53
|
-
"""
|
54
|
-
Add an H1 heading.
|
55
|
-
|
56
|
-
Args:
|
57
|
-
text: The heading text content
|
58
|
-
"""
|
59
|
-
self.children.append(HeadingMarkdownNode(text=text, level=1))
|
60
|
-
return self
|
61
|
-
|
62
|
-
def h2(self, text: str) -> Self:
|
63
|
-
"""
|
64
|
-
Add an H2 heading.
|
65
|
-
|
66
|
-
Args:
|
67
|
-
text: The heading text content
|
68
|
-
"""
|
69
|
-
self.children.append(HeadingMarkdownNode(text=text, level=2))
|
70
|
-
return self
|
71
|
-
|
72
|
-
def h3(self, text: str) -> Self:
|
73
|
-
"""
|
74
|
-
Add an H3 heading.
|
75
|
-
|
76
|
-
Args:
|
77
|
-
text: The heading text content
|
78
|
-
"""
|
79
|
-
self.children.append(HeadingMarkdownNode(text=text, level=3))
|
80
|
-
return self
|
81
|
-
|
82
|
-
def heading(self, text: str, level: int = 2) -> Self:
|
83
|
-
"""
|
84
|
-
Add a heading with specified level.
|
85
|
-
|
86
|
-
Args:
|
87
|
-
text: The heading text content
|
88
|
-
level: Heading level (1-3), defaults to 2
|
89
|
-
"""
|
90
|
-
self.children.append(HeadingMarkdownNode(text=text, level=level))
|
91
|
-
return self
|
92
|
-
|
93
|
-
def paragraph(self, text: str) -> Self:
|
94
|
-
"""
|
95
|
-
Add a paragraph block.
|
96
|
-
|
97
|
-
Args:
|
98
|
-
text: The paragraph text content
|
99
|
-
"""
|
100
|
-
self.children.append(ParagraphMarkdownNode(text=text))
|
101
|
-
return self
|
102
|
-
|
103
|
-
def text(self, content: str) -> Self:
|
104
|
-
"""
|
105
|
-
Add a text paragraph (alias for paragraph).
|
106
|
-
|
107
|
-
Args:
|
108
|
-
content: The text content
|
109
|
-
"""
|
110
|
-
return self.paragraph(content)
|
111
|
-
|
112
|
-
def quote(self, text: str) -> Self:
|
113
|
-
"""
|
114
|
-
Add a blockquote.
|
115
|
-
|
116
|
-
Args:
|
117
|
-
text: Quote text content
|
118
|
-
author: Optional quote author/attribution
|
119
|
-
"""
|
120
|
-
self.children.append(QuoteMarkdownNode(text=text))
|
121
|
-
return self
|
122
|
-
|
123
|
-
def divider(self) -> Self:
|
124
|
-
"""Add a horizontal divider."""
|
125
|
-
self.children.append(DividerMarkdownNode())
|
126
|
-
return self
|
127
|
-
|
128
|
-
def numbered_list(self, items: list[str]) -> Self:
|
129
|
-
"""
|
130
|
-
Add a numbered list.
|
131
|
-
|
132
|
-
Args:
|
133
|
-
items: List of text items for the numbered list
|
134
|
-
"""
|
135
|
-
self.children.append(NumberedListMarkdownNode(texts=items))
|
136
|
-
return self
|
137
|
-
|
138
|
-
def bulleted_list(self, items: list[str]) -> Self:
|
139
|
-
"""
|
140
|
-
Add a bulleted list.
|
141
|
-
|
142
|
-
Args:
|
143
|
-
items: List of text items for the bulleted list
|
144
|
-
"""
|
145
|
-
self.children.append(BulletedListMarkdownNode(texts=items))
|
146
|
-
return self
|
147
|
-
|
148
|
-
def todo(self, text: str, checked: bool = False) -> Self:
|
149
|
-
"""
|
150
|
-
Add a single todo item.
|
151
|
-
|
152
|
-
Args:
|
153
|
-
text: The todo item text
|
154
|
-
checked: Whether the todo item is completed, defaults to False
|
155
|
-
"""
|
156
|
-
self.children.append(TodoMarkdownNode(text=text, checked=checked))
|
157
|
-
return self
|
158
|
-
|
159
|
-
def todo_list(
|
160
|
-
self, items: list[str], completed: Optional[list[bool]] = None
|
161
|
-
) -> Self:
|
162
|
-
"""
|
163
|
-
Add multiple todo items.
|
164
|
-
|
165
|
-
Args:
|
166
|
-
items: List of todo item texts
|
167
|
-
completed: List of completion states for each item, defaults to all False
|
168
|
-
"""
|
169
|
-
if completed is None:
|
170
|
-
completed = [False] * len(items)
|
171
|
-
|
172
|
-
for i, item in enumerate(items):
|
173
|
-
is_done = completed[i] if i < len(completed) else False
|
174
|
-
self.children.append(TodoMarkdownNode(text=item, checked=is_done))
|
175
|
-
return self
|
176
|
-
|
177
|
-
def callout(self, text: str, emoji: Optional[str] = None) -> Self:
|
178
|
-
"""
|
179
|
-
Add a callout block.
|
180
|
-
|
181
|
-
Args:
|
182
|
-
text: The callout text content
|
183
|
-
emoji: Optional emoji for the callout icon
|
184
|
-
"""
|
185
|
-
self.children.append(CalloutMarkdownNode(text=text, emoji=emoji))
|
186
|
-
return self
|
187
|
-
|
188
|
-
def toggle(
|
189
|
-
self, title: str, builder_func: Callable[["MarkdownBuilder"], "MarkdownBuilder"]
|
190
|
-
) -> Self:
|
191
|
-
"""
|
192
|
-
Add a toggle block with content built using the builder API.
|
193
|
-
|
194
|
-
Args:
|
195
|
-
title: The toggle title/header text
|
196
|
-
builder_func: Function that receives a MarkdownBuilder and returns it configured
|
197
|
-
|
198
|
-
Example:
|
199
|
-
builder.toggle("Advanced Settings", lambda t:
|
200
|
-
t.h3("Configuration")
|
201
|
-
.paragraph("Settings description")
|
202
|
-
.table(["Setting", "Value"], [["Debug", "True"]])
|
203
|
-
.callout("Important note", "⚠️")
|
204
|
-
)
|
205
|
-
"""
|
206
|
-
toggle_builder = MarkdownBuilder()
|
207
|
-
builder_func(toggle_builder)
|
208
|
-
self.children.append(
|
209
|
-
ToggleMarkdownNode(title=title, children=toggle_builder.children)
|
210
|
-
)
|
211
|
-
return self
|
212
|
-
|
213
|
-
def toggleable_heading(
|
214
|
-
self,
|
215
|
-
text: str,
|
216
|
-
level: int,
|
217
|
-
builder_func: Callable[["MarkdownBuilder"], "MarkdownBuilder"],
|
218
|
-
) -> Self:
|
219
|
-
"""
|
220
|
-
Add a toggleable heading with content built using the builder API.
|
221
|
-
|
222
|
-
Args:
|
223
|
-
text: The heading text content
|
224
|
-
level: Heading level (1-3)
|
225
|
-
builder_func: Function that receives a MarkdownBuilder and returns it configured
|
226
|
-
|
227
|
-
Example:
|
228
|
-
builder.toggleable_heading("Advanced Section", 2, lambda t:
|
229
|
-
t.paragraph("Introduction to this section")
|
230
|
-
.numbered_list(["Step 1", "Step 2", "Step 3"])
|
231
|
-
.code("example_code()", "python")
|
232
|
-
.table(["Feature", "Status"], [["API", "Ready"]])
|
233
|
-
)
|
234
|
-
"""
|
235
|
-
toggle_builder = MarkdownBuilder()
|
236
|
-
builder_func(toggle_builder)
|
237
|
-
self.children.append(
|
238
|
-
ToggleableHeadingMarkdownNode(
|
239
|
-
text=text, level=level, children=toggle_builder.children
|
240
|
-
)
|
241
|
-
)
|
242
|
-
return self
|
243
|
-
|
244
|
-
def image(
|
245
|
-
self, url: str, caption: Optional[str] = None, alt: Optional[str] = None
|
246
|
-
) -> Self:
|
247
|
-
"""
|
248
|
-
Add an image.
|
249
|
-
|
250
|
-
Args:
|
251
|
-
url: Image URL or file path
|
252
|
-
caption: Optional image caption text
|
253
|
-
alt: Optional alternative text for accessibility
|
254
|
-
"""
|
255
|
-
self.children.append(ImageMarkdownNode(url=url, caption=caption, alt=alt))
|
256
|
-
return self
|
257
|
-
|
258
|
-
def video(self, url: str, caption: Optional[str] = None) -> Self:
|
259
|
-
"""
|
260
|
-
Add a video.
|
261
|
-
|
262
|
-
Args:
|
263
|
-
url: Video URL or file path
|
264
|
-
caption: Optional video caption text
|
265
|
-
"""
|
266
|
-
self.children.append(VideoMarkdownNode(url=url, caption=caption))
|
267
|
-
return self
|
268
|
-
|
269
|
-
def audio(self, url: str, caption: Optional[str] = None) -> Self:
|
270
|
-
"""
|
271
|
-
Add audio content.
|
272
|
-
|
273
|
-
Args:
|
274
|
-
url: Audio file URL or path
|
275
|
-
caption: Optional audio caption text
|
276
|
-
"""
|
277
|
-
self.children.append(AudioMarkdownNode(url=url, caption=caption))
|
278
|
-
return self
|
279
|
-
|
280
|
-
def file(self, url: str, caption: Optional[str] = None) -> Self:
|
281
|
-
"""
|
282
|
-
Add a file.
|
283
|
-
|
284
|
-
Args:
|
285
|
-
url: File URL or path
|
286
|
-
caption: Optional file caption text
|
287
|
-
"""
|
288
|
-
self.children.append(FileMarkdownNode(url=url, caption=caption))
|
289
|
-
return self
|
290
|
-
|
291
|
-
def pdf(self, url: str, caption: Optional[str] = None) -> Self:
|
292
|
-
"""
|
293
|
-
Add a PDF document.
|
294
|
-
|
295
|
-
Args:
|
296
|
-
url: PDF URL or file path
|
297
|
-
caption: Optional PDF caption text
|
298
|
-
"""
|
299
|
-
self.children.append(PdfMarkdownNode(url=url, caption=caption))
|
300
|
-
return self
|
301
|
-
|
302
|
-
def bookmark(
|
303
|
-
self, url: str, title: Optional[str] = None, caption: Optional[str] = None
|
304
|
-
) -> Self:
|
305
|
-
"""
|
306
|
-
Add a bookmark.
|
307
|
-
|
308
|
-
Args:
|
309
|
-
url: Bookmark URL
|
310
|
-
title: Optional bookmark title
|
311
|
-
description: Optional bookmark description text
|
312
|
-
"""
|
313
|
-
self.children.append(
|
314
|
-
BookmarkMarkdownNode(url=url, title=title, caption=caption)
|
315
|
-
)
|
316
|
-
return self
|
317
|
-
|
318
|
-
def embed(self, url: str, caption: Optional[str] = None) -> Self:
|
319
|
-
"""
|
320
|
-
Add an embed.
|
321
|
-
|
322
|
-
Args:
|
323
|
-
url: URL to embed (e.g., YouTube, Twitter, etc.)
|
324
|
-
caption: Optional embed caption text
|
325
|
-
"""
|
326
|
-
self.children.append(EmbedMarkdownNode(url=url, caption=caption))
|
327
|
-
return self
|
328
|
-
|
329
|
-
def code(
|
330
|
-
self, code: str, language: Optional[str] = None, caption: Optional[str] = None
|
331
|
-
) -> Self:
|
332
|
-
"""
|
333
|
-
Add a code block.
|
334
|
-
|
335
|
-
Args:
|
336
|
-
code: The source code content
|
337
|
-
language: Optional programming language for syntax highlighting
|
338
|
-
caption: Optional code block caption text
|
339
|
-
"""
|
340
|
-
self.children.append(
|
341
|
-
CodeMarkdownNode(code=code, language=language, caption=caption)
|
342
|
-
)
|
343
|
-
return self
|
344
|
-
|
345
|
-
def mermaid(self, diagram: str, caption: Optional[str] = None) -> Self:
|
346
|
-
"""
|
347
|
-
Add a Mermaid diagram block.
|
348
|
-
|
349
|
-
Args:
|
350
|
-
diagram: The Mermaid diagram source code
|
351
|
-
caption: Optional diagram caption text
|
352
|
-
"""
|
353
|
-
self.children.append(
|
354
|
-
CodeMarkdownNode(
|
355
|
-
code=diagram, language=CodeLanguage.MERMAID.value, caption=caption
|
356
|
-
)
|
357
|
-
)
|
358
|
-
return self
|
359
|
-
|
360
|
-
def table(self, headers: list[str], rows: list[list[str]]) -> Self:
|
361
|
-
"""
|
362
|
-
Add a table.
|
363
|
-
|
364
|
-
Args:
|
365
|
-
headers: List of column header texts
|
366
|
-
rows: List of rows, where each row is a list of cell texts
|
367
|
-
"""
|
368
|
-
self.children.append(TableMarkdownNode(headers=headers, rows=rows))
|
369
|
-
return self
|
370
|
-
|
371
|
-
def add_custom(self, node: MarkdownNode) -> Self:
|
372
|
-
"""
|
373
|
-
Add a custom MarkdownNode.
|
374
|
-
|
375
|
-
Args:
|
376
|
-
node: A custom MarkdownNode instance
|
377
|
-
"""
|
378
|
-
self.children.append(node)
|
379
|
-
return self
|
380
|
-
|
381
|
-
def breadcrumb(self) -> Self:
|
382
|
-
"""Add a breadcrumb navigation block."""
|
383
|
-
self.children.append(BreadcrumbMarkdownNode())
|
384
|
-
return self
|
385
|
-
|
386
|
-
def equation(self, expression: str) -> Self:
|
387
|
-
"""
|
388
|
-
Add a LaTeX equation block.
|
389
|
-
|
390
|
-
Args:
|
391
|
-
expression: LaTeX mathematical expression
|
392
|
-
|
393
|
-
Example:
|
394
|
-
builder.equation("E = mc^2")
|
395
|
-
builder.equation("f(x) = \\sin(x) + \\cos(x)")
|
396
|
-
builder.equation("x = \\frac{-b \\pm \\sqrt{b^2-4ac}}{2a}")
|
397
|
-
"""
|
398
|
-
self.children.append(EquationMarkdownNode(expression=expression))
|
399
|
-
return self
|
400
|
-
|
401
|
-
def table_of_contents(self, color: Optional[str] = None) -> Self:
|
402
|
-
"""
|
403
|
-
Add a table of contents.
|
404
|
-
|
405
|
-
Args:
|
406
|
-
color: Optional color for the table of contents (e.g., "blue", "blue_background")
|
407
|
-
"""
|
408
|
-
self.children.append(TableOfContentsMarkdownNode(color=color))
|
409
|
-
return self
|
410
|
-
|
411
|
-
def columns(
|
412
|
-
self,
|
413
|
-
*builder_funcs: Callable[["MarkdownBuilder"], "MarkdownBuilder"],
|
414
|
-
width_ratios: Optional[list[float]] = None,
|
415
|
-
) -> Self:
|
416
|
-
"""
|
417
|
-
Add multiple columns in a layout.
|
418
|
-
|
419
|
-
Args:
|
420
|
-
*builder_funcs: Multiple functions, each building one column
|
421
|
-
width_ratios: Optional list of width ratios (0.0 to 1.0).
|
422
|
-
If None, columns have equal width.
|
423
|
-
Length must match number of builder_funcs.
|
424
|
-
|
425
|
-
Examples:
|
426
|
-
# Equal width (original API unchanged):
|
427
|
-
builder.columns(
|
428
|
-
lambda col: col.h2("Left").paragraph("Left content"),
|
429
|
-
lambda col: col.h2("Right").paragraph("Right content")
|
430
|
-
)
|
431
|
-
|
432
|
-
# Custom ratios:
|
433
|
-
builder.columns(
|
434
|
-
lambda col: col.h2("Main").paragraph("70% width"),
|
435
|
-
lambda col: col.h2("Sidebar").paragraph("30% width"),
|
436
|
-
width_ratios=[0.7, 0.3]
|
437
|
-
)
|
438
|
-
|
439
|
-
# Three columns with custom ratios:
|
440
|
-
builder.columns(
|
441
|
-
lambda col: col.h3("Nav").paragraph("Navigation"),
|
442
|
-
lambda col: col.h2("Main").paragraph("Main content"),
|
443
|
-
lambda col: col.h3("Ads").paragraph("Advertisement"),
|
444
|
-
width_ratios=[0.2, 0.6, 0.2]
|
445
|
-
)
|
446
|
-
"""
|
447
|
-
if len(builder_funcs) < 2:
|
448
|
-
raise ValueError("Column layout requires at least 2 columns")
|
449
|
-
|
450
|
-
if width_ratios is not None:
|
451
|
-
if len(width_ratios) != len(builder_funcs):
|
452
|
-
raise ValueError(
|
453
|
-
f"width_ratios length ({len(width_ratios)}) must match number of columns ({len(builder_funcs)})"
|
454
|
-
)
|
455
|
-
|
456
|
-
ratio_sum = sum(width_ratios)
|
457
|
-
if not (0.9 <= ratio_sum <= 1.1): # Allow small floating point errors
|
458
|
-
raise ValueError(f"width_ratios should sum to 1.0, got {ratio_sum}")
|
459
|
-
|
460
|
-
# Create all columns
|
461
|
-
columns = []
|
462
|
-
for i, builder_func in enumerate(builder_funcs):
|
463
|
-
width_ratio = width_ratios[i] if width_ratios else None
|
464
|
-
|
465
|
-
col_builder = MarkdownBuilder()
|
466
|
-
builder_func(col_builder)
|
467
|
-
|
468
|
-
column_node = ColumnMarkdownNode(
|
469
|
-
children=col_builder.children, width_ratio=width_ratio
|
470
|
-
)
|
471
|
-
columns.append(column_node)
|
472
|
-
|
473
|
-
self.children.append(ColumnListMarkdownNode(columns=columns))
|
474
|
-
return self
|
475
|
-
|
476
|
-
def column_with_nodes(
|
477
|
-
self, *nodes: MarkdownNode, width_ratio: Optional[float] = None
|
478
|
-
) -> Self:
|
479
|
-
"""
|
480
|
-
Add a column with pre-built MarkdownNode objects.
|
481
|
-
|
482
|
-
Args:
|
483
|
-
*nodes: MarkdownNode objects to include in the column
|
484
|
-
width_ratio: Optional width ratio (0.0 to 1.0)
|
485
|
-
|
486
|
-
Examples:
|
487
|
-
# Original API (unchanged):
|
488
|
-
builder.column_with_nodes(
|
489
|
-
HeadingMarkdownNode(text="Title", level=2),
|
490
|
-
ParagraphMarkdownNode(text="Content")
|
491
|
-
)
|
492
|
-
|
493
|
-
# New API with ratio:
|
494
|
-
builder.column_with_nodes(
|
495
|
-
HeadingMarkdownNode(text="Sidebar", level=2),
|
496
|
-
ParagraphMarkdownNode(text="Narrow content"),
|
497
|
-
width_ratio=0.25
|
498
|
-
)
|
499
|
-
"""
|
500
|
-
from notionary.blocks.column.column_markdown_node import ColumnMarkdownNode
|
501
|
-
|
502
|
-
column_node = ColumnMarkdownNode(children=list(nodes), width_ratio=width_ratio)
|
503
|
-
self.children.append(column_node)
|
504
|
-
return self
|
505
|
-
|
506
|
-
def _column(
|
507
|
-
self, builder_func: Callable[[MarkdownBuilder], MarkdownBuilder]
|
508
|
-
) -> ColumnMarkdownNode:
|
509
|
-
"""
|
510
|
-
Internal helper to create a single column.
|
511
|
-
Use columns() instead for public API.
|
512
|
-
"""
|
513
|
-
col_builder = MarkdownBuilder()
|
514
|
-
builder_func(col_builder)
|
515
|
-
return ColumnMarkdownNode(children=col_builder.children)
|
516
|
-
|
517
|
-
def space(self) -> Self:
|
518
|
-
"""Add vertical spacing."""
|
519
|
-
return self.paragraph("")
|
520
|
-
|
521
|
-
def build(self) -> str:
|
522
|
-
"""Build and return the final markdown string."""
|
523
|
-
return "\n\n".join(
|
524
|
-
child.to_markdown() for child in self.children if child is not None
|
525
|
-
)
|
File without changes
|
@@ -1,25 +0,0 @@
|
|
1
|
-
from abc import ABC, abstractmethod
|
2
|
-
from pydantic import BaseModel
|
3
|
-
|
4
|
-
|
5
|
-
class MarkdownNode(BaseModel, ABC):
|
6
|
-
"""
|
7
|
-
Enhanced base class for all Markdown nodes with Pydantic integration.
|
8
|
-
|
9
|
-
This class serves dual purposes:
|
10
|
-
1. Runtime representation for markdown generation
|
11
|
-
2. Serializable model for structured output (LLM/API)
|
12
|
-
|
13
|
-
The 'type' field acts as a discriminator for Union types and processing.
|
14
|
-
"""
|
15
|
-
|
16
|
-
@abstractmethod
|
17
|
-
def to_markdown(self) -> str:
|
18
|
-
"""
|
19
|
-
Returns the Markdown representation of the block.
|
20
|
-
Must be implemented by subclasses.
|
21
|
-
"""
|
22
|
-
pass
|
23
|
-
|
24
|
-
def __str__(self):
|
25
|
-
return self.to_markdown()
|
@@ -1,31 +0,0 @@
|
|
1
|
-
from typing import Optional
|
2
|
-
|
3
|
-
|
4
|
-
class CaptionMarkdownNodeMixin:
|
5
|
-
"""Mixin to add caption functionality to MarkdownNode classes."""
|
6
|
-
|
7
|
-
@classmethod
|
8
|
-
def append_caption_to_markdown(
|
9
|
-
cls, base_markdown: str, caption: Optional[str]
|
10
|
-
) -> str:
|
11
|
-
"""
|
12
|
-
Append caption to existing markdown if caption is present.
|
13
|
-
Returns: base_markdown + "(caption:...)" or just base_markdown
|
14
|
-
"""
|
15
|
-
if not caption:
|
16
|
-
return base_markdown
|
17
|
-
return f"{base_markdown}(caption:{caption})"
|
18
|
-
|
19
|
-
@classmethod
|
20
|
-
def format_caption_for_markdown(cls, caption: Optional[str]) -> str:
|
21
|
-
"""
|
22
|
-
Format caption text for markdown output.
|
23
|
-
Returns: "(caption:...)" or empty string
|
24
|
-
"""
|
25
|
-
if not caption:
|
26
|
-
return ""
|
27
|
-
return f"(caption:{caption})"
|
28
|
-
|
29
|
-
def has_caption(self) -> bool:
|
30
|
-
"""Check if this node has a caption."""
|
31
|
-
return hasattr(self, "caption") and bool(getattr(self, "caption", None))
|
@@ -1,92 +0,0 @@
|
|
1
|
-
from typing import Optional
|
2
|
-
import re
|
3
|
-
from notionary.blocks.rich_text.rich_text_models import RichTextObject
|
4
|
-
from notionary.blocks.rich_text.text_inline_formatter import TextInlineFormatter
|
5
|
-
|
6
|
-
|
7
|
-
class CaptionMixin:
|
8
|
-
"""Mixin to add caption parsing functionality to block elements."""
|
9
|
-
|
10
|
-
# Generic caption pattern - finds caption anywhere in text
|
11
|
-
CAPTION_PATTERN = re.compile(r"\(caption:([^)]*)\)")
|
12
|
-
|
13
|
-
@classmethod
|
14
|
-
def extract_caption(cls, text: str) -> Optional[str]:
|
15
|
-
"""
|
16
|
-
Extract caption text from anywhere in the input text.
|
17
|
-
Returns only the caption content, preserving parentheses in content.
|
18
|
-
"""
|
19
|
-
# Look for (caption: followed by content followed by )
|
20
|
-
# Handle cases where caption content contains parentheses
|
21
|
-
caption_start = text.find("(caption:")
|
22
|
-
if caption_start == -1:
|
23
|
-
return None
|
24
|
-
|
25
|
-
# Find the matching closing parenthesis
|
26
|
-
# Start after "(caption:"
|
27
|
-
content_start = caption_start + 9 # len("(caption:")
|
28
|
-
paren_count = 1
|
29
|
-
pos = content_start
|
30
|
-
|
31
|
-
while pos < len(text) and paren_count > 0:
|
32
|
-
if text[pos] == "(":
|
33
|
-
paren_count += 1
|
34
|
-
elif text[pos] == ")":
|
35
|
-
paren_count -= 1
|
36
|
-
pos += 1
|
37
|
-
|
38
|
-
if paren_count == 0:
|
39
|
-
# Found matching closing parenthesis
|
40
|
-
return text[content_start : pos - 1]
|
41
|
-
|
42
|
-
return None
|
43
|
-
|
44
|
-
@classmethod
|
45
|
-
def remove_caption(cls, text: str) -> str:
|
46
|
-
"""
|
47
|
-
Remove caption from text and return clean text.
|
48
|
-
Uses the same balanced parentheses logic as extract_caption.
|
49
|
-
"""
|
50
|
-
caption_start = text.find("(caption:")
|
51
|
-
if caption_start == -1:
|
52
|
-
return text.strip()
|
53
|
-
|
54
|
-
# Find the matching closing parenthesis
|
55
|
-
content_start = caption_start + 9 # len("(caption:")
|
56
|
-
paren_count = 1
|
57
|
-
pos = content_start
|
58
|
-
|
59
|
-
while pos < len(text) and paren_count > 0:
|
60
|
-
if text[pos] == "(":
|
61
|
-
paren_count += 1
|
62
|
-
elif text[pos] == ")":
|
63
|
-
paren_count -= 1
|
64
|
-
pos += 1
|
65
|
-
|
66
|
-
if paren_count == 0:
|
67
|
-
# Remove the entire caption including the outer parentheses
|
68
|
-
return (text[:caption_start] + text[pos:]).strip()
|
69
|
-
|
70
|
-
# Fallback to regex-based removal if balanced parsing fails
|
71
|
-
return cls.CAPTION_PATTERN.sub("", text).strip()
|
72
|
-
|
73
|
-
@classmethod
|
74
|
-
def build_caption_rich_text(cls, caption_text: str) -> list[RichTextObject]:
|
75
|
-
"""Return caption as canonical rich text list (with annotations)."""
|
76
|
-
if not caption_text:
|
77
|
-
return []
|
78
|
-
# IMPORTANT: use the same formatter used elsewhere in the app
|
79
|
-
return [RichTextObject.for_caption(caption_text)]
|
80
|
-
|
81
|
-
@classmethod
|
82
|
-
async def format_caption_for_markdown(
|
83
|
-
cls, caption_list: list[RichTextObject]
|
84
|
-
) -> str:
|
85
|
-
"""Convert rich text caption back to markdown format."""
|
86
|
-
if not caption_list:
|
87
|
-
return ""
|
88
|
-
# Preserve markdown formatting (bold, italic, etc.)
|
89
|
-
caption_text = await TextInlineFormatter.extract_text_with_formatting(
|
90
|
-
caption_list
|
91
|
-
)
|
92
|
-
return f"(caption:{caption_text})" if caption_text else ""
|